Skip to content

Commit

Permalink
add page and chart
Browse files Browse the repository at this point in the history
  • Loading branch information
u4aew committed Aug 15, 2024
1 parent fe00d09 commit 57ccaff
Show file tree
Hide file tree
Showing 16 changed files with 223 additions and 36 deletions.
10 changes: 10 additions & 0 deletions actions/shares/shares.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,13 @@ export const getLastPriceByTicker = async (ticker: string): Promise<any> => {
};
};

export const getCandlesByTicker = async (ticker: string): Promise<any> => {
const { data } = await axios.get<{
data: IInstrument;
}>(`${API_BASE_URL}/candlesByTicker`, {
params: { ticker },
});
return {
data,
};
};
81 changes: 64 additions & 17 deletions app/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,72 @@
import React from 'react';
import styles from './styles.module.scss';
import { Metadata, ResolvingMetadata } from 'next';
import { serviceShares } from '@/services';
import { ShareIntro } from '@/components/ShareIntro';
import { BuyStock } from '@/components/BuyStock';
import { serviceShares } from '@/services';
import Candles from '@/components/Candles/Candles';
import styles from './styles.module.scss';

const PageStock = async ({ params }: { params: { slug: string } }) => {
const { data } = await serviceShares.getByTicker(params.slug);
const { data: dataLastPrice } = await serviceShares.getLastPriceByTicker(
params.slug,
);
return (
<div className={styles.page}>
<div className={styles.main}>
{/*@ts-ignore*/}
<ShareIntro value={data} />
</div>
<div className={styles.side}>
<BuyStock info={dataLastPrice} />
type Props = {
params: { slug: string };
};

export async function generateMetadata(
{ params }: Props,
parent: ResolvingMetadata,
): Promise<Metadata> {
const shareData = await serviceShares.getByTicker(params.slug);
const dataIntro = shareData?.data;

if (!dataIntro) {
return {
title: 'Stock not found',
};
}

const metaDescription = `Information about stock ${dataIntro.name} (${dataIntro.ticker}). Sector: ${dataIntro.sector}, Country: ${dataIntro.countryOfRiskName}`;
const keywords = `${dataIntro.name}, ${dataIntro.ticker}, ${dataIntro.sector}, ${dataIntro.countryOfRiskName}, stock, invest`;

return {
title: `${dataIntro.name} (${dataIntro.ticker}) - Information about stock`,
description: metaDescription,
keywords: keywords,
openGraph: {
title: `${dataIntro.name} (${dataIntro.ticker}) - Information about stock`,
description: metaDescription,
type: 'website',
},
};
}

const PageStock = async ({ params }: Props) => {
try {
const [shareData, lastPriceData, candlesData] = await Promise.all([
serviceShares.getByTicker(params.slug),
serviceShares.getLastPriceByTicker(params.slug),
serviceShares.getCandlesByTicker(params.slug),
]);

const dataIntro = shareData?.data;
const dataLastPrice = lastPriceData?.data;
const dataCandlesByTicker = candlesData?.data;

return (
<div className={styles.page}>
<div className={styles.main}>
<div className={styles.intro}>
{dataIntro && <ShareIntro value={dataIntro} />}
</div>
{dataCandlesByTicker && <Candles data={dataCandlesByTicker} />}
</div>
<div className={styles.side}>
<BuyStock info={dataLastPrice} />
</div>
</div>
</div>
);
);
} catch (error) {
console.error('Error loading stock data:', error);
return <div>Error loading stock data</div>;
}
};

export default PageStock;
5 changes: 5 additions & 0 deletions app/[slug]/styles.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
max-width: var(--box-max-width);
margin: 0 auto;
display: flex;
margin-bottom: 30px;
}

