create-pxlr 1.0.0
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/README.md +160 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +264 -0
- package/package.json +51 -0
- package/templates/blog/frontend/app/blog/[slug]/page.tsx +175 -0
- package/templates/blog/frontend/app/blog/page.tsx +102 -0
- package/templates/blog/frontend/app/components/footer.tsx +21 -0
- package/templates/blog/frontend/app/components/header.tsx +45 -0
- package/templates/blog/frontend/app/globals.css +30 -0
- package/templates/blog/frontend/app/layout.tsx +38 -0
- package/templates/blog/frontend/app/lib/cms.ts +71 -0
- package/templates/blog/frontend/app/page.tsx +155 -0
- package/templates/blog/frontend/next.config.ts +16 -0
- package/templates/blog/frontend/package.json +24 -0
- package/templates/blog/frontend/postcss.config.mjs +7 -0
- package/templates/blog/frontend/tsconfig.json +23 -0
- package/templates/blog/pxlr-cms/README.md +188 -0
- package/templates/blog/pxlr-cms/docker-compose.yml +132 -0
- package/templates/blog/pxlr-cms/nginx/nginx.conf +107 -0
- package/templates/blog/pxlr-cms/packages/admin/.dockerignore +4 -0
- package/templates/blog/pxlr-cms/packages/admin/.env.example +2 -0
- package/templates/blog/pxlr-cms/packages/admin/Dockerfile +19 -0
- package/templates/blog/pxlr-cms/packages/admin/next-env.d.ts +6 -0
- package/templates/blog/pxlr-cms/packages/admin/next.config.ts +22 -0
- package/templates/blog/pxlr-cms/packages/admin/package.json +63 -0
- package/templates/blog/pxlr-cms/packages/admin/pnpm-lock.yaml +5748 -0
- package/templates/blog/pxlr-cms/packages/admin/postcss.config.mjs +9 -0
- package/templates/blog/pxlr-cms/packages/admin/src/app/content/[id]/page.tsx +503 -0
- package/templates/blog/pxlr-cms/packages/admin/src/app/content/layout.tsx +7 -0
- package/templates/blog/pxlr-cms/packages/admin/src/app/content/new/page.tsx +424 -0
- package/templates/blog/pxlr-cms/packages/admin/src/app/content/page.tsx +191 -0
- package/templates/blog/pxlr-cms/packages/admin/src/app/globals.css +132 -0
- package/templates/blog/pxlr-cms/packages/admin/src/app/layout.tsx +25 -0
- package/templates/blog/pxlr-cms/packages/admin/src/app/login/page.tsx +119 -0
- package/templates/blog/pxlr-cms/packages/admin/src/app/media/layout.tsx +7 -0
- package/templates/blog/pxlr-cms/packages/admin/src/app/media/page.tsx +362 -0
- package/templates/blog/pxlr-cms/packages/admin/src/app/page.tsx +184 -0
- package/templates/blog/pxlr-cms/packages/admin/src/app/profile/layout.tsx +7 -0
- package/templates/blog/pxlr-cms/packages/admin/src/app/profile/page.tsx +206 -0
- package/templates/blog/pxlr-cms/packages/admin/src/app/schemas/[name]/page.tsx +312 -0
- package/templates/blog/pxlr-cms/packages/admin/src/app/schemas/layout.tsx +7 -0
- package/templates/blog/pxlr-cms/packages/admin/src/app/schemas/page.tsx +210 -0
- package/templates/blog/pxlr-cms/packages/admin/src/app/settings/layout.tsx +7 -0
- package/templates/blog/pxlr-cms/packages/admin/src/app/settings/page.tsx +178 -0
- package/templates/blog/pxlr-cms/packages/admin/src/components/editor/media-picker.tsx +202 -0
- package/templates/blog/pxlr-cms/packages/admin/src/components/editor/rich-text-editor.tsx +387 -0
- package/templates/blog/pxlr-cms/packages/admin/src/components/layout/auth-layout.tsx +43 -0
- package/templates/blog/pxlr-cms/packages/admin/src/components/layout/header.tsx +79 -0
- package/templates/blog/pxlr-cms/packages/admin/src/components/layout/sidebar.tsx +68 -0
- package/templates/blog/pxlr-cms/packages/admin/src/components/providers.tsx +29 -0
- package/templates/blog/pxlr-cms/packages/admin/src/components/schema-code-generator.tsx +326 -0
- package/templates/blog/pxlr-cms/packages/admin/src/components/ui/avatar.tsx +49 -0
- package/templates/blog/pxlr-cms/packages/admin/src/components/ui/button.tsx +55 -0
- package/templates/blog/pxlr-cms/packages/admin/src/components/ui/dropdown-menu.tsx +194 -0
- package/templates/blog/pxlr-cms/packages/admin/src/components/ui/input.tsx +24 -0
- package/templates/blog/pxlr-cms/packages/admin/src/components/ui/label.tsx +25 -0
- package/templates/blog/pxlr-cms/packages/admin/src/components/ui/toast.tsx +127 -0
- package/templates/blog/pxlr-cms/packages/admin/src/components/ui/toaster.tsx +35 -0
- package/templates/blog/pxlr-cms/packages/admin/src/components/ui/use-toast.ts +187 -0
- package/templates/blog/pxlr-cms/packages/admin/src/lib/api.ts +96 -0
- package/templates/blog/pxlr-cms/packages/admin/src/lib/i18n/context.tsx +60 -0
- package/templates/blog/pxlr-cms/packages/admin/src/lib/i18n/translations.ts +317 -0
- package/templates/blog/pxlr-cms/packages/admin/src/lib/store/auth.ts +51 -0
- package/templates/blog/pxlr-cms/packages/admin/src/lib/utils.ts +29 -0
- package/templates/blog/pxlr-cms/packages/admin/tailwind.config.ts +57 -0
- package/templates/blog/pxlr-cms/packages/admin/tsconfig.json +27 -0
- package/templates/blog/pxlr-cms/packages/api/.env.example +23 -0
- package/templates/blog/pxlr-cms/packages/api/Dockerfile +26 -0
- package/templates/blog/pxlr-cms/packages/api/package.json +42 -0
- package/templates/blog/pxlr-cms/packages/api/src/config.ts +39 -0
- package/templates/blog/pxlr-cms/packages/api/src/database/index.ts +60 -0
- package/templates/blog/pxlr-cms/packages/api/src/database/init.sql +258 -0
- package/templates/blog/pxlr-cms/packages/api/src/database/redis.ts +95 -0
- package/templates/blog/pxlr-cms/packages/api/src/database/seed.sql +78 -0
- package/templates/blog/pxlr-cms/packages/api/src/index.ts +157 -0
- package/templates/blog/pxlr-cms/packages/api/src/modules/auth/routes.ts +256 -0
- package/templates/blog/pxlr-cms/packages/api/src/modules/content/routes.ts +385 -0
- package/templates/blog/pxlr-cms/packages/api/src/modules/media/routes.ts +312 -0
- package/templates/blog/pxlr-cms/packages/api/src/modules/realtime/handler.ts +228 -0
- package/templates/blog/pxlr-cms/packages/api/src/modules/schema/routes.ts +284 -0
- package/templates/blog/pxlr-cms/packages/api/src/modules/versions/routes.ts +70 -0
- package/templates/blog/pxlr-cms/packages/api/tsconfig.json +24 -0
- package/templates/blog/pxlr-cms/packages/shared/package.json +14 -0
- package/templates/blog/pxlr-cms/packages/shared/src/types/index.ts +139 -0
- package/templates/blog/pxlr-cms/packages/shared/tsconfig.json +18 -0
- package/templates/clean/pxlr-cms/README.md +188 -0
- package/templates/clean/pxlr-cms/docker-compose.yml +132 -0
- package/templates/clean/pxlr-cms/nginx/nginx.conf +107 -0
- package/templates/clean/pxlr-cms/packages/admin/.dockerignore +4 -0
- package/templates/clean/pxlr-cms/packages/admin/.env.example +2 -0
- package/templates/clean/pxlr-cms/packages/admin/Dockerfile +19 -0
- package/templates/clean/pxlr-cms/packages/admin/next-env.d.ts +6 -0
- package/templates/clean/pxlr-cms/packages/admin/next.config.ts +22 -0
- package/templates/clean/pxlr-cms/packages/admin/package.json +63 -0
- package/templates/clean/pxlr-cms/packages/admin/pnpm-lock.yaml +5748 -0
- package/templates/clean/pxlr-cms/packages/admin/postcss.config.mjs +9 -0
- package/templates/clean/pxlr-cms/packages/admin/src/app/content/[id]/page.tsx +503 -0
- package/templates/clean/pxlr-cms/packages/admin/src/app/content/layout.tsx +7 -0
- package/templates/clean/pxlr-cms/packages/admin/src/app/content/new/page.tsx +424 -0
- package/templates/clean/pxlr-cms/packages/admin/src/app/content/page.tsx +191 -0
- package/templates/clean/pxlr-cms/packages/admin/src/app/globals.css +132 -0
- package/templates/clean/pxlr-cms/packages/admin/src/app/layout.tsx +25 -0
- package/templates/clean/pxlr-cms/packages/admin/src/app/login/page.tsx +119 -0
- package/templates/clean/pxlr-cms/packages/admin/src/app/media/layout.tsx +7 -0
- package/templates/clean/pxlr-cms/packages/admin/src/app/media/page.tsx +362 -0
- package/templates/clean/pxlr-cms/packages/admin/src/app/page.tsx +184 -0
- package/templates/clean/pxlr-cms/packages/admin/src/app/profile/layout.tsx +7 -0
- package/templates/clean/pxlr-cms/packages/admin/src/app/profile/page.tsx +206 -0
- package/templates/clean/pxlr-cms/packages/admin/src/app/schemas/[name]/page.tsx +312 -0
- package/templates/clean/pxlr-cms/packages/admin/src/app/schemas/layout.tsx +7 -0
- package/templates/clean/pxlr-cms/packages/admin/src/app/schemas/page.tsx +210 -0
- package/templates/clean/pxlr-cms/packages/admin/src/app/settings/layout.tsx +7 -0
- package/templates/clean/pxlr-cms/packages/admin/src/app/settings/page.tsx +178 -0
- package/templates/clean/pxlr-cms/packages/admin/src/components/editor/media-picker.tsx +202 -0
- package/templates/clean/pxlr-cms/packages/admin/src/components/editor/rich-text-editor.tsx +387 -0
- package/templates/clean/pxlr-cms/packages/admin/src/components/layout/auth-layout.tsx +43 -0
- package/templates/clean/pxlr-cms/packages/admin/src/components/layout/header.tsx +79 -0
- package/templates/clean/pxlr-cms/packages/admin/src/components/layout/sidebar.tsx +68 -0
- package/templates/clean/pxlr-cms/packages/admin/src/components/providers.tsx +29 -0
- package/templates/clean/pxlr-cms/packages/admin/src/components/schema-code-generator.tsx +326 -0
- package/templates/clean/pxlr-cms/packages/admin/src/components/ui/avatar.tsx +49 -0
- package/templates/clean/pxlr-cms/packages/admin/src/components/ui/button.tsx +55 -0
- package/templates/clean/pxlr-cms/packages/admin/src/components/ui/dropdown-menu.tsx +194 -0
- package/templates/clean/pxlr-cms/packages/admin/src/components/ui/input.tsx +24 -0
- package/templates/clean/pxlr-cms/packages/admin/src/components/ui/label.tsx +25 -0
- package/templates/clean/pxlr-cms/packages/admin/src/components/ui/toast.tsx +127 -0
- package/templates/clean/pxlr-cms/packages/admin/src/components/ui/toaster.tsx +35 -0
- package/templates/clean/pxlr-cms/packages/admin/src/components/ui/use-toast.ts +187 -0
- package/templates/clean/pxlr-cms/packages/admin/src/lib/api.ts +96 -0
- package/templates/clean/pxlr-cms/packages/admin/src/lib/i18n/context.tsx +60 -0
- package/templates/clean/pxlr-cms/packages/admin/src/lib/i18n/translations.ts +317 -0
- package/templates/clean/pxlr-cms/packages/admin/src/lib/store/auth.ts +51 -0
- package/templates/clean/pxlr-cms/packages/admin/src/lib/utils.ts +29 -0
- package/templates/clean/pxlr-cms/packages/admin/tailwind.config.ts +57 -0
- package/templates/clean/pxlr-cms/packages/admin/tsconfig.json +27 -0
- package/templates/clean/pxlr-cms/packages/api/.env.example +23 -0
- package/templates/clean/pxlr-cms/packages/api/Dockerfile +26 -0
- package/templates/clean/pxlr-cms/packages/api/package.json +42 -0
- package/templates/clean/pxlr-cms/packages/api/src/config.ts +39 -0
- package/templates/clean/pxlr-cms/packages/api/src/database/index.ts +60 -0
- package/templates/clean/pxlr-cms/packages/api/src/database/init.sql +178 -0
- package/templates/clean/pxlr-cms/packages/api/src/database/redis.ts +95 -0
- package/templates/clean/pxlr-cms/packages/api/src/index.ts +157 -0
- package/templates/clean/pxlr-cms/packages/api/src/modules/auth/routes.ts +256 -0
- package/templates/clean/pxlr-cms/packages/api/src/modules/content/routes.ts +385 -0
- package/templates/clean/pxlr-cms/packages/api/src/modules/media/routes.ts +312 -0
- package/templates/clean/pxlr-cms/packages/api/src/modules/realtime/handler.ts +228 -0
- package/templates/clean/pxlr-cms/packages/api/src/modules/schema/routes.ts +284 -0
- package/templates/clean/pxlr-cms/packages/api/src/modules/versions/routes.ts +70 -0
- package/templates/clean/pxlr-cms/packages/api/tsconfig.json +24 -0
- package/templates/clean/pxlr-cms/packages/shared/package.json +14 -0
- package/templates/clean/pxlr-cms/packages/shared/src/types/index.ts +139 -0
- package/templates/clean/pxlr-cms/packages/shared/tsconfig.json +18 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import { useQuery } from '@tanstack/react-query';
|
|
6
|
+
import { useAuthStore } from '@/lib/store/auth';
|
|
7
|
+
import { api } from '@/lib/api';
|
|
8
|
+
import { Sidebar } from '@/components/layout/sidebar';
|
|
9
|
+
import { Header } from '@/components/layout/header';
|
|
10
|
+
import { FileText, Images, Database, Users, Loader2 } from 'lucide-react';
|
|
11
|
+
|
|
12
|
+
interface StatsCard {
|
|
13
|
+
title: string;
|
|
14
|
+
value: string | number;
|
|
15
|
+
icon: React.ReactNode;
|
|
16
|
+
description: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function DashboardContent() {
|
|
20
|
+
const { data: schemas } = useQuery({
|
|
21
|
+
queryKey: ['schemas'],
|
|
22
|
+
queryFn: () => api.get('/schemas'),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const { data: documents } = useQuery({
|
|
26
|
+
queryKey: ['documents'],
|
|
27
|
+
queryFn: () => api.get('/content?limit=1'),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const { data: media } = useQuery({
|
|
31
|
+
queryKey: ['media'],
|
|
32
|
+
queryFn: () => api.get('/media?limit=1'),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const stats: StatsCard[] = [
|
|
36
|
+
{
|
|
37
|
+
title: 'Content Types',
|
|
38
|
+
value: schemas?.schemas?.length || 0,
|
|
39
|
+
icon: <Database className="h-5 w-5" />,
|
|
40
|
+
description: 'Defined schemas',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
title: 'Documents',
|
|
44
|
+
value: documents?.pagination?.total || 0,
|
|
45
|
+
icon: <FileText className="h-5 w-5" />,
|
|
46
|
+
description: 'Total content items',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
title: 'Media Files',
|
|
50
|
+
value: media?.pagination?.total || 0,
|
|
51
|
+
icon: <Images className="h-5 w-5" />,
|
|
52
|
+
description: 'Uploaded files',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
title: 'System Status',
|
|
56
|
+
value: 'Online',
|
|
57
|
+
icon: <Users className="h-5 w-5" />,
|
|
58
|
+
description: 'All services running',
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div className="space-y-6">
|
|
64
|
+
<div>
|
|
65
|
+
<h1 className="text-2xl font-bold tracking-tight">Dashboard</h1>
|
|
66
|
+
<p className="text-muted-foreground">
|
|
67
|
+
Welcome to PXLR CMS. Manage your content from here.
|
|
68
|
+
</p>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
|
72
|
+
{stats.map((stat) => (
|
|
73
|
+
<div
|
|
74
|
+
key={stat.title}
|
|
75
|
+
className="rounded-lg border bg-card p-6 shadow-sm"
|
|
76
|
+
>
|
|
77
|
+
<div className="flex items-center justify-between">
|
|
78
|
+
<span className="text-sm font-medium text-muted-foreground">
|
|
79
|
+
{stat.title}
|
|
80
|
+
</span>
|
|
81
|
+
<span className="text-muted-foreground">{stat.icon}</span>
|
|
82
|
+
</div>
|
|
83
|
+
<div className="mt-2">
|
|
84
|
+
<span className="text-3xl font-bold">{stat.value}</span>
|
|
85
|
+
</div>
|
|
86
|
+
<p className="mt-1 text-sm text-muted-foreground">
|
|
87
|
+
{stat.description}
|
|
88
|
+
</p>
|
|
89
|
+
</div>
|
|
90
|
+
))}
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div className="grid gap-6 md:grid-cols-2">
|
|
94
|
+
<div className="rounded-lg border bg-card p-6 shadow-sm">
|
|
95
|
+
<h2 className="text-lg font-semibold">Quick Start</h2>
|
|
96
|
+
<ul className="mt-4 space-y-3 text-sm">
|
|
97
|
+
<li className="flex items-center gap-2">
|
|
98
|
+
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-primary text-xs text-primary-foreground">
|
|
99
|
+
1
|
|
100
|
+
</span>
|
|
101
|
+
<span>Create a content schema in the Schemas section</span>
|
|
102
|
+
</li>
|
|
103
|
+
<li className="flex items-center gap-2">
|
|
104
|
+
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-primary text-xs text-primary-foreground">
|
|
105
|
+
2
|
|
106
|
+
</span>
|
|
107
|
+
<span>Add content documents based on your schemas</span>
|
|
108
|
+
</li>
|
|
109
|
+
<li className="flex items-center gap-2">
|
|
110
|
+
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-primary text-xs text-primary-foreground">
|
|
111
|
+
3
|
|
112
|
+
</span>
|
|
113
|
+
<span>Upload media files to the Media Library</span>
|
|
114
|
+
</li>
|
|
115
|
+
<li className="flex items-center gap-2">
|
|
116
|
+
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-primary text-xs text-primary-foreground">
|
|
117
|
+
4
|
|
118
|
+
</span>
|
|
119
|
+
<span>Access your content via the REST API</span>
|
|
120
|
+
</li>
|
|
121
|
+
</ul>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div className="rounded-lg border bg-card p-6 shadow-sm">
|
|
125
|
+
<h2 className="text-lg font-semibold">API Access</h2>
|
|
126
|
+
<div className="mt-4 space-y-3">
|
|
127
|
+
<div>
|
|
128
|
+
<p className="text-sm text-muted-foreground">API Endpoint</p>
|
|
129
|
+
<code className="mt-1 block rounded bg-muted px-2 py-1 text-sm">
|
|
130
|
+
http://localhost:4000
|
|
131
|
+
</code>
|
|
132
|
+
</div>
|
|
133
|
+
<div>
|
|
134
|
+
<p className="text-sm text-muted-foreground">Documentation</p>
|
|
135
|
+
<a
|
|
136
|
+
href="http://localhost:4000/docs"
|
|
137
|
+
target="_blank"
|
|
138
|
+
rel="noopener noreferrer"
|
|
139
|
+
className="mt-1 block text-sm text-primary hover:underline"
|
|
140
|
+
>
|
|
141
|
+
View Swagger API Docs
|
|
142
|
+
</a>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export default function HomePage() {
|
|
152
|
+
const router = useRouter();
|
|
153
|
+
const { isAuthenticated, isLoading } = useAuthStore();
|
|
154
|
+
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
if (!isLoading && !isAuthenticated) {
|
|
157
|
+
router.replace('/login');
|
|
158
|
+
}
|
|
159
|
+
}, [isAuthenticated, isLoading, router]);
|
|
160
|
+
|
|
161
|
+
if (isLoading) {
|
|
162
|
+
return (
|
|
163
|
+
<div className="flex items-center justify-center min-h-screen">
|
|
164
|
+
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!isAuthenticated) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<div className="flex h-screen overflow-hidden">
|
|
175
|
+
<Sidebar />
|
|
176
|
+
<div className="flex flex-1 flex-col overflow-hidden">
|
|
177
|
+
<Header />
|
|
178
|
+
<main className="flex-1 overflow-auto bg-muted/30 p-6">
|
|
179
|
+
<DashboardContent />
|
|
180
|
+
</main>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { useMutation } from '@tanstack/react-query';
|
|
5
|
+
import { useAuthStore } from '@/lib/store/auth';
|
|
6
|
+
import { api } from '@/lib/api';
|
|
7
|
+
import { Button } from '@/components/ui/button';
|
|
8
|
+
import { Input } from '@/components/ui/input';
|
|
9
|
+
import { Label } from '@/components/ui/label';
|
|
10
|
+
import { toast } from '@/components/ui/use-toast';
|
|
11
|
+
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
|
12
|
+
import { Loader2, Save, User, Mail, Shield, Calendar } from 'lucide-react';
|
|
13
|
+
|
|
14
|
+
export default function ProfilePage() {
|
|
15
|
+
const { user, setUser } = useAuthStore();
|
|
16
|
+
const [name, setName] = useState(user?.name || '');
|
|
17
|
+
const [currentPassword, setCurrentPassword] = useState('');
|
|
18
|
+
const [newPassword, setNewPassword] = useState('');
|
|
19
|
+
const [confirmPassword, setConfirmPassword] = useState('');
|
|
20
|
+
|
|
21
|
+
const updateProfileMutation = useMutation({
|
|
22
|
+
mutationFn: (data: { name: string }) => api.put('/auth/profile', data),
|
|
23
|
+
onSuccess: (data) => {
|
|
24
|
+
if (data.user) {
|
|
25
|
+
setUser(data.user);
|
|
26
|
+
}
|
|
27
|
+
toast({ title: 'Profile updated successfully' });
|
|
28
|
+
},
|
|
29
|
+
onError: (error: any) => {
|
|
30
|
+
toast({ title: 'Error', description: error.message, variant: 'destructive' });
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const changePasswordMutation = useMutation({
|
|
35
|
+
mutationFn: (data: { currentPassword: string; newPassword: string }) =>
|
|
36
|
+
api.put('/auth/password', data),
|
|
37
|
+
onSuccess: () => {
|
|
38
|
+
toast({ title: 'Password changed successfully' });
|
|
39
|
+
setCurrentPassword('');
|
|
40
|
+
setNewPassword('');
|
|
41
|
+
setConfirmPassword('');
|
|
42
|
+
},
|
|
43
|
+
onError: (error: any) => {
|
|
44
|
+
toast({ title: 'Error', description: error.message, variant: 'destructive' });
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const handleUpdateProfile = () => {
|
|
49
|
+
if (!name.trim()) {
|
|
50
|
+
toast({ title: 'Name is required', variant: 'destructive' });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
updateProfileMutation.mutate({ name });
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const handleChangePassword = () => {
|
|
57
|
+
if (!currentPassword || !newPassword) {
|
|
58
|
+
toast({ title: 'Please fill in all password fields', variant: 'destructive' });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (newPassword !== confirmPassword) {
|
|
62
|
+
toast({ title: 'New passwords do not match', variant: 'destructive' });
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (newPassword.length < 6) {
|
|
66
|
+
toast({ title: 'Password must be at least 6 characters', variant: 'destructive' });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
changePasswordMutation.mutate({ currentPassword, newPassword });
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const initials = user?.name
|
|
73
|
+
? user.name
|
|
74
|
+
.split(' ')
|
|
75
|
+
.map((n) => n[0])
|
|
76
|
+
.join('')
|
|
77
|
+
.toUpperCase()
|
|
78
|
+
: 'U';
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div className="space-y-6 max-w-2xl">
|
|
82
|
+
<div>
|
|
83
|
+
<h1 className="text-2xl font-bold tracking-tight">Profile</h1>
|
|
84
|
+
<p className="text-muted-foreground">
|
|
85
|
+
Manage your account settings
|
|
86
|
+
</p>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
{/* User Info Card */}
|
|
90
|
+
<div className="rounded-lg border bg-card p-6">
|
|
91
|
+
<div className="flex items-center gap-4 mb-6">
|
|
92
|
+
<Avatar className="h-20 w-20">
|
|
93
|
+
<AvatarFallback className="text-2xl">{initials}</AvatarFallback>
|
|
94
|
+
</Avatar>
|
|
95
|
+
<div>
|
|
96
|
+
<h2 className="text-xl font-semibold">{user?.name}</h2>
|
|
97
|
+
<p className="text-muted-foreground">{user?.email}</p>
|
|
98
|
+
<span className="inline-flex items-center gap-1 mt-1 text-xs bg-primary/10 text-primary px-2 py-0.5 rounded">
|
|
99
|
+
<Shield className="h-3 w-3" />
|
|
100
|
+
{user?.role || 'admin'}
|
|
101
|
+
</span>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div className="grid gap-4 text-sm">
|
|
106
|
+
<div className="flex items-center gap-2 text-muted-foreground">
|
|
107
|
+
<Mail className="h-4 w-4" />
|
|
108
|
+
<span>{user?.email}</span>
|
|
109
|
+
</div>
|
|
110
|
+
<div className="flex items-center gap-2 text-muted-foreground">
|
|
111
|
+
<Calendar className="h-4 w-4" />
|
|
112
|
+
<span>Member since {new Date().toLocaleDateString()}</span>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
{/* Update Profile */}
|
|
118
|
+
<div className="rounded-lg border bg-card p-6">
|
|
119
|
+
<h2 className="text-lg font-semibold mb-4">Update Profile</h2>
|
|
120
|
+
<div className="space-y-4">
|
|
121
|
+
<div className="space-y-2">
|
|
122
|
+
<Label htmlFor="name">Display Name</Label>
|
|
123
|
+
<Input
|
|
124
|
+
id="name"
|
|
125
|
+
value={name}
|
|
126
|
+
onChange={(e) => setName(e.target.value)}
|
|
127
|
+
placeholder="Your name"
|
|
128
|
+
/>
|
|
129
|
+
</div>
|
|
130
|
+
<div className="space-y-2">
|
|
131
|
+
<Label htmlFor="email">Email</Label>
|
|
132
|
+
<Input
|
|
133
|
+
id="email"
|
|
134
|
+
value={user?.email || ''}
|
|
135
|
+
disabled
|
|
136
|
+
className="bg-muted"
|
|
137
|
+
/>
|
|
138
|
+
<p className="text-xs text-muted-foreground">
|
|
139
|
+
Email cannot be changed
|
|
140
|
+
</p>
|
|
141
|
+
</div>
|
|
142
|
+
<Button
|
|
143
|
+
onClick={handleUpdateProfile}
|
|
144
|
+
disabled={updateProfileMutation.isPending}
|
|
145
|
+
>
|
|
146
|
+
{updateProfileMutation.isPending ? (
|
|
147
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
148
|
+
) : (
|
|
149
|
+
<Save className="mr-2 h-4 w-4" />
|
|
150
|
+
)}
|
|
151
|
+
Save Changes
|
|
152
|
+
</Button>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
{/* Change Password */}
|
|
157
|
+
<div className="rounded-lg border bg-card p-6">
|
|
158
|
+
<h2 className="text-lg font-semibold mb-4">Change Password</h2>
|
|
159
|
+
<div className="space-y-4">
|
|
160
|
+
<div className="space-y-2">
|
|
161
|
+
<Label htmlFor="currentPassword">Current Password</Label>
|
|
162
|
+
<Input
|
|
163
|
+
id="currentPassword"
|
|
164
|
+
type="password"
|
|
165
|
+
value={currentPassword}
|
|
166
|
+
onChange={(e) => setCurrentPassword(e.target.value)}
|
|
167
|
+
placeholder="Enter current password"
|
|
168
|
+
/>
|
|
169
|
+
</div>
|
|
170
|
+
<div className="space-y-2">
|
|
171
|
+
<Label htmlFor="newPassword">New Password</Label>
|
|
172
|
+
<Input
|
|
173
|
+
id="newPassword"
|
|
174
|
+
type="password"
|
|
175
|
+
value={newPassword}
|
|
176
|
+
onChange={(e) => setNewPassword(e.target.value)}
|
|
177
|
+
placeholder="Enter new password"
|
|
178
|
+
/>
|
|
179
|
+
</div>
|
|
180
|
+
<div className="space-y-2">
|
|
181
|
+
<Label htmlFor="confirmPassword">Confirm New Password</Label>
|
|
182
|
+
<Input
|
|
183
|
+
id="confirmPassword"
|
|
184
|
+
type="password"
|
|
185
|
+
value={confirmPassword}
|
|
186
|
+
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
187
|
+
placeholder="Confirm new password"
|
|
188
|
+
/>
|
|
189
|
+
</div>
|
|
190
|
+
<Button
|
|
191
|
+
onClick={handleChangePassword}
|
|
192
|
+
disabled={changePasswordMutation.isPending}
|
|
193
|
+
variant="outline"
|
|
194
|
+
>
|
|
195
|
+
{changePasswordMutation.isPending ? (
|
|
196
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
197
|
+
) : (
|
|
198
|
+
<Shield className="mr-2 h-4 w-4" />
|
|
199
|
+
)}
|
|
200
|
+
Change Password
|
|
201
|
+
</Button>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
);
|
|
206
|
+
}
|