From d618caf7b4837f0d8ba47441654f84da6014e4b1 Mon Sep 17 00:00:00 2001 From: Enoxlaalu Date: Fri, 1 Nov 2024 11:23:41 +0300 Subject: [PATCH 01/13] added design and info to SymbolCard, added dumb components inside it --- .../src/components/SymbolCard/SymbolCard.tsx | 23 ++++++---- .../SymbolCardBody/SymbolCardBody.tsx | 44 +++++++++++++++++++ .../SymbolCard/SymbolCardBody/index.tsx | 2 + .../SymbolCardBody/symbolCardBody.css | 4 ++ .../SymbolCardHeader/SymbolCardHeader.tsx | 19 ++++++++ .../SymbolCard/SymbolCardHeader/index.tsx | 2 + .../SymbolCardHeader/symbolCardHeader.css | 15 +++++++ .../SymbolCardPrice/SymbolCardPrice.tsx | 17 +++++++ .../SymbolCard/SymbolCardPrice/index.tsx | 2 + .../SymbolCardPrice/symbolCardPrice.css | 14 ++++++ .../src/components/SymbolCard/symbolCard.css | 6 --- .../components/SymbolsGrid/SymbolsGrid.tsx | 3 +- .../components/SymbolsGrid/symbolsGrid.css | 5 +++ .../components/SymbolsView/SymbolsView.tsx | 20 ++++----- frontend/src/helpers/currency.ts | 11 +++++ frontend/src/store/stocksSlice.ts | 2 +- 16 files changed, 162 insertions(+), 27 deletions(-) create mode 100644 frontend/src/components/SymbolCard/SymbolCardBody/SymbolCardBody.tsx create mode 100644 frontend/src/components/SymbolCard/SymbolCardBody/index.tsx create mode 100644 frontend/src/components/SymbolCard/SymbolCardBody/symbolCardBody.css create mode 100644 frontend/src/components/SymbolCard/SymbolCardHeader/SymbolCardHeader.tsx create mode 100644 frontend/src/components/SymbolCard/SymbolCardHeader/index.tsx create mode 100644 frontend/src/components/SymbolCard/SymbolCardHeader/symbolCardHeader.css create mode 100644 frontend/src/components/SymbolCard/SymbolCardPrice/SymbolCardPrice.tsx create mode 100644 frontend/src/components/SymbolCard/SymbolCardPrice/index.tsx create mode 100644 frontend/src/components/SymbolCard/SymbolCardPrice/symbolCardPrice.css create mode 100644 frontend/src/components/SymbolsGrid/symbolsGrid.css create mode 100644 frontend/src/helpers/currency.ts diff --git a/frontend/src/components/SymbolCard/SymbolCard.tsx b/frontend/src/components/SymbolCard/SymbolCard.tsx index 248de34..f43e989 100644 --- a/frontend/src/components/SymbolCard/SymbolCard.tsx +++ b/frontend/src/components/SymbolCard/SymbolCard.tsx @@ -1,7 +1,7 @@ import './symbolCard.css'; -import { ReactComponent as CompanyIcon } from '@/assets/company.svg'; import { useAppSelector } from '@/hooks/redux'; -import ListItem from '@/components/ListItem'; +import SymbolCardHeader from '@/components/SymbolCard/SymbolCardHeader'; +import SymbolCardBody from '@/components/SymbolCard/SymbolCardBody'; type SymbolCardProps = { id: string; @@ -10,18 +10,23 @@ type SymbolCardProps = { }; const SymbolCard = ({ id, onClick, price }: SymbolCardProps) => { - const { trend, companyName } = useAppSelector((state) => state.stocks.entities[id]); + const { trend, companyName, industry, marketCap } = useAppSelector( + (state) => state.stocks.entities[id] + ); + const handleOnClick = () => { onClick(id); }; + return (
-
- {id} - {trend} -
-
Price:
-
{price || '--'}
- } label={companyName} /> + +
); }; diff --git a/frontend/src/components/SymbolCard/SymbolCardBody/SymbolCardBody.tsx b/frontend/src/components/SymbolCard/SymbolCardBody/SymbolCardBody.tsx new file mode 100644 index 0000000..ed0af4b --- /dev/null +++ b/frontend/src/components/SymbolCard/SymbolCardBody/SymbolCardBody.tsx @@ -0,0 +1,44 @@ +import ListItem from '@/components/ListItem'; +import './symbolCardBody.css'; +import { ReactComponent as CompanyIcon } from '@/assets/company.svg'; +import { ReactComponent as IndustryIcon } from '@/assets/industry.svg'; +import { ReactComponent as MarketCapIcon } from '@/assets/market_cap.svg'; +import SymbolCardPrice from '@/components/SymbolCard/SymbolCardPrice'; +import { Stock } from '@/store/stocksSlice'; +import { showShortenedAmount } from '@/helpers/currency'; + +type SymbolCardBodyProps = { + price: number; +} & Pick; + +const SymbolCardBody = ({ price, companyName, industry, marketCap }: SymbolCardBodyProps) => { + const listItems = [ + { + id: 'companyName', + label: companyName, + icon: CompanyIcon + }, + { + id: 'industry', + label: industry, + icon: IndustryIcon + }, + { + id: 'marketCap', + label: showShortenedAmount(marketCap), + icon: MarketCapIcon + } + ]; + + return ( +
+ + {listItems.map(({ id, label, icon }) => { + const Icon = icon; + return } label={label} spacing="space-between" />; + })} +
+ ); +}; + +export default SymbolCardBody; diff --git a/frontend/src/components/SymbolCard/SymbolCardBody/index.tsx b/frontend/src/components/SymbolCard/SymbolCardBody/index.tsx new file mode 100644 index 0000000..4dc5b23 --- /dev/null +++ b/frontend/src/components/SymbolCard/SymbolCardBody/index.tsx @@ -0,0 +1,2 @@ +import SymbolCardBody from './SymbolCardBody'; +export default SymbolCardBody; diff --git a/frontend/src/components/SymbolCard/SymbolCardBody/symbolCardBody.css b/frontend/src/components/SymbolCard/SymbolCardBody/symbolCardBody.css new file mode 100644 index 0000000..dca8d7c --- /dev/null +++ b/frontend/src/components/SymbolCard/SymbolCardBody/symbolCardBody.css @@ -0,0 +1,4 @@ +.symbolCard__body { + background-color: var(--colorWhite); + padding: 8px; +} diff --git a/frontend/src/components/SymbolCard/SymbolCardHeader/SymbolCardHeader.tsx b/frontend/src/components/SymbolCard/SymbolCardHeader/SymbolCardHeader.tsx new file mode 100644 index 0000000..0a01816 --- /dev/null +++ b/frontend/src/components/SymbolCard/SymbolCardHeader/SymbolCardHeader.tsx @@ -0,0 +1,19 @@ +import './symbolCardHeader.css'; +import arrowDown from '@/assets/down.png'; +import arrowUp from '@/assets/up.png'; + +type SymbolCardHeaderProps = { + id: string; + trend: string | null; +}; + +const SymbolCardHeader = ({ id, trend }: SymbolCardHeaderProps) => { + return ( +
+ + {id} +
+ ); +}; + +export default SymbolCardHeader; diff --git a/frontend/src/components/SymbolCard/SymbolCardHeader/index.tsx b/frontend/src/components/SymbolCard/SymbolCardHeader/index.tsx new file mode 100644 index 0000000..4c1c63b --- /dev/null +++ b/frontend/src/components/SymbolCard/SymbolCardHeader/index.tsx @@ -0,0 +1,2 @@ +import SymbolCardHeader from './SymbolCardHeader'; +export default SymbolCardHeader; diff --git a/frontend/src/components/SymbolCard/SymbolCardHeader/symbolCardHeader.css b/frontend/src/components/SymbolCard/SymbolCardHeader/symbolCardHeader.css new file mode 100644 index 0000000..c50445d --- /dev/null +++ b/frontend/src/components/SymbolCard/SymbolCardHeader/symbolCardHeader.css @@ -0,0 +1,15 @@ +.symbolCard__header { + position: relative; + background-color: var(--colorDark); + color: var(--colorWhite); + font-size: 18px; + font-weight: 500; + padding: 0 8px; +} + +.symbolCard__arrowIcon { + position: absolute; + top: -16px; + right: -16px; + width: 40px; +} diff --git a/frontend/src/components/SymbolCard/SymbolCardPrice/SymbolCardPrice.tsx b/frontend/src/components/SymbolCard/SymbolCardPrice/SymbolCardPrice.tsx new file mode 100644 index 0000000..596c7a5 --- /dev/null +++ b/frontend/src/components/SymbolCard/SymbolCardPrice/SymbolCardPrice.tsx @@ -0,0 +1,17 @@ +import { addCurrency, getInteger } from '@/helpers/currency'; +import './symbolCardPrice.css'; + +type SymbolCardPriceProps = { + price: number; +}; + +const SymbolCardPrice = ({ price }: SymbolCardPriceProps) => { + return ( +
+ Price: + {price ? addCurrency(getInteger(price)) : '--'} +
+ ); +}; + +export default SymbolCardPrice; diff --git a/frontend/src/components/SymbolCard/SymbolCardPrice/index.tsx b/frontend/src/components/SymbolCard/SymbolCardPrice/index.tsx new file mode 100644 index 0000000..10259e1 --- /dev/null +++ b/frontend/src/components/SymbolCard/SymbolCardPrice/index.tsx @@ -0,0 +1,2 @@ +import SymbolCardPrice from './SymbolCardPrice'; +export default SymbolCardPrice; diff --git a/frontend/src/components/SymbolCard/SymbolCardPrice/symbolCardPrice.css b/frontend/src/components/SymbolCard/SymbolCardPrice/symbolCardPrice.css new file mode 100644 index 0000000..8567361 --- /dev/null +++ b/frontend/src/components/SymbolCard/SymbolCardPrice/symbolCardPrice.css @@ -0,0 +1,14 @@ +.symbolCard__price { + display: flex; + align-items: center; + justify-content: space-between; +} + +.symbolCard__price span { + font-size: 12px; + text-transform: uppercase; +} + +.symbolCard__price b { + font-size: 24px; +} diff --git a/frontend/src/components/SymbolCard/symbolCard.css b/frontend/src/components/SymbolCard/symbolCard.css index 08f2b47..5a4311d 100644 --- a/frontend/src/components/SymbolCard/symbolCard.css +++ b/frontend/src/components/SymbolCard/symbolCard.css @@ -1,9 +1,3 @@ -.symbolCard { - border: 2px solid black; /* just for visibility, feel free to remove it */ - margin: 10px; /* just for visibility, feel free to remove it */ - padding: 10px; /* just for visibility, feel free to remove it */ -} - .symbolCard__shake { animation: shake 0.62s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; } diff --git a/frontend/src/components/SymbolsGrid/SymbolsGrid.tsx b/frontend/src/components/SymbolsGrid/SymbolsGrid.tsx index 10b1ae7..3253edb 100644 --- a/frontend/src/components/SymbolsGrid/SymbolsGrid.tsx +++ b/frontend/src/components/SymbolsGrid/SymbolsGrid.tsx @@ -1,3 +1,4 @@ +import './symbolsGrid.css'; import { useEffect } from 'react'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import SymbolCard from '../SymbolCard'; @@ -15,7 +16,7 @@ const SymbolsGrid = ({ onSymbolClick }: SymbolsGridProps) => { }, [dispatch]); return ( -
+
{stockSymbols.map((id, i) => ( ))} diff --git a/frontend/src/components/SymbolsGrid/symbolsGrid.css b/frontend/src/components/SymbolsGrid/symbolsGrid.css new file mode 100644 index 0000000..dcab296 --- /dev/null +++ b/frontend/src/components/SymbolsGrid/symbolsGrid.css @@ -0,0 +1,5 @@ +.symbolsGrid { + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-gap: 16px; +} diff --git a/frontend/src/components/SymbolsView/SymbolsView.tsx b/frontend/src/components/SymbolsView/SymbolsView.tsx index 129d46f..91efda6 100644 --- a/frontend/src/components/SymbolsView/SymbolsView.tsx +++ b/frontend/src/components/SymbolsView/SymbolsView.tsx @@ -10,18 +10,18 @@ const SymbolsView = () => { }; return ( -
- -
-

PRICE HISTORY

-
-
- -
- -
+
+ +
+

PRICE HISTORY

+
+
+ +
+
+
); }; diff --git a/frontend/src/helpers/currency.ts b/frontend/src/helpers/currency.ts new file mode 100644 index 0000000..5b30af9 --- /dev/null +++ b/frontend/src/helpers/currency.ts @@ -0,0 +1,11 @@ +const addCurrency = (value: number | string) => { + return `$${value}`; +}; + +const getInteger = (amount: number) => Math.ceil(amount); + +const showShortenedAmount = (amount: number) => { + return addCurrency(Intl.NumberFormat('en', { notation: 'compact' }).format(amount)); +}; + +export { addCurrency, getInteger, showShortenedAmount }; diff --git a/frontend/src/store/stocksSlice.ts b/frontend/src/store/stocksSlice.ts index e7eb911..25f09b0 100644 --- a/frontend/src/store/stocksSlice.ts +++ b/frontend/src/store/stocksSlice.ts @@ -1,7 +1,7 @@ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; import { RootState } from '@/store/index'; -type Stock = { +export type Stock = { symbol: string; companyName: string; industry: string; From 70efbac10ab096d3bb536340e5f8bbcd5dca5a6b Mon Sep 17 00:00:00 2001 From: Enoxlaalu Date: Fri, 1 Nov 2024 13:58:07 +0300 Subject: [PATCH 02/13] added shadow and shake animation to SymbolCard --- .../src/components/SymbolCard/SymbolCard.tsx | 32 +++++++++++++- .../src/components/SymbolCard/symbolCard.css | 44 ++++++++++++++++++- .../components/SymbolsGrid/SymbolsGrid.tsx | 13 +++++- .../components/SymbolsGrid/symbolsGrid.css | 2 +- .../components/SymbolsView/SymbolsView.tsx | 3 +- 5 files changed, 86 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/SymbolCard/SymbolCard.tsx b/frontend/src/components/SymbolCard/SymbolCard.tsx index f43e989..0ac3f5b 100644 --- a/frontend/src/components/SymbolCard/SymbolCard.tsx +++ b/frontend/src/components/SymbolCard/SymbolCard.tsx @@ -2,14 +2,17 @@ import './symbolCard.css'; import { useAppSelector } from '@/hooks/redux'; import SymbolCardHeader from '@/components/SymbolCard/SymbolCardHeader'; import SymbolCardBody from '@/components/SymbolCard/SymbolCardBody'; +import { useRef } from 'react'; type SymbolCardProps = { id: string; onClick: (symbolId: string) => void; price: number; + activeSymbol: string | null; }; -const SymbolCard = ({ id, onClick, price }: SymbolCardProps) => { +const SymbolCard = ({ id, onClick, price, activeSymbol }: SymbolCardProps) => { + const ref = useRef(null); const { trend, companyName, industry, marketCap } = useAppSelector( (state) => state.stocks.entities[id] ); @@ -18,8 +21,33 @@ const SymbolCard = ({ id, onClick, price }: SymbolCardProps) => { onClick(id); }; + const getClassName = () => { + let className = 'symbolCard'; + + if (activeSymbol) { + if (activeSymbol === id) className = className + ' symbolCard--active'; + if (activeSymbol !== id) className = className + ' symbolCard--nonactive'; + } + + const prevPrice = Number(ref.current?.dataset.price); + const increasePercent = (100 * (price - prevPrice)) / prevPrice; + + if (price > prevPrice) { + if (increasePercent >= 25) { + className = className + ' symbolCard--shake'; + } + + className = className + ' symbolCard--green'; + } + if (price < Number(ref.current?.dataset.price)) { + className = className + ' symbolCard--red'; + } + + return className; + }; + return ( -
+
void; + activeSymbol: string | null; }; -const SymbolsGrid = ({ onSymbolClick }: SymbolsGridProps) => { +const SymbolsGrid = ({ onSymbolClick, activeSymbol }: SymbolsGridProps) => { const stockSymbols = useAppSelector(selectors.selectStockIds); const prices = useAppSelector((state) => state.prices); const dispatch = useAppDispatch(); + useEffect(() => { dispatch(fetchAllStocks()); }, [dispatch]); @@ -18,7 +21,13 @@ const SymbolsGrid = ({ onSymbolClick }: SymbolsGridProps) => { return (
{stockSymbols.map((id, i) => ( - + ))}
); diff --git a/frontend/src/components/SymbolsGrid/symbolsGrid.css b/frontend/src/components/SymbolsGrid/symbolsGrid.css index dcab296..a76aa30 100644 --- a/frontend/src/components/SymbolsGrid/symbolsGrid.css +++ b/frontend/src/components/SymbolsGrid/symbolsGrid.css @@ -1,5 +1,5 @@ .symbolsGrid { display: grid; grid-template-columns: repeat(4, 1fr); - grid-gap: 16px; + grid-gap: 24px; } diff --git a/frontend/src/components/SymbolsView/SymbolsView.tsx b/frontend/src/components/SymbolsView/SymbolsView.tsx index 91efda6..99649e8 100644 --- a/frontend/src/components/SymbolsView/SymbolsView.tsx +++ b/frontend/src/components/SymbolsView/SymbolsView.tsx @@ -5,6 +5,7 @@ import { useState } from 'react'; const SymbolsView = () => { const [activeSymbol, setActiveSymbol] = useState(null); + const handleSymbolClick = (symbolId: string) => { setActiveSymbol((s) => (s === symbolId ? null : symbolId)); }; @@ -18,7 +19,7 @@ const SymbolsView = () => {
- +
From e9ea8c2f9de2012d914632e25ae56c3df42a5217 Mon Sep 17 00:00:00 2001 From: Enoxlaalu Date: Sun, 3 Nov 2024 15:41:41 +0700 Subject: [PATCH 03/13] improved performance of SymbolCard by adding components and memoizing them --- frontend/package.json | 2 +- frontend/src/components/ListItem/ListItem.tsx | 4 +-- .../src/components/SymbolCard/SymbolCard.tsx | 9 +++-- .../SymbolCardBody/SymbolCardBody.tsx | 33 +++-------------- .../SymbolCardHeader/SymbolCardHeader.tsx | 5 +-- .../SymbolCardList/SymbolCardList.tsx | 36 +++++++++++++++++++ .../SymbolCard/SymbolCardList/index.tsx | 3 ++ frontend/yarn.lock | 14 ++++++-- yarn.lock | 14 ++++++-- 9 files changed, 79 insertions(+), 41 deletions(-) create mode 100644 frontend/src/components/SymbolCard/SymbolCardList/SymbolCardList.tsx create mode 100644 frontend/src/components/SymbolCard/SymbolCardList/index.tsx diff --git a/frontend/package.json b/frontend/package.json index fadd90d..0405070 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,7 +22,7 @@ }, "devDependencies": { "@types/node": "^22.5.4", - "@types/react": "^18.0.28", + "@types/react": "latest", "@types/react-dom": "^18.0.11", "@types/vite-plugin-react-svg": "^0.2.2", "@vitejs/plugin-react": "^3.1.0", diff --git a/frontend/src/components/ListItem/ListItem.tsx b/frontend/src/components/ListItem/ListItem.tsx index f6c3b7a..c996969 100644 --- a/frontend/src/components/ListItem/ListItem.tsx +++ b/frontend/src/components/ListItem/ListItem.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { memo } from 'react'; import './listItem.css'; type ListItemProps = { Icon: React.ReactNode; @@ -23,4 +23,4 @@ const ListItem = ({ Icon, label, spacing }: ListItemProps) => { ); }; -export default ListItem; +export default memo(ListItem); diff --git a/frontend/src/components/SymbolCard/SymbolCard.tsx b/frontend/src/components/SymbolCard/SymbolCard.tsx index 0ac3f5b..9738d6d 100644 --- a/frontend/src/components/SymbolCard/SymbolCard.tsx +++ b/frontend/src/components/SymbolCard/SymbolCard.tsx @@ -2,7 +2,7 @@ import './symbolCard.css'; import { useAppSelector } from '@/hooks/redux'; import SymbolCardHeader from '@/components/SymbolCard/SymbolCardHeader'; import SymbolCardBody from '@/components/SymbolCard/SymbolCardBody'; -import { useRef } from 'react'; +import { memo, useRef } from 'react'; type SymbolCardProps = { id: string; @@ -11,7 +11,7 @@ type SymbolCardProps = { activeSymbol: string | null; }; -const SymbolCard = ({ id, onClick, price, activeSymbol }: SymbolCardProps) => { +const SymbolCard = memo(({ id, onClick, price, activeSymbol }: SymbolCardProps) => { const ref = useRef(null); const { trend, companyName, industry, marketCap } = useAppSelector( (state) => state.stocks.entities[id] @@ -57,5 +57,8 @@ const SymbolCard = ({ id, onClick, price, activeSymbol }: SymbolCardProps) => { />
); -}; +}); + export default SymbolCard; + +SymbolCard.displayName = 'SymbolCard'; diff --git a/frontend/src/components/SymbolCard/SymbolCardBody/SymbolCardBody.tsx b/frontend/src/components/SymbolCard/SymbolCardBody/SymbolCardBody.tsx index ed0af4b..c6ac548 100644 --- a/frontend/src/components/SymbolCard/SymbolCardBody/SymbolCardBody.tsx +++ b/frontend/src/components/SymbolCard/SymbolCardBody/SymbolCardBody.tsx @@ -1,42 +1,17 @@ -import ListItem from '@/components/ListItem'; import './symbolCardBody.css'; -import { ReactComponent as CompanyIcon } from '@/assets/company.svg'; -import { ReactComponent as IndustryIcon } from '@/assets/industry.svg'; -import { ReactComponent as MarketCapIcon } from '@/assets/market_cap.svg'; import SymbolCardPrice from '@/components/SymbolCard/SymbolCardPrice'; import { Stock } from '@/store/stocksSlice'; -import { showShortenedAmount } from '@/helpers/currency'; +import SymbolCardList from '@/components/SymbolCard/SymbolCardList'; -type SymbolCardBodyProps = { +export type SymbolCardBodyProps = { price: number; } & Pick; -const SymbolCardBody = ({ price, companyName, industry, marketCap }: SymbolCardBodyProps) => { - const listItems = [ - { - id: 'companyName', - label: companyName, - icon: CompanyIcon - }, - { - id: 'industry', - label: industry, - icon: IndustryIcon - }, - { - id: 'marketCap', - label: showShortenedAmount(marketCap), - icon: MarketCapIcon - } - ]; - +const SymbolCardBody = ({ price, ...rest }: SymbolCardBodyProps) => { return (
- {listItems.map(({ id, label, icon }) => { - const Icon = icon; - return } label={label} spacing="space-between" />; - })} +
); }; diff --git a/frontend/src/components/SymbolCard/SymbolCardHeader/SymbolCardHeader.tsx b/frontend/src/components/SymbolCard/SymbolCardHeader/SymbolCardHeader.tsx index 0a01816..7d35e29 100644 --- a/frontend/src/components/SymbolCard/SymbolCardHeader/SymbolCardHeader.tsx +++ b/frontend/src/components/SymbolCard/SymbolCardHeader/SymbolCardHeader.tsx @@ -1,19 +1,20 @@ import './symbolCardHeader.css'; import arrowDown from '@/assets/down.png'; import arrowUp from '@/assets/up.png'; +import { memo } from 'react'; type SymbolCardHeaderProps = { id: string; trend: string | null; }; -const SymbolCardHeader = ({ id, trend }: SymbolCardHeaderProps) => { +const SymbolCardHeader = memo(({ id, trend }: SymbolCardHeaderProps) => { return (
{id}
); -}; +}); export default SymbolCardHeader; diff --git a/frontend/src/components/SymbolCard/SymbolCardList/SymbolCardList.tsx b/frontend/src/components/SymbolCard/SymbolCardList/SymbolCardList.tsx new file mode 100644 index 0000000..42d473f --- /dev/null +++ b/frontend/src/components/SymbolCard/SymbolCardList/SymbolCardList.tsx @@ -0,0 +1,36 @@ +import ListItem from '@/components/ListItem'; +import { SymbolCardBodyProps } from '@/components/SymbolCard/SymbolCardBody/SymbolCardBody'; +import { showShortenedAmount } from '@/helpers/currency'; +import { ReactComponent as CompanyIcon } from '@/assets/company.svg'; +import { ReactComponent as IndustryIcon } from '@/assets/industry.svg'; +import { ReactComponent as MarketCapIcon } from '@/assets/market_cap.svg'; +import { memo } from 'react'; + +type SymbolCardListProps = Omit; + +const SymbolCardList = ({ companyName, industry, marketCap }: SymbolCardListProps) => { + const listItems = [ + { + id: 'companyName', + label: companyName, + icon: CompanyIcon + }, + { + id: 'industry', + label: industry, + icon: IndustryIcon + }, + { + id: 'marketCap', + label: showShortenedAmount(marketCap), + icon: MarketCapIcon + } + ]; + + return listItems.map(({ id, label, icon }) => { + const Icon = icon; + return } label={label} spacing="space-between" />; + }); +}; + +export default memo(SymbolCardList); diff --git a/frontend/src/components/SymbolCard/SymbolCardList/index.tsx b/frontend/src/components/SymbolCard/SymbolCardList/index.tsx new file mode 100644 index 0000000..046daf3 --- /dev/null +++ b/frontend/src/components/SymbolCard/SymbolCardList/index.tsx @@ -0,0 +1,3 @@ +import SymbolCardList from './SymbolCardList'; + +export default SymbolCardList; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index a24c0b9..872606f 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -850,7 +850,7 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:*, @types/react@npm:^18.0.28": +"@types/react@npm:*": version: 18.0.31 resolution: "@types/react@npm:18.0.31" dependencies: @@ -861,6 +861,16 @@ __metadata: languageName: node linkType: hard +"@types/react@npm:latest": + version: 18.3.12 + resolution: "@types/react@npm:18.3.12" + dependencies: + "@types/prop-types": "*" + csstype: ^3.0.2 + checksum: 4ab1577a8c2105a5e316536f724117c90eee5f4bd5c137fc82a2253d8c1fd299dedaa07e8dfc95d6e2f04a4be3cb8b0e1b06098c6233ebd55c508d88099395b7 + languageName: node + linkType: hard + "@types/scheduler@npm:*": version: 0.16.3 resolution: "@types/scheduler@npm:0.16.3" @@ -1767,7 +1777,7 @@ __metadata: dependencies: "@reduxjs/toolkit": ^1.9.3 "@types/node": ^22.5.4 - "@types/react": ^18.0.28 + "@types/react": latest "@types/react-dom": ^18.0.11 "@types/vite-plugin-react-svg": ^0.2.2 "@vitejs/plugin-react": ^3.1.0 diff --git a/yarn.lock b/yarn.lock index 0141185..eeef3c2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -824,7 +824,7 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:*, @types/react@npm:^18.0.28": +"@types/react@npm:*": version: 18.3.5 resolution: "@types/react@npm:18.3.5" dependencies: @@ -834,6 +834,16 @@ __metadata: languageName: node linkType: hard +"@types/react@npm:^18.3.12": + version: 18.3.12 + resolution: "@types/react@npm:18.3.12" + dependencies: + "@types/prop-types": "*" + csstype: ^3.0.2 + checksum: 4ab1577a8c2105a5e316536f724117c90eee5f4bd5c137fc82a2253d8c1fd299dedaa07e8dfc95d6e2f04a4be3cb8b0e1b06098c6233ebd55c508d88099395b7 + languageName: node + linkType: hard + "@types/strip-bom@npm:^3.0.0": version: 3.0.0 resolution: "@types/strip-bom@npm:3.0.0" @@ -2303,7 +2313,7 @@ __metadata: dependencies: "@reduxjs/toolkit": ^1.9.3 "@types/node": ^22.5.4 - "@types/react": ^18.0.28 + "@types/react": ^18.3.12 "@types/react-dom": ^18.0.11 "@types/vite-plugin-react-svg": ^0.2.2 "@vitejs/plugin-react": ^3.1.0 From 8bc2856e63392e879030495cf9c6530455b87529 Mon Sep 17 00:00:00 2001 From: Enoxlaalu Date: Sun, 3 Nov 2024 16:12:57 +0700 Subject: [PATCH 04/13] made chart and cards layout responsive on mobile and desktop --- .../src/components/PriceChart/priceChart.css | 3 +- .../components/SymbolsGrid/symbolsGrid.css | 2 +- .../components/SymbolsView/SymbolsView.tsx | 10 ++++--- .../components/SymbolsView/symbolsView.css | 28 +++++++++++++++++++ 4 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 frontend/src/components/SymbolsView/symbolsView.css diff --git a/frontend/src/components/PriceChart/priceChart.css b/frontend/src/components/PriceChart/priceChart.css index 6d8cb45..b9bfe87 100644 --- a/frontend/src/components/PriceChart/priceChart.css +++ b/frontend/src/components/PriceChart/priceChart.css @@ -1,7 +1,8 @@ .priceChart { + display: grid; + grid-template-columns: auto 1fr; width: 400px; height: 300px; - display: flex; } @media (max-width: 1023px) { diff --git a/frontend/src/components/SymbolsGrid/symbolsGrid.css b/frontend/src/components/SymbolsGrid/symbolsGrid.css index a76aa30..8197da1 100644 --- a/frontend/src/components/SymbolsGrid/symbolsGrid.css +++ b/frontend/src/components/SymbolsGrid/symbolsGrid.css @@ -1,5 +1,5 @@ .symbolsGrid { display: grid; - grid-template-columns: repeat(4, 1fr); + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); grid-gap: 24px; } diff --git a/frontend/src/components/SymbolsView/SymbolsView.tsx b/frontend/src/components/SymbolsView/SymbolsView.tsx index 99649e8..17871bd 100644 --- a/frontend/src/components/SymbolsView/SymbolsView.tsx +++ b/frontend/src/components/SymbolsView/SymbolsView.tsx @@ -3,6 +3,8 @@ import PriceChart from '@/components/PriceChart'; import DesktopInfo from './src/DesktopInfo'; import { useState } from 'react'; +import './symbolsView.css'; + const SymbolsView = () => { const [activeSymbol, setActiveSymbol] = useState(null); @@ -13,11 +15,11 @@ const SymbolsView = () => { return (
-
-

PRICE HISTORY

-
- +
+

PRICE HISTORY

+ +
diff --git a/frontend/src/components/SymbolsView/symbolsView.css b/frontend/src/components/SymbolsView/symbolsView.css new file mode 100644 index 0000000..81848c3 --- /dev/null +++ b/frontend/src/components/SymbolsView/symbolsView.css @@ -0,0 +1,28 @@ +.symbolsView { + display: grid; +} + +.symbolsView__content { + display: grid; +} + +.symbolsView__chart { + padding: 12px; +} + +.symbolsView__cards { + padding: 16px; + background-color: #dbdbdb; + overflow: auto; + scrollbar-width: none; +} + +@media (min-width: 1024px) { + .symbolsView__content { + grid-template-columns: 1fr 400px; + } + + .symbolsView__chart { + order: 1; + } +} From 7b138c10e04b574f4a42511ca73c02b2e4484f42 Mon Sep 17 00:00:00 2001 From: Enoxlaalu Date: Sun, 3 Nov 2024 16:21:44 +0700 Subject: [PATCH 05/13] added logic for showing and hiding info in cards --- frontend/src/components/SymbolCard/SymbolCard.tsx | 3 +++ .../components/SymbolCard/SymbolCardBody/SymbolCardBody.tsx | 5 +++-- .../components/SymbolCard/SymbolCardList/SymbolCardList.tsx | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/SymbolCard/SymbolCard.tsx b/frontend/src/components/SymbolCard/SymbolCard.tsx index 9738d6d..a6ba9c4 100644 --- a/frontend/src/components/SymbolCard/SymbolCard.tsx +++ b/frontend/src/components/SymbolCard/SymbolCard.tsx @@ -3,6 +3,7 @@ import { useAppSelector } from '@/hooks/redux'; import SymbolCardHeader from '@/components/SymbolCard/SymbolCardHeader'; import SymbolCardBody from '@/components/SymbolCard/SymbolCardBody'; import { memo, useRef } from 'react'; +import { selectShowCardInfo } from '@/store/dashboardOptionsSlice'; type SymbolCardProps = { id: string; @@ -16,6 +17,7 @@ const SymbolCard = memo(({ id, onClick, price, activeSymbol }: SymbolCardProps) const { trend, companyName, industry, marketCap } = useAppSelector( (state) => state.stocks.entities[id] ); + const showCardInfo = useAppSelector(selectShowCardInfo); const handleOnClick = () => { onClick(id); @@ -54,6 +56,7 @@ const SymbolCard = memo(({ id, onClick, price, activeSymbol }: SymbolCardProps) price={price} industry={industry} marketCap={marketCap} + showCardInfo={showCardInfo} />
); diff --git a/frontend/src/components/SymbolCard/SymbolCardBody/SymbolCardBody.tsx b/frontend/src/components/SymbolCard/SymbolCardBody/SymbolCardBody.tsx index c6ac548..1d300fc 100644 --- a/frontend/src/components/SymbolCard/SymbolCardBody/SymbolCardBody.tsx +++ b/frontend/src/components/SymbolCard/SymbolCardBody/SymbolCardBody.tsx @@ -5,13 +5,14 @@ import SymbolCardList from '@/components/SymbolCard/SymbolCardList'; export type SymbolCardBodyProps = { price: number; + showCardInfo: boolean; } & Pick; -const SymbolCardBody = ({ price, ...rest }: SymbolCardBodyProps) => { +const SymbolCardBody = ({ price, showCardInfo, ...rest }: SymbolCardBodyProps) => { return (
- + {showCardInfo && }
); }; diff --git a/frontend/src/components/SymbolCard/SymbolCardList/SymbolCardList.tsx b/frontend/src/components/SymbolCard/SymbolCardList/SymbolCardList.tsx index 42d473f..0f40109 100644 --- a/frontend/src/components/SymbolCard/SymbolCardList/SymbolCardList.tsx +++ b/frontend/src/components/SymbolCard/SymbolCardList/SymbolCardList.tsx @@ -6,7 +6,7 @@ import { ReactComponent as IndustryIcon } from '@/assets/industry.svg'; import { ReactComponent as MarketCapIcon } from '@/assets/market_cap.svg'; import { memo } from 'react'; -type SymbolCardListProps = Omit; +type SymbolCardListProps = Omit; const SymbolCardList = ({ companyName, industry, marketCap }: SymbolCardListProps) => { const listItems = [ From 5d17bfa52962211ed1167d06977d94919e5f4765 Mon Sep 17 00:00:00 2001 From: Enoxlaalu Date: Sun, 3 Nov 2024 17:48:32 +0700 Subject: [PATCH 06/13] added suspense to router and fixed responsive design --- frontend/src/App.css | 3 +++ .../src/components/PriceChart/priceChart.css | 2 -- .../SymbolsView/src/desktopInfo.css | 4 ++++ .../components/SymbolsView/symbolsView.css | 9 ++++--- frontend/src/router/index.tsx | 24 ++++++++++++++----- 5 files changed, 29 insertions(+), 13 deletions(-) diff --git a/frontend/src/App.css b/frontend/src/App.css index c608d44..040acba 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -76,12 +76,15 @@ select { #root { height: 100vh; width: 100%; + display: grid; } .App { padding: 12px 24px 0; background-color: rgba(14, 17, 35, 0.02); min-height: 100%; + display: grid; + align-content: start; } @media (max-width: 767px) { diff --git a/frontend/src/components/PriceChart/priceChart.css b/frontend/src/components/PriceChart/priceChart.css index b9bfe87..143b6a8 100644 --- a/frontend/src/components/PriceChart/priceChart.css +++ b/frontend/src/components/PriceChart/priceChart.css @@ -1,6 +1,4 @@ .priceChart { - display: grid; - grid-template-columns: auto 1fr; width: 400px; height: 300px; } diff --git a/frontend/src/components/SymbolsView/src/desktopInfo.css b/frontend/src/components/SymbolsView/src/desktopInfo.css index efe9d55..157aaca 100644 --- a/frontend/src/components/SymbolsView/src/desktopInfo.css +++ b/frontend/src/components/SymbolsView/src/desktopInfo.css @@ -1,3 +1,7 @@ +.desktopInfo { + overflow: hidden; +} + @media (max-width: 1023px) { .desktopInfo { display: none; diff --git a/frontend/src/components/SymbolsView/symbolsView.css b/frontend/src/components/SymbolsView/symbolsView.css index 81848c3..3a93ae5 100644 --- a/frontend/src/components/SymbolsView/symbolsView.css +++ b/frontend/src/components/SymbolsView/symbolsView.css @@ -1,9 +1,7 @@ -.symbolsView { - display: grid; -} - +.symbolsView, .symbolsView__content { display: grid; + overflow: hidden; } .symbolsView__chart { @@ -13,7 +11,8 @@ .symbolsView__cards { padding: 16px; background-color: #dbdbdb; - overflow: auto; + overflow-y: auto; + overflow-x: hidden; scrollbar-width: none; } diff --git a/frontend/src/router/index.tsx b/frontend/src/router/index.tsx index 105644d..f6a76c9 100644 --- a/frontend/src/router/index.tsx +++ b/frontend/src/router/index.tsx @@ -1,16 +1,28 @@ -import SymbolsView from '@/components/SymbolsView'; -import { Route, Routes, Navigate } from 'react-router-dom'; -import StatementsView from "@/components/StatementsView"; -import ProfileView from "@/components/ProfileView"; +import { Route, Routes, Navigate, Outlet } from 'react-router-dom'; +import { lazy, Suspense } from 'react'; + +const SymbolsView = lazy(() => import('@/components/SymbolsView')); +const StatementsView = lazy(() => import('@/components/StatementsView')); +const ProfileView = lazy(() => import('@/components/ProfileView')); + +function Layout() { + return ( + + + + ); +} const Router = () => { return ( - + + }> } /> } /> } /> } /> - + + ); }; From f2caad15757150a771d5637c4a706c6d913ca77c Mon Sep 17 00:00:00 2001 From: Enoxlaalu Date: Mon, 4 Nov 2024 06:51:46 +0700 Subject: [PATCH 07/13] put shadow and effect states in separate custom hooks --- .../src/components/SymbolCard/SymbolCard.tsx | 49 +++++++++++++------ .../src/components/SymbolCard/symbolCard.css | 6 --- .../components/SymbolsView/symbolsView.css | 2 +- frontend/src/hooks/useAddBoxShadow.ts | 19 +++++++ frontend/src/hooks/useAddEffect.ts | 17 +++++++ 5 files changed, 70 insertions(+), 23 deletions(-) create mode 100644 frontend/src/hooks/useAddBoxShadow.ts create mode 100644 frontend/src/hooks/useAddEffect.ts diff --git a/frontend/src/components/SymbolCard/SymbolCard.tsx b/frontend/src/components/SymbolCard/SymbolCard.tsx index a6ba9c4..ed034b3 100644 --- a/frontend/src/components/SymbolCard/SymbolCard.tsx +++ b/frontend/src/components/SymbolCard/SymbolCard.tsx @@ -2,8 +2,10 @@ import './symbolCard.css'; import { useAppSelector } from '@/hooks/redux'; import SymbolCardHeader from '@/components/SymbolCard/SymbolCardHeader'; import SymbolCardBody from '@/components/SymbolCard/SymbolCardBody'; -import { memo, useRef } from 'react'; +import { memo, useEffect, useRef } from 'react'; import { selectShowCardInfo } from '@/store/dashboardOptionsSlice'; +import useAddBoxShadow from '@/hooks/useAddBoxShadow'; +import useAddEffect from '@/hooks/useAddEffect'; type SymbolCardProps = { id: string; @@ -14,6 +16,9 @@ type SymbolCardProps = { const SymbolCard = memo(({ id, onClick, price, activeSymbol }: SymbolCardProps) => { const ref = useRef(null); + const ref2 = useRef(price); + const { shadow, addShadow, setShadow } = useAddBoxShadow(); + const { effect, addEffect, setEffect } = useAddEffect(); const { trend, companyName, industry, marketCap } = useAppSelector( (state) => state.stocks.entities[id] ); @@ -23,33 +28,45 @@ const SymbolCard = memo(({ id, onClick, price, activeSymbol }: SymbolCardProps) onClick(id); }; + useEffect(() => { + addShadow(price, ref2.current); + }, [price]); + + useEffect(() => { + addEffect(price, ref2.current); + ref2.current = price; + }, [activeSymbol, price]); + const getClassName = () => { let className = 'symbolCard'; if (activeSymbol) { - if (activeSymbol === id) className = className + ' symbolCard--active'; - if (activeSymbol !== id) className = className + ' symbolCard--nonactive'; + if (activeSymbol === id) { + className = className + ` symbolCard--active`; + } else className = className + ` symbolCard--nonactive`; } - const prevPrice = Number(ref.current?.dataset.price); - const increasePercent = (100 * (price - prevPrice)) / prevPrice; + if (effect) className = className + ` symbolCard--${effect}`; + if (shadow) className = className + ` symbolCard--${shadow}`; - if (price > prevPrice) { - if (increasePercent >= 25) { - className = className + ' symbolCard--shake'; - } + return className; + }; - className = className + ' symbolCard--green'; + const onAnimationEnd = () => { + if (ref.current) { + setShadow(''); + setEffect(''); } - if (price < Number(ref.current?.dataset.price)) { - className = className + ' symbolCard--red'; - } - - return className; }; return ( -
+
{ + const [shadow, setShadow] = useState(''); + + const addShadow = (price: number, prevPrice = 0) => { + if (price > prevPrice) { + setShadow('green'); + return; + } + + if (price < prevPrice) { + setShadow('red'); + return; + } + }; + + return { shadow, addShadow, setShadow }; +}; diff --git a/frontend/src/hooks/useAddEffect.ts b/frontend/src/hooks/useAddEffect.ts new file mode 100644 index 0000000..91c43df --- /dev/null +++ b/frontend/src/hooks/useAddEffect.ts @@ -0,0 +1,17 @@ +import { useState } from 'react'; + +export default () => { + const [effect, setEffect] = useState(''); + + const addEffect = (price: number, prevPrice: number) => { + if (price > prevPrice) { + const increasePercent = (100 * (price - prevPrice)) / prevPrice; + + if (increasePercent >= 25) { + setEffect('shake'); + } + } + }; + + return { effect, setEffect, addEffect }; +}; From 58a97ef508c016871d0704ae4a1757d0931683c0 Mon Sep 17 00:00:00 2001 From: Enoxlaalu Date: Mon, 4 Nov 2024 16:21:06 +0700 Subject: [PATCH 08/13] added formatting of the currency for VolumeLabel --- frontend/src/components/PerformanceCard/src/VolumeLabel.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/PerformanceCard/src/VolumeLabel.tsx b/frontend/src/components/PerformanceCard/src/VolumeLabel.tsx index d20f3f2..75175a4 100644 --- a/frontend/src/components/PerformanceCard/src/VolumeLabel.tsx +++ b/frontend/src/components/PerformanceCard/src/VolumeLabel.tsx @@ -3,6 +3,7 @@ import './performanceInfo.css'; import { ReactComponent as UpArrow } from '@/assets/up-arrow.svg'; import { ReactComponent as DownArrow } from '@/assets/down-arrow.svg'; import ListItem from '@/components/ListItem'; +import { showShortenedAmount } from '@/helpers/currency'; type TrendLabelProps = { volume: number; @@ -11,6 +12,6 @@ type TrendLabelProps = { const VolumeLabel = ({ volume, change }: TrendLabelProps) => { const arrow = change > 1 ? : ; - return ; + return ; }; export default memo(VolumeLabel); From 94e0962d989223cf95561a9d332c8f754b48d982 Mon Sep 17 00:00:00 2001 From: Enoxlaalu Date: Mon, 4 Nov 2024 16:34:22 +0700 Subject: [PATCH 09/13] Made active card id to be saved to localStorage --- frontend/src/components/SymbolCard/SymbolCard.tsx | 8 ++++---- frontend/src/components/SymbolsView/SymbolsView.tsx | 11 +++++++++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/SymbolCard/SymbolCard.tsx b/frontend/src/components/SymbolCard/SymbolCard.tsx index ed034b3..e8de490 100644 --- a/frontend/src/components/SymbolCard/SymbolCard.tsx +++ b/frontend/src/components/SymbolCard/SymbolCard.tsx @@ -16,7 +16,7 @@ type SymbolCardProps = { const SymbolCard = memo(({ id, onClick, price, activeSymbol }: SymbolCardProps) => { const ref = useRef(null); - const ref2 = useRef(price); + const priceRef = useRef(price); const { shadow, addShadow, setShadow } = useAddBoxShadow(); const { effect, addEffect, setEffect } = useAddEffect(); const { trend, companyName, industry, marketCap } = useAppSelector( @@ -29,12 +29,12 @@ const SymbolCard = memo(({ id, onClick, price, activeSymbol }: SymbolCardProps) }; useEffect(() => { - addShadow(price, ref2.current); + addShadow(price, priceRef.current); }, [price]); useEffect(() => { - addEffect(price, ref2.current); - ref2.current = price; + addEffect(price, priceRef.current); + priceRef.current = price; }, [activeSymbol, price]); const getClassName = () => { diff --git a/frontend/src/components/SymbolsView/SymbolsView.tsx b/frontend/src/components/SymbolsView/SymbolsView.tsx index 17871bd..032a343 100644 --- a/frontend/src/components/SymbolsView/SymbolsView.tsx +++ b/frontend/src/components/SymbolsView/SymbolsView.tsx @@ -6,10 +6,17 @@ import { useState } from 'react'; import './symbolsView.css'; const SymbolsView = () => { - const [activeSymbol, setActiveSymbol] = useState(null); + const savedSymbol = localStorage.getItem('activeSymbol'); + + const [activeSymbol, setActiveSymbol] = useState(savedSymbol || null); const handleSymbolClick = (symbolId: string) => { - setActiveSymbol((s) => (s === symbolId ? null : symbolId)); + setActiveSymbol((s) => { + const newActiveSymbol = s === symbolId ? null : symbolId; + localStorage.setItem('activeSymbol', newActiveSymbol!); + + return newActiveSymbol; + }); }; return ( From 7fd8a188cfaeb022e8077d39820c18c3668d9956 Mon Sep 17 00:00:00 2001 From: Enoxlaalu Date: Mon, 4 Nov 2024 16:39:16 +0700 Subject: [PATCH 10/13] fixed folder structure --- frontend/src/components/SymbolCard/SymbolCard.tsx | 4 ++-- frontend/src/components/SymbolCard/SymbolCardBody/index.tsx | 2 -- frontend/src/components/SymbolCard/SymbolCardHeader/index.tsx | 2 -- frontend/src/components/SymbolCard/SymbolCardList/index.tsx | 3 --- frontend/src/components/SymbolCard/SymbolCardPrice/index.tsx | 2 -- .../SymbolCard/{SymbolCardBody => src}/SymbolCardBody.tsx | 4 ++-- .../SymbolCard/{SymbolCardHeader => src}/SymbolCardHeader.tsx | 0 .../SymbolCard/{SymbolCardList => src}/SymbolCardList.tsx | 2 +- .../SymbolCard/{SymbolCardPrice => src}/SymbolCardPrice.tsx | 0 .../SymbolCard/{SymbolCardBody => src}/symbolCardBody.css | 0 .../SymbolCard/{SymbolCardHeader => src}/symbolCardHeader.css | 0 .../SymbolCard/{SymbolCardPrice => src}/symbolCardPrice.css | 0 12 files changed, 5 insertions(+), 14 deletions(-) delete mode 100644 frontend/src/components/SymbolCard/SymbolCardBody/index.tsx delete mode 100644 frontend/src/components/SymbolCard/SymbolCardHeader/index.tsx delete mode 100644 frontend/src/components/SymbolCard/SymbolCardList/index.tsx delete mode 100644 frontend/src/components/SymbolCard/SymbolCardPrice/index.tsx rename frontend/src/components/SymbolCard/{SymbolCardBody => src}/SymbolCardBody.tsx (76%) rename frontend/src/components/SymbolCard/{SymbolCardHeader => src}/SymbolCardHeader.tsx (100%) rename frontend/src/components/SymbolCard/{SymbolCardList => src}/SymbolCardList.tsx (91%) rename frontend/src/components/SymbolCard/{SymbolCardPrice => src}/SymbolCardPrice.tsx (100%) rename frontend/src/components/SymbolCard/{SymbolCardBody => src}/symbolCardBody.css (100%) rename frontend/src/components/SymbolCard/{SymbolCardHeader => src}/symbolCardHeader.css (100%) rename frontend/src/components/SymbolCard/{SymbolCardPrice => src}/symbolCardPrice.css (100%) diff --git a/frontend/src/components/SymbolCard/SymbolCard.tsx b/frontend/src/components/SymbolCard/SymbolCard.tsx index e8de490..46a05b9 100644 --- a/frontend/src/components/SymbolCard/SymbolCard.tsx +++ b/frontend/src/components/SymbolCard/SymbolCard.tsx @@ -1,11 +1,11 @@ import './symbolCard.css'; import { useAppSelector } from '@/hooks/redux'; -import SymbolCardHeader from '@/components/SymbolCard/SymbolCardHeader'; -import SymbolCardBody from '@/components/SymbolCard/SymbolCardBody'; import { memo, useEffect, useRef } from 'react'; import { selectShowCardInfo } from '@/store/dashboardOptionsSlice'; import useAddBoxShadow from '@/hooks/useAddBoxShadow'; import useAddEffect from '@/hooks/useAddEffect'; +import SymbolCardBody from '@/components/SymbolCard/src/SymbolCardBody'; +import SymbolCardHeader from '@/components/SymbolCard/src/SymbolCardHeader'; type SymbolCardProps = { id: string; diff --git a/frontend/src/components/SymbolCard/SymbolCardBody/index.tsx b/frontend/src/components/SymbolCard/SymbolCardBody/index.tsx deleted file mode 100644 index 4dc5b23..0000000 --- a/frontend/src/components/SymbolCard/SymbolCardBody/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -import SymbolCardBody from './SymbolCardBody'; -export default SymbolCardBody; diff --git a/frontend/src/components/SymbolCard/SymbolCardHeader/index.tsx b/frontend/src/components/SymbolCard/SymbolCardHeader/index.tsx deleted file mode 100644 index 4c1c63b..0000000 --- a/frontend/src/components/SymbolCard/SymbolCardHeader/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -import SymbolCardHeader from './SymbolCardHeader'; -export default SymbolCardHeader; diff --git a/frontend/src/components/SymbolCard/SymbolCardList/index.tsx b/frontend/src/components/SymbolCard/SymbolCardList/index.tsx deleted file mode 100644 index 046daf3..0000000 --- a/frontend/src/components/SymbolCard/SymbolCardList/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import SymbolCardList from './SymbolCardList'; - -export default SymbolCardList; diff --git a/frontend/src/components/SymbolCard/SymbolCardPrice/index.tsx b/frontend/src/components/SymbolCard/SymbolCardPrice/index.tsx deleted file mode 100644 index 10259e1..0000000 --- a/frontend/src/components/SymbolCard/SymbolCardPrice/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -import SymbolCardPrice from './SymbolCardPrice'; -export default SymbolCardPrice; diff --git a/frontend/src/components/SymbolCard/SymbolCardBody/SymbolCardBody.tsx b/frontend/src/components/SymbolCard/src/SymbolCardBody.tsx similarity index 76% rename from frontend/src/components/SymbolCard/SymbolCardBody/SymbolCardBody.tsx rename to frontend/src/components/SymbolCard/src/SymbolCardBody.tsx index 1d300fc..b0584fa 100644 --- a/frontend/src/components/SymbolCard/SymbolCardBody/SymbolCardBody.tsx +++ b/frontend/src/components/SymbolCard/src/SymbolCardBody.tsx @@ -1,7 +1,7 @@ +import SymbolCardList from '@/components/SymbolCard/src/SymbolCardList'; +import SymbolCardPrice from '@/components/SymbolCard/src/SymbolCardPrice'; import './symbolCardBody.css'; -import SymbolCardPrice from '@/components/SymbolCard/SymbolCardPrice'; import { Stock } from '@/store/stocksSlice'; -import SymbolCardList from '@/components/SymbolCard/SymbolCardList'; export type SymbolCardBodyProps = { price: number; diff --git a/frontend/src/components/SymbolCard/SymbolCardHeader/SymbolCardHeader.tsx b/frontend/src/components/SymbolCard/src/SymbolCardHeader.tsx similarity index 100% rename from frontend/src/components/SymbolCard/SymbolCardHeader/SymbolCardHeader.tsx rename to frontend/src/components/SymbolCard/src/SymbolCardHeader.tsx diff --git a/frontend/src/components/SymbolCard/SymbolCardList/SymbolCardList.tsx b/frontend/src/components/SymbolCard/src/SymbolCardList.tsx similarity index 91% rename from frontend/src/components/SymbolCard/SymbolCardList/SymbolCardList.tsx rename to frontend/src/components/SymbolCard/src/SymbolCardList.tsx index 0f40109..e30a949 100644 --- a/frontend/src/components/SymbolCard/SymbolCardList/SymbolCardList.tsx +++ b/frontend/src/components/SymbolCard/src/SymbolCardList.tsx @@ -1,5 +1,5 @@ import ListItem from '@/components/ListItem'; -import { SymbolCardBodyProps } from '@/components/SymbolCard/SymbolCardBody/SymbolCardBody'; +import { SymbolCardBodyProps } from '@/components/SymbolCard/src/SymbolCardBody'; import { showShortenedAmount } from '@/helpers/currency'; import { ReactComponent as CompanyIcon } from '@/assets/company.svg'; import { ReactComponent as IndustryIcon } from '@/assets/industry.svg'; diff --git a/frontend/src/components/SymbolCard/SymbolCardPrice/SymbolCardPrice.tsx b/frontend/src/components/SymbolCard/src/SymbolCardPrice.tsx similarity index 100% rename from frontend/src/components/SymbolCard/SymbolCardPrice/SymbolCardPrice.tsx rename to frontend/src/components/SymbolCard/src/SymbolCardPrice.tsx diff --git a/frontend/src/components/SymbolCard/SymbolCardBody/symbolCardBody.css b/frontend/src/components/SymbolCard/src/symbolCardBody.css similarity index 100% rename from frontend/src/components/SymbolCard/SymbolCardBody/symbolCardBody.css rename to frontend/src/components/SymbolCard/src/symbolCardBody.css diff --git a/frontend/src/components/SymbolCard/SymbolCardHeader/symbolCardHeader.css b/frontend/src/components/SymbolCard/src/symbolCardHeader.css similarity index 100% rename from frontend/src/components/SymbolCard/SymbolCardHeader/symbolCardHeader.css rename to frontend/src/components/SymbolCard/src/symbolCardHeader.css diff --git a/frontend/src/components/SymbolCard/SymbolCardPrice/symbolCardPrice.css b/frontend/src/components/SymbolCard/src/symbolCardPrice.css similarity index 100% rename from frontend/src/components/SymbolCard/SymbolCardPrice/symbolCardPrice.css rename to frontend/src/components/SymbolCard/src/symbolCardPrice.css From ca2972f4eb4603a5ed21ded8578c172963fada5a Mon Sep 17 00:00:00 2001 From: Enoxlaalu Date: Mon, 4 Nov 2024 17:50:58 +0700 Subject: [PATCH 11/13] fixed race condition in price chart --- frontend/src/components/PriceChart/PriceChart.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/PriceChart/PriceChart.tsx b/frontend/src/components/PriceChart/PriceChart.tsx index 3846e8d..de0e14c 100644 --- a/frontend/src/components/PriceChart/PriceChart.tsx +++ b/frontend/src/components/PriceChart/PriceChart.tsx @@ -10,9 +10,11 @@ type PriceChartProps = { const PriceChart = ({ symbolId }: PriceChartProps) => { const dispatch = useAppDispatch(); + useEffect(() => { if (symbolId) { - dispatch(fetchPriceHistory(symbolId)); + const promise = dispatch(fetchPriceHistory(symbolId)); + return () => promise?.abort(); } }, [dispatch, symbolId]); From 26ba58b808e441ed1b27fc362b47467fef8b94e2 Mon Sep 17 00:00:00 2001 From: Enoxlaalu Date: Mon, 4 Nov 2024 21:21:42 +0700 Subject: [PATCH 12/13] fixed trends and symbolCard performance --- frontend/src/components/PriceChart/PriceChart.tsx | 2 +- .../src/components/SymbolCard/src/SymbolCardBody.tsx | 3 ++- .../src/components/SymbolCard/src/SymbolCardHeader.tsx | 9 +++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/PriceChart/PriceChart.tsx b/frontend/src/components/PriceChart/PriceChart.tsx index de0e14c..1d06788 100644 --- a/frontend/src/components/PriceChart/PriceChart.tsx +++ b/frontend/src/components/PriceChart/PriceChart.tsx @@ -33,7 +33,7 @@ const PriceChart = ({ symbolId }: PriceChartProps) => { return (
{symbolInfo}
- + ({ ...e, time: new Date(e.time).toLocaleTimeString() }))}> diff --git a/frontend/src/components/SymbolCard/src/SymbolCardBody.tsx b/frontend/src/components/SymbolCard/src/SymbolCardBody.tsx index b0584fa..c1cef36 100644 --- a/frontend/src/components/SymbolCard/src/SymbolCardBody.tsx +++ b/frontend/src/components/SymbolCard/src/SymbolCardBody.tsx @@ -2,6 +2,7 @@ import SymbolCardList from '@/components/SymbolCard/src/SymbolCardList'; import SymbolCardPrice from '@/components/SymbolCard/src/SymbolCardPrice'; import './symbolCardBody.css'; import { Stock } from '@/store/stocksSlice'; +import { memo } from 'react'; export type SymbolCardBodyProps = { price: number; @@ -17,4 +18,4 @@ const SymbolCardBody = ({ price, showCardInfo, ...rest }: SymbolCardBodyProps) = ); }; -export default SymbolCardBody; +export default memo(SymbolCardBody); diff --git a/frontend/src/components/SymbolCard/src/SymbolCardHeader.tsx b/frontend/src/components/SymbolCard/src/SymbolCardHeader.tsx index 7d35e29..e1a55b2 100644 --- a/frontend/src/components/SymbolCard/src/SymbolCardHeader.tsx +++ b/frontend/src/components/SymbolCard/src/SymbolCardHeader.tsx @@ -5,13 +5,18 @@ import { memo } from 'react'; type SymbolCardHeaderProps = { id: string; - trend: string | null; + trend: 'UP' | 'DOWN' | null; }; const SymbolCardHeader = memo(({ id, trend }: SymbolCardHeaderProps) => { return (
- + {trend && ( + + )} {id}
); From eb6bee99da034ef4a63ed83e372599d688643ef1 Mon Sep 17 00:00:00 2001 From: Enoxlaalu Date: Mon, 4 Nov 2024 21:37:11 +0700 Subject: [PATCH 13/13] fixed bad behavior of active item from localstorage --- frontend/src/components/PriceChart/PriceChart.tsx | 3 ++- frontend/src/components/PriceChart/priceChart.css | 2 ++ frontend/src/components/SymbolsView/SymbolsView.tsx | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/PriceChart/PriceChart.tsx b/frontend/src/components/PriceChart/PriceChart.tsx index 1d06788..3e78a09 100644 --- a/frontend/src/components/PriceChart/PriceChart.tsx +++ b/frontend/src/components/PriceChart/PriceChart.tsx @@ -22,12 +22,13 @@ const PriceChart = ({ symbolId }: PriceChartProps) => { const data = useAppSelector(selectors.selectPriceHistory); const symbolInfo = useAppSelector(selectors.selectSymbolInfo); - if (apiState.loading && symbolId !== null) + if (apiState.loading && symbolId !== null) { return (
); + } if (apiState.error) return
Failed to get price history!
; if (!symbolId) return
Select stock
; return ( diff --git a/frontend/src/components/PriceChart/priceChart.css b/frontend/src/components/PriceChart/priceChart.css index 143b6a8..18636a6 100644 --- a/frontend/src/components/PriceChart/priceChart.css +++ b/frontend/src/components/PriceChart/priceChart.css @@ -1,4 +1,6 @@ .priceChart { + display: flex; + flex-direction: column; width: 400px; height: 300px; } diff --git a/frontend/src/components/SymbolsView/SymbolsView.tsx b/frontend/src/components/SymbolsView/SymbolsView.tsx index 032a343..0f1b20c 100644 --- a/frontend/src/components/SymbolsView/SymbolsView.tsx +++ b/frontend/src/components/SymbolsView/SymbolsView.tsx @@ -13,7 +13,7 @@ const SymbolsView = () => { const handleSymbolClick = (symbolId: string) => { setActiveSymbol((s) => { const newActiveSymbol = s === symbolId ? null : symbolId; - localStorage.setItem('activeSymbol', newActiveSymbol!); + localStorage.setItem('activeSymbol', newActiveSymbol || ''); return newActiveSymbol; });