mirror of
https://github.com/arkorty/B.Tech-Project-III.git
synced 2026-04-19 20:51:49 +00:00
430 lines
18 KiB
TypeScript
430 lines
18 KiB
TypeScript
'use client';
|
|
|
|
import { useWalletConnection } from '@/hooks/useWalletConnection';
|
|
import { api } from '@/lib/api';
|
|
import { formatCurrency } from '@/lib/utils';
|
|
import { VerificationStatus } from '@/types';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import { motion } from 'framer-motion';
|
|
import { Award, CheckCircle2, Clock, ExternalLink, Lock, TrendingUp, Wallet, XCircle } from 'lucide-react';
|
|
import Link from 'next/link';
|
|
import { useEffect } from 'react';
|
|
import { Bar, BarChart, CartesianGrid, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
|
|
import { LoadingSpinner } from '@/components/ui/LoadingSpinner';
|
|
|
|
const earningsData = [
|
|
{ day: "Mon", earnings: 12.5 },
|
|
{ day: "Tue", earnings: 18.3 },
|
|
{ day: "Wed", earnings: 15.7 },
|
|
{ day: "Thu", earnings: 22.1 },
|
|
{ day: "Fri", earnings: 25.4 },
|
|
{ day: "Sat", earnings: 19.8 },
|
|
{ day: "Sun", earnings: 28.6 },
|
|
]
|
|
|
|
const tasksData = [
|
|
{ day: "Mon", completed: 4 },
|
|
{ day: "Tue", completed: 6 },
|
|
{ day: "Wed", completed: 5 },
|
|
{ day: "Thu", completed: 7 },
|
|
{ day: "Fri", completed: 8 },
|
|
{ day: "Sat", completed: 6 },
|
|
{ day: "Sun", completed: 9 },
|
|
]
|
|
|
|
export default function DashboardPage() {
|
|
const { isConnected, address, connect } = useWalletConnection();
|
|
|
|
const { data: profileData, isLoading: profileLoading, refetch: refetchProfile } = useQuery({
|
|
queryKey: ['profile'],
|
|
queryFn: () => api.users.getProfile(),
|
|
enabled: isConnected,
|
|
});
|
|
|
|
const { data: submissionsData, isLoading: submissionsLoading, refetch: refetchSubmissions } = useQuery({
|
|
queryKey: ['submissions'],
|
|
queryFn: () => api.submissions.mySubmissions(),
|
|
enabled: isConnected,
|
|
});
|
|
|
|
// Refetch when wallet connects
|
|
useEffect(() => {
|
|
if (isConnected) {
|
|
refetchProfile();
|
|
refetchSubmissions();
|
|
}
|
|
}, [isConnected, refetchProfile, refetchSubmissions]);
|
|
|
|
if (!isConnected) {
|
|
return (
|
|
<div className="relative min-h-screen overflow-hidden">
|
|
{/* Animated Background */}
|
|
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
|
<motion.div
|
|
className="absolute w-96 h-96 bg-linear-to-br from-green-500/20 to-green-600/10 blur-3xl"
|
|
animate={{
|
|
x: [0, 100, 0],
|
|
y: [0, 50, 0],
|
|
}}
|
|
transition={{ duration: 20, repeat: Infinity }}
|
|
style={{ top: "10%", left: "-10%" }}
|
|
/>
|
|
<motion.div
|
|
className="absolute w-96 h-96 bg-linear-to-br from-green-600/10 to-green-500/20 blur-3xl"
|
|
animate={{
|
|
x: [0, -100, 0],
|
|
y: [0, -50, 0],
|
|
}}
|
|
transition={{ duration: 25, repeat: Infinity }}
|
|
style={{ bottom: "10%", right: "-10%" }}
|
|
/>
|
|
</div>
|
|
|
|
<div className="relative z-10 max-w-2xl mx-auto px-4 py-32 text-center">
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.6 }}
|
|
>
|
|
<motion.div
|
|
animate={{ scale: [1, 1.1, 1] }}
|
|
transition={{ duration: 2, repeat: Infinity }}
|
|
className="inline-flex items-center justify-center w-24 h-24 bg-linear-to-br from-green-500/20 to-green-600/10 mb-6"
|
|
>
|
|
<Lock className="w-12 h-12 text-green-500" />
|
|
</motion.div>
|
|
<h2 className="text-4xl font-bold mb-4">
|
|
<span className="bg-linear-to-r from-green-400 via-green-500 to-green-600 bg-clip-text text-transparent">
|
|
Connect Your Wallet
|
|
</span>
|
|
</h2>
|
|
<p className="text-foreground/60 mb-8 text-lg">
|
|
Please connect your wallet to view your dashboard and track your earnings
|
|
</p>
|
|
<motion.button
|
|
onClick={connect}
|
|
whileHover={{ scale: 1.05 }}
|
|
whileTap={{ scale: 0.95 }}
|
|
className="px-8 py-4 bg-linear-to-r from-green-500 to-green-600 text-white hover:from-green-600 hover:to-green-700 transition-all font-bold text-lg shadow-lg shadow-green-500/25 inline-flex items-center gap-2"
|
|
>
|
|
<Wallet className="w-5 h-5" />
|
|
Connect Wallet
|
|
</motion.button>
|
|
</motion.div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (profileLoading || submissionsLoading) {
|
|
return (
|
|
<div className="flex items-center justify-center min-h-[400px]">
|
|
<LoadingSpinner size="lg" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const profile = profileData?.data;
|
|
const submissions = submissionsData?.data || [];
|
|
|
|
const stats = [
|
|
{
|
|
icon: Wallet,
|
|
label: 'Total Earnings',
|
|
value: formatCurrency(profile?.totalEarnings || 0),
|
|
gradient: 'from-green-500 to-green-600',
|
|
bgGradient: 'from-green-500/10 to-green-600/5',
|
|
border: 'border-green-500/30',
|
|
},
|
|
{
|
|
icon: CheckCircle2,
|
|
label: 'Completed Tasks',
|
|
value: profile?.stats?.submissionsApproved || 0,
|
|
gradient: 'from-green-500 to-green-600',
|
|
bgGradient: 'from-green-500/10 to-green-600/5',
|
|
border: 'border-green-500/30',
|
|
},
|
|
{
|
|
icon: TrendingUp,
|
|
label: 'Approval Rate',
|
|
value: `${profile?.stats?.approvalRate || 0}%`,
|
|
gradient: 'from-blue-500 to-blue-600',
|
|
bgGradient: 'from-blue-500/10 to-blue-600/5',
|
|
border: 'border-blue-500/30',
|
|
},
|
|
{
|
|
icon: Award,
|
|
label: 'Reputation',
|
|
value: `⭐ ${profile?.reputationScore || 0}`,
|
|
gradient: 'from-green-400 to-green-500',
|
|
bgGradient: 'from-green-400/10 to-green-500/5',
|
|
border: 'border-green-400/30',
|
|
},
|
|
];
|
|
|
|
const getStatusConfig = (status: string) => {
|
|
switch (status) {
|
|
case VerificationStatus.APPROVED:
|
|
return { icon: CheckCircle2, color: 'text-green-600', bg: 'bg-green-100', text: 'Approved' };
|
|
case VerificationStatus.REJECTED:
|
|
return { icon: XCircle, color: 'text-red-600', bg: 'bg-red-100', text: 'Rejected' };
|
|
default:
|
|
return { icon: Clock, color: 'text-green-600', bg: 'bg-green-100', text: 'Pending' };
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="relative min-h-screen overflow-hidden">
|
|
{/* Animated Background */}
|
|
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
|
<motion.div
|
|
className="absolute w-96 h-96 bg-linear-to-br from-green-500/20 to-green-600/10 blur-3xl"
|
|
animate={{
|
|
x: [0, 100, 0],
|
|
y: [0, 50, 0],
|
|
}}
|
|
transition={{ duration: 20, repeat: Infinity }}
|
|
style={{ top: "10%", left: "-10%" }}
|
|
/>
|
|
<motion.div
|
|
className="absolute w-96 h-96 bg-linear-to-br from-green-600/10 to-green-500/20 blur-3xl"
|
|
animate={{
|
|
x: [0, -100, 0],
|
|
y: [0, -50, 0],
|
|
}}
|
|
transition={{ duration: 25, repeat: Infinity }}
|
|
style={{ bottom: "10%", right: "-10%" }}
|
|
/>
|
|
</div>
|
|
|
|
<div className="relative z-10 max-w-7xl mx-auto px-4 py-12 pt-32">
|
|
{/* Header */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: -20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.6 }}
|
|
className="mb-8"
|
|
>
|
|
<div className="flex items-center justify-between flex-wrap gap-4">
|
|
<div>
|
|
<h1 className="text-4xl font-bold mb-2">
|
|
<span className="bg-linear-to-r from-green-400 via-green-500 to-green-600 bg-clip-text text-transparent">
|
|
Dashboard
|
|
</span>
|
|
</h1>
|
|
<div className="flex items-center gap-2 text-sm text-foreground/60">
|
|
<Wallet className="w-4 h-4" />
|
|
<span className="font-mono">{address?.slice(0, 6)}...{address?.slice(-4)}</span>
|
|
</div>
|
|
</div>
|
|
<div className="inline-flex items-center gap-2 px-4 py-2 border border-green-500/30 bg-green-500/5 backdrop-blur-sm">
|
|
<motion.div
|
|
animate={{ rotate: 360 }}
|
|
transition={{ duration: 3, repeat: Infinity, ease: "linear" }}
|
|
>
|
|
<LoadingSpinner />
|
|
</motion.div>
|
|
<span className="text-sm font-semibold bg-linear-to-r from-green-400 to-green-600 bg-clip-text text-transparent">
|
|
Active Worker
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
|
|
{/* Stats Grid */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
|
{stats.map((stat, index) => (
|
|
<motion.div
|
|
key={index}
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.6, delay: index * 0.1 }}
|
|
whileHover={{ y: -4 }}
|
|
className={`bg-linear-to-br ${stat.bgGradient} backdrop-blur-md border ${stat.border} p-6 group cursor-pointer`}
|
|
>
|
|
<div className="flex items-start justify-between mb-4">
|
|
<div className={`w-12 h-12 bg-linear-to-r ${stat.gradient} flex items-center justify-center`}>
|
|
<stat.icon className="w-6 h-6 text-white" />
|
|
</div>
|
|
<motion.div
|
|
animate={{ scale: [1, 1.2, 1] }}
|
|
transition={{ duration: 2, repeat: Infinity, delay: index * 0.2 }}
|
|
className={`w-2 h-2 bg-linear-to-r ${stat.gradient} `}
|
|
/>
|
|
</div>
|
|
<div className="text-sm text-foreground/60 mb-1">{stat.label}</div>
|
|
<div className="text-3xl font-bold text-foreground">{stat.value}</div>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Recent Submissions */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.6, delay: 0.4 }}
|
|
className="bg-background/80 backdrop-blur-md border border-green-500/20 overflow-hidden"
|
|
>
|
|
<div className="p-6 border-b border-green-500/10">
|
|
<h2 className="text-2xl font-bold text-foreground flex items-center gap-2">
|
|
<LoadingSpinner />
|
|
Recent Submissions
|
|
</h2>
|
|
</div>
|
|
|
|
{submissions.length === 0 ? (
|
|
<div className="text-center py-20">
|
|
<motion.div
|
|
animate={{ y: [0, -10, 0] }}
|
|
transition={{ duration: 2, repeat: Infinity }}
|
|
className="inline-flex items-center justify-center w-20 h-20 bg-linear-to-br from-green-500/20 to-green-600/10 mb-4"
|
|
>
|
|
<LoadingSpinner />
|
|
</motion.div>
|
|
<p className="text-foreground/60 mb-6 text-lg">No submissions yet</p>
|
|
<Link href="/tasks">
|
|
<motion.button
|
|
whileHover={{ scale: 1.05 }}
|
|
whileTap={{ scale: 0.95 }}
|
|
className="px-6 py-3 bg-linear-to-r from-green-500 to-green-600 text-white font-semibold inline-flex items-center gap-2"
|
|
>
|
|
Browse Tasks
|
|
<ExternalLink className="w-4 h-4" />
|
|
</motion.button>
|
|
</Link>
|
|
</div>
|
|
) : (
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full">
|
|
<thead className="bg-foreground/5">
|
|
<tr>
|
|
<th className="px-6 py-4 text-left text-xs font-bold text-foreground/60 uppercase tracking-wider">
|
|
Task
|
|
</th>
|
|
<th className="px-6 py-4 text-left text-xs font-bold text-foreground/60 uppercase tracking-wider">
|
|
Amount
|
|
</th>
|
|
<th className="px-6 py-4 text-left text-xs font-bold text-foreground/60 uppercase tracking-wider">
|
|
Status
|
|
</th>
|
|
<th className="px-6 py-4 text-left text-xs font-bold text-foreground/60 uppercase tracking-wider">
|
|
Date
|
|
</th>
|
|
<th className="px-6 py-4 text-left text-xs font-bold text-foreground/60 uppercase tracking-wider">
|
|
Action
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-green-500/10">
|
|
{submissions.map((submission: any, index: number) => {
|
|
const statusConfig = getStatusConfig(submission.verificationStatus);
|
|
const StatusIcon = statusConfig.icon;
|
|
|
|
return (
|
|
<motion.tr
|
|
key={submission.id}
|
|
initial={{ opacity: 0, x: -20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
transition={{ delay: index * 0.05 }}
|
|
className="hover:bg-green-500/5 transition-colors"
|
|
>
|
|
<td className="px-6 py-4">
|
|
<div className="text-sm font-semibold text-foreground">
|
|
{submission.task.title}
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4">
|
|
<div className="text-sm font-bold bg-linear-to-r from-green-400 to-green-600 bg-clip-text text-transparent">
|
|
{formatCurrency(submission.task.paymentAmount)}
|
|
</div>
|
|
</td>
|
|
<td className="px-6 py-4">
|
|
<span className={`px-3 py-1.5 inline-flex items-center gap-1.5 text-xs font-semibold ${statusConfig.bg} ${statusConfig.color}`}>
|
|
<StatusIcon className="w-3.5 h-3.5" />
|
|
{statusConfig.text}
|
|
</span>
|
|
</td>
|
|
<td className="px-6 py-4 text-sm text-foreground/60">
|
|
{new Date(submission.createdAt).toLocaleDateString()}
|
|
</td>
|
|
<td className="px-6 py-4">
|
|
<Link
|
|
href={`/submissions/${submission.id}`}
|
|
className="text-sm text-green-500 hover:text-green-600 font-semibold inline-flex items-center gap-1 transition-colors"
|
|
>
|
|
View Details
|
|
<ExternalLink className="w-3.5 h-3.5" />
|
|
</Link>
|
|
</td>
|
|
</motion.tr>
|
|
);
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
</motion.div>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 my-8">
|
|
{/* Earnings Chart */}
|
|
<div className="bg-black/40 backdrop-blur-xl border border-green-500/20 p-6 hover:border-green-500/40 transition-colors">
|
|
<h3 className="font-semibold text-lg mb-4">Weekly Earnings</h3>
|
|
<ResponsiveContainer width="100%" height={300}>
|
|
<BarChart data={earningsData}>
|
|
<defs>
|
|
<linearGradient id="colorEarnings" x1="0" y1="0" x2="0" y2="1">
|
|
<stop offset="5%" stopColor="#ff8c00" stopOpacity={0.8} />
|
|
<stop offset="95%" stopColor="#ffa500" stopOpacity={0.3} />
|
|
</linearGradient>
|
|
</defs>
|
|
<CartesianGrid strokeDasharray="3 3" stroke="rgba(255,140,0,0.1)" />
|
|
<XAxis dataKey="day" stroke="rgba(255,255,255,0.5)" />
|
|
<YAxis stroke="rgba(255,255,255,0.5)" />
|
|
<Tooltip
|
|
contentStyle={{
|
|
backgroundColor: "rgba(15, 15, 15, 0.95)",
|
|
border: "1px solid rgba(255,140,0,0.3)",
|
|
borderRadius: "8px",
|
|
}}
|
|
/>
|
|
<Bar dataKey="earnings" fill="url(#colorEarnings)" radius={[8, 8, 0, 0]} />
|
|
</BarChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
|
|
{/* Tasks Chart */}
|
|
<div className="bg-black/40 backdrop-blur-xl border border-green-500/20 p-6 hover:border-green-500/40 transition-colors">
|
|
<h3 className="font-semibold text-lg mb-4">Tasks Completed</h3>
|
|
<ResponsiveContainer width="100%" height={300}>
|
|
<LineChart data={tasksData}>
|
|
<defs>
|
|
<linearGradient id="colorTasks" x1="0" y1="0" x2="1" y2="0">
|
|
<stop offset="0%" stopColor="#ff8c00" stopOpacity={1} />
|
|
<stop offset="100%" stopColor="#ffa500" stopOpacity={1} />
|
|
</linearGradient>
|
|
</defs>
|
|
<CartesianGrid strokeDasharray="3 3" stroke="rgba(255,140,0,0.1)" />
|
|
<XAxis dataKey="day" stroke="rgba(255,255,255,0.5)" />
|
|
<YAxis stroke="rgba(255,255,255,0.5)" />
|
|
<Tooltip
|
|
contentStyle={{
|
|
backgroundColor: "rgba(15, 15, 15, 0.95)",
|
|
border: "1px solid rgba(255,140,0,0.3)",
|
|
borderRadius: "8px",
|
|
}}
|
|
/>
|
|
<Line
|
|
type="monotone"
|
|
dataKey="completed"
|
|
stroke="url(#colorTasks)"
|
|
strokeWidth={3}
|
|
dot={{ fill: "#ff8c00", r: 5 }}
|
|
/>
|
|
</LineChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
} |