Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/table-coins #8

Merged
merged 4 commits into from Mar 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
157 changes: 157 additions & 0 deletions src/app/components/CoinsMarketStats.tsx
@@ -0,0 +1,157 @@
import Image from "next/image";
import { useAppSelector } from "../../redux/store";
import { ChevronUpIcon } from "../icons/ChevronUpIcon";
import { ChevronDownIcon } from "../icons/ChevronDownIcon";
import { Progress } from "../components/ui/progress";
import formatNumber from "../../app/utils/formatNumber";
import TableGraph from "./TableGraph";
import { TableCell, TableRow } from "./ui/table";

type Coin = {
id: string;
image: string;
symbol: string;
current_price: number;
price_change_percentage_1h_in_currency: number;
price_change_percentage_24h_in_currency: number;
price_change_percentage_7d_in_currency: number;
total_volume: number;
market_cap: number;
circulating_supply: number;
total_supply: number;
sparkline_in_7d: SparklineIn7d;
};

type SparklineIn7d = {
price: number[];
};

type CoinsMarketStatsProps = {
coin: Coin;
index: number;
};

export default function CoinsMarketStats({
coin,
index,
}: CoinsMarketStatsProps) {
const currencySymbol = useAppSelector(
(state) => state.currency.currentCurrency.symbol
);
return (
<>
<tr className="bg-transparent h-2"></tr>
<TableRow className="dark:bg-[#191925] bg-white border-0">
<TableCell className="dark:text-white text-[#232336] text-base font-medium leading-6 rounded-tl-xl rounded-bl-xl">
{index + 1}
</TableCell>
<TableCell>
<div className="flex items-center gap-4">
<Image src={coin.image} alt={coin.id} width={32} height={32} />
<>
<span className="dark:text-white text-[#232336] text-base font-medium capitalize leading-6">
{coin.id}
</span>
<span className="dark:text-white text-[#232336] text-base font-medium leading-6 uppercase">
({coin.symbol})
</span>
</>
</div>
</TableCell>
<TableCell className="dark:text-white text-[#232336] text-base font-medium leading-6">
{currencySymbol}
{coin.current_price}
</TableCell>
<TableCell>
<div className="flex items-center gap-[6px]">
{coin.price_change_percentage_1h_in_currency > 0 ? (
<ChevronUpIcon />
) : (
<ChevronDownIcon />
)}
<span
className={`text-base font-medium leading-6 ${
coin.price_change_percentage_1h_in_currency > 0
? "text-[#00F0E2]"
: "text-[#FD2263]"
}`}
>{`${Math.abs(coin.price_change_percentage_1h_in_currency).toFixed(
2
)}%`}</span>
</div>
</TableCell>
<TableCell>
<div className="flex items-center gap-[6px]">
{coin.price_change_percentage_24h_in_currency > 0 ? (
<ChevronUpIcon />
) : (
<ChevronDownIcon />
)}
<span
className={`text-base font-medium leading-6 ${
coin.price_change_percentage_24h_in_currency > 0
? "text-[#00F0E2]"
: "text-[#FD2263]"
}`}
>{`${Math.abs(coin.price_change_percentage_24h_in_currency).toFixed(
2
)}%`}</span>
</div>
</TableCell>
<TableCell>
<div className="flex items-center gap-[6px]">
{coin.price_change_percentage_7d_in_currency > 0 ? (
<ChevronUpIcon />
) : (
<ChevronDownIcon />
)}
<span
className={`text-base font-medium leading-6 ${
coin.price_change_percentage_7d_in_currency > 0
? "text-[#00F0E2]"
: "text-[#FD2263]"
}`}
>{`${Math.abs(coin.price_change_percentage_7d_in_currency).toFixed(
2
)}%`}</span>
</div>
</TableCell>
<TableCell className="dark:text-white text-[#232336] text-base font-medium leading-6">
<div className="flex justify-between">
<span className="text-xs font-normal">
{currencySymbol}
{formatNumber(coin.total_volume)}
</span>
<span className="text-xs font-normal">
{currencySymbol}
{formatNumber(coin.market_cap)}
</span>
</div>
<Progress
className="w-full h-[6px] bg-gray-500"
value={(coin.total_volume / coin.market_cap) * 100}
indicator="bg-[#F7931A]"
/>
</TableCell>
<TableCell className="dark:text-white text-[#232336] text-base font-medium leading-6">
<div className="flex justify-between">
<span className="text-xs font-normal">
{formatNumber(coin.circulating_supply)}
</span>
<span className="text-xs font-normal">
{formatNumber(coin.total_supply)}
</span>
</div>
<Progress
className="w-full h-[6px] bg-gray-500"
value={(coin.circulating_supply / coin.total_supply) * 100}
indicator="bg-[#F7931A]"
/>
</TableCell>
<TableCell className="dark:text-white text-[#232336] text-base font-medium leading-6 rounded-tr-xl rounded-br-xl">
<TableGraph sparklineIn7Days={coin.sparkline_in_7d.price} />
</TableCell>
</TableRow>
</>
);
}
68 changes: 68 additions & 0 deletions src/app/components/TableCoins.tsx
@@ -0,0 +1,68 @@
"use client";
import { useEffect } from "react";
import { useDispatch } from "react-redux";
import { AppDispatch, useAppSelector } from "../../redux/store";
import { getCoinMarketData } from "../../redux/features/coinsTableSlice";
import { Table, TableBody, TableHead, TableHeader, TableRow } from "./ui/table";

