Browse Source

add stock page

Daniel Bohry 9 tháng trước cách đây
mục cha
commit
ed23694452
2 tập tin đã thay đổi với 255 bổ sung4 xóa
  1. 7 4
      src/routes/stocks/+page.svelte
  2. 248 0
      src/routes/stocks/[code]/+page.svelte

+ 7 - 4
src/routes/stocks/+page.svelte

@@ -27,7 +27,6 @@
 		let query = stock === undefined ? '/api/stocks' : '/api/stocks?q=' + stock;
 
 		try {
-
 			const response = await getRequest(import.meta.env.VITE_STOCKS_HOST + query, {}, null);
 
 			if (response.ok) {
@@ -38,7 +37,6 @@
 			}
 		} catch (err) {
 			console.error('Error during fetch:', err);
-			await goto('/logout');
 		}
 	}
 
@@ -66,6 +64,11 @@
 
 		return country[market] || 'us';
 	}
+
+	function openStock(code) {
+		goto(`/stocks/${code}`);
+	}
+
 </script>
 
 <svelte:head>
@@ -107,9 +110,9 @@
 					</div>
 					<a
 						class="view-button"
-						href="https://www.google.com/search?q={stock.code}+{stock.name}+stock"
 						target="_blank"
 						rel="noopener noreferrer"
+						on:click={openStock(stock.code)}
 					>
 						View
 					</a>
