wexts 2.0.7 → 2.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-63MTCWU2.mjs +361 -0
- package/dist/chunk-63MTCWU2.mjs.map +1 -0
- package/dist/chunk-67IJ6H4J.mjs +44 -0
- package/dist/chunk-67IJ6H4J.mjs.map +1 -0
- package/dist/chunk-7NSRDJ5C.mjs +1 -0
- package/dist/chunk-7NSRDJ5C.mjs.map +1 -0
- package/dist/chunk-ASDXAK6G.js +44 -0
- package/dist/chunk-ASDXAK6G.js.map +1 -0
- package/dist/chunk-CKZ4VSCB.mjs +18 -0
- package/dist/chunk-CKZ4VSCB.mjs.map +1 -0
- package/dist/chunk-DW6GOKMF.js +57 -0
- package/dist/chunk-DW6GOKMF.js.map +1 -0
- package/dist/chunk-GKVPGKAH.js +66 -0
- package/dist/chunk-GKVPGKAH.js.map +1 -0
- package/dist/chunk-HSFLZUJN.mjs +57 -0
- package/dist/chunk-HSFLZUJN.mjs.map +1 -0
- package/dist/chunk-HU63F22V.js +361 -0
- package/dist/chunk-HU63F22V.js.map +1 -0
- package/dist/chunk-JMBD6DOP.js +225 -0
- package/dist/chunk-JMBD6DOP.js.map +1 -0
- package/dist/chunk-K7EIJSYQ.js +1 -0
- package/dist/chunk-K7EIJSYQ.js.map +1 -0
- package/dist/chunk-OTBYRUBE.mjs +225 -0
- package/dist/chunk-OTBYRUBE.mjs.map +1 -0
- package/dist/chunk-WMHVXEYQ.mjs +66 -0
- package/dist/chunk-WMHVXEYQ.mjs.map +1 -0
- package/dist/cli/index.js +146 -25
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +130 -7
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/index.js +2 -2
- package/dist/client/index.mjs +2 -2
- package/dist/codegen/index.d.mts +1 -0
- package/dist/codegen/index.d.ts +1 -0
- package/dist/codegen/index.js +13 -0
- package/dist/codegen/index.js.map +1 -0
- package/dist/codegen/index.mjs +13 -0
- package/dist/codegen/index.mjs.map +1 -0
- package/dist/dev-server/index.d.mts +1 -0
- package/dist/dev-server/index.d.ts +1 -0
- package/dist/dev-server/index.js +13 -0
- package/dist/dev-server/index.js.map +1 -0
- package/dist/dev-server/index.mjs +13 -0
- package/dist/dev-server/index.mjs.map +1 -0
- package/dist/index-SjUaHgFr.d.mts +75 -0
- package/dist/index-SjUaHgFr.d.ts +75 -0
- package/dist/index-tFGPFVfQ.d.mts +67 -0
- package/dist/index-tFGPFVfQ.d.ts +67 -0
- package/dist/index.d.mts +83 -164
- package/dist/index.d.ts +83 -164
- package/dist/index.js +89 -22
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +76 -9
- package/dist/index.mjs.map +1 -1
- package/dist/nest/index.js +2 -2
- package/dist/nest/index.mjs +2 -2
- package/dist/next/index.d.mts +61 -3
- package/dist/next/index.d.ts +61 -3
- package/dist/next/index.js +140 -7
- package/dist/next/index.js.map +1 -1
- package/dist/next/index.mjs +102 -7
- package/dist/next/index.mjs.map +1 -1
- package/dist/types/index.js +1 -1
- package/dist/types/index.mjs +2 -1
- package/package.json +1 -1
- package/templates/nestjs-api/package-lock.json +5623 -0
- package/templates/nestjs-api/package.json +21 -19
- package/templates/nestjs-api/prisma/migrations/20251123205437_init/migration.sql +24 -0
- package/templates/nestjs-api/prisma/migrations/migration_lock.toml +3 -0
- package/templates/nestjs-api/src/auth/auth.controller.ts +5 -5
- package/templates/nestjs-api/src/main.ts +1 -1
- package/templates/nestjs-api/src/todos/todos.controller.ts +7 -7
- package/templates/nestjs-api/src/users/users.controller.ts +3 -3
- package/templates/nestjs-api/tsconfig.json +20 -1
- package/templates/nextjs-web/app/actions/auth.ts +79 -0
- package/templates/nextjs-web/app/dashboard/error.tsx +39 -0
- package/templates/nextjs-web/app/dashboard/loading.tsx +14 -0
- package/templates/nextjs-web/app/dashboard/page.tsx +2 -172
- package/templates/nextjs-web/app/globals.css +80 -15
- package/templates/nextjs-web/app/layout.tsx +7 -5
- package/templates/nextjs-web/app/login/page.tsx +2 -104
- package/templates/nextjs-web/app/page.tsx +1 -1
- package/templates/nextjs-web/app/register/page.tsx +2 -127
- package/templates/nextjs-web/components/ui/button.tsx +56 -0
- package/templates/nextjs-web/components/ui/card.tsx +79 -0
- package/templates/nextjs-web/components/ui/input.tsx +25 -0
- package/templates/nextjs-web/components/ui/label.tsx +24 -0
- package/templates/nextjs-web/features/auth/LoginForm.tsx +140 -0
- package/templates/nextjs-web/features/auth/RegisterForm.tsx +159 -0
- package/templates/nextjs-web/features/auth/api.ts +35 -0
- package/templates/nextjs-web/features/auth/index.ts +3 -0
- package/templates/nextjs-web/features/dashboard/DashboardView.tsx +204 -0
- package/templates/nextjs-web/features/dashboard/api.ts +9 -0
- package/templates/nextjs-web/features/dashboard/components.tsx +74 -0
- package/templates/nextjs-web/features/dashboard/index.ts +3 -0
- package/templates/nextjs-web/hooks/index.ts +4 -0
- package/templates/nextjs-web/lib/api-client.ts +89 -0
- package/templates/nextjs-web/lib/axios-global-config.ts +17 -0
- package/templates/nextjs-web/lib/utils.ts +6 -0
- package/templates/nextjs-web/lib/wexts-client.ts +4 -0
- package/templates/nextjs-web/next-env.d.ts +6 -0
- package/templates/nextjs-web/next.config.ts +20 -0
- package/templates/nextjs-web/package-lock.json +3254 -0
- package/templates/nextjs-web/package.json +23 -14
- package/templates/nextjs-web/postcss.config.js +6 -0
- package/templates/nextjs-web/tailwind.config.ts +55 -1
- package/templates/nextjs-web/tsconfig.json +41 -39
- package/templates/nestjs-api/.env.example +0 -4
- package/templates/nextjs-web/next.config.mjs +0 -4
- /package/templates/nextjs-web/{.env.local.example → .env} +0 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import { register } from './api';
|
|
6
|
+
import { Button } from '@/components/ui/button';
|
|
7
|
+
import { Input } from '@/components/ui/input';
|
|
8
|
+
import { Label } from '@/components/ui/label';
|
|
9
|
+
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
|
|
10
|
+
import Link from 'next/link';
|
|
11
|
+
import { Zap, Mail, Lock, ArrowRight, Loader2, UserPlus } from 'lucide-react';
|
|
12
|
+
|
|
13
|
+
export function RegisterForm() {
|
|
14
|
+
const router = useRouter();
|
|
15
|
+
const [loading, setLoading] = useState(false);
|
|
16
|
+
const [error, setError] = useState('');
|
|
17
|
+
|
|
18
|
+
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
setLoading(true);
|
|
21
|
+
setError('');
|
|
22
|
+
|
|
23
|
+
const formData = new FormData(e.currentTarget);
|
|
24
|
+
const data = Object.fromEntries(formData.entries());
|
|
25
|
+
|
|
26
|
+
// Validate passwords match
|
|
27
|
+
if (data.password !== data.confirmPassword) {
|
|
28
|
+
setError('Passwords do not match');
|
|
29
|
+
setLoading(false);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
await register(data);
|
|
35
|
+
router.push('/login?registered=true');
|
|
36
|
+
} catch (err: any) {
|
|
37
|
+
console.error(err);
|
|
38
|
+
setError(err.response?.data?.message || 'Registration failed. Please try again.');
|
|
39
|
+
} finally {
|
|
40
|
+
setLoading(false);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className="min-h-screen flex items-center justify-center relative overflow-hidden bg-background">
|
|
46
|
+
{/* Animated Background Elements */}
|
|
47
|
+
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
|
48
|
+
<div className="absolute -top-[20%] -left-[10%] w-[50%] h-[50%] rounded-full bg-primary/20 blur-[100px] animate-float" />
|
|
49
|
+
<div className="absolute top-[40%] -right-[10%] w-[40%] h-[40%] rounded-full bg-indigo-500/20 blur-[100px] animate-float" style={{ animationDelay: '2s' }} />
|
|
50
|
+
<div className="absolute -bottom-[10%] left-[20%] w-[30%] h-[30%] rounded-full bg-violet-500/20 blur-[100px] animate-float" style={{ animationDelay: '4s' }} />
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div className="w-full max-w-md p-8 relative z-10">
|
|
54
|
+
<Card className="glass border-white/20 shadow-2xl backdrop-blur-xl">
|
|
55
|
+
<CardHeader className="text-center">
|
|
56
|
+
<div className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary/10 mb-6 mx-auto text-primary">
|
|
57
|
+
<UserPlus className="w-8 h-8" />
|
|
58
|
+
</div>
|
|
59
|
+
<CardTitle className="text-3xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-primary to-indigo-600">
|
|
60
|
+
Create Account
|
|
61
|
+
</CardTitle>
|
|
62
|
+
<CardDescription>
|
|
63
|
+
Join wexts today
|
|
64
|
+
</CardDescription>
|
|
65
|
+
</CardHeader>
|
|
66
|
+
<CardContent>
|
|
67
|
+
<form onSubmit={handleSubmit} className="space-y-6">
|
|
68
|
+
{error && (
|
|
69
|
+
<div className="p-4 rounded-xl bg-destructive/10 text-destructive text-sm font-medium border border-destructive/20 animate-pulse-slow flex items-center gap-2">
|
|
70
|
+
<span className="text-lg">⚠️</span>
|
|
71
|
+
{error}
|
|
72
|
+
</div>
|
|
73
|
+
)}
|
|
74
|
+
|
|
75
|
+
<div className="space-y-4">
|
|
76
|
+
<div className="space-y-2">
|
|
77
|
+
<Label htmlFor="email">Email Address</Label>
|
|
78
|
+
<div className="relative">
|
|
79
|
+
<Mail className="absolute left-3 top-3 w-4 h-4 text-muted-foreground" />
|
|
80
|
+
<Input
|
|
81
|
+
id="email"
|
|
82
|
+
name="email"
|
|
83
|
+
type="email"
|
|
84
|
+
placeholder="name@example.com"
|
|
85
|
+
required
|
|
86
|
+
className="pl-10 bg-secondary/50 border-transparent focus:border-primary/50 focus:bg-background transition-all"
|
|
87
|
+
/>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<div className="space-y-2">
|
|
92
|
+
<Label htmlFor="password">Password</Label>
|
|
93
|
+
<div className="relative">
|
|
94
|
+
<Lock className="absolute left-3 top-3 w-4 h-4 text-muted-foreground" />
|
|
95
|
+
<Input
|
|
96
|
+
id="password"
|
|
97
|
+
name="password"
|
|
98
|
+
type="password"
|
|
99
|
+
placeholder="••••••••"
|
|
100
|
+
required
|
|
101
|
+
minLength={6}
|
|
102
|
+
className="pl-10 bg-secondary/50 border-transparent focus:border-primary/50 focus:bg-background transition-all"
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div className="space-y-2">
|
|
108
|
+
<Label htmlFor="confirmPassword">Confirm Password</Label>
|
|
109
|
+
<div className="relative">
|
|
110
|
+
<Lock className="absolute left-3 top-3 w-4 h-4 text-muted-foreground" />
|
|
111
|
+
<Input
|
|
112
|
+
id="confirmPassword"
|
|
113
|
+
name="confirmPassword"
|
|
114
|
+
type="password"
|
|
115
|
+
placeholder="••••••••"
|
|
116
|
+
required
|
|
117
|
+
minLength={6}
|
|
118
|
+
className="pl-10 bg-secondary/50 border-transparent focus:border-primary/50 focus:bg-background transition-all"
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<Button
|
|
125
|
+
type="submit"
|
|
126
|
+
disabled={loading}
|
|
127
|
+
className="w-full bg-gradient-to-r from-primary to-indigo-600 hover:from-primary/90 hover:to-indigo-600/90 shadow-lg shadow-primary/25 group"
|
|
128
|
+
>
|
|
129
|
+
{loading ? (
|
|
130
|
+
<>
|
|
131
|
+
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
|
132
|
+
Creating account...
|
|
133
|
+
</>
|
|
134
|
+
) : (
|
|
135
|
+
<>
|
|
136
|
+
Create account
|
|
137
|
+
<ArrowRight className="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" />
|
|
138
|
+
</>
|
|
139
|
+
)}
|
|
140
|
+
</Button>
|
|
141
|
+
</form>
|
|
142
|
+
</CardContent>
|
|
143
|
+
<CardFooter className="justify-center">
|
|
144
|
+
<div className="text-center text-sm text-muted-foreground">
|
|
145
|
+
Already have an account?{' '}
|
|
146
|
+
<Link href="/login" className="font-semibold text-primary hover:text-primary/80 transition-colors">
|
|
147
|
+
Sign in
|
|
148
|
+
</Link>
|
|
149
|
+
</div>
|
|
150
|
+
</CardFooter>
|
|
151
|
+
</Card>
|
|
152
|
+
|
|
153
|
+
<p className="text-center mt-8 text-xs text-muted-foreground/60">
|
|
154
|
+
© 2025 wexts Inc. All rights reserved.
|
|
155
|
+
</p>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { postData, applyLogin, applyLogout } from '@/lib/api-client';
|
|
2
|
+
import { toast } from 'react-hot-toast';
|
|
3
|
+
|
|
4
|
+
export const login = async (data: any) => {
|
|
5
|
+
try {
|
|
6
|
+
const response = await postData('/auth/login', data, false);
|
|
7
|
+
if (response.token) {
|
|
8
|
+
applyLogin(response.token);
|
|
9
|
+
toast.success('Logged in successfully! 🚀');
|
|
10
|
+
return response;
|
|
11
|
+
}
|
|
12
|
+
} catch (error: any) {
|
|
13
|
+
toast.error(error.response?.data?.message || 'Login failed');
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const register = async (data: any) => {
|
|
19
|
+
try {
|
|
20
|
+
const response = await postData('/auth/register', data, false);
|
|
21
|
+
if (response.token) {
|
|
22
|
+
applyLogin(response.token);
|
|
23
|
+
toast.success('Account created successfully! 🎉');
|
|
24
|
+
return response;
|
|
25
|
+
}
|
|
26
|
+
} catch (error: any) {
|
|
27
|
+
toast.error(error.response?.data?.message || 'Registration failed');
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const logout = async () => {
|
|
33
|
+
await applyLogout();
|
|
34
|
+
toast.success('Logged out');
|
|
35
|
+
};
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { motion } from 'framer-motion';
|
|
4
|
+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
5
|
+
import { Button } from '@/components/ui/button';
|
|
6
|
+
import { logout } from '@/features/auth';
|
|
7
|
+
import { toast } from 'react-hot-toast';
|
|
8
|
+
import {
|
|
9
|
+
DollarSign,
|
|
10
|
+
Users,
|
|
11
|
+
ShoppingBag,
|
|
12
|
+
Activity,
|
|
13
|
+
ClipboardList,
|
|
14
|
+
CheckCircle2,
|
|
15
|
+
Clock,
|
|
16
|
+
Hourglass,
|
|
17
|
+
Bell,
|
|
18
|
+
LogOut
|
|
19
|
+
} from 'lucide-react';
|
|
20
|
+
|
|
21
|
+
// Mock Data for Demo
|
|
22
|
+
const MOCK_STATS = [
|
|
23
|
+
{ title: 'Total Revenue', value: '$45,231.89', change: '+20.1% from last month', icon: DollarSign, color: 'from-green-500/20 to-emerald-500/20', iconColor: 'text-green-500' },
|
|
24
|
+
{ title: 'Subscriptions', value: '+2350', change: '+180.1% from last month', icon: Users, color: 'from-blue-500/20 to-indigo-500/20', iconColor: 'text-blue-500' },
|
|
25
|
+
{ title: 'Sales', value: '+12,234', change: '+19% from last month', icon: ShoppingBag, color: 'from-orange-500/20 to-red-500/20', iconColor: 'text-orange-500' },
|
|
26
|
+
{ title: 'Active Now', value: '+573', change: '+201 since last hour', icon: Activity, color: 'from-purple-500/20 to-pink-500/20', iconColor: 'text-purple-500' },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const MOCK_TODOS = [
|
|
30
|
+
{ id: 1, title: 'Review project proposal', status: 'completed', date: 'Today, 10:00 AM' },
|
|
31
|
+
{ id: 2, title: 'Team meeting with design team', status: 'pending', date: 'Today, 2:00 PM' },
|
|
32
|
+
{ id: 3, title: 'Update documentation', status: 'in-progress', date: 'Tomorrow, 11:00 AM' },
|
|
33
|
+
{ id: 4, title: 'Deploy to production', status: 'pending', date: 'Wed, 4:00 PM' },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const MOCK_ACTIVITIES = [
|
|
37
|
+
{ id: 1, user: 'Alice Smith', action: 'created a new project', time: '2 min ago' },
|
|
38
|
+
{ id: 2, user: 'Bob Johnson', action: 'commented on task #123', time: '15 min ago' },
|
|
39
|
+
{ id: 3, user: 'Charlie Brown', action: 'completed onboarding', time: '1 hour ago' },
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
export function DashboardView() {
|
|
43
|
+
const handleLogout = async () => {
|
|
44
|
+
await logout();
|
|
45
|
+
toast.success('Logged out successfully');
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const container = {
|
|
49
|
+
hidden: { opacity: 0 },
|
|
50
|
+
show: {
|
|
51
|
+
opacity: 1,
|
|
52
|
+
transition: {
|
|
53
|
+
staggerChildren: 0.1
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const item = {
|
|
59
|
+
hidden: { y: 20, opacity: 0 },
|
|
60
|
+
show: { y: 0, opacity: 1 }
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div className="min-h-screen bg-background p-8 relative overflow-hidden">
|
|
65
|
+
{/* Animated Background */}
|
|
66
|
+
<div className="fixed inset-0 overflow-hidden pointer-events-none -z-10">
|
|
67
|
+
<div className="absolute -top-[40%] -right-[30%] w-[70%] h-[70%] rounded-full bg-primary/10 blur-[120px] animate-float" />
|
|
68
|
+
<div className="absolute top-[60%] -left-[20%] w-[50%] h-[50%] rounded-full bg-indigo-500/10 blur-[100px] animate-float" style={{ animationDelay: '3s' }} />
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div className="max-w-7xl mx-auto space-y-8">
|
|
72
|
+
{/* Header */}
|
|
73
|
+
<motion.div
|
|
74
|
+
initial={{ opacity: 0, y: -20 }}
|
|
75
|
+
animate={{ opacity: 1, y: 0 }}
|
|
76
|
+
className="flex items-center justify-between"
|
|
77
|
+
>
|
|
78
|
+
<div>
|
|
79
|
+
<h1 className="text-4xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-primary to-indigo-600 mb-2">
|
|
80
|
+
Dashboard Overview
|
|
81
|
+
</h1>
|
|
82
|
+
<p className="text-muted-foreground">Welcome back, Ziad! Here's what's happening today.</p>
|
|
83
|
+
</div>
|
|
84
|
+
<Button onClick={handleLogout} variant="outline" className="border-primary/20 hover:bg-primary/10 hover:text-primary transition-colors gap-2">
|
|
85
|
+
<LogOut className="w-4 h-4" />
|
|
86
|
+
Sign out
|
|
87
|
+
</Button>
|
|
88
|
+
</motion.div>
|
|
89
|
+
|
|
90
|
+
{/* Stats Grid */}
|
|
91
|
+
<motion.div
|
|
92
|
+
variants={container}
|
|
93
|
+
initial="hidden"
|
|
94
|
+
animate="show"
|
|
95
|
+
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"
|
|
96
|
+
>
|
|
97
|
+
{MOCK_STATS.map((stat, index) => (
|
|
98
|
+
<motion.div key={index} variants={item}>
|
|
99
|
+
<Card className={`glass border-white/10 hover:border-primary/20 transition-all duration-300 hover:shadow-lg hover:shadow-primary/5 group overflow-hidden relative`}>
|
|
100
|
+
<div className={`absolute inset-0 bg-gradient-to-br ${stat.color} opacity-0 group-hover:opacity-100 transition-opacity duration-500`} />
|
|
101
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2 relative z-10">
|
|
102
|
+
<CardTitle className="text-sm font-medium">{stat.title}</CardTitle>
|
|
103
|
+
<div className={`p-2 rounded-lg bg-background/50 ${stat.iconColor}`}>
|
|
104
|
+
<stat.icon className="w-5 h-5" />
|
|
105
|
+
</div>
|
|
106
|
+
</CardHeader>
|
|
107
|
+
<CardContent className="relative z-10">
|
|
108
|
+
<div className="text-2xl font-bold">{stat.value}</div>
|
|
109
|
+
<p className="text-xs text-muted-foreground mt-1">{stat.change}</p>
|
|
110
|
+
</CardContent>
|
|
111
|
+
</Card>
|
|
112
|
+
</motion.div>
|
|
113
|
+
))}
|
|
114
|
+
</motion.div>
|
|
115
|
+
|
|
116
|
+
{/* Main Content Grid */}
|
|
117
|
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
118
|
+
{/* Recent Tasks */}
|
|
119
|
+
<motion.div
|
|
120
|
+
initial={{ opacity: 0, x: -20 }}
|
|
121
|
+
animate={{ opacity: 1, x: 0 }}
|
|
122
|
+
transition={{ delay: 0.4 }}
|
|
123
|
+
className="lg:col-span-2"
|
|
124
|
+
>
|
|
125
|
+
<Card className="glass border-white/10 h-full">
|
|
126
|
+
<CardHeader>
|
|
127
|
+
<CardTitle className="flex items-center gap-2">
|
|
128
|
+
<ClipboardList className="w-5 h-5 text-primary" />
|
|
129
|
+
Recent Tasks
|
|
130
|
+
</CardTitle>
|
|
131
|
+
</CardHeader>
|
|
132
|
+
<CardContent>
|
|
133
|
+
<div className="space-y-4">
|
|
134
|
+
{MOCK_TODOS.map((todo) => (
|
|
135
|
+
<div key={todo.id} className="flex items-center justify-between p-4 rounded-xl bg-secondary/30 hover:bg-secondary/50 transition-colors group cursor-pointer border border-transparent hover:border-primary/10">
|
|
136
|
+
<div className="flex items-center gap-4">
|
|
137
|
+
<div className={`h-10 w-10 rounded-full flex items-center justify-center ${todo.status === 'completed' ? 'bg-green-500/10 text-green-500' :
|
|
138
|
+
todo.status === 'in-progress' ? 'bg-blue-500/10 text-blue-500' :
|
|
139
|
+
'bg-orange-500/10 text-orange-500'
|
|
140
|
+
}`}>
|
|
141
|
+
{todo.status === 'completed' ? <CheckCircle2 className="w-5 h-5" /> :
|
|
142
|
+
todo.status === 'in-progress' ? <Activity className="w-5 h-5" /> :
|
|
143
|
+
<Hourglass className="w-5 h-5" />}
|
|
144
|
+
</div>
|
|
145
|
+
<div>
|
|
146
|
+
<p className="font-medium group-hover:text-primary transition-colors">{todo.title}</p>
|
|
147
|
+
<p className="text-xs text-muted-foreground flex items-center gap-1">
|
|
148
|
+
<Clock className="w-3 h-3" /> {todo.date}
|
|
149
|
+
</p>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
<span className={`text-xs px-2 py-1 rounded-full capitalize ${todo.status === 'completed' ? 'bg-green-500/10 text-green-500' :
|
|
153
|
+
todo.status === 'in-progress' ? 'bg-blue-500/10 text-blue-500' :
|
|
154
|
+
'bg-orange-500/10 text-orange-500'
|
|
155
|
+
}`}>
|
|
156
|
+
{todo.status.replace('-', ' ')}
|
|
157
|
+
</span>
|
|
158
|
+
</div>
|
|
159
|
+
))}
|
|
160
|
+
</div>
|
|
161
|
+
</CardContent>
|
|
162
|
+
</Card>
|
|
163
|
+
</motion.div>
|
|
164
|
+
|
|
165
|
+
{/* Recent Activity */}
|
|
166
|
+
<motion.div
|
|
167
|
+
initial={{ opacity: 0, x: 20 }}
|
|
168
|
+
animate={{ opacity: 1, x: 0 }}
|
|
169
|
+
transition={{ delay: 0.5 }}
|
|
170
|
+
>
|
|
171
|
+
<Card className="glass border-white/10 h-full">
|
|
172
|
+
<CardHeader>
|
|
173
|
+
<CardTitle className="flex items-center gap-2">
|
|
174
|
+
<Bell className="w-5 h-5 text-primary" />
|
|
175
|
+
Recent Activity
|
|
176
|
+
</CardTitle>
|
|
177
|
+
</CardHeader>
|
|
178
|
+
<CardContent>
|
|
179
|
+
<div className="space-y-6">
|
|
180
|
+
{MOCK_ACTIVITIES.map((activity, index) => (
|
|
181
|
+
<div key={activity.id} className="flex gap-4 relative">
|
|
182
|
+
{index !== MOCK_ACTIVITIES.length - 1 && (
|
|
183
|
+
<div className="absolute left-[19px] top-10 bottom-[-24px] w-[2px] bg-secondary" />
|
|
184
|
+
)}
|
|
185
|
+
<div className="h-10 w-10 rounded-full bg-primary/10 flex items-center justify-center shrink-0 text-sm font-bold text-primary">
|
|
186
|
+
{activity.user[0]}
|
|
187
|
+
</div>
|
|
188
|
+
<div>
|
|
189
|
+
<p className="text-sm">
|
|
190
|
+
<span className="font-semibold">{activity.user}</span> {activity.action}
|
|
191
|
+
</p>
|
|
192
|
+
<p className="text-xs text-muted-foreground mt-1">{activity.time}</p>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
))}
|
|
196
|
+
</div>
|
|
197
|
+
</CardContent>
|
|
198
|
+
</Card>
|
|
199
|
+
</motion.div>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { connection } from 'next/server';
|
|
2
|
+
import { Suspense } from 'react';
|
|
3
|
+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
4
|
+
import { getTodos, getUser } from './api';
|
|
5
|
+
|
|
6
|
+
export async function UserProfile() {
|
|
7
|
+
await connection();
|
|
8
|
+
try {
|
|
9
|
+
const user = await getUser();
|
|
10
|
+
if (!user) return null;
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className="space-y-2">
|
|
14
|
+
<div className="h-20 w-20 rounded-full bg-gradient-to-br from-primary to-indigo-600 flex items-center justify-center text-3xl text-white font-bold mx-auto mb-4 shadow-lg shadow-primary/25">
|
|
15
|
+
{user.email?.[0]?.toUpperCase() || 'U'}
|
|
16
|
+
</div>
|
|
17
|
+
<div className="text-center">
|
|
18
|
+
<h3 className="text-xl font-semibold">User ID: {user.id}</h3>
|
|
19
|
+
<p className="text-sm text-muted-foreground">{user.email}</p>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
} catch (e) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function TodoList() {
|
|
29
|
+
await connection();
|
|
30
|
+
try {
|
|
31
|
+
const todos = await getTodos();
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="space-y-4">
|
|
35
|
+
{todos.length === 0 ? (
|
|
36
|
+
<p className="text-center text-muted-foreground py-4">No todos found.</p>
|
|
37
|
+
) : (
|
|
38
|
+
todos.map((todo: any) => (
|
|
39
|
+
<div key={todo.id} className="flex items-center gap-4 p-3 rounded-lg bg-secondary/50 hover:bg-secondary transition-colors">
|
|
40
|
+
<div className={`h-10 w-10 rounded-full flex items-center justify-center ${todo.completed ? 'bg-green-100 text-green-600' : 'bg-primary/10 text-primary'}`}>
|
|
41
|
+
{todo.completed ? '✓' : '⚡'}
|
|
42
|
+
</div>
|
|
43
|
+
<div>
|
|
44
|
+
<p className={`font-medium ${todo.completed ? 'line-through text-muted-foreground' : ''}`}>{todo.title}</p>
|
|
45
|
+
<p className="text-xs text-muted-foreground">{new Date(todo.createdAt).toLocaleDateString()}</p>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
))
|
|
49
|
+
)}
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
return <p className="text-center text-destructive py-4">Failed to load todos.</p>;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function UserProfileSkeleton() {
|
|
58
|
+
return (
|
|
59
|
+
<div className="animate-pulse space-y-4">
|
|
60
|
+
<div className="h-20 w-20 rounded-full bg-secondary mx-auto" />
|
|
61
|
+
<div className="h-4 w-32 bg-secondary mx-auto rounded" />
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function TodoListSkeleton() {
|
|
67
|
+
return (
|
|
68
|
+
<div className="space-y-4">
|
|
69
|
+
{[1, 2, 3].map(i => (
|
|
70
|
+
<div key={i} className="h-16 w-full bg-secondary rounded-lg animate-pulse" />
|
|
71
|
+
))}
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { axiosInstance } from './axios-global-config';
|
|
2
|
+
import { deleteCookie, getCookie, setCookie } from 'cookies-next';
|
|
3
|
+
|
|
4
|
+
// <======================== axios main configurations ======================>
|
|
5
|
+
export const axios_config = (
|
|
6
|
+
useToken = true,
|
|
7
|
+
isFormData = false,
|
|
8
|
+
method = "GET"
|
|
9
|
+
) => {
|
|
10
|
+
const headers: Record<string, string> = {
|
|
11
|
+
Accept: "application/json",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
if (!isFormData) {
|
|
15
|
+
headers["Content-Type"] = "application/json";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Add specific headers for PATCH requests to handle CORS
|
|
19
|
+
if (method === "PATCH") {
|
|
20
|
+
headers["Access-Control-Request-Method"] = "PATCH";
|
|
21
|
+
headers["Access-Control-Request-Headers"] = "Content-Type, Authorization";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (useToken) {
|
|
25
|
+
const token = getCookie("wexts_token");
|
|
26
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { headers };
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// <========================= get data in client side ======================>
|
|
33
|
+
export const getData = async (endpoint: string) => {
|
|
34
|
+
const response = await axiosInstance.get(endpoint, axios_config());
|
|
35
|
+
return response.data;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// <========================= post data in client side ======================>
|
|
39
|
+
export const postData = async (
|
|
40
|
+
endpoint: string,
|
|
41
|
+
data: any,
|
|
42
|
+
useToken = true
|
|
43
|
+
) => {
|
|
44
|
+
const isFormData = data instanceof FormData;
|
|
45
|
+
const response = await axiosInstance.post(endpoint, data, axios_config(useToken, isFormData, "POST"));
|
|
46
|
+
return response.data;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// <========================= put data in client side ======================>
|
|
50
|
+
export const putData = async (endpoint: string, data: any) => {
|
|
51
|
+
const response = await axiosInstance.put(endpoint, data, axios_config(true, false, "PUT"));
|
|
52
|
+
return response.data;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// <========================= patch data in client side ======================>
|
|
56
|
+
export const patchData = async (
|
|
57
|
+
endpoint: string,
|
|
58
|
+
data: any,
|
|
59
|
+
useToken = true
|
|
60
|
+
) => {
|
|
61
|
+
const isFormData = data instanceof FormData;
|
|
62
|
+
const response = await axiosInstance.patch(endpoint, data, axios_config(useToken, isFormData, "PATCH"));
|
|
63
|
+
return response.data;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// <========================= delete data in client side ======================>
|
|
67
|
+
export const deleteData = async (endpoint: string, data?: any) => {
|
|
68
|
+
const config = axios_config(true, false, "DELETE");
|
|
69
|
+
const response = await axiosInstance.delete(endpoint, {
|
|
70
|
+
...config,
|
|
71
|
+
data: data
|
|
72
|
+
});
|
|
73
|
+
return response.data;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// ======= Auth helpers to integrate with login/logout =======
|
|
77
|
+
export function applyLogin(accessToken: string) {
|
|
78
|
+
setCookie('wexts_token', accessToken, {
|
|
79
|
+
maxAge: 60 * 60 * 24 * 7, // 1 week
|
|
80
|
+
path: '/',
|
|
81
|
+
secure: process.env.NODE_ENV === 'production',
|
|
82
|
+
sameSite: 'lax'
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function applyLogout() {
|
|
87
|
+
deleteCookie('wexts_token');
|
|
88
|
+
if (typeof window !== 'undefined') window.location.replace('/login');
|
|
89
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
|
|
3
|
+
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5050';
|
|
4
|
+
|
|
5
|
+
export const axiosInstance = axios.create({
|
|
6
|
+
baseURL: API_URL,
|
|
7
|
+
timeout: 10000,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
// Add response interceptor for error handling
|
|
11
|
+
axiosInstance.interceptors.response.use(
|
|
12
|
+
(response) => response,
|
|
13
|
+
(error) => {
|
|
14
|
+
// Handle global errors here if needed
|
|
15
|
+
return Promise.reject(error);
|
|
16
|
+
}
|
|
17
|
+
);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { NextConfig } from "next";
|
|
2
|
+
|
|
3
|
+
const nextConfig: NextConfig = {
|
|
4
|
+
experimental: {
|
|
5
|
+
// Enable authentication interrupts for forbidden() and unauthorized()
|
|
6
|
+
authInterrupts: true,
|
|
7
|
+
},
|
|
8
|
+
// Enable component-level caching
|
|
9
|
+
cacheComponents: true,
|
|
10
|
+
cacheLife: {
|
|
11
|
+
// Define custom cache profiles
|
|
12
|
+
default: {
|
|
13
|
+
stale: 300, // 5 minutes
|
|
14
|
+
revalidate: 900, // 15 minutes
|
|
15
|
+
expire: 3600, // 1 hour
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default nextConfig;
|