import CoinsMarketStats from "./CoinsMarketStats";

export default function TableCoins() {
const dispatch: AppDispatch = useDispatch();
const { coins, hasError, currentPage } = useAppSelector(
(state) => state.coinsTable
);

const currencyCode = useAppSelector(
(state) => state.currency.currentCurrency.code
);

useEffect(() => {
dispatch(getCoinMarketData({ currency: currencyCode, page: currentPage }));
}, [dispatch, currencyCode, currentPage]);

if (hasError) {
return <div>Error fetching data</div>;
}

return (
<Table>
<TableHeader>
<TableRow>
<TableHead className="text-sm font-normal dark:text-[#D1D1D1] text-[#424286] leading-4">
#
</TableHead>
<TableHead className="text-sm font-normal dark:text-[#D1D1D1] text-[#424286] leading-4">
Name
</TableHead>
<TableHead className="text-sm font-normal dark:text-[#D1D1D1] text-[#424286] leading-4">
Price
</TableHead>
<TableHead className="text-sm font-normal dark:text-[#D1D1D1] text-[#424286] leading-4">
1h%
</TableHead>
<TableHead className="text-sm font-normal dark:text-[#D1D1D1] text-[#424286] leading-4">
24h%
</TableHead>
<TableHead className="text-sm font-normal dark:text-[#D1D1D1] text-[#424286] leading-4">
7d%
</TableHead>
<TableHead className="text-sm font-normal dark:text-[#D1D1D1] text-[#424286] leading-4">
24h volume / Market Cap
</TableHead>
<TableHead className="text-sm font-normal dark:text-[#D1D1D1] text-[#424286] leading-4">
Circulating / Total supply
</TableHead>
<TableHead className="w-[162px] text-sm font-normal dark:text-[#D1D1D1] text-[#424286] leading-4">
Last 7d
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{coins.map((coin, index) => (
<CoinsMarketStats coin={coin} index={index} key={coin.id} />
))}
</TableBody>
</Table>
);
}
32 changes: 32 additions & 0 deletions src/app/components/TableGraph.tsx
@@ -0,0 +1,32 @@
"use client";
import { ResponsiveContainer, AreaChart, Area, YAxis } from "recharts";

type TableGraphProps = {
sparklineIn7Days: number[];
};

export default function TableGraph({ sparklineIn7Days }: TableGraphProps) {
const formattedData = sparklineIn7Days.map((price) => ({
price,
}));
return (
<ResponsiveContainer width="100%" height={50}>
<AreaChart data={formattedData}>
<defs>
<linearGradient id="colorAreaChart" x1="0" y1="0" x2="0" y2="1">
<stop offset="80%" stopColor="#7474F2" stopOpacity={0.4} />
<stop offset="95%" stopColor="#7474F2" stopOpacity={0.1} />
</linearGradient>
</defs>
<YAxis hide={true} domain={["dataMin", "dataMax"]} />
<Area
type="monotone"
dataKey="price"
stroke="#7878FA"
strokeWidth="2"
fill="url(#colorAreaChart)"
/>
</AreaChart>
</ResponsiveContainer>
);
}
117 changes: 117 additions & 0 deletions src/app/components/ui/table.tsx
@@ -0,0 +1,117 @@
import * as React from "react";

import { cn } from "@/lib/utils";

const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
</div>
));
Table.displayName = "Table";

const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-0", className)} {...props} />
));
TableHeader.displayName = "TableHeader";

const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
));
TableBody.displayName = "TableBody";

const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn(
"border-t bg-slate-100/50 font-medium [&>tr]:last:border-b-0 dark:bg-slate-800/50",
className
)}
{...props}
/>
));
TableFooter.displayName = "TableFooter";

const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b transition-colors data-[state=selected]:bg-slate-100 dark:data-[state=selected]:bg-slate-800",
className
)}
{...props}
/>
));
TableRow.displayName = "TableRow";

const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-12 px-4 text-left align-middle font-medium text-slate-500 [&:has([role=checkbox])]:pr-0 dark:text-slate-400",
className
)}
{...props}
/>
));
TableHead.displayName = "TableHead";

const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
{...props}
/>
));
TableCell.displayName = "TableCell";

const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-slate-500 dark:text-slate-400", className)}
{...props}
/>
));
TableCaption.displayName = "TableCaption";

export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
};
12 changes: 9 additions & 3 deletions src/app/icons/ChevronDownIcon.tsx
@@ -1,10 +1,16 @@
export const ChevronDownIcon = () => {
return (
<svg width="17" height="16" viewBox="0 0 17 16" fill="none">
<svg
width="8"
height="4"
viewBox="0 0 8 4"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.39974 9.66699L11.7331 6.33366L5.06641 6.33366L8.39974 9.66699Z"
d="M3.99935 3.66699L7.33268 0.333659L0.666016 0.333659L3.99935 3.66699Z"
fill="#FE2264"
style={{ fill: "#FE2264", fillOpacity: 1 }}
style={{ fillOpacity: 1 }}
/>
</svg>
);
Expand Down