Leaderboard
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,26 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import SpeedrunTable, { Speedrun } from "@/components/Table";
|
||||
|
||||
export default function Home() {
|
||||
const users: Speedrun[] = [
|
||||
{ rank: 1, name: 'Kleo', time: 63, problem: 'Two Sum', date: 1651600541000 },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full h-14 bg-neutral-900"></div>
|
||||
|
||||
<div className="mt-5 mx-[35rem]">
|
||||
<div className="w-full">
|
||||
<SpeedrunTable data={users} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { format, timeAgo } from '@/lib/utils';
|
||||
import { formatDate } from 'date-fns';
|
||||
import React from 'react';
|
||||
|
||||
export interface Speedrun {
|
||||
rank: number;
|
||||
name: string;
|
||||
time: number;
|
||||
date: number;
|
||||
problem: string;
|
||||
}
|
||||
|
||||
interface TableProps {
|
||||
data: Speedrun[];
|
||||
}
|
||||
|
||||
const SpeedrunTable: React.FC<TableProps> = ({ data }) => {
|
||||
return (
|
||||
<div className="overflow-x-auto p-4 bg-[#121212] rounded-lg shadow-inner">
|
||||
<table className="min-w-full text-sm text-[#e0e0e0]">
|
||||
<thead>
|
||||
<tr className="border-b border-[#2a2a2a]">
|
||||
<th className="px-6 py-3 text-left font-medium tracking-wide text-[#aaaaaa] uppercase w-14">#</th>
|
||||
<th className="px-6 py-3 text-left font-medium tracking-wide text-[#aaaaaa] uppercase w-52">Player</th>
|
||||
<th className="px-6 py-3 text-left font-medium tracking-wide text-[#aaaaaa] uppercase w-52">Time</th>
|
||||
<th className="px-6 py-3 text-left font-medium tracking-wide text-[#aaaaaa] uppercase w-52">Date</th>
|
||||
<th className="px-6 py-3 text-left font-medium tracking-wide text-[#aaaaaa] uppercase">Problem</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.map((user) => (
|
||||
<tr
|
||||
key={user.rank}
|
||||
className="hover:bg-[#1f1f1f] transition-colors border-b border-[#1e1e1e]"
|
||||
>
|
||||
<td className="px-6 py-4 whitespace-nowrap">{user.rank}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">{user.name}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">{format(user.time)}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap" title={new Date(user.date).toLocaleString()}>{timeAgo(user.date)}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">{user.problem}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpeedrunTable;
|
||||
@@ -0,0 +1,36 @@
|
||||
export function format(timeSeconds: number): string {
|
||||
const days = Math.floor(timeSeconds / 86400);
|
||||
const hours = Math.floor((timeSeconds % 86400) / 3600);
|
||||
const minutes = Math.floor((timeSeconds % 3600) / 60);
|
||||
const seconds = Math.floor(timeSeconds % 60);
|
||||
|
||||
if (days > 0) {
|
||||
return `${days}d ${hours}h ${minutes}m ${seconds}s`;
|
||||
} else if (hours > 0) {
|
||||
return `${hours}h ${minutes}m ${seconds}s`;
|
||||
} else if (minutes > 0) {
|
||||
return `${minutes}m ${seconds}s`;
|
||||
} else {
|
||||
return `${seconds}s`;
|
||||
}
|
||||
}
|
||||
|
||||
export function timeAgo(timestamp: number) {
|
||||
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
|
||||
const now = Date.now();
|
||||
const diff = timestamp - now; // positive if in the future
|
||||
|
||||
const seconds = Math.round(diff / 1000);
|
||||
const minutes = Math.round(diff / (1000 * 60));
|
||||
const hours = Math.round(diff / (1000 * 60 * 60));
|
||||
const days = Math.round(diff / (1000 * 60 * 60 * 24));
|
||||
const months = Math.round(diff / (1000 * 60 * 60 * 24 * 30));
|
||||
const years = Math.round(diff / (1000 * 60 * 60 * 24 * 365));
|
||||
|
||||
if (Math.abs(years) >= 1) return rtf.format(years, 'year');
|
||||
if (Math.abs(months) >= 1) return rtf.format(months, 'month');
|
||||
if (Math.abs(days) >= 1) return rtf.format(days, 'day');
|
||||
if (Math.abs(hours) >= 1) return rtf.format(hours, 'hour');
|
||||
if (Math.abs(minutes) >= 1) return rtf.format(minutes, 'minute');
|
||||
return rtf.format(seconds, 'second');
|
||||
}
|
||||
Reference in New Issue
Block a user