|
|
@@ -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>
|