Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
yarnPath: .yarn/releases/yarn-3.6.1.cjs
compressionLevel: mixed

enableGlobalCache: false

nodeLinker: node-modules

yarnPath: .yarn/releases/yarn-3.6.1.cjs
45 changes: 45 additions & 0 deletions frontend/src/components/Card/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import './card.css';

import React from 'react';
import { useAppSelector } from '@/hooks/redux';
import { useCallback } from 'react';
import { selectShowCardInfo } from '@/store/dashboardOptionsSlice';
import { formatCurrencyUsd } from '@/utils/formatCurrencyUsd';
import CardHeader from './CardHeader';
import CardPrice from './CardPrice';
import CardBody from './CardBody';
import { useCardEffects } from '../../hooks/useCardEffects';
import { cn } from '@/utils/cn';

type CardProps = {
id: string;
onClick: (symbolId: string) => void;
price: number;
symbolId: string | null;
};

const Card = React.memo(({ id, onClick, price, symbolId }: CardProps) => {
const { trend, companyName, industry, marketCap } = useAppSelector(
(state) => state.stocks.entities[id]
);
const showCardInfo = useAppSelector(selectShowCardInfo);
// custom hook for bonus points.
const { glowClass, shakeClass, isSelected } = useCardEffects(price, symbolId, id);
const marketcapFormatted = formatCurrencyUsd(marketCap);
const priceFormatted = formatCurrencyUsd(price);

const handleOnClick = useCallback(() => {
onClick(id);
}, [onClick, id]);

return (
<div onClick={handleOnClick} className={cn('card', glowClass, isSelected, shakeClass)}>
<CardHeader trend={trend} id={id} />
<CardPrice price={priceFormatted} />
{showCardInfo && (
<CardBody companyName={companyName} industry={industry} marketCap={marketcapFormatted} />
)}
</div>
);
});
export default Card;
25 changes: 25 additions & 0 deletions frontend/src/components/Card/CardBody/CardBody.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import './cardBody.css';
import React from 'react';
import ListItem from '@/components/ListItem';
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';

type CardBodyProps = {
companyName: string;
industry: string;
marketCap: string;
};
const CardBody = React.memo(({ companyName, industry, marketCap }: CardBodyProps) => {
//If there where more listItems i would consider using a map function to render them reducing code duplication
//But since there are only 3, I will keep it simple
return (
<div className="card__body">
<ListItem Icon={<CompanyIcon />} label={companyName} spacing="space-between" />
<ListItem Icon={<IndustryIcon />} label={industry} spacing="space-between" />
<ListItem Icon={<MarketCapIcon />} label={marketCap} spacing="space-between" />
</div>
);
});

export default CardBody;
3 changes: 3 additions & 0 deletions frontend/src/components/Card/CardBody/cardBody.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.card__body {
padding: 0 10px 10px;
}
2 changes: 2 additions & 0 deletions frontend/src/components/Card/CardBody/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import CardBody from './CardBody';
export default CardBody;
22 changes: 22 additions & 0 deletions frontend/src/components/Card/CardHeader/CardHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import trendUp from '@/assets/up.png';
import trendDown from '@/assets/down.png';

import './cardHeader.css';

type CardHeaderProps = { trend: 'UP' | 'DOWN' | null; id: string };

const CardHeader = React.memo(({ trend, id }: CardHeaderProps) => {
return (
<div className="card__trend">
<div>{id}</div>
{trend === 'UP' ? (
<img src={trendUp} alt="Trend Up" className="card__icon" />
) : trend === 'DOWN' ? (
<img src={trendDown} alt="Trend Down" className="card__icon" />
) : null}
</div>
);
});

export default CardHeader;
20 changes: 20 additions & 0 deletions frontend/src/components/Card/CardHeader/cardHeader.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.card__trend {
display: flex;
justify-content: space-between;
align-items: center;
height: 30px;
font-size: 1.2rem;
font-weight: 700;
background-color: var(--colorDark);
color: var(--colorWhite);
padding: 0.5rem;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}

.card__icon {
position: relative;
width: 3.5rem;
top: -11px;
right: -29px;
}
2 changes: 2 additions & 0 deletions frontend/src/components/Card/CardHeader/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import CardHeader from './CardHeader';
export default CardHeader;
16 changes: 16 additions & 0 deletions frontend/src/components/Card/CardPrice/CardPrice.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import './cardPrice.css';
import React from 'react';

type CardPriceProps = {
price: string;
};
const CardPrice = React.memo(({ price }: CardPriceProps) => {
return (
<div className="card__content">
<div className="card__price--text">PRICE:</div>
<div className="card__price--value">{price || '--'} </div>
</div>
);
});