@@ -123,7 +126,7 @@
 
 <style>
     .filter-card {
-        margin: 1.5rem auto;
+        margin: 0.5rem auto;
         padding: 1rem 1.5rem;
         background: linear-gradient(to right, #f9f9f9, #ffffff);
         border-radius: 1rem;

+ 248 - 0
src/routes/stocks/[code]/+page.svelte

@@ -0,0 +1,248 @@
+<script>
+	import { page } from '$app/stores';
+	import { onMount } from 'svelte';
+	import { getRequest } from '../../../utils/api.js';
+
+	let code = '';
+	let stockInfo = null;
+
+	$: code = $page.params.code;
+
+	onMount(async () => {
+		if (code) {
+			stockInfo = await get(code);
+		}
+	});
+
+	async function get(code) {
+		try {
+			const [response1, response2] = await Promise.all([
+				getRequest(import.meta.env.VITE_STOCKS_HOST + '/api/stocks/' + code, {}, null),
+				getRequest(import.meta.env.VITE_STOCKS_HOST + '/api/stocks/' + code + '/info', {}, null)
+			]);
+
+			if (response1.ok) {
+				const data1 = await response1.json();
+				let data2 = {};
+
+				if (response2.ok) {
+					data2 = await response2.json();
+				} else if (response2.status !== 404) {
+					const error2 = await response2.json();
+					console.error('Error in /info:', error2);
+				}
+
+				return { ...data1, ...data2 };
+			} else {
+				const error1 = await response1.json();
+				console.error('Error in base /:', error1);
+			}
+		} catch (err) {
+			console.error('Error during fetch:', err);
+		}
+	}
+
+
+	function getFlag(code) {
+		const market = code.includes(':') ? code.split(':')[0].toUpperCase() : code.toUpperCase();
+
+		const country = {
+			BVMF: 'br',
+			FRA: 'de',
+			ETR: 'eu'
+		};
+
+		return country[market] || 'us';
+	}
+</script>
+
+<svelte:head>
+	<title>Stock: {code}</title>
+	<meta name="description" content="Details for stock {code}" />
+</svelte:head>
+
+{#if !stockInfo}
+	<div class="loading">Loading...</div>
+{:else}
+	<div class="stock-page">
+		<div class="stock-header">
+			<img
+				src={`https://flagcdn.com/16x12/${getFlag(code)}.png`}
+				alt="{getFlag(code)} flag"
+				class="flag-icon"
+			/>
+			<h1>{stockInfo.code}</h1>
+			<h2>{stockInfo.name}</h2>
+		</div>
+
+		<div class="stock-body">
+			<div class="stock-line">
+				<span class="label">Code:</span>
+				<span class="value">{stockInfo.code}</span>
+			</div>
+
+			<div class="stock-line">
+				<span class="label">Name:</span>
+				<span class="value">{stockInfo.name}</span>
+			</div>
+
+			<div class="stock-line">
+				<span class="label">Description:</span>
+				<span class="value">{stockInfo.description ?? 'N/A'}</span>
+			</div>
+
+			<div class="stock-line">
+				<span class="label">Foundation:</span>
+				<span class="value">{stockInfo.foundation ?? 'N/A'}</span>
+			</div>
+
+			<div class="stock-line">
+				<span class="label">IPO:</span>
+				<span class="value">{stockInfo.ipo ?? 'N/A'}</span>
+			</div>
+
+			<div class="stock-line">
+				<span class="label">Exchange:</span>
+				<span class="value">{stockInfo.exchange ?? 'N/A'}</span>
+			</div>
+
+			<div class="stock-line">
+				<span class="label">Headquarters:</span>
+				<span class="value">{stockInfo.headquarters ?? 'N/A'}</span>
+			</div>
+
+			<div class="stock-line">
+				<span class="label">Industry:</span>
+				<span class="value">{stockInfo.industry ?? 'N/A'}</span>
+			</div>
+
+			<div class="stock-line">
+				<span class="label">Company Website:</span>
+				<span class="value">
+			{#if stockInfo.companyWebsite}
+				<a href={stockInfo.companyWebsite} target="_blank" rel="noopener noreferrer">website</a>
+			{:else}
+				N/A
+			{/if}
+		</span>
+			</div>
+
+			<div class="stock-line">
+				<span class="label">SEC Website:</span>
+				<span class="value">
+			{#if stockInfo.secWebsite}
+				<a href={stockInfo.secWebsite} target="_blank" rel="noopener noreferrer">sec</a>
+			{:else}
+				N/A
+			{/if}
+		</span>
+			</div>
+
+			<a
+				class="view-button"
+				href="https://www.google.com/search?q={stockInfo.code}+{stockInfo.name}+stock"
+				target="_blank"
+				rel="noopener noreferrer"
+			>
+				View More
+			</a>
+
+		</div>
+
+	</div>
+{/if}
+
+<style>
+    :global(body) {
+        margin: 0;
+        padding: 0;
+    }
+
+    .loading {
+        text-align: center;
+        padding: 2rem;
+        font-size: 1.2rem;
+        color: #888;
+    }
+
+    .stock-page {
+        width: 100%;
+        background: #ffffff;
+        box-sizing: border-box;
+        border-radius: 1rem;
+        box-shadow: 0 6px 14px rgba(0, 0, 0, 0.05);
+        padding: 1.25rem;
+        transition: transform 0.3s ease, box-shadow 0.3s ease;
+    }
+
+    .stock-header {
+        display: flex;
+        align-items: center;
+        gap: 1rem;
+        border-bottom: 2px solid #f0f0f0;
+        padding-bottom: 1.5rem;
+        margin-bottom: 2rem;
+    }
+
+    .flag-icon {
+        width: auto;
+        height: auto;
+    }
+
+    h1 {
+        font-size: 2.5rem;
+        color: #2c3e50;
+        margin: 0;
+    }
+
+    h2 {
+        font-size: 1.25rem;
+        color: #7f8c8d;
+        font-weight: normal;
+        margin: 0;
+    }
+
+    .stock-body {
+        display: grid;
+        grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+        gap: 2rem;
+    }
+
+    .stock-line {
+        background: #f9f9f9;
+        padding: 1.25rem;
+        border-radius: 0.75rem;
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+    }
+
+    .label {
+        color: #666;
+        font-weight: 500;
+        font-size: 1.1rem;
+    }
+
+    .value {
+        color: #2ecc71;
+        font-weight: 700;
+        font-size: 1.2rem;
+    }
+
+    .view-button {
+        margin-top: 1rem;
+        align-self: flex-end;
+        padding: 0.5rem 1rem;
+        background-color: #3498db;
+        color: white;
+        font-size: 0.9rem;
+        border: none;
+        border-radius: 0.5rem;
+        cursor: pointer;
+        transition: background-color 0.2s ease;
+        text-decoration: none;
+        text-align: center;
+				width: 75px;
+    }
+</style>