first commit
This commit is contained in:
172
app/(auth)/login/page.tsx
Normal file
172
app/(auth)/login/page.tsx
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState } from "react"
|
||||||
|
import { Card } from "@/components/ui/card"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import { Label } from "@/components/ui/label"
|
||||||
|
import Link from "next/link"
|
||||||
|
import { Eye, EyeOff } from "lucide-react"
|
||||||
|
|
||||||
|
export default function LoginPage() {
|
||||||
|
const [showPassword, setShowPassword] = useState(false)
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
username: "",
|
||||||
|
password: ""
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
// Здесь будет логика авторизации
|
||||||
|
console.log("Login attempt:", formData)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-[#0a0e1a] flex items-center justify-center p-6">
|
||||||
|
{/* Background grid */}
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 pointer-events-none -z-10"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `
|
||||||
|
linear-gradient(rgba(6, 182, 212, 0.03) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(6, 182, 212, 0.03) 1px, transparent 1px)
|
||||||
|
`,
|
||||||
|
backgroundSize: '50px 50px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Glowing effects */}
|
||||||
|
<div className="fixed top-20 left-20 w-96 h-96 bg-cyan-500/10 rounded-full blur-[120px] animate-pulse" />
|
||||||
|
<div className="fixed bottom-20 right-20 w-96 h-96 bg-blue-500/10 rounded-full blur-[120px] animate-pulse" />
|
||||||
|
|
||||||
|
<Card className="w-full max-w-md bg-[#0d1117] border-2 border-cyan-500/40 shadow-[0_0_50px_rgba(6,182,212,0.2)] relative overflow-hidden">
|
||||||
|
{/* Top accent line */}
|
||||||
|
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-transparent via-cyan-400 to-transparent" />
|
||||||
|
|
||||||
|
<div className="p-8">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<h1 className="text-4xl font-bold text-cyan-400 font-mono mb-2">
|
||||||
|
Login
|
||||||
|
</h1>
|
||||||
|
<p className="text-cyan-400/50 font-mono text-sm">
|
||||||
|
Access your learning dashboard
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Login Form */}
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label
|
||||||
|
htmlFor="username"
|
||||||
|
className="text-cyan-400 font-mono text-sm"
|
||||||
|
>
|
||||||
|
Username
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="username"
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter your username"
|
||||||
|
value={formData.username}
|
||||||
|
onChange={(e) => setFormData({...formData, username: e.target.value})}
|
||||||
|
className="bg-[#0a0e1a] border-cyan-500/30 text-cyan-400 placeholder:text-cyan-400/30 font-mono focus:border-cyan-400 focus:ring-cyan-400/20"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label
|
||||||
|
htmlFor="password"
|
||||||
|
className="text-cyan-400 font-mono text-sm"
|
||||||
|
>
|
||||||
|
Password
|
||||||
|
</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
placeholder="Enter your password"
|
||||||
|
value={formData.password}
|
||||||
|
onChange={(e) => setFormData({...formData, password: e.target.value})}
|
||||||
|
className="bg-[#0a0e1a] border-cyan-500/30 text-cyan-400 placeholder:text-cyan-400/30 font-mono focus:border-cyan-400 focus:ring-cyan-400/20 pr-10"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
className="absolute right-3 top-1/2 -translate-y-1/2 text-cyan-400/50 hover:text-cyan-400 transition-colors"
|
||||||
|
>
|
||||||
|
{showPassword ? (
|
||||||
|
<EyeOff className="w-4 h-4" />
|
||||||
|
) : (
|
||||||
|
<Eye className="w-4 h-4" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between text-sm">
|
||||||
|
<label className="flex items-center gap-2 cursor-pointer group">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="w-4 h-4 rounded border-cyan-500/30 bg-[#0a0e1a] text-cyan-400 focus:ring-cyan-400/20"
|
||||||
|
/>
|
||||||
|
<span className="text-cyan-400/60 group-hover:text-cyan-400/80 font-mono transition-colors">
|
||||||
|
Remember me
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<Link
|
||||||
|
href="/reset-password"
|
||||||
|
className="text-cyan-400/60 hover:text-cyan-400 font-mono transition-colors"
|
||||||
|
>
|
||||||
|
Forgot password?
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
className="w-full bg-cyan-500 hover:bg-cyan-400 text-black font-bold font-mono tracking-wider py-6 text-base shadow-[0_0_30px_rgba(6,182,212,0.3)] hover:shadow-[0_0_50px_rgba(6,182,212,0.5)] transition-all"
|
||||||
|
>
|
||||||
|
LOGIN
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{/* Footer Links */}
|
||||||
|
<div className="mt-8 text-center space-y-4">
|
||||||
|
<div className="relative">
|
||||||
|
<div className="absolute inset-0 flex items-center">
|
||||||
|
<div className="w-full border-t border-cyan-500/20"></div>
|
||||||
|
</div>
|
||||||
|
<div className="relative flex justify-center text-xs">
|
||||||
|
<span className="bg-[#0d1117] px-4 text-cyan-400/40 font-mono">
|
||||||
|
OR
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-cyan-400/60 font-mono text-sm">
|
||||||
|
Don't have an account?{" "}
|
||||||
|
<Link
|
||||||
|
href="/register"
|
||||||
|
className="text-cyan-400 hover:text-cyan-300 font-bold transition-colors"
|
||||||
|
>
|
||||||
|
Sign up
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="inline-block text-cyan-400/50 hover:text-cyan-400 font-mono text-xs transition-colors"
|
||||||
|
>
|
||||||
|
← Back to home
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Corner decorations */}
|
||||||
|
<div className="absolute top-2 left-2 w-4 h-4 border-t-2 border-l-2 border-cyan-500/30"></div>
|
||||||
|
<div className="absolute top-2 right-2 w-4 h-4 border-t-2 border-r-2 border-cyan-500/30"></div>
|
||||||
|
<div className="absolute bottom-2 left-2 w-4 h-4 border-b-2 border-l-2 border-cyan-500/30"></div>
|
||||||
|
<div className="absolute bottom-2 right-2 w-4 h-4 border-b-2 border-r-2 border-cyan-500/30"></div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
280
app/(home)/main/page.tsx
Normal file
280
app/(home)/main/page.tsx
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState } from "react"
|
||||||
|
import { Card } from "@/components/ui/card"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import {
|
||||||
|
BookOpen,
|
||||||
|
Trophy,
|
||||||
|
Target,
|
||||||
|
TrendingUp,
|
||||||
|
Clock,
|
||||||
|
Award,
|
||||||
|
CheckCircle2,
|
||||||
|
PlayCircle
|
||||||
|
} from "lucide-react"
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
const [activeTab, setActiveTab] = useState("main")
|
||||||
|
|
||||||
|
// Mock data для демонстрации
|
||||||
|
const stats = {
|
||||||
|
coursesInProgress: 3,
|
||||||
|
completedCourses: 12,
|
||||||
|
totalPoints: 2450,
|
||||||
|
rank: "Advanced"
|
||||||
|
}
|
||||||
|
|
||||||
|
const recentCourses = [
|
||||||
|
{ id: 1, title: "Advanced React Patterns", progress: 65, time: "2h 30m left" },
|
||||||
|
{ id: 2, title: "System Design", progress: 40, time: "5h 15m left" },
|
||||||
|
{ id: 3, title: "TypeScript Deep Dive", progress: 85, time: "45m left" }
|
||||||
|
]
|
||||||
|
|
||||||
|
const achievements = [
|
||||||
|
{ id: 1, title: "Fast Learner", icon: Trophy, unlocked: true },
|
||||||
|
{ id: 2, title: "Course Master", icon: Award, unlocked: true },
|
||||||
|
{ id: 3, title: "Consistency King", icon: Target, unlocked: false }
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-[#0a0e1a]">
|
||||||
|
{/* Header */}
|
||||||
|
<header className="bg-[#0d1117] border-b border-cyan-500/30 sticky top-0 z-50 backdrop-blur-sm">
|
||||||
|
<nav className="container mx-auto px-6 py-4">
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<div className="flex items-center gap-12">
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab("main")}
|
||||||
|
className={`text-sm font-bold tracking-[0.2em] transition-all duration-200 font-mono uppercase ${
|
||||||
|
activeTab === "main"
|
||||||
|
? "text-cyan-400"
|
||||||
|
: "text-cyan-500/50 hover:text-cyan-400/80"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Main
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab("progress")}
|
||||||
|
className={`text-sm font-bold tracking-[0.2em] transition-all duration-200 font-mono uppercase ${
|
||||||
|
activeTab === "progress"
|
||||||
|
? "text-cyan-400"
|
||||||
|
: "text-cyan-500/50 hover:text-cyan-400/80"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Progress
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab("profile")}
|
||||||
|
className={`text-sm font-bold tracking-[0.2em] transition-all duration-200 font-mono uppercase ${
|
||||||
|
activeTab === "profile"
|
||||||
|
? "text-cyan-400"
|
||||||
|
: "text-cyan-500/50 hover:text-cyan-400/80"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Profile
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab("rules")}
|
||||||
|
className={`text-sm font-bold tracking-[0.2em] transition-all duration-200 font-mono uppercase ${
|
||||||
|
activeTab === "rules"
|
||||||
|
? "text-cyan-400"
|
||||||
|
: "text-cyan-500/50 hover:text-cyan-400/80"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Rules
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab("login")}
|
||||||
|
className={`text-sm font-bold tracking-[0.2em] transition-all duration-200 font-mono uppercase ${
|
||||||
|
activeTab === "login"
|
||||||
|
? "text-cyan-400"
|
||||||
|
: "text-cyan-500/50 hover:text-cyan-400/80"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<main className="container mx-auto px-6 py-8">
|
||||||
|
{/* Stats Grid */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
|
||||||
|
<Card className="bg-[#0d1117] border-2 border-cyan-500/20 p-6 hover:border-cyan-500/40 transition-all group">
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<BookOpen className="w-8 h-8 text-cyan-400 group-hover:scale-110 transition-transform" />
|
||||||
|
<span className="text-xs font-mono text-cyan-400/50">IN PROGRESS</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl font-bold text-cyan-400 font-mono">
|
||||||
|
{stats.coursesInProgress}
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-cyan-400/60 font-mono mt-1">Active Courses</p>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="bg-[#0d1117] border-2 border-cyan-500/20 p-6 hover:border-cyan-500/40 transition-all group">
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<CheckCircle2 className="w-8 h-8 text-cyan-400 group-hover:scale-110 transition-transform" />
|
||||||
|
<span className="text-xs font-mono text-cyan-400/50">COMPLETED</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl font-bold text-cyan-400 font-mono">
|
||||||
|
{stats.completedCourses}
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-cyan-400/60 font-mono mt-1">Finished</p>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="bg-[#0d1117] border-2 border-cyan-500/20 p-6 hover:border-cyan-500/40 transition-all group">
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<Trophy className="w-8 h-8 text-cyan-400 group-hover:scale-110 transition-transform" />
|
||||||
|
<span className="text-xs font-mono text-cyan-400/50">TOTAL</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl font-bold text-cyan-400 font-mono">
|
||||||
|
{stats.totalPoints}
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-cyan-400/60 font-mono mt-1">Points Earned</p>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="bg-[#0d1117] border-2 border-cyan-500/20 p-6 hover:border-cyan-500/40 transition-all group">
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<TrendingUp className="w-8 h-8 text-cyan-400 group-hover:scale-110 transition-transform" />
|
||||||
|
<span className="text-xs font-mono text-cyan-400/50">RANK</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-3xl font-bold text-cyan-400 font-mono">
|
||||||
|
{stats.rank}
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-cyan-400/60 font-mono mt-1">Current Level</p>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
|
{/* Continue Learning Section */}
|
||||||
|
<Card className="lg:col-span-2 bg-[#0d1117] border-2 border-cyan-500/30 p-6">
|
||||||
|
<div className="flex items-center justify-between mb-6">
|
||||||
|
<h2 className="text-2xl font-bold text-cyan-400 font-mono">
|
||||||
|
Continue Learning
|
||||||
|
</h2>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="border-cyan-500/50 text-cyan-400 hover:bg-cyan-500/10 font-mono text-xs"
|
||||||
|
>
|
||||||
|
VIEW ALL
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{recentCourses.map((course) => (
|
||||||
|
<Card
|
||||||
|
key={course.id}
|
||||||
|
className="bg-[#0a0e1a] border border-cyan-500/20 p-4 hover:border-cyan-500/40 transition-all group cursor-pointer"
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between mb-3">
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="text-lg font-bold text-cyan-400 font-mono mb-1 group-hover:text-cyan-300 transition-colors">
|
||||||
|
{course.title}
|
||||||
|
</h3>
|
||||||
|
<div className="flex items-center gap-2 text-xs text-cyan-400/50 font-mono">
|
||||||
|
<Clock className="w-3 h-3" />
|
||||||
|
{course.time}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
className="bg-cyan-500 hover:bg-cyan-400 text-black font-mono"
|
||||||
|
>
|
||||||
|
<PlayCircle className="w-4 h-4 mr-1" />
|
||||||
|
RESUME
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Progress Bar */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between text-xs font-mono">
|
||||||
|
<span className="text-cyan-400/60">Progress</span>
|
||||||
|
<span className="text-cyan-400">{course.progress}%</span>
|
||||||
|
</div>
|
||||||
|
<div className="h-2 bg-[#0d1117] rounded-full overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="h-full bg-gradient-to-r from-cyan-500 to-blue-500 rounded-full transition-all duration-500"
|
||||||
|
style={{ width: `${course.progress}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Achievements Section */}
|
||||||
|
<Card className="bg-[#0d1117] border-2 border-cyan-500/30 p-6">
|
||||||
|
<h2 className="text-2xl font-bold text-cyan-400 font-mono mb-6">
|
||||||
|
Achievements
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{achievements.map((achievement) => {
|
||||||
|
const Icon = achievement.icon
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
key={achievement.id}
|
||||||
|
className={`p-4 transition-all ${
|
||||||
|
achievement.unlocked
|
||||||
|
? 'bg-cyan-500/10 border-cyan-500/40'
|
||||||
|
: 'bg-[#0a0e1a] border-cyan-500/10 opacity-50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className={`w-12 h-12 rounded-lg flex items-center justify-center ${
|
||||||
|
achievement.unlocked
|
||||||
|
? 'bg-cyan-500/20'
|
||||||
|
: 'bg-cyan-500/5'
|
||||||
|
}`}>
|
||||||
|
<Icon className={`w-6 h-6 ${
|
||||||
|
achievement.unlocked
|
||||||
|
? 'text-cyan-400'
|
||||||
|
: 'text-cyan-400/30'
|
||||||
|
}`} />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className={`font-bold font-mono text-sm ${
|
||||||
|
achievement.unlocked
|
||||||
|
? 'text-cyan-400'
|
||||||
|
: 'text-cyan-400/30'
|
||||||
|
}`}>
|
||||||
|
{achievement.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs text-cyan-400/50 font-mono">
|
||||||
|
{achievement.unlocked ? 'Unlocked' : 'Locked'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className="w-full mt-6 bg-[#0a0e1a] border border-cyan-500/30 text-cyan-400 hover:bg-cyan-500/10 font-mono"
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
VIEW ALL ACHIEVEMENTS
|
||||||
|
</Button>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* Subtle grid background */}
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 pointer-events-none -z-10"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `
|
||||||
|
linear-gradient(rgba(6, 182, 212, 0.03) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(6, 182, 212, 0.03) 1px, transparent 1px)
|
||||||
|
`,
|
||||||
|
backgroundSize: '50px 50px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
236
app/(home)/main/profile/page.tsx
Normal file
236
app/(home)/main/profile/page.tsx
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState } from "react"
|
||||||
|
import { Card } from "@/components/ui/card"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Progress } from "@/components/ui/progress"
|
||||||
|
import { User, Award, BookOpen, Trophy, Star } from "lucide-react"
|
||||||
|
import Link from "next/link"
|
||||||
|
|
||||||
|
export default function ProfilePage() {
|
||||||
|
const [activeTab, setActiveTab] = useState("profile")
|
||||||
|
|
||||||
|
// Mock user data
|
||||||
|
const userData = {
|
||||||
|
name: "Alex Johnson",
|
||||||
|
level: 5,
|
||||||
|
xp: 2450,
|
||||||
|
maxXp: 3000,
|
||||||
|
rank: "Advanced",
|
||||||
|
achievements: 8,
|
||||||
|
coursesCompleted: 12,
|
||||||
|
totalStudyTime: "124h"
|
||||||
|
}
|
||||||
|
|
||||||
|
const stats = [
|
||||||
|
{ label: "Level", value: userData.level, icon: Star },
|
||||||
|
{ label: "XP", value: `${userData.xp}/${userData.maxXp}`, icon: Trophy },
|
||||||
|
{ label: "Rank", value: userData.rank, icon: Award }
|
||||||
|
]
|
||||||
|
|
||||||
|
const achievements = [
|
||||||
|
{ title: "First Steps", description: "Complete your first course", unlocked: true },
|
||||||
|
{ title: "Quick Learner", description: "Complete a course in under 24h", unlocked: true },
|
||||||
|
{ title: "Dedication", description: "Study 7 days in a row", unlocked: true },
|
||||||
|
{ title: "Master", description: "Complete 10 courses", unlocked: true },
|
||||||
|
{ title: "Expert", description: "Reach level 5", unlocked: true },
|
||||||
|
{ title: "Marathon", description: "Study for 100 hours", unlocked: true },
|
||||||
|
{ title: "Perfectionist", description: "Score 100% on 5 tests", unlocked: false },
|
||||||
|
{ title: "Legend", description: "Reach level 10", unlocked: false }
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-[#0a0e1a]">
|
||||||
|
{/* Header */}
|
||||||
|
<header className="bg-[#0d1117] border-b border-cyan-500/30 sticky top-0 z-50">
|
||||||
|
<nav className="container mx-auto px-6 py-4">
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<div className="flex items-center gap-12">
|
||||||
|
<Link href="/home/main">
|
||||||
|
<button
|
||||||
|
className="text-sm font-bold tracking-[0.2em] transition-all duration-200 font-mono uppercase text-cyan-500/50 hover:text-cyan-400/80"
|
||||||
|
>
|
||||||
|
Main
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
<Link href="/home/progress">
|
||||||
|
<button
|
||||||
|
className="text-sm font-bold tracking-[0.2em] transition-all duration-200 font-mono uppercase text-cyan-500/50 hover:text-cyan-400/80"
|
||||||
|
>
|
||||||
|
Progress
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab("profile")}
|
||||||
|
className="text-sm font-bold tracking-[0.2em] transition-all duration-200 font-mono uppercase text-cyan-400"
|
||||||
|
>
|
||||||
|
Profile
|
||||||
|
</button>
|
||||||
|
<Link href="/home/rules">
|
||||||
|
<button
|
||||||
|
className="text-sm font-bold tracking-[0.2em] transition-all duration-200 font-mono uppercase text-cyan-500/50 hover:text-cyan-400/80"
|
||||||
|
>
|
||||||
|
Rules
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
<Link href="/login">
|
||||||
|
<button
|
||||||
|
className="text-sm font-bold tracking-[0.2em] transition-all duration-200 font-mono uppercase text-cyan-500/50 hover:text-cyan-400/80"
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<main className="container mx-auto px-6 py-8 max-w-6xl">
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
|
{/* User Profile Card */}
|
||||||
|
<Card className="lg:col-span-1 bg-[#0d1117] border-2 border-cyan-500/30 p-6">
|
||||||
|
<div className="text-center">
|
||||||
|
{/* Avatar */}
|
||||||
|
<div className="w-32 h-32 bg-gradient-to-br from-cyan-500/20 to-blue-500/20 rounded-full flex items-center justify-center mx-auto mb-4 border-4 border-cyan-500/30">
|
||||||
|
<User className="w-16 h-16 text-cyan-400" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* User Info */}
|
||||||
|
<h2 className="text-2xl font-bold text-cyan-400 font-mono mb-1">
|
||||||
|
{userData.name}
|
||||||
|
</h2>
|
||||||
|
<p className="text-cyan-400/60 font-mono text-sm mb-6">
|
||||||
|
{userData.rank} • Level {userData.level}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* XP Progress */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<div className="flex justify-between items-center mb-2">
|
||||||
|
<span className="text-xs font-mono text-cyan-400/60">XP Progress</span>
|
||||||
|
<span className="text-xs font-mono text-cyan-400">
|
||||||
|
{userData.xp}/{userData.maxXp}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Progress
|
||||||
|
value={(userData.xp / userData.maxXp) * 100}
|
||||||
|
className="h-3 bg-[#0a0e1a]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Quick Stats */}
|
||||||
|
<div className="grid grid-cols-2 gap-4 mb-6">
|
||||||
|
<div className="bg-[#0a0e1a] border border-cyan-500/20 rounded-lg p-3">
|
||||||
|
<BookOpen className="w-5 h-5 text-cyan-400 mx-auto mb-1" />
|
||||||
|
<div className="text-xl font-bold text-cyan-400 font-mono">
|
||||||
|
{userData.coursesCompleted}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-cyan-400/60 font-mono">Courses</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-[#0a0e1a] border border-cyan-500/20 rounded-lg p-3">
|
||||||
|
<Award className="w-5 h-5 text-cyan-400 mx-auto mb-1" />
|
||||||
|
<div className="text-xl font-bold text-cyan-400 font-mono">
|
||||||
|
{userData.achievements}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-cyan-400/60 font-mono">Badges</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className="w-full bg-cyan-500 hover:bg-cyan-400 text-black font-mono font-bold"
|
||||||
|
>
|
||||||
|
EDIT PROFILE
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Stats and Achievements */}
|
||||||
|
<div className="lg:col-span-2 space-y-6">
|
||||||
|
{/* Personal Progress Card */}
|
||||||
|
<Card className="bg-[#0d1117] border-2 border-cyan-500/30 p-6">
|
||||||
|
<h3 className="text-xl font-bold text-cyan-400 font-mono mb-4">
|
||||||
|
Personal progress
|
||||||
|
</h3>
|
||||||
|
<div className="grid grid-cols-3 gap-4">
|
||||||
|
{stats.map((stat, index) => {
|
||||||
|
const Icon = stat.icon
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="bg-[#0a0e1a] border border-cyan-500/20 rounded-lg p-4 text-center"
|
||||||
|
>
|
||||||
|
<Icon className="w-6 h-6 text-cyan-400 mx-auto mb-2" />
|
||||||
|
<div className="text-2xl font-bold text-cyan-400 font-mono mb-1">
|
||||||
|
{stat.value}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-cyan-400/60 font-mono">
|
||||||
|
{stat.label}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Achievements Card */}
|
||||||
|
<Card className="bg-[#0d1117] border-2 border-cyan-500/30 p-6">
|
||||||
|
<h3 className="text-xl font-bold text-cyan-400 font-mono mb-4">
|
||||||
|
Achievements
|
||||||
|
</h3>
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
{achievements.map((achievement, index) => (
|
||||||
|
<Card
|
||||||
|
key={index}
|
||||||
|
className={`p-4 transition-all ${
|
||||||
|
achievement.unlocked
|
||||||
|
? 'bg-cyan-500/10 border-cyan-500/40 hover:border-cyan-500/60'
|
||||||
|
: 'bg-[#0a0e1a] border-cyan-500/10 opacity-40'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className={`w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0 ${
|
||||||
|
achievement.unlocked
|
||||||
|
? 'bg-cyan-500/20'
|
||||||
|
: 'bg-cyan-500/5'
|
||||||
|
}`}>
|
||||||
|
<Trophy className={`w-5 h-5 ${
|
||||||
|
achievement.unlocked
|
||||||
|
? 'text-cyan-400'
|
||||||
|
: 'text-cyan-400/30'
|
||||||
|
}`} />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<h4 className={`font-bold font-mono text-sm mb-1 ${
|
||||||
|
achievement.unlocked
|
||||||
|
? 'text-cyan-400'
|
||||||
|
: 'text-cyan-400/30'
|
||||||
|
}`}>
|
||||||
|
{achievement.title}
|
||||||
|
</h4>
|
||||||
|
<p className="text-xs text-cyan-400/50 font-mono line-clamp-2">
|
||||||
|
{achievement.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* Background grid */}
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 pointer-events-none -z-10"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `
|
||||||
|
linear-gradient(rgba(6, 182, 212, 0.03) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(6, 182, 212, 0.03) 1px, transparent 1px)
|
||||||
|
`,
|
||||||
|
backgroundSize: '50px 50px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
286
app/(home)/main/progress/page.tsx
Normal file
286
app/(home)/main/progress/page.tsx
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState } from "react"
|
||||||
|
import { Card } from "@/components/ui/card"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Progress } from "@/components/ui/progress"
|
||||||
|
import Link from "next/link"
|
||||||
|
import { CheckCircle2, Circle, Clock } from "lucide-react"
|
||||||
|
|
||||||
|
export default function ProgressPage() {
|
||||||
|
const [activeTab, setActiveTab] = useState("progress")
|
||||||
|
|
||||||
|
// Mock data
|
||||||
|
const courses = [
|
||||||
|
{ id: 1, name: "React", lessons: 12, completed: 8 },
|
||||||
|
{ id: 2, name: "TypeScript", lessons: 10, completed: 6 },
|
||||||
|
{ id: 3, name: "Next.js", lessons: 15, completed: 4 },
|
||||||
|
{ id: 4, name: "Node.js", lessons: 20, completed: 0 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const assignments = [
|
||||||
|
{ id: 1, course: "React", name: "Build a Todo App", grade: 95, status: "completed" },
|
||||||
|
{ id: 2, course: "TypeScript", name: "Type System", grade: 88, status: "completed" },
|
||||||
|
{ id: 3, course: "Next.js", name: "SSR Project", grade: null, status: "pending" },
|
||||||
|
{ id: 4, course: "Node.js", name: "REST API", grade: null, status: "not_started" }
|
||||||
|
]
|
||||||
|
|
||||||
|
const weekActivity = [
|
||||||
|
{ day: "Mon", hours: 2.5 },
|
||||||
|
{ day: "Tue", hours: 3.2 },
|
||||||
|
{ day: "Wed", hours: 1.8 },
|
||||||
|
{ day: "Thu", hours: 4.1 },
|
||||||
|
{ day: "Fri", hours: 2.9 },
|
||||||
|
{ day: "Sat", hours: 3.5 },
|
||||||
|
{ day: "Sun", hours: 1.2 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const maxHours = Math.max(...weekActivity.map(d => d.hours))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-[#0a0e1a]">
|
||||||
|
{/* Header */}
|
||||||
|
<header className="bg-[#0d1117] border-b border-cyan-500/30 sticky top-0 z-50">
|
||||||
|
<nav className="container mx-auto px-6 py-4">
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<div className="flex items-center gap-12">
|
||||||
|
<Link href="/home/main">
|
||||||
|
<button className="text-sm font-bold tracking-[0.2em] transition-all duration-200 font-mono uppercase text-cyan-500/50 hover:text-cyan-400/80">
|
||||||
|
Main
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab("progress")}
|
||||||
|
className="text-sm font-bold tracking-[0.2em] transition-all duration-200 font-mono uppercase text-cyan-400"
|
||||||
|
>
|
||||||
|
Progress
|
||||||
|
</button>
|
||||||
|
<Link href="/home/profile">
|
||||||
|
<button className="text-sm font-bold tracking-[0.2em] transition-all duration-200 font-mono uppercase text-cyan-500/50 hover:text-cyan-400/80">
|
||||||
|
Profile
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
<Link href="/home/rules">
|
||||||
|
<button className="text-sm font-bold tracking-[0.2em] transition-all duration-200 font-mono uppercase text-cyan-500/50 hover:text-cyan-400/80">
|
||||||
|
Rules
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
<Link href="/login">
|
||||||
|
<button className="text-sm font-bold tracking-[0.2em] transition-all duration-200 font-mono uppercase text-cyan-500/50 hover:text-cyan-400/80">
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<main className="container mx-auto px-6 py-8 max-w-7xl">
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
|
{/* Left Column - Course Progress */}
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* My Courses */}
|
||||||
|
<Card className="bg-[#0d1117] border-2 border-cyan-500/30 p-6">
|
||||||
|
<h3 className="text-xl font-bold text-cyan-400 font-mono mb-4">
|
||||||
|
My courses
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{courses.map((course) => {
|
||||||
|
const progress = (course.completed / course.lessons) * 100
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
key={course.id}
|
||||||
|
className="bg-[#0a0e1a] border border-cyan-500/20 p-4 hover:border-cyan-500/40 transition-all"
|
||||||
|
>
|
||||||
|
<div className="flex justify-between items-start mb-2">
|
||||||
|
<h4 className="font-bold text-cyan-400 font-mono text-sm">
|
||||||
|
{course.name}
|
||||||
|
</h4>
|
||||||
|
<span className="text-xs text-cyan-400/60 font-mono">
|
||||||
|
{course.completed}/{course.lessons}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Progress value={progress} className="h-2 bg-[#0d1117]" />
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* My Grades */}
|
||||||
|
<Card className="bg-[#0d1117] border-2 border-cyan-500/30 p-6">
|
||||||
|
<h3 className="text-xl font-bold text-cyan-400 font-mono mb-4">
|
||||||
|
My grades
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{assignments.slice(0, 3).map((assignment) => (
|
||||||
|
<Card
|
||||||
|
key={assignment.id}
|
||||||
|
className="bg-[#0a0e1a] border border-cyan-500/20 p-3"
|
||||||
|
>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="text-sm font-bold text-cyan-400 font-mono mb-1">
|
||||||
|
{assignment.course}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-cyan-400/60 font-mono">
|
||||||
|
{assignment.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{assignment.grade ? (
|
||||||
|
<div className="text-xl font-bold text-cyan-400 font-mono">
|
||||||
|
{assignment.grade}%
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-xs text-cyan-400/40 font-mono">
|
||||||
|
Pending
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Mathematics (Separate) */}
|
||||||
|
<Card className="bg-[#0d1117] border-2 border-cyan-500/30 p-6">
|
||||||
|
<h3 className="text-xl font-bold text-cyan-400 font-mono mb-4">
|
||||||
|
Mathematics
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Card className="bg-[#0a0e1a] border border-cyan-500/20 p-4">
|
||||||
|
<div className="flex justify-between items-start mb-2">
|
||||||
|
<h4 className="font-bold text-cyan-400 font-mono text-sm">
|
||||||
|
Algebra
|
||||||
|
</h4>
|
||||||
|
<span className="text-xs text-cyan-400/60 font-mono">
|
||||||
|
8/10
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Progress value={80} className="h-2 bg-[#0d1117]" />
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Center Column - Assignments Table */}
|
||||||
|
<Card className="lg:col-span-2 bg-[#0d1117] border-2 border-cyan-500/30 p-6">
|
||||||
|
<h3 className="text-xl font-bold text-cyan-400 font-mono mb-4">
|
||||||
|
All Assignments
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{/* Table */}
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr className="border-b border-cyan-500/20">
|
||||||
|
<th className="text-left py-3 px-4 text-xs font-mono text-cyan-400/60 uppercase">
|
||||||
|
#
|
||||||
|
</th>
|
||||||
|
<th className="text-left py-3 px-4 text-xs font-mono text-cyan-400/60 uppercase">
|
||||||
|
Course
|
||||||
|
</th>
|
||||||
|
<th className="text-left py-3 px-4 text-xs font-mono text-cyan-400/60 uppercase">
|
||||||
|
Assignment
|
||||||
|
</th>
|
||||||
|
<th className="text-left py-3 px-4 text-xs font-mono text-cyan-400/60 uppercase">
|
||||||
|
Grade
|
||||||
|
</th>
|
||||||
|
<th className="text-left py-3 px-4 text-xs font-mono text-cyan-400/60 uppercase">
|
||||||
|
Status
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{assignments.map((assignment, index) => (
|
||||||
|
<tr
|
||||||
|
key={assignment.id}
|
||||||
|
className="border-b border-cyan-500/10 hover:bg-cyan-500/5 transition-colors"
|
||||||
|
>
|
||||||
|
<td className="py-3 px-4 text-sm font-mono text-cyan-400">
|
||||||
|
{index + 1}
|
||||||
|
</td>
|
||||||
|
<td className="py-3 px-4 text-sm font-mono text-cyan-400">
|
||||||
|
{assignment.course}
|
||||||
|
</td>
|
||||||
|
<td className="py-3 px-4 text-sm font-mono text-cyan-400">
|
||||||
|
{assignment.name}
|
||||||
|
</td>
|
||||||
|
<td className="py-3 px-4 text-sm font-mono text-cyan-400">
|
||||||
|
{assignment.grade ? `${assignment.grade}%` : '-'}
|
||||||
|
</td>
|
||||||
|
<td className="py-3 px-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{assignment.status === 'completed' ? (
|
||||||
|
<>
|
||||||
|
<CheckCircle2 className="w-4 h-4 text-green-400" />
|
||||||
|
<span className="text-xs font-mono text-green-400">
|
||||||
|
Completed
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : assignment.status === 'pending' ? (
|
||||||
|
<>
|
||||||
|
<Clock className="w-4 h-4 text-yellow-400" />
|
||||||
|
<span className="text-xs font-mono text-yellow-400">
|
||||||
|
Pending
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Circle className="w-4 h-4 text-cyan-400/30" />
|
||||||
|
<span className="text-xs font-mono text-cyan-400/30">
|
||||||
|
Not Started
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Weekly Activity Chart */}
|
||||||
|
<div className="mt-8">
|
||||||
|
<h4 className="text-lg font-bold text-cyan-400 font-mono mb-4">
|
||||||
|
Weekly Activity
|
||||||
|
</h4>
|
||||||
|
<div className="flex items-end justify-between gap-2 h-48">
|
||||||
|
{weekActivity.map((day) => (
|
||||||
|
<div key={day.day} className="flex-1 flex flex-col items-center gap-2">
|
||||||
|
<div className="w-full relative flex-1">
|
||||||
|
<div
|
||||||
|
className="absolute bottom-0 w-full bg-gradient-to-t from-cyan-500 to-cyan-400 rounded-t-lg transition-all hover:from-cyan-400 hover:to-cyan-300"
|
||||||
|
style={{ height: `${(day.hours / maxHours) * 100}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs font-mono text-cyan-400/60">
|
||||||
|
{day.day}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs font-mono text-cyan-400 font-bold">
|
||||||
|
{day.hours}h
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* Background grid */}
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 pointer-events-none -z-10"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `
|
||||||
|
linear-gradient(rgba(6, 182, 212, 0.03) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(6, 182, 212, 0.03) 1px, transparent 1px)
|
||||||
|
`,
|
||||||
|
backgroundSize: '50px 50px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
137
app/(home)/main/reset-password/page.tsx
Normal file
137
app/(home)/main/reset-password/page.tsx
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState } from "react"
|
||||||
|
import { Card } from "@/components/ui/card"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import { Label } from "@/components/ui/label"
|
||||||
|
import Link from "next/link"
|
||||||
|
import { ArrowLeft, Mail } from "lucide-react"
|
||||||
|
|
||||||
|
export default function ResetPasswordPage() {
|
||||||
|
const [email, setEmail] = useState("")
|
||||||
|
const [submitted, setSubmitted] = useState(false)
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
// Здесь будет логика отправки письма для сброса пароля
|
||||||
|
console.log("Reset password for:", email)
|
||||||
|
setSubmitted(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-[#0a0e1a] flex items-center justify-center p-6">
|
||||||
|
{/* Background grid */}
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 pointer-events-none -z-10"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `
|
||||||
|
linear-gradient(rgba(6, 182, 212, 0.03) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(6, 182, 212, 0.03) 1px, transparent 1px)
|
||||||
|
`,
|
||||||
|
backgroundSize: '50px 50px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Glowing effects */}
|
||||||
|
<div className="fixed top-20 left-20 w-96 h-96 bg-cyan-500/10 rounded-full blur-[120px] animate-pulse" />
|
||||||
|
<div className="fixed bottom-20 right-20 w-96 h-96 bg-blue-500/10 rounded-full blur-[120px] animate-pulse" />
|
||||||
|
|
||||||
|
<Card className="w-full max-w-md bg-[#0d1117] border-2 border-cyan-500/40 shadow-[0_0_50px_rgba(6,182,212,0.2)] relative overflow-hidden">
|
||||||
|
{/* Top accent line */}
|
||||||
|
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-transparent via-cyan-400 to-transparent" />
|
||||||
|
|
||||||
|
<div className="p-8">
|
||||||
|
{/* Back button */}
|
||||||
|
<Link
|
||||||
|
href="/login"
|
||||||
|
className="inline-flex items-center gap-2 text-cyan-400/60 hover:text-cyan-400 font-mono text-sm mb-6 transition-colors group"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
|
||||||
|
Back to login
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{!submitted ? (
|
||||||
|
<>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<div className="w-16 h-16 bg-cyan-500/10 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<Mail className="w-8 h-8 text-cyan-400" />
|
||||||
|
</div>
|
||||||
|
<h1 className="text-4xl font-bold text-cyan-400 font-mono mb-2">
|
||||||
|
Reset my password
|
||||||
|
</h1>
|
||||||
|
<p className="text-cyan-400/50 font-mono text-sm">
|
||||||
|
Enter your email to receive reset instructions
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Reset Form */}
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label
|
||||||
|
htmlFor="email"
|
||||||
|
className="text-cyan-400 font-mono text-sm"
|
||||||
|
>
|
||||||
|
Email Address
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="your.email@example.com"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
required
|
||||||
|
className="bg-[#0a0e1a] border-cyan-500/30 text-cyan-400 placeholder:text-cyan-400/30 font-mono focus:border-cyan-400 focus:ring-cyan-400/20"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
className="w-full bg-cyan-500 hover:bg-cyan-400 text-black font-bold font-mono tracking-wider py-6 text-base shadow-[0_0_30px_rgba(6,182,212,0.3)] hover:shadow-[0_0_50px_rgba(6,182,212,0.5)] transition-all"
|
||||||
|
>
|
||||||
|
SEND RESET LINK
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* Success State */}
|
||||||
|
<div className="text-center py-8">
|
||||||
|
<div className="w-20 h-20 bg-cyan-500/10 rounded-full flex items-center justify-center mx-auto mb-6 animate-pulse">
|
||||||
|
<Mail className="w-10 h-10 text-cyan-400" />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-3xl font-bold text-cyan-400 font-mono mb-3">
|
||||||
|
Check your email
|
||||||
|
</h2>
|
||||||
|
<p className="text-cyan-400/60 font-mono text-sm mb-6">
|
||||||
|
We've sent password reset instructions to<br />
|
||||||
|
<span className="text-cyan-400">{email}</span>
|
||||||
|
</p>
|
||||||
|
<div className="bg-cyan-500/5 border border-cyan-500/20 rounded-lg p-4 mb-6">
|
||||||
|
<p className="text-cyan-400/70 font-mono text-xs">
|
||||||
|
Didn't receive the email? Check your spam folder or try again in a few minutes.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Link href="/login">
|
||||||
|
<Button
|
||||||
|
className="w-full bg-[#0a0e1a] border-2 border-cyan-500/30 text-cyan-400 hover:bg-cyan-500/10 font-mono"
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
|
BACK TO LOGIN
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Corner decorations */}
|
||||||
|
<div className="absolute top-2 left-2 w-4 h-4 border-t-2 border-l-2 border-cyan-500/30"></div>
|
||||||
|
<div className="absolute top-2 right-2 w-4 h-4 border-t-2 border-r-2 border-cyan-500/30"></div>
|
||||||
|
<div className="absolute bottom-2 left-2 w-4 h-4 border-b-2 border-l-2 border-cyan-500/30"></div>
|
||||||
|
<div className="absolute bottom-2 right-2 w-4 h-4 border-b-2 border-r-2 border-cyan-500/30"></div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
127
app/globals.css
127
app/globals.css
@@ -1,26 +1,125 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
@import "tw-animate-css";
|
||||||
|
|
||||||
:root {
|
@custom-variant dark (&:is(.dark *));
|
||||||
--background: #ffffff;
|
|
||||||
--foreground: #171717;
|
|
||||||
}
|
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
--color-background: var(--background);
|
--color-background: var(--background);
|
||||||
--color-foreground: var(--foreground);
|
--color-foreground: var(--foreground);
|
||||||
--font-sans: var(--font-geist-sans);
|
--font-sans: var(--font-geist-sans);
|
||||||
--font-mono: var(--font-geist-mono);
|
--font-mono: var(--font-geist-mono);
|
||||||
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
|
--color-sidebar-accent: var(--sidebar-accent);
|
||||||
|
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||||
|
--color-sidebar-primary: var(--sidebar-primary);
|
||||||
|
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||||
|
--color-sidebar: var(--sidebar);
|
||||||
|
--color-chart-5: var(--chart-5);
|
||||||
|
--color-chart-4: var(--chart-4);
|
||||||
|
--color-chart-3: var(--chart-3);
|
||||||
|
--color-chart-2: var(--chart-2);
|
||||||
|
--color-chart-1: var(--chart-1);
|
||||||
|
--color-ring: var(--ring);
|
||||||
|
--color-input: var(--input);
|
||||||
|
--color-border: var(--border);
|
||||||
|
--color-destructive: var(--destructive);
|
||||||
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
|
--color-accent: var(--accent);
|
||||||
|
--color-muted-foreground: var(--muted-foreground);
|
||||||
|
--color-muted: var(--muted);
|
||||||
|
--color-secondary-foreground: var(--secondary-foreground);
|
||||||
|
--color-secondary: var(--secondary);
|
||||||
|
--color-primary-foreground: var(--primary-foreground);
|
||||||
|
--color-primary: var(--primary);
|
||||||
|
--color-popover-foreground: var(--popover-foreground);
|
||||||
|
--color-popover: var(--popover);
|
||||||
|
--color-card-foreground: var(--card-foreground);
|
||||||
|
--color-card: var(--card);
|
||||||
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
|
--radius-md: calc(var(--radius) - 2px);
|
||||||
|
--radius-lg: var(--radius);
|
||||||
|
--radius-xl: calc(var(--radius) + 4px);
|
||||||
|
--radius-2xl: calc(var(--radius) + 8px);
|
||||||
|
--radius-3xl: calc(var(--radius) + 12px);
|
||||||
|
--radius-4xl: calc(var(--radius) + 16px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
:root {
|
||||||
:root {
|
--radius: 0.625rem;
|
||||||
--background: #0a0a0a;
|
--background: oklch(1 0 0);
|
||||||
--foreground: #ededed;
|
--foreground: oklch(0.145 0 0);
|
||||||
|
--card: oklch(1 0 0);
|
||||||
|
--card-foreground: oklch(0.145 0 0);
|
||||||
|
--popover: oklch(1 0 0);
|
||||||
|
--popover-foreground: oklch(0.145 0 0);
|
||||||
|
--primary: oklch(0.205 0 0);
|
||||||
|
--primary-foreground: oklch(0.985 0 0);
|
||||||
|
--secondary: oklch(0.97 0 0);
|
||||||
|
--secondary-foreground: oklch(0.205 0 0);
|
||||||
|
--muted: oklch(0.97 0 0);
|
||||||
|
--muted-foreground: oklch(0.556 0 0);
|
||||||
|
--accent: oklch(0.97 0 0);
|
||||||
|
--accent-foreground: oklch(0.205 0 0);
|
||||||
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
|
--border: oklch(0.922 0 0);
|
||||||
|
--input: oklch(0.922 0 0);
|
||||||
|
--ring: oklch(0.708 0 0);
|
||||||
|
--chart-1: oklch(0.646 0.222 41.116);
|
||||||
|
--chart-2: oklch(0.6 0.118 184.704);
|
||||||
|
--chart-3: oklch(0.398 0.07 227.392);
|
||||||
|
--chart-4: oklch(0.828 0.189 84.429);
|
||||||
|
--chart-5: oklch(0.769 0.188 70.08);
|
||||||
|
--sidebar: oklch(0.985 0 0);
|
||||||
|
--sidebar-foreground: oklch(0.145 0 0);
|
||||||
|
--sidebar-primary: oklch(0.205 0 0);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-accent: oklch(0.97 0 0);
|
||||||
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||||
|
--sidebar-border: oklch(0.922 0 0);
|
||||||
|
--sidebar-ring: oklch(0.708 0 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: oklch(0.145 0 0);
|
||||||
|
--foreground: oklch(0.985 0 0);
|
||||||
|
--card: oklch(0.205 0 0);
|
||||||
|
--card-foreground: oklch(0.985 0 0);
|
||||||
|
--popover: oklch(0.205 0 0);
|
||||||
|
--popover-foreground: oklch(0.985 0 0);
|
||||||
|
--primary: oklch(0.922 0 0);
|
||||||
|
--primary-foreground: oklch(0.205 0 0);
|
||||||
|
--secondary: oklch(0.269 0 0);
|
||||||
|
--secondary-foreground: oklch(0.985 0 0);
|
||||||
|
--muted: oklch(0.269 0 0);
|
||||||
|
--muted-foreground: oklch(0.708 0 0);
|
||||||
|
--accent: oklch(0.269 0 0);
|
||||||
|
--accent-foreground: oklch(0.985 0 0);
|
||||||
|
--destructive: oklch(0.704 0.191 22.216);
|
||||||
|
--border: oklch(1 0 0 / 10%);
|
||||||
|
--input: oklch(1 0 0 / 15%);
|
||||||
|
--ring: oklch(0.556 0 0);
|
||||||
|
--chart-1: oklch(0.488 0.243 264.376);
|
||||||
|
--chart-2: oklch(0.696 0.17 162.48);
|
||||||
|
--chart-3: oklch(0.769 0.188 70.08);
|
||||||
|
--chart-4: oklch(0.627 0.265 303.9);
|
||||||
|
--chart-5: oklch(0.645 0.246 16.439);
|
||||||
|
--sidebar: oklch(0.205 0 0);
|
||||||
|
--sidebar-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-accent: oklch(0.269 0 0);
|
||||||
|
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-border: oklch(1 0 0 / 10%);
|
||||||
|
--sidebar-ring: oklch(0.556 0 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border outline-ring/50;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
|
||||||
background: var(--background);
|
|
||||||
color: var(--foreground);
|
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ const geistMono = Geist_Mono({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Create Next App",
|
title: "Dashboard App",
|
||||||
description: "Generated by create next app",
|
description: "Modern dashboard application",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
@@ -23,7 +23,7 @@ export default function RootLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en" className="dark">
|
||||||
<body
|
<body
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||||
>
|
>
|
||||||
@@ -31,4 +31,4 @@ export default function RootLayout({
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
208
app/page.tsx
208
app/page.tsx
@@ -1,65 +1,153 @@
|
|||||||
import Image from "next/image";
|
"use client"
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react"
|
||||||
|
import { ArrowRight, Sparkles, Zap, Shield } from "lucide-react"
|
||||||
|
import { Card } from "@/components/ui/card"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import Link from "next/link"
|
||||||
|
|
||||||
|
export default function WelcomePage() {
|
||||||
|
const [mounted, setMounted] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMounted(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
export default function Home() {
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
<div className="min-h-screen bg-[#0a0e1a] relative overflow-hidden">
|
||||||
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
{/* Animated background grid */}
|
||||||
<Image
|
<div
|
||||||
className="dark:invert"
|
className="fixed inset-0 pointer-events-none -z-10 opacity-30"
|
||||||
src="/next.svg"
|
style={{
|
||||||
alt="Next.js logo"
|
backgroundImage: `
|
||||||
width={100}
|
linear-gradient(rgba(6, 182, 212, 0.1) 1px, transparent 1px),
|
||||||
height={20}
|
linear-gradient(90deg, rgba(6, 182, 212, 0.1) 1px, transparent 1px)
|
||||||
priority
|
`,
|
||||||
/>
|
backgroundSize: '50px 50px',
|
||||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
animation: mounted ? 'grid-move 20s linear infinite' : 'none'
|
||||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
}}
|
||||||
To get started, edit the page.tsx file.
|
/>
|
||||||
</h1>
|
|
||||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
{/* Glowing orbs */}
|
||||||
Looking for a starting point or more instructions? Head over to{" "}
|
<div className="fixed top-20 left-20 w-72 h-72 bg-cyan-500/20 rounded-full blur-[100px] animate-pulse" />
|
||||||
<a
|
<div className="fixed bottom-20 right-20 w-96 h-96 bg-blue-500/10 rounded-full blur-[120px] animate-pulse delay-1000" />
|
||||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
{/* Main Content */}
|
||||||
>
|
<main className="container mx-auto px-6 py-12 relative z-10">
|
||||||
Templates
|
<div className="max-w-4xl mx-auto">
|
||||||
</a>{" "}
|
{/* Hero Section */}
|
||||||
or the{" "}
|
<div className="text-center mb-16 pt-20">
|
||||||
<a
|
<div className="inline-block mb-6">
|
||||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
<div className="flex items-center gap-2 px-4 py-2 bg-cyan-500/10 border border-cyan-500/30 rounded-full">
|
||||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
<Sparkles className="w-4 h-4 text-cyan-400" />
|
||||||
>
|
<span className="text-xs font-mono text-cyan-400 tracking-wider">
|
||||||
Learning
|
WELCOME TO THE FUTURE
|
||||||
</a>{" "}
|
</span>
|
||||||
center.
|
</div>
|
||||||
</p>
|
</div>
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
<h1 className="text-7xl md:text-8xl font-bold mb-6 text-transparent bg-clip-text bg-gradient-to-r from-cyan-400 via-cyan-300 to-blue-400 font-mono tracking-tight">
|
||||||
<a
|
Dashboard
|
||||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
</h1>
|
||||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
<p className="text-xl text-cyan-400/60 font-mono mb-12 tracking-wide">
|
||||||
rel="noopener noreferrer"
|
Управляй своим прогрессом в киберпространстве
|
||||||
>
|
</p>
|
||||||
<Image
|
|
||||||
className="dark:invert"
|
<Link href="/home/main">
|
||||||
src="/vercel.svg"
|
<Button
|
||||||
alt="Vercel logomark"
|
size="lg"
|
||||||
width={16}
|
className="bg-cyan-500 hover:bg-cyan-400 text-black font-bold font-mono tracking-wider px-8 py-6 text-lg rounded-lg shadow-[0_0_30px_rgba(6,182,212,0.4)] hover:shadow-[0_0_50px_rgba(6,182,212,0.6)] transition-all duration-300 group"
|
||||||
height={16}
|
>
|
||||||
/>
|
Войти в систему
|
||||||
Deploy Now
|
<ArrowRight className="ml-2 w-5 h-5 group-hover:translate-x-1 transition-transform" />
|
||||||
</a>
|
</Button>
|
||||||
<a
|
</Link>
|
||||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
</div>
|
||||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
{/* Features Grid */}
|
||||||
rel="noopener noreferrer"
|
<div className="grid md:grid-cols-3 gap-6 mt-20">
|
||||||
>
|
<Card className="bg-[#0d1117] border-2 border-cyan-500/20 p-6 hover:border-cyan-500/40 transition-all duration-300 group hover:shadow-[0_0_30px_rgba(6,182,212,0.1)]">
|
||||||
Documentation
|
<div className="w-12 h-12 bg-cyan-500/10 rounded-lg flex items-center justify-center mb-4 group-hover:bg-cyan-500/20 transition-colors">
|
||||||
</a>
|
<Zap className="w-6 h-6 text-cyan-400" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-lg font-bold text-cyan-400 font-mono mb-2">
|
||||||
|
Быстрый доступ
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-cyan-400/50 font-mono leading-relaxed">
|
||||||
|
Мгновенный доступ ко всем функциям системы
|
||||||
|
</p>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="bg-[#0d1117] border-2 border-cyan-500/20 p-6 hover:border-cyan-500/40 transition-all duration-300 group hover:shadow-[0_0_30px_rgba(6,182,212,0.1)]">
|
||||||
|
<div className="w-12 h-12 bg-cyan-500/10 rounded-lg flex items-center justify-center mb-4 group-hover:bg-cyan-500/20 transition-colors">
|
||||||
|
<Shield className="w-6 h-6 text-cyan-400" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-lg font-bold text-cyan-400 font-mono mb-2">
|
||||||
|
Безопасность
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-cyan-400/50 font-mono leading-relaxed">
|
||||||
|
Защищённое хранение данных и прогресса
|
||||||
|
</p>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="bg-[#0d1117] border-2 border-cyan-500/20 p-6 hover:border-cyan-500/40 transition-all duration-300 group hover:shadow-[0_0_30px_rgba(6,182,212,0.1)]">
|
||||||
|
<div className="w-12 h-12 bg-cyan-500/10 rounded-lg flex items-center justify-center mb-4 group-hover:bg-cyan-500/20 transition-colors">
|
||||||
|
<Sparkles className="w-6 h-6 text-cyan-400" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-lg font-bold text-cyan-400 font-mono mb-2">
|
||||||
|
Прогресс
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-cyan-400/50 font-mono leading-relaxed">
|
||||||
|
Отслеживание достижений в реальном времени
|
||||||
|
</p>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Stats Section */}
|
||||||
|
<div className="mt-16 text-center">
|
||||||
|
<Card className="bg-[#0d1117] border-2 border-cyan-500/30 p-8 shadow-[0_0_50px_rgba(6,182,212,0.15)]">
|
||||||
|
<div className="grid grid-cols-3 gap-8">
|
||||||
|
<div>
|
||||||
|
<div className="text-4xl font-bold text-cyan-400 font-mono mb-2">
|
||||||
|
24/7
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-cyan-400/50 font-mono">
|
||||||
|
Онлайн
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-4xl font-bold text-cyan-400 font-mono mb-2">
|
||||||
|
100%
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-cyan-400/50 font-mono">
|
||||||
|
Безопасность
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-4xl font-bold text-cyan-400 font-mono mb-2">
|
||||||
|
∞
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-cyan-400/50 font-mono">
|
||||||
|
Возможности
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<style jsx>{`
|
||||||
|
@keyframes grid-move {
|
||||||
|
0% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate(50px, 50px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
22
components.json
Normal file
22
components.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "new-york",
|
||||||
|
"rsc": true,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "",
|
||||||
|
"css": "app/globals.css",
|
||||||
|
"baseColor": "neutral",
|
||||||
|
"cssVariables": true,
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"iconLibrary": "lucide",
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"utils": "@/lib/utils",
|
||||||
|
"ui": "@/components/ui",
|
||||||
|
"lib": "@/lib",
|
||||||
|
"hooks": "@/hooks"
|
||||||
|
},
|
||||||
|
"registries": {}
|
||||||
|
}
|
||||||
11
components/theme-provider.tsx
Normal file
11
components/theme-provider.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
||||||
|
|
||||||
|
export function ThemeProvider({
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof NextThemesProvider>) {
|
||||||
|
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
||||||
|
}
|
||||||
62
components/ui/button.tsx
Normal file
62
components/ui/button.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||||
|
destructive:
|
||||||
|
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||||
|
outline:
|
||||||
|
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
||||||
|
secondary:
|
||||||
|
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
|
ghost:
|
||||||
|
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||||
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||||
|
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
||||||
|
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
||||||
|
icon: "size-9",
|
||||||
|
"icon-sm": "size-8",
|
||||||
|
"icon-lg": "size-10",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function Button({
|
||||||
|
className,
|
||||||
|
variant = "default",
|
||||||
|
size = "default",
|
||||||
|
asChild = false,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"button"> &
|
||||||
|
VariantProps<typeof buttonVariants> & {
|
||||||
|
asChild?: boolean
|
||||||
|
}) {
|
||||||
|
const Comp = asChild ? Slot : "button"
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
data-slot="button"
|
||||||
|
data-variant={variant}
|
||||||
|
data-size={size}
|
||||||
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Button, buttonVariants }
|
||||||
92
components/ui/card.tsx
Normal file
92
components/ui/card.tsx
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="card"
|
||||||
|
className={cn(
|
||||||
|
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="card-header"
|
||||||
|
className={cn(
|
||||||
|
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="card-title"
|
||||||
|
className={cn("leading-none font-semibold", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="card-description"
|
||||||
|
className={cn("text-muted-foreground text-sm", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="card-action"
|
||||||
|
className={cn(
|
||||||
|
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="card-content"
|
||||||
|
className={cn("px-6", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="card-footer"
|
||||||
|
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Card,
|
||||||
|
CardHeader,
|
||||||
|
CardFooter,
|
||||||
|
CardTitle,
|
||||||
|
CardAction,
|
||||||
|
CardDescription,
|
||||||
|
CardContent,
|
||||||
|
}
|
||||||
21
components/ui/input.tsx
Normal file
21
components/ui/input.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
data-slot="input"
|
||||||
|
className={cn(
|
||||||
|
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
|
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
||||||
|
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Input }
|
||||||
24
components/ui/label.tsx
Normal file
24
components/ui/label.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Label({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<LabelPrimitive.Root
|
||||||
|
data-slot="label"
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Label }
|
||||||
31
components/ui/progress.tsx
Normal file
31
components/ui/progress.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as ProgressPrimitive from "@radix-ui/react-progress"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Progress({
|
||||||
|
className,
|
||||||
|
value,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<ProgressPrimitive.Root
|
||||||
|
data-slot="progress"
|
||||||
|
className={cn(
|
||||||
|
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ProgressPrimitive.Indicator
|
||||||
|
data-slot="progress-indicator"
|
||||||
|
className="bg-primary h-full w-full flex-1 transition-all"
|
||||||
|
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
||||||
|
/>
|
||||||
|
</ProgressPrimitive.Root>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Progress }
|
||||||
6
lib/utils.ts
Normal file
6
lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { clsx, type ClassValue } from "clsx"
|
||||||
|
import { twMerge } from "tailwind-merge"
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs))
|
||||||
|
}
|
||||||
195
package-lock.json
generated
195
package-lock.json
generated
@@ -8,9 +8,17 @@
|
|||||||
"name": "s21",
|
"name": "s21",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@radix-ui/react-label": "^2.1.8",
|
||||||
|
"@radix-ui/react-progress": "^1.1.8",
|
||||||
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"lucide-react": "^0.561.0",
|
||||||
"next": "16.0.10",
|
"next": "16.0.10",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
"react": "19.2.1",
|
"react": "19.2.1",
|
||||||
"react-dom": "19.2.1"
|
"react-dom": "19.2.1",
|
||||||
|
"tailwind-merge": "^3.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
@@ -20,6 +28,7 @@
|
|||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "16.0.10",
|
"eslint-config-next": "16.0.10",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1226,6 +1235,124 @@
|
|||||||
"node": ">=12.4.0"
|
"node": ">=12.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-compose-refs": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-context": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-label": {
|
||||||
|
"version": "2.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz",
|
||||||
|
"integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-primitive": "2.1.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-primitive": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-slot": "1.2.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-progress": {
|
||||||
|
"version": "1.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.8.tgz",
|
||||||
|
"integrity": "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-context": "1.1.3",
|
||||||
|
"@radix-ui/react-primitive": "2.1.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rtsao/scc": {
|
"node_modules/@rtsao/scc": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
||||||
@@ -1559,7 +1686,7 @@
|
|||||||
"version": "19.2.7",
|
"version": "19.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
|
||||||
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
|
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
@@ -1569,7 +1696,7 @@
|
|||||||
"version": "19.2.3",
|
"version": "19.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
||||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^19.2.0"
|
"@types/react": "^19.2.0"
|
||||||
@@ -2571,12 +2698,33 @@
|
|||||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/class-variance-authority": {
|
||||||
|
"version": "0.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
|
||||||
|
"integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"clsx": "^2.1.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://polar.sh/cva"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/client-only": {
|
"node_modules/client-only": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/clsx": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
@@ -2630,7 +2778,7 @@
|
|||||||
"version": "3.2.3",
|
"version": "3.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/damerau-levenshtein": {
|
"node_modules/damerau-levenshtein": {
|
||||||
@@ -4834,6 +4982,15 @@
|
|||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lucide-react": {
|
||||||
|
"version": "0.561.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.561.0.tgz",
|
||||||
|
"integrity": "sha512-Y59gMY38tl4/i0qewcqohPdEbieBy7SovpBL9IFebhc2mDd8x4PZSOsiFRkpPcOq6bj1r/mjH/Rk73gSlIJP2A==",
|
||||||
|
"license": "ISC",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.21",
|
"version": "0.30.21",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||||
@@ -5001,6 +5158,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/next-themes": {
|
||||||
|
"version": "0.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
|
||||||
|
"integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/next/node_modules/postcss": {
|
"node_modules/next/node_modules/postcss": {
|
||||||
"version": "8.4.31",
|
"version": "8.4.31",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||||
@@ -6018,6 +6185,16 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tailwind-merge": {
|
||||||
|
"version": "3.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
|
||||||
|
"integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/dcastil"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "4.1.18",
|
"version": "4.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
||||||
@@ -6145,6 +6322,16 @@
|
|||||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
"license": "0BSD"
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
|
"node_modules/tw-animate-css": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/Wombosvideo"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/type-check": {
|
"node_modules/type-check": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||||
|
|||||||
11
package.json
11
package.json
@@ -9,9 +9,17 @@
|
|||||||
"lint": "eslint"
|
"lint": "eslint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@radix-ui/react-label": "^2.1.8",
|
||||||
|
"@radix-ui/react-progress": "^1.1.8",
|
||||||
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"lucide-react": "^0.561.0",
|
||||||
"next": "16.0.10",
|
"next": "16.0.10",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
"react": "19.2.1",
|
"react": "19.2.1",
|
||||||
"react-dom": "19.2.1"
|
"react-dom": "19.2.1",
|
||||||
|
"tailwind-merge": "^3.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
@@ -21,6 +29,7 @@
|
|||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "16.0.10",
|
"eslint-config-next": "16.0.10",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user