export default CardPrice;
15 changes: 15 additions & 0 deletions frontend/src/components/Card/CardPrice/cardPrice.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.card__content {
display: flex;
justify-content: space-between;
align-items: flex-end;
padding: 10px;
}
.card__price--text {
font-size: 0.8rem;
font-weight: 500;
}

.card__price--value {
font-size: 1.5rem;
font-weight: bold;
}
2 changes: 2 additions & 0 deletions frontend/src/components/Card/CardPrice/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import CardPrice from './CardPrice';
export default CardPrice;
63 changes: 63 additions & 0 deletions frontend/src/components/Card/card.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
.card {
flex: 1 1 200px;
min-width: 200px;
max-width: 350px;
border-radius: 4px;
background-color: var(--colorWhite);
filter: drop-shadow(0px 0px 4px rgba(0, 0, 0, 0.1));
transition: all 0.2s;
cursor: pointer;
}
.card--selected {
transform: scale(1.05);
box-shadow: 0px 0px 7px rgba(0, 0, 0, 1);
}

.cards__active .card:not(.card--selected) {
transform: scale(0.95);
}

.card__price--up {
box-shadow: 0px 0px 4px 8px rgba(132, 255, 0, 0.3);
}
.card__price--down {
box-shadow: 0px 0px 4px 8px rgba(255, 0, 0, 0.3);
}
.card__shake {
animation: shake 0.62s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
}

@media (max-width: 600px) {
.card {
flex: 1 1 100%;
max-width: 100%;
}
}
@keyframes shake {
10%,
90% {
transform: translateX(-2px);
}

20%,
80% {
transform: translateX(4px);
}

25%,
35%,
45%,
55%,
65%,
75% {
transform: translateX(-6px);
}

30%,
40%,
50%,
60%,
70% {
transform: translateX(6px);
}
}
2 changes: 2 additions & 0 deletions frontend/src/components/Card/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import Card from './Card';
export default Card;
2 changes: 1 addition & 1 deletion frontend/src/components/ListItem/ListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type ListItemProps = {
};
const ListItem = ({ Icon, label, spacing }: ListItemProps) => {
return (
<div style={{ justifyContent: spacing }} className={`listItem`}>
<div style={{ justifyContent: spacing }} className="listItem">
<div className="listItem__icon">{Icon}</div>
<div className="listItem__value">{label}</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Navbar/src/ToggleCardInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { toggleShowCardInfo, selectShowCardInfo } from '@/store/dashboardOptionsSlice'; // Adjust the import path
import './toggleCardInfo.css';
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
const ToggleCardInfo: React.FC = () => {
const ToggleCardInfo = () => {
const dispatch = useAppDispatch();
const showCardInfo = useAppSelector(selectShowCardInfo);

Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/PerformanceCard/src/VolumeLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ 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 { formatCurrencyUsd } from '@/utils/formatCurrencyUsd';

type TrendLabelProps = {
volume: number;
change: number;
};

const VolumeLabel = ({ volume, change }: TrendLabelProps) => {
const formattedVolume = formatCurrencyUsd(volume);
const arrow = change > 1 ? <UpArrow /> : <DownArrow />;
return <ListItem Icon={arrow} label={volume.toString()} />;
return <ListItem Icon={arrow} label={formattedVolume} />;
};
export default memo(VolumeLabel);
14 changes: 10 additions & 4 deletions frontend/src/components/PriceChart/PriceChart.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect } from 'react';
import React, { useEffect } from 'react';
import './priceChart.css';
import { Line, LineChart, XAxis, YAxis, ResponsiveContainer } from 'recharts';
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
Expand All @@ -8,12 +8,18 @@ type PriceChartProps = {
symbolId: string | null;
};

const PriceChart = ({ symbolId }: PriceChartProps) => {
const PriceChart = React.memo(({ symbolId }: PriceChartProps) => {
const dispatch = useAppDispatch();
useEffect(() => {
let promise: { abort: () => void } | undefined;
if (symbolId) {
dispatch(fetchPriceHistory(symbolId));
promise = dispatch(fetchPriceHistory(symbolId));
}
return () => {
if (promise && promise.abort) {
promise.abort();
}
};
}, [dispatch, symbolId]);

const apiState = useAppSelector(selectors.apiState);
Expand All @@ -40,6 +46,6 @@ const PriceChart = ({ symbolId }: PriceChartProps) => {
</ResponsiveContainer>
</div>
);
};
});

export default PriceChart;
28 changes: 0 additions & 28 deletions frontend/src/components/SymbolCard/SymbolCard.tsx

This file was deleted.

2 changes: 0 additions & 2 deletions frontend/src/components/SymbolCard/index.tsx

This file was deleted.

38 changes: 0 additions & 38 deletions frontend/src/components/SymbolCard/symbolCard.css

This file was deleted.

Loading