Skip to content

Commit 81253e0

Browse files
committed
complete
0 parents  commit 81253e0

38 files changed

+7528
-0
lines changed

.eslintrc.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": ["next/core-web-vitals", "next/typescript"]
3+
}

.gitignore

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
.yarn/install-state.gz
8+
9+
# testing
10+
/coverage
11+
12+
# next.js
13+
/.next/
14+
/out/
15+
16+
# production
17+
/build
18+
19+
# misc
20+
.DS_Store
21+
*.pem
22+
23+
# debug
24+
npm-debug.log*
25+
yarn-debug.log*
26+
yarn-error.log*
27+
28+
# local env files
29+
.env*
30+
.local
31+
.env
32+
33+
# vercel
34+
.vercel
35+
36+
# typescript
37+
*.tsbuildinfo
38+
next-env.d.ts

README.md

Whitespace-only changes.

app/api/login/route.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { NextResponse } from 'next/server'
2+
import { PrismaClient } from '@prisma/client'
3+
import { cookies } from 'next/headers'
4+
5+
const prisma = new PrismaClient()
6+
7+
export async function POST(request: Request) {
8+
const { username, password } = await request.json()
9+
console.log('Received username:', username)
10+
console.log('Received password:', password)
11+
12+
try {
13+
const user = await prisma.user.findUnique({ where: { username } })
14+
if (!user) {
15+
console.log('User not found')
16+
return NextResponse.json({ error: 'Invalid credentials' }, { status: 400 })
17+
}
18+
19+
if (user.password !== password) {
20+
console.log('Invalid password')
21+
return NextResponse.json({ error: 'Invalid credentials' }, { status: 400 })
22+
}
23+
24+
cookies().set('userId', user.id, { httpOnly: true, secure: process.env.NODE_ENV === 'production' })
25+
cookies().set('username', user.username, { httpOnly: true, secure: process.env.NODE_ENV === 'production' })
26+
27+
return NextResponse.json({ message: 'Login successful' })
28+
} catch (error) {
29+
console.error('Login error:', error)
30+
return NextResponse.json({ error: 'An error occurred during login' }, { status: 500 })
31+
}
32+
}
33+

app/dashboard/action.ts

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
'use server'
2+
3+
import { cookies } from 'next/headers'
4+
import { getUserById, getAllUsers, updateUserTopic } from '@/lib/data'
5+
import { User, LeaderboardEntry, Topic } from './types'
6+
7+
export async function getUserData(): Promise<User> {
8+
const userId = cookies().get('userId')?.value
9+
if (!userId) {
10+
throw new Error('User not authenticated')
11+
}
12+
const user = await getUserById(userId)
13+
if (!user) {
14+
throw new Error('User not found')
15+
}
16+
return user
17+
}
18+
19+
export async function getLeaderboardData(): Promise<LeaderboardEntry[]> {
20+
const users = await getAllUsers()
21+
return users.map(user => ({
22+
name: user.username,
23+
progress: calculateOverallProgress(user.topics),
24+
avatar: '/placeholder.svg?height=40&width=40',
25+
topics: user.topics,
26+
})).sort((a, b) => b.progress - a.progress)
27+
}
28+
29+
export async function updateTopic(topicName: string, field: keyof Topic, value: number): Promise<Topic> {
30+
const userId = cookies().get('userId')?.value
31+
if (!userId) {
32+
throw new Error('User not authenticated')
33+
}
34+
return await updateUserTopic(userId, topicName, field, value)
35+
}
36+
37+
function calculateOverallProgress(topics: Topic[]): number {
38+
return Math.round(topics.reduce((sum, topic) => sum + topic.progress, 0) / topics.length)
39+
}

app/dashboard/page.tsx

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { DashboardComponent } from "@/components/dashboard";
2+
3+
export default function Dashboard() {
4+
return (
5+
6+
7+
<div className="bg-stone-950 text-white">
8+
<DashboardComponent />
9+
</div>
10+
11+
12+
);
13+
}