.main {
Expand All @@ -12,3 +13,7 @@
.side {
flex: 0 0 300px
}

.intro {
margin-bottom: 15px;
}
3 changes: 2 additions & 1 deletion components/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import styles from './styles.module.scss';

export const Button = ({ children, ...props }: any) => {
return <button {...props}>{children}</button>;
return <button className={styles.btn} {...props}>{children}</button>;
};
13 changes: 13 additions & 0 deletions components/Button/styles.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.btn {
padding: 10px;
background-color: var(--button-background-color);
color: var(--button-text-color);
border-radius: var(--button-border-radius);
border: none;
width: 100%;
cursor: pointer;
transition: .3s;
&:hover {
opacity: .8;
}
}
11 changes: 6 additions & 5 deletions components/BuyStock/BuyStock.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import styles from './styles.module.scss';
import { Button } from '@/components/Button';

interface Price {
units: string;
Expand All @@ -15,16 +16,16 @@ interface PriceInfo {
}

export const BuyStock: React.FC<{ info: PriceInfo }> = ({ info }) => {
const formattedDate = new Date(info.time).toLocaleString();
const formattedPrice = parseFloat(
`${info.price.units}.${info.price.nano / 1e9}`,
`${info?.price?.units}.${info?.price?.nano / 1e9}`,
).toFixed(2);

return (
<div className={styles.box}>
<div>Date: {formattedDate}</div>
<div>Price: {formattedPrice}</div>
<button className={styles.buyButton}>Buy</button>
<div className={styles.price}>Price: {formattedPrice}</div>
<div className={styles.btn}>
<Button>Buy</Button>
</div>
</div>
);
};
18 changes: 8 additions & 10 deletions components/BuyStock/styles.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,14 @@
text-align: center;
}

.buyButton {
margin-top: 15px;
padding: 10px 20px;
background-color: #4caf50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
.btn {
max-width: 120px;
margin: 0 auto;
}

.buyButton:hover {
background-color: #45a049;
.date {
margin-bottom: 0;
}
.price {
margin-bottom: 15px;
}
16 changes: 16 additions & 0 deletions components/Candles/Candles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use client';

import React from 'react';
import dynamic from 'next/dynamic';
import styles from './styles.module.scss';
const Chart = dynamic(() => import('./Chart/Chart'), { ssr: false });

const Candles = ({ data }: { data: any }) => {
return (
<div className={styles.chart}>
<Chart data={data} />
</div>
);
};

export default Candles;
55 changes: 55 additions & 0 deletions components/Candles/Chart/Chart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { FC } from 'react';
// @ts-ignore
import CanvasJSReact from '@canvasjs/react-charts';
const CanvasJSChart = CanvasJSReact.CanvasJSChart;

interface CandleData {
open: { units: string; nano: number };
high: { units: string; nano: number };
low: { units: string; nano: number };
close: { units: string; nano: number };
volume: string;
time: string;
isComplete: boolean;
candleSource: string;
}

interface CandlesProps {
data: {
candles: CandleData[];
};
}

const Candles: FC<CandlesProps> = ({ data }) => {
const dataPoints = data.candles.map((candle) => ({
x: new Date(candle.time),
y: [
parseFloat(`${candle.open.units}.${candle.open.nano}`),
parseFloat(`${candle.high.units}.${candle.high.nano}`),
parseFloat(`${candle.low.units}.${candle.low.nano}`),
parseFloat(`${candle.close.units}.${candle.close.nano}`),
],
}));

const options = {
theme: 'light2',
axisX: {
valueFormatString: 'DD MMM',
},
axisY: {
prefix: '$',
},
data: [
{
type: 'candlestick',
yValueFormatString: '$###0.00',
xValueFormatString: 'DD MMM YYYY',
dataPoints: dataPoints,
},
],
};

return <CanvasJSChart options={options} />;
};

export default Candles;
1 change: 1 addition & 0 deletions components/Candles/Chart/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Chart'
1 change: 1 addition & 0 deletions components/Candles/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Candles'
4 changes: 4 additions & 0 deletions components/Candles/styles.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.chart {
border-radius: 8px;
overflow: hidden;
}
1 change: 1 addition & 0 deletions components/ShareIntro/styles.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
min-height: 185px;
border-radius: var(--intro-shares-border-radius);
padding: 1em;
border: 1px solid var(--body-border-color);
}
.title {
font-size: 22px;
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
"lint": "next lint"
},
"dependencies": {
"@canvasjs/charts": "^3.10.3",
"@canvasjs/react-charts": "^1.0.2",
"@canvasjs/react-stockcharts": "^1.0.2",
"@reduxjs/toolkit": "^2.2.6",
"axios": "^1.7.2",
"classnames": "^2.5.1",
Expand Down
15 changes: 13 additions & 2 deletions services/shares/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { getLastPriceByTicker, getShares, getSharesByTicker } from '@/actions/shares';
import { IApiResponseShares, IPagination } from '@/typing';
import {
getLastPriceByTicker,
getShares,
getSharesByTicker,
getCandlesByTicker,
} from '@/actions/shares';
import { IPagination } from '@/typing';

class ServiceShares {
loading: boolean = false;
Expand Down Expand Up @@ -27,6 +32,12 @@ class ServiceShares {
} catch (e) {}
}

async getCandlesByTicker(ticker: string) {
try {
const res = await getCandlesByTicker(ticker);
return res;
} catch (e) {}
}

handleError(err: Error) {}
}
Expand Down
22 changes: 21 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,26 @@
# yarn lockfile v1


"@canvasjs/charts@^3.10.3", "@canvasjs/charts@^3.7.5":
version "3.10.3"
resolved "https://registry.npmjs.org/@canvasjs/charts/-/charts-3.10.3.tgz"
integrity sha512-R9MMtyXYPRfzs9l29o3K+jM3uTM7zfJ0IogEthATbe/tgWuBV0fIbw+qZ+CCPJy5StWy64RJlE0ys493WyyMvw==

"@canvasjs/react-charts@^1.0.2":
version "1.0.2"
resolved "https://registry.npmjs.org/@canvasjs/react-charts/-/react-charts-1.0.2.tgz"
integrity sha512-PZgJlDbGdMF4AN/KvrvGY9X50EByJMZ7MHfQB/U0aky9Onn9mt0CpsvwudBsBe+DofaV3SHR4SHn/Wfo/pubDw==

"@canvasjs/react-stockcharts@^1.0.2":
version "1.0.2"
resolved "https://registry.npmjs.org/@canvasjs/react-stockcharts/-/react-stockcharts-1.0.2.tgz"
integrity sha512-4KowrU35mEJhzXqWL7bc/3mPuwKUEODuhse5ya/Ciuz+vt2LzFeoDQavBBg98JtwUKhfyx3oHJdGLCzhKNHX8w==

"@canvasjs/stockcharts@^1.7.5":
version "1.10.3"
resolved "https://registry.npmjs.org/@canvasjs/stockcharts/-/stockcharts-1.10.3.tgz"
integrity sha512-e2PnI632EntKQqCxTK7fo1BDoaQHoFaoZV6BuiAxqtCSApyiMtIMGw8fiP/ErVOsgXSB5usKJkt4qBPzA0y/Hg==

"@eslint-community/eslint-utils@^4.2.0":
version "4.4.0"
resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz"
Expand Down Expand Up @@ -2027,7 +2047,7 @@ react-redux@*, "react-redux@^7.2.1 || ^8.1.3 || ^9.0.0", react-redux@^9.1.2:
"@types/use-sync-external-store" "^0.0.3"
use-sync-external-store "^1.0.0"

react@*, "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.9.0 || ^17.0.0 || ^18", react@^18, react@^18.0, react@^18.2.0, react@^18.3.1, "react@>= 16.8.0 || 17.x.x || ^18.0.0-0":
react@*, "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.9.0 || ^17.0.0 || ^18", react@^18, react@^18.0, react@^18.2.0, react@^18.3.1, "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", react@>=16.0.0:
version "18.3.1"
resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
Expand Down

0 comments on commit 57ccaff

Please sign in to comment.