This repository has been archived on 2025-08-19. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
coinstorge/website/src/routes/+page.svelte

156 lines
4.7 KiB
Svelte
Raw Normal View History

2025-05-21 21:34:22 +03:00
<script lang="ts">
import * as Card from '$lib/components/ui/card';
import { Badge } from '$lib/components/ui/badge';
import { getTimeBasedGreeting, formatPrice, formatMarketCap } from '$lib/utils';
2025-05-22 14:00:43 +03:00
import { USER_DATA } from '$lib/stores/user-data';
import SignInConfirmDialog from '$lib/components/self/SignInConfirmDialog.svelte';
import CoinIcon from '$lib/components/self/CoinIcon.svelte';
import DataTable from '$lib/components/self/DataTable.svelte';
2025-05-27 14:12:29 +03:00
import HomeSkeleton from '$lib/components/self/skeletons/HomeSkeleton.svelte';
2025-05-30 13:15:00 +03:00
import SEO from '$lib/components/self/SEO.svelte';
import { onMount } from 'svelte';
import { toast } from 'svelte-sonner';
import { goto } from '$app/navigation';
2025-05-22 14:00:43 +03:00
let shouldSignIn = $state(false);
let coins = $state<any[]>([]);
let loading = $state(true);
onMount(async () => {
try {
const response = await fetch('/api/coins/top');
if (response.ok) {
const result = await response.json();
coins = result.coins;
} else {
toast.error('Failed to load coins');
}
} catch (e) {
console.error('Failed to fetch coins:', e);
toast.error('Failed to load coins');
} finally {
loading = false;
}
});
const marketColumns = [
{
key: 'name',
label: 'Name',
class: 'font-medium',
render: (value: any, row: any) => {
return {
2025-05-31 16:26:51 +03:00
component: 'coin',
icon: row.icon,
symbol: row.symbol,
name: row.name,
size: 6
};
}
},
{
key: 'price',
label: 'Price',
render: (value: any) => `$${formatPrice(value)}`
},
{
key: 'change24h',
label: '24h Change',
render: (value: any) => ({
component: 'badge',
variant: value >= 0 ? 'success' : 'destructive',
text: `${value >= 0 ? '+' : ''}${value.toFixed(2)}%`
})
},
{
key: 'marketCap',
label: 'Market Cap',
render: (value: any) => formatMarketCap(value)
},
{
key: 'volume24h',
label: 'Volume (24h)',
render: (value: any) => formatMarketCap(value)
}
];
2025-05-21 21:34:22 +03:00
</script>
2025-06-08 21:16:21 +03:00
<SEO
2025-05-30 13:15:00 +03:00
title="Rugplay"
2025-06-28 17:31:05 +03:00
description="A realistic crypto trading simulator that lets you experience the risks and mechanics of decentralized exchanges without real financial consequences. Create coins, trade with liquidity pools, and learn about 'rug pulls' in a... relatively safe environment :)"
2025-05-30 13:15:00 +03:00
keywords="crypto simulation game, trading practice game, rug pull simulation, virtual cryptocurrency game"
/>
2025-05-22 14:00:43 +03:00
<SignInConfirmDialog bind:open={shouldSignIn} />
2025-05-21 21:34:22 +03:00
<div class="container mx-auto p-6">
<header class="mb-8">
2025-06-08 21:16:21 +03:00
<h1 class="mb-2 truncate text-3xl font-bold">
2025-05-22 14:00:43 +03:00
{$USER_DATA ? getTimeBasedGreeting($USER_DATA?.name) : 'Welcome to Rugplay!'}
</h1>
<p class="text-muted-foreground">
{#if $USER_DATA}
Here's the market overview for today.
{:else}
You need to <button
class="text-primary underline hover:cursor-pointer"
onclick={() => (shouldSignIn = !shouldSignIn)}>sign in</button
>
to play.
2025-05-22 14:00:43 +03:00
{/if}
</p>
2025-05-21 21:34:22 +03:00
</header>
{#if loading}
2025-05-27 14:12:29 +03:00
<HomeSkeleton />
{:else if coins.length === 0}
<div class="flex h-96 items-center justify-center">
<div class="text-center">
<div class="text-muted-foreground mb-4 text-xl">No coins available</div>
<p class="text-muted-foreground text-sm">Be the first to create a coin!</p>
</div>
</div>
2025-06-08 21:16:21 +03:00
{:else}
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
2025-05-31 16:26:51 +03:00
{#each coins.slice(0, 6) as coin (coin.symbol)}
<a href={`/coin/${coin.symbol}`} class="block">
<Card.Root class="hover:bg-card/50 h-full transition-all hover:shadow-md">
<Card.Header>
<Card.Title class="flex items-center justify-between">
<div class="flex items-center gap-2">
<CoinIcon icon={coin.icon} symbol={coin.symbol} name={coin.name} size={6} />
<span>{coin.name} (*{coin.symbol})</span>
</div>
<Badge variant={coin.change24h >= 0 ? 'success' : 'destructive'} class="ml-2">
{coin.change24h >= 0 ? '+' : ''}{coin.change24h.toFixed(2)}%
</Badge>
</Card.Title>
<Card.Description>Market Cap: {formatMarketCap(coin.marketCap)}</Card.Description>
</Card.Header>
<Card.Content>
<div class="flex items-baseline justify-between">
<span class="text-3xl font-bold">${formatPrice(coin.price)}</span>
<span class="text-muted-foreground text-sm">
24h Vol: {formatMarketCap(coin.volume24h)}
</span>
</div>
</Card.Content>
</Card.Root>
</a>
{/each}
</div>
2025-05-21 21:34:22 +03:00
<div class="mt-12">
<h2 class="mb-4 text-2xl font-bold">Market Overview</h2>
<Card.Root>
2025-05-27 16:19:57 +03:00
<Card.Content>
<DataTable
columns={marketColumns}
data={coins}
onRowClick={(coin) => goto(`/coin/${coin.symbol}`)}
/>
</Card.Content>
</Card.Root>
</div>
{/if}
2025-05-21 21:34:22 +03:00
</div>