app/dashboard/types.ts

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
export interface Topic {
2+
name: string;
3+
progress: number;
4+
learning: number;
5+
leetcodeEasy: number;
6+
leetcodeMedium: number;
7+
leetcodeHard: number;
8+
}
9+
10+
export interface User {
11+
id: string;
12+
username: string;
13+
topics: Topic[];
14+
}
15+
16+
export interface LeaderboardEntry {
17+
name: string;
18+
progress: number;
19+
avatar: string;
20+
topics: Topic[];
21+
}
22+
23+
24+
25+
export interface Topic {
26+
id: string;
27+
name: string;
28+
progress: number;
29+
learning: number;
30+
leetcodeEasy: number;
31+
leetcodeMedium: number;
32+
leetcodeHard: number;
33+
userId: string;
34+
}
35+
36+
export interface Toast {
37+
title: string;
38+
description: string;
39+
variant?: 'default' | 'destructive';
40+
}

app/favicon.ico

25.3 KB
Binary file not shown.

app/fonts/GeistMonoVF.woff

66.3 KB
Binary file not shown.

app/fonts/GeistVF.woff

64.7 KB
Binary file not shown.

app/globals.css

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
@tailwind base;
2+
@tailwind components;
3+
@tailwind utilities;
4+
5+
body {
6+
font-family: Arial, Helvetica, sans-serif;
7+
}
8+
9+
@layer utilities {
10+
.text-balance {
11+
text-wrap: balance;
12+
}
13+
}
14+
15+
@layer base {
16+
:root {
17+
--background: 0 0% 100%;
18+
--foreground: 20 14.3% 4.1%;
19+
--card: 0 0% 100%;
20+
--card-foreground: 20 14.3% 4.1%;
21+
--popover: 0 0% 100%;
22+
--popover-foreground: 20 14.3% 4.1%;
23+
--primary: 24 9.8% 10%;
24+
--primary-foreground: 60 9.1% 97.8%;
25+
--secondary: 60 4.8% 95.9%;
26+
--secondary-foreground: 24 9.8% 10%;
27+
--muted: 60 4.8% 95.9%;
28+
--muted-foreground: 25 5.3% 44.7%;
29+
--accent: 60 4.8% 95.9%;
30+
--accent-foreground: 24 9.8% 10%;
31+
--destructive: 0 84.2% 60.2%;
32+
--destructive-foreground: 60 9.1% 97.8%;
33+
--border: 20 5.9% 90%;
34+
--input: 20 5.9% 90%;
35+
--ring: 20 14.3% 4.1%;
36+
--chart-1: 12 76% 61%;
37+
--chart-2: 173 58% 39%;
38+
--chart-3: 197 37% 24%;
39+
--chart-4: 43 74% 66%;
40+
--chart-5: 27 87% 67%;
41+
--radius: 0.5rem;
42+
}
43+
.dark {
44+
--background: 20 14.3% 4.1%;
45+
--foreground: 60 9.1% 97.8%;
46+
--card: 20 14.3% 4.1%;
47+
--card-foreground: 60 9.1% 97.8%;
48+
--popover: 20 14.3% 4.1%;
49+
--popover-foreground: 60 9.1% 97.8%;
50+
--primary: 60 9.1% 97.8%;
51+
--primary-foreground: 24 9.8% 10%;
52+
--secondary: 12 6.5% 15.1%;
53+
--secondary-foreground: 60 9.1% 97.8%;
54+
--muted: 12 6.5% 15.1%;
55+
--muted-foreground: 24 5.4% 63.9%;
56+
--accent: 12 6.5% 15.1%;
57+
--accent-foreground: 60 9.1% 97.8%;
58+
--destructive: 0 62.8% 30.6%;
59+
--destructive-foreground: 60 9.1% 97.8%;
60+
--border: 12 6.5% 15.1%;
61+
--input: 12 6.5% 15.1%;
62+
--ring: 24 5.7% 82.9%;
63+
--chart-1: 220 70% 50%;
64+
--chart-2: 160 60% 45%;
65+
--chart-3: 30 80% 55%;
66+
--chart-4: 280 65% 60%;
67+
--chart-5: 340 75% 55%;
68+
}
69+
}
70+
71+
@layer base {
72+
* {
73+
@apply border-border;
74+
}
75+
body {
76+
@apply bg-background text-foreground;
77+
}
78+
}
79+
80+
.toast {
81+
background-color: #1a202c;
82+
color: white;
83+
padding: 12px;
84+
border-radius: 8px;
85+
display: flex;
86+
flex-direction: column;
87+
}
88+
89+
.toast.destructive {
90+
background-color: #e53e3e;
91+
}
92+
93+
.toast.default {
94+
background-color: #2d3748;
95+
}

