-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
57a86f3
commit 638d611
Showing
44 changed files
with
2,353 additions
and
1,492 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { Appbar } from '@/components/Appbar' | ||
export default function RootLayout({ | ||
children, | ||
}: { | ||
children: React.ReactNode | ||
}) { | ||
|
||
return ( | ||
<html lang="en"> | ||
<body> | ||
<Appbar/> | ||
{children} | ||
</body> | ||
</html> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
"use client" | ||
|
||
import { useState, useEffect } from "react" | ||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" | ||
import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table" | ||
import { ArrowDown, ArrowUp } from "lucide-react" | ||
import { getCrypto } from "@/app/utils/ServerProps" | ||
import Image from "next/image" | ||
import { useRouter } from "next/navigation" | ||
|
||
interface CryptoData { | ||
symbol: string | ||
current_price: string | ||
priceChangePercent: string | ||
volume: string | ||
marketCap: string | ||
image: string | ||
name: string | ||
} | ||
|
||
export default function CryptoList() { | ||
const [cryptoData, setCryptoData] = useState<CryptoData[]>([]) | ||
const [sortColumn, setSortColumn] = useState<keyof CryptoData>("marketCap") | ||
const [sortDirection, setSortDirection] = useState<"asc" | "desc">("desc") | ||
const [activeTab, setActiveTab] = useState("all") | ||
const router = useRouter() | ||
|
||
const fetchData = async () => { | ||
try { | ||
const data = await getCrypto() | ||
const formattedData: CryptoData[] = data.map((item: any) => ({ | ||
image: item.image, | ||
symbol: item.symbol, | ||
current_price: item.current_price, | ||
name: item.name, | ||
priceChangePercent: parseFloat(item.price_change_percentage_24h).toFixed(2), | ||
volume: formatVolume(parseFloat(item.total_volume)), | ||
marketCap: formatMarketCap(parseFloat(item.market_cap)), | ||
})) | ||
setCryptoData(formattedData) | ||
} catch (error) { | ||
console.error("Error fetching data:", error) | ||
} | ||
} | ||
|
||
const formatVolume = (volume: number): string => { | ||
if (volume >= 1e12) return `${(volume / 1e12).toFixed(2)}T` | ||
if (volume >= 1e9) return `${(volume / 1e9).toFixed(2)}B` | ||
if (volume >= 1e6) return `${(volume / 1e6).toFixed(2)}M` | ||
return volume.toFixed(2) | ||
} | ||
|
||
const formatMarketCap = (marketCap: number): string => { | ||
if (marketCap >= 1e12) return `$${(marketCap / 1e12).toFixed(2)}T` | ||
if (marketCap >= 1e9) return `$${(marketCap / 1e9).toFixed(2)}B` | ||
if (marketCap >= 1e6) return `$${(marketCap / 1e6).toFixed(2)}M` | ||
return `$${marketCap.toFixed(2)}` | ||
} | ||
|
||
useEffect(() => { | ||
fetchData() | ||
const interval = setInterval(fetchData, 2000) | ||
return () => clearInterval(interval) | ||
}, []) | ||
|
||
const sortData = (column: keyof CryptoData) => { | ||
if (column === sortColumn) { | ||
setSortDirection(sortDirection === "asc" ? "desc" : "asc") | ||
} else { | ||
setSortColumn(column) | ||
setSortDirection("desc") | ||
} | ||
} | ||
|
||
const handleTabClick = (tab: string) => { | ||
setActiveTab(tab) | ||
if (tab === "24h") { | ||
setSortColumn("priceChangePercent") | ||
} else if (tab === "volume") { | ||
setSortColumn("volume") | ||
} else if (tab === "marketCap") { | ||
setSortColumn("marketCap") | ||
} | ||
setSortDirection("desc") | ||
} | ||
|
||
const sortedData = [...cryptoData].sort((a, b) => { | ||
const aValue = parseFloat(a[sortColumn].replace(/[^\d.-]/g, "")) | ||
const bValue = parseFloat(b[sortColumn].replace(/[^\d.-]/g, "")) | ||
return sortDirection === "asc" ? aValue - bValue : bValue - aValue | ||
}) | ||
|
||
const renderTable = (data: CryptoData[]) => ( | ||
<div className="overflow-x-auto"> | ||
<Table> | ||
<TableBody> | ||
{data.map((crypto) => ( | ||
<TableRow | ||
key={crypto.symbol} | ||
className="hover:cursor-pointer" | ||
onClick={() => router.push(`/trade/${crypto.symbol}usdt`)} | ||
> | ||
<TableCell className="font-medium flex items-center space-x-2"> | ||
<Image src={crypto.image} alt={crypto.name} width={20} height={20} /> | ||
<span className="hidden sm:inline">{crypto.name}</span> | ||
<span className="sm:hidden">{crypto.symbol.toUpperCase()}</span> | ||
</TableCell> | ||
<TableCell className="text-right">${crypto.current_price}</TableCell> | ||
<TableCell | ||
className={`text-right ${ | ||
crypto.priceChangePercent[0] === "-" ? "text-red-500" : "text-green-500" | ||
}`} | ||
> | ||
{crypto.priceChangePercent}% | ||
</TableCell> | ||
<TableCell className="text-right hidden sm:table-cell">{crypto.volume}</TableCell> | ||
<TableCell className="text-right hidden md:table-cell">{crypto.marketCap}</TableCell> | ||
</TableRow> | ||
))} | ||
</TableBody> | ||
</Table> | ||
</div> | ||
) | ||
|
||
return ( | ||
<div className="container mx-auto py-4 px-2 sm:px-4 md:px-6"> | ||
<Tabs value={activeTab} onValueChange={handleTabClick} className="w-full"> | ||
<TabsList className="grid grid-cols-2 sm:grid-cols-4 w-1/2 rounded-lg"> | ||
<TabsTrigger value="all">All</TabsTrigger> | ||
<TabsTrigger value="24h">24h Change</TabsTrigger> | ||
<TabsTrigger value="volume">Volume</TabsTrigger> | ||
<TabsTrigger value="marketCap">Market Cap</TabsTrigger> | ||
</TabsList> | ||
<div className="mt-4"> | ||
<div className="flex justify-between items-center mb-2"> | ||
<h2 className="text-lg font-semibold">{activeTab === "all" ? "All Cryptocurrencies" : `Sorted by ${activeTab}`}</h2> | ||
<button onClick={() => sortData(sortColumn)} className="flex items-center text-sm"> | ||
{sortDirection === "asc" ? <ArrowUp className="h-4 w-4 mr-1" /> : <ArrowDown className="h-4 w-4 mr-1" />} | ||
{sortColumn === "priceChangePercent" ? "24h" : sortColumn} | ||
</button> | ||
</div> | ||
{renderTable(sortedData)} | ||
</div> | ||
</Tabs> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
"use client"; | ||
|
||
import { useState, useEffect } from "react"; | ||
import useWebSocket from "react-use-websocket"; | ||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; | ||
import { Ask } from "@/components/depth/AskTable"; | ||
import { Bid } from "@/components/depth/BidTable"; | ||
import { OrderUI } from "@/components/OrderUI"; | ||
import { useParams } from "next/navigation"; | ||
import { MarketBar } from "@/components/MarketBar"; | ||
import TradeViewChart from "@/components/TradeView"; | ||
import { Skeleton } from "@/components/ui/skeleton"; | ||
import { TradeViewChartSkeleton } from "@/components/Skeletons/TradingViewSkeleton"; | ||
import { MarketBarSkeleton } from "@/components/Skeletons/MarketBarSkeleton"; | ||
import { AskSkeleton } from "@/components/Skeletons/AskBidSkeleton"; | ||
|
||
type Order = [string, string]; | ||
type OrderBookUpdate = { | ||
e: string; | ||
E: number; | ||
s: string; | ||
U: number; | ||
u: number; | ||
b: Order[]; | ||
a: Order[]; | ||
}; | ||
|
||
type OrderBookState = { | ||
bids: Map<string, string>; | ||
asks: Map<string, string>; | ||
}; | ||
|
||
export default function Markets() { | ||
const { market } = useParams(); | ||
const [orderBook, setOrderBook] = useState<OrderBookState>({ | ||
bids: new Map(), | ||
asks: new Map(), | ||
}); | ||
const [isLoading, setIsLoading] = useState(true); | ||
const { lastJsonMessage, readyState } = useWebSocket( | ||
`wss://stream.binance.com:9443/ws/${market}@depth` | ||
); | ||
|
||
useEffect(() => { | ||
if (lastJsonMessage) { | ||
const update = lastJsonMessage as OrderBookUpdate; | ||
setIsLoading(false); | ||
setOrderBook((prevOrderBook) => { | ||
const newBids = new Map(prevOrderBook.bids); | ||
const newAsks = new Map(prevOrderBook.asks); | ||
|
||
update.b.forEach(([price, quantity]) => { | ||
if (parseFloat(quantity) === 0) { | ||
newBids.delete(price); | ||
} else { | ||
newBids.set(price, quantity); | ||
} | ||
}); | ||
|
||
update.a.forEach(([price, quantity]) => { | ||
if (parseFloat(quantity) === 0) { | ||
newAsks.delete(price); | ||
} else { | ||
newAsks.set(price, quantity); | ||
} | ||
}); | ||
|
||
return { bids: newBids, asks: newAsks }; | ||
}); | ||
setIsLoading(false); | ||
} | ||
}, [lastJsonMessage]); | ||
|
||
const LoadingSkeleton = () => ( | ||
<div className="space-y-2"> | ||
<Skeleton className="h-4 w-[250px]" /> | ||
<Skeleton className="h-4 w-[200px]" /> | ||
</div> | ||
); | ||
|
||
return ( | ||
<div className="container mx-auto p-4"> | ||
<MarketBar market={market as string} /> | ||
<div className="flex gap-4"> | ||
<Card className="mb-4 flex-1 w-[4/7] border-none"> | ||
<div className="h-[400px]"> | ||
{isLoading ? ( | ||
<TradeViewChartSkeleton /> | ||
) : ( | ||
<TradeViewChart market={market.toString()} /> | ||
)} | ||
</div> | ||
</Card> | ||
|
||
<div className="hidden sm:flex flex-col md:grid-cols-2 gap-4"> | ||
{isLoading ? <AskSkeleton /> : <Ask asks={orderBook.asks} />} | ||
{isLoading ? <AskSkeleton /> : <Bid bids={orderBook.bids} />} | ||
</div> | ||
|
||
<div className="flex h-full justify-items-end"> | ||
{isLoading ? ( | ||
<Skeleton className="h-[400px] w-[300px]" /> | ||
) : ( | ||
<OrderUI market={market as string} /> | ||
)} | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,76 +1,6 @@ | ||
import NextAuth, { NextAuthOptions } from "next-auth" | ||
import GoogleProvider from "next-auth/providers/google" | ||
import CredentialsProvider from "next-auth/providers/credentials" | ||
import { PrismaAdapter } from "@next-auth/prisma-adapter" | ||
import { PrismaClient } from "@prisma/client" | ||
import bcrypt from "bcryptjs" | ||
import { authOptions } from "@/lib/auth" | ||
import NextAuth from "next-auth" | ||
|
||
const prisma = new PrismaClient() | ||
|
||
export const authOptions: NextAuthOptions = { | ||
adapter: PrismaAdapter(prisma), | ||
providers: [ | ||
GoogleProvider({ | ||
clientId: process.env.GOOGLE_CLIENT_ID!, | ||
clientSecret: process.env.GOOGLE_CLIENT_SECRET!, | ||
}), | ||
CredentialsProvider({ | ||
name: 'Credentials', | ||
credentials: { | ||
email: { label: "Email", type: "text" }, | ||
password: { label: "Password", type: "password" } | ||
}, | ||
async authorize(credentials) { | ||
if (!credentials?.email || !credentials?.password) { | ||
return null | ||
} | ||
|
||
const user = await prisma.user.findUnique({ | ||
where: { email: credentials.email } | ||
}) | ||
//@ts-ignore | ||
if (!user || !user.password) { | ||
return null | ||
} | ||
//@ts-ignore | ||
const isPasswordValid = await bcrypt.compare(credentials.password, user.password) | ||
|
||
if (!isPasswordValid) { | ||
return null | ||
} | ||
|
||
return { | ||
id: user.id, | ||
email: user.email, | ||
name: user.name, | ||
} | ||
} | ||
}) | ||
], | ||
session: { | ||
strategy: 'jwt' | ||
}, | ||
pages: { | ||
signIn: '/auth/signin', | ||
// signUp :"/api/signup" | ||
}, | ||
callbacks: { | ||
async jwt({ token, user }) { | ||
if (user) { | ||
token.id = user.id | ||
} | ||
return token | ||
}, | ||
async session({ session, token }) { | ||
if (session.user) { | ||
//@ts-ignore | ||
session.user.id = token.id as string | ||
} | ||
return session | ||
}, | ||
}, | ||
} | ||
|
||
const handler = NextAuth(authOptions) | ||
const handler = NextAuth(authOptions) | ||
|
||
export { handler as GET, handler as POST } |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.