app/layout.tsx

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type { Metadata } from "next";
2+
import localFont from "next/font/local";
3+
import "./globals.css";
4+
import { ToastProvider } from "@/components/ui/toast";
5+
6+
const geistSans = localFont({
7+
src: "./fonts/GeistVF.woff",
8+
variable: "--font-geist-sans",
9+
weight: "100 900",
10+
});
11+
const geistMono = localFont({
12+
src: "./fonts/GeistMonoVF.woff",
13+
variable: "--font-geist-mono",
14+
weight: "100 900",
15+
});
16+
17+
export const metadata: Metadata = {
18+
title: "DSA Progress",
19+
description: "Track your DSA progress",
20+
};
21+
22+
export default function RootLayout({
23+
children,
24+
}: Readonly<{
25+
children: React.ReactNode;
26+
}>) {
27+
return (
28+
<html lang="en">
29+
<body
30+
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
31+
>
32+
<ToastProvider>
33+
{children}
34+
</ToastProvider>
35+
</body>
36+
</html>
37+
);
38+
}

app/login/page.tsx

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
'use client'
2+
3+
import { useState } from 'react'
4+
import { useRouter } from 'next/navigation'
5+
import { Button } from "@/components/ui/button"
6+
import { Input } from "@/components/ui/input"
7+
import { useToast } from "@/components/ui/toast"
8+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
9+
10+
11+
export default function LoginPage() {
12+
const [username, setUsername] = useState('')
13+
const [password, setPassword] = useState('')
14+
const router = useRouter()
15+
const { toast } = useToast()
16+
17+
const handleLogin = async (e: React.FormEvent) => {
18+
e.preventDefault()
19+
try {
20+
const response = await fetch('/api/login', {
21+
method: 'POST',
22+
headers: { 'Content-Type': 'application/json' },
23+
body: JSON.stringify({ username, password }),
24+
})
25+
if (!response.ok) {
26+
throw new Error('Login failed')
27+
}
28+
router.push('/dashboard')
29+
} catch (error) {
30+
toast({
31+
title: "Login Error",
32+
description: error instanceof Error ? error.message : 'An error occurred during login',
33+
variant: "destructive",
34+
})
35+
}
36+
}
37+
38+
return (
39+
<div className="flex items-center justify-center min-h-screen bg-black">
40+
<Card className="w-full max-w-md bg-black border-green-600">
41+
<CardHeader>
42+
<CardTitle className='text-green-600 flex justify-center items-center'>Login to DSA Progress</CardTitle>
43+
<CardDescription className='text-gray-600 flex justify-center items-center'>Enter your credentials to access your dashboard</CardDescription>
44+
</CardHeader>
45+
<CardContent>
46+
<form onSubmit={handleLogin} className="space-y-4">
47+
<Input
48+
className='text-white bg-black border-green-600'
49+
type="text"
50+
value={username}
51+
onChange={(e) => setUsername(e.target.value)}
52+
placeholder="Username"
53+
required
54+
/>
55+
<Input
56+
className='text-white bg-black border-green-600'
57+
type="password"
58+
value={password}
59+
onChange={(e) => setPassword(e.target.value)}
60+
placeholder="Password"
61+
required
62+
/>
63+
<Button className='text-white w-full text-lg my-10 border border-input bg-green-600 hover:bg-green-950 hover:text-white' type="submit">Login</Button>
64+
</form>
65+
</CardContent>
66+
</Card>
67+
</div>
68+
)
69+
}

app/page.tsx

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { DashboardComponent } from "../components/dashboard";
2+
3+
export default function Dashboard() {
4+
return (
5+
6+
7+
<div className="bg-stone-950 text-white">
8+
<DashboardComponent />
9+
</div>
10+
11+
12+
);
13+
}

0 commit comments

Comments
 (0)