create-croissant 0.1.48 → 0.1.50
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/package.json +1 -1
- package/template/apps/desktop/electron.vite.config.ts +11 -1
- package/template/apps/desktop/package.json +19 -1
- package/template/apps/desktop/src/renderer/index.html +1 -1
- package/template/apps/desktop/src/renderer/src/components/app-sidebar.tsx +186 -0
- package/template/apps/desktop/src/renderer/src/components/login-form.tsx +154 -0
- package/template/apps/desktop/src/renderer/src/components/signup-form.tsx +201 -0
- package/template/apps/desktop/src/renderer/src/lib/auth-client.ts +3 -0
- package/template/apps/desktop/src/renderer/src/lib/orpc.ts +12 -0
- package/template/apps/desktop/src/renderer/src/main.tsx +15 -4
- package/template/apps/desktop/src/renderer/src/routeTree.gen.ts +240 -0
- package/template/apps/desktop/src/renderer/src/routes/__root.tsx +29 -0
- package/template/apps/desktop/src/renderer/src/routes/_auth/account.tsx +267 -0
- package/template/apps/desktop/src/renderer/src/routes/_auth/dashboard.tsx +46 -0
- package/template/apps/desktop/src/renderer/src/routes/_auth/examples/client-orpc-auth.tsx +35 -0
- package/template/apps/desktop/src/renderer/src/routes/_auth.tsx +35 -0
- package/template/apps/desktop/src/renderer/src/routes/_public/examples/client-orpc.tsx +306 -0
- package/template/apps/desktop/src/renderer/src/routes/_public/index.tsx +54 -0
- package/template/apps/desktop/src/renderer/src/routes/_public/login.tsx +16 -0
- package/template/apps/desktop/src/renderer/src/routes/_public/signup.tsx +16 -0
- package/template/apps/desktop/src/renderer/src/routes/_public.tsx +23 -0
- package/template/apps/desktop/tsconfig.web.json +1 -0
- package/template/apps/mobile/.vscode/extensions.json +1 -0
- package/template/apps/mobile/.vscode/settings.json +7 -0
- package/template/apps/mobile/README.md +50 -0
- package/template/apps/mobile/app/(tabs)/_layout.tsx +35 -0
- package/template/apps/mobile/app/(tabs)/explore.tsx +112 -0
- package/template/apps/mobile/app/(tabs)/index.tsx +98 -0
- package/template/apps/mobile/app/_layout.tsx +24 -0
- package/template/apps/mobile/app/modal.tsx +29 -0
- package/template/apps/mobile/app.json +48 -0
- package/template/apps/mobile/assets/images/android-icon-background.png +0 -0
- package/template/apps/mobile/assets/images/android-icon-foreground.png +0 -0
- package/template/apps/mobile/assets/images/android-icon-monochrome.png +0 -0
- package/template/apps/mobile/assets/images/favicon.png +0 -0
- package/template/apps/mobile/assets/images/icon.png +0 -0
- package/template/apps/mobile/assets/images/partial-react-logo.png +0 -0
- package/template/apps/mobile/assets/images/react-logo.png +0 -0
- package/template/apps/mobile/assets/images/react-logo@2x.png +0 -0
- package/template/apps/mobile/assets/images/react-logo@3x.png +0 -0
- package/template/apps/mobile/assets/images/splash-icon.png +0 -0
- package/template/apps/mobile/components/external-link.tsx +25 -0
- package/template/apps/mobile/components/haptic-tab.tsx +18 -0
- package/template/apps/mobile/components/hello-wave.tsx +19 -0
- package/template/apps/mobile/components/parallax-scroll-view.tsx +79 -0
- package/template/apps/mobile/components/themed-text.tsx +60 -0
- package/template/apps/mobile/components/themed-view.tsx +14 -0
- package/template/apps/mobile/components/ui/collapsible.tsx +45 -0
- package/template/apps/mobile/components/ui/icon-symbol.ios.tsx +32 -0
- package/template/apps/mobile/components/ui/icon-symbol.tsx +41 -0
- package/template/apps/mobile/constants/theme.ts +53 -0
- package/template/apps/mobile/hooks/use-color-scheme.ts +1 -0
- package/template/apps/mobile/hooks/use-color-scheme.web.ts +21 -0
- package/template/apps/mobile/hooks/use-theme-color.ts +21 -0
- package/template/apps/mobile/package.json +45 -0
- package/template/apps/mobile/scripts/reset-project.js +112 -0
- package/template/apps/mobile/tsconfig.json +20 -0
- package/template/package.json +14 -8
- package/template/packages/ui/package.json +4 -2
- package/template/pnpm-workspace.yaml +8 -0
- package/template/apps/desktop/src/renderer/src/App.tsx +0 -35
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
|
|
3
|
+
// @ts-nocheck
|
|
4
|
+
|
|
5
|
+
// noinspection JSUnusedGlobalSymbols
|
|
6
|
+
|
|
7
|
+
// This file was automatically generated by TanStack Router.
|
|
8
|
+
// You should NOT make any changes in this file as it will be overwritten.
|
|
9
|
+
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
|
10
|
+
|
|
11
|
+
import { Route as rootRouteImport } from './routes/__root'
|
|
12
|
+
import { Route as PublicRouteImport } from './routes/_public'
|
|
13
|
+
import { Route as AuthRouteImport } from './routes/_auth'
|
|
14
|
+
import { Route as PublicIndexRouteImport } from './routes/_public/index'
|
|
15
|
+
import { Route as PublicSignupRouteImport } from './routes/_public/signup'
|
|
16
|
+
import { Route as PublicLoginRouteImport } from './routes/_public/login'
|
|
17
|
+
import { Route as AuthDashboardRouteImport } from './routes/_auth/dashboard'
|
|
18
|
+
import { Route as AuthAccountRouteImport } from './routes/_auth/account'
|
|
19
|
+
import { Route as PublicExamplesClientOrpcRouteImport } from './routes/_public/examples/client-orpc'
|
|
20
|
+
import { Route as AuthExamplesClientOrpcAuthRouteImport } from './routes/_auth/examples/client-orpc-auth'
|
|
21
|
+
|
|
22
|
+
const PublicRoute = PublicRouteImport.update({
|
|
23
|
+
id: '/_public',
|
|
24
|
+
getParentRoute: () => rootRouteImport,
|
|
25
|
+
} as any)
|
|
26
|
+
const AuthRoute = AuthRouteImport.update({
|
|
27
|
+
id: '/_auth',
|
|
28
|
+
getParentRoute: () => rootRouteImport,
|
|
29
|
+
} as any)
|
|
30
|
+
const PublicIndexRoute = PublicIndexRouteImport.update({
|
|
31
|
+
id: '/',
|
|
32
|
+
path: '/',
|
|
33
|
+
getParentRoute: () => PublicRoute,
|
|
34
|
+
} as any)
|
|
35
|
+
const PublicSignupRoute = PublicSignupRouteImport.update({
|
|
36
|
+
id: '/signup',
|
|
37
|
+
path: '/signup',
|
|
38
|
+
getParentRoute: () => PublicRoute,
|
|
39
|
+
} as any)
|
|
40
|
+
const PublicLoginRoute = PublicLoginRouteImport.update({
|
|
41
|
+
id: '/login',
|
|
42
|
+
path: '/login',
|
|
43
|
+
getParentRoute: () => PublicRoute,
|
|
44
|
+
} as any)
|
|
45
|
+
const AuthDashboardRoute = AuthDashboardRouteImport.update({
|
|
46
|
+
id: '/dashboard',
|
|
47
|
+
path: '/dashboard',
|
|
48
|
+
getParentRoute: () => AuthRoute,
|
|
49
|
+
} as any)
|
|
50
|
+
const AuthAccountRoute = AuthAccountRouteImport.update({
|
|
51
|
+
id: '/account',
|
|
52
|
+
path: '/account',
|
|
53
|
+
getParentRoute: () => AuthRoute,
|
|
54
|
+
} as any)
|
|
55
|
+
const PublicExamplesClientOrpcRoute =
|
|
56
|
+
PublicExamplesClientOrpcRouteImport.update({
|
|
57
|
+
id: '/examples/client-orpc',
|
|
58
|
+
path: '/examples/client-orpc',
|
|
59
|
+
getParentRoute: () => PublicRoute,
|
|
60
|
+
} as any)
|
|
61
|
+
const AuthExamplesClientOrpcAuthRoute =
|
|
62
|
+
AuthExamplesClientOrpcAuthRouteImport.update({
|
|
63
|
+
id: '/examples/client-orpc-auth',
|
|
64
|
+
path: '/examples/client-orpc-auth',
|
|
65
|
+
getParentRoute: () => AuthRoute,
|
|
66
|
+
} as any)
|
|
67
|
+
|
|
68
|
+
export interface FileRoutesByFullPath {
|
|
69
|
+
'/': typeof PublicIndexRoute
|
|
70
|
+
'/account': typeof AuthAccountRoute
|
|
71
|
+
'/dashboard': typeof AuthDashboardRoute
|
|
72
|
+
'/login': typeof PublicLoginRoute
|
|
73
|
+
'/signup': typeof PublicSignupRoute
|
|
74
|
+
'/examples/client-orpc-auth': typeof AuthExamplesClientOrpcAuthRoute
|
|
75
|
+
'/examples/client-orpc': typeof PublicExamplesClientOrpcRoute
|
|
76
|
+
}
|
|
77
|
+
export interface FileRoutesByTo {
|
|
78
|
+
'/': typeof PublicIndexRoute
|
|
79
|
+
'/account': typeof AuthAccountRoute
|
|
80
|
+
'/dashboard': typeof AuthDashboardRoute
|
|
81
|
+
'/login': typeof PublicLoginRoute
|
|
82
|
+
'/signup': typeof PublicSignupRoute
|
|
83
|
+
'/examples/client-orpc-auth': typeof AuthExamplesClientOrpcAuthRoute
|
|
84
|
+
'/examples/client-orpc': typeof PublicExamplesClientOrpcRoute
|
|
85
|
+
}
|
|
86
|
+
export interface FileRoutesById {
|
|
87
|
+
__root__: typeof rootRouteImport
|
|
88
|
+
'/_auth': typeof AuthRouteWithChildren
|
|
89
|
+
'/_public': typeof PublicRouteWithChildren
|
|
90
|
+
'/_auth/account': typeof AuthAccountRoute
|
|
91
|
+
'/_auth/dashboard': typeof AuthDashboardRoute
|
|
92
|
+
'/_public/login': typeof PublicLoginRoute
|
|
93
|
+
'/_public/signup': typeof PublicSignupRoute
|
|
94
|
+
'/_public/': typeof PublicIndexRoute
|
|
95
|
+
'/_auth/examples/client-orpc-auth': typeof AuthExamplesClientOrpcAuthRoute
|
|
96
|
+
'/_public/examples/client-orpc': typeof PublicExamplesClientOrpcRoute
|
|
97
|
+
}
|
|
98
|
+
export interface FileRouteTypes {
|
|
99
|
+
fileRoutesByFullPath: FileRoutesByFullPath
|
|
100
|
+
fullPaths:
|
|
101
|
+
| '/'
|
|
102
|
+
| '/account'
|
|
103
|
+
| '/dashboard'
|
|
104
|
+
| '/login'
|
|
105
|
+
| '/signup'
|
|
106
|
+
| '/examples/client-orpc-auth'
|
|
107
|
+
| '/examples/client-orpc'
|
|
108
|
+
fileRoutesByTo: FileRoutesByTo
|
|
109
|
+
to:
|
|
110
|
+
| '/'
|
|
111
|
+
| '/account'
|
|
112
|
+
| '/dashboard'
|
|
113
|
+
| '/login'
|
|
114
|
+
| '/signup'
|
|
115
|
+
| '/examples/client-orpc-auth'
|
|
116
|
+
| '/examples/client-orpc'
|
|
117
|
+
id:
|
|
118
|
+
| '__root__'
|
|
119
|
+
| '/_auth'
|
|
120
|
+
| '/_public'
|
|
121
|
+
| '/_auth/account'
|
|
122
|
+
| '/_auth/dashboard'
|
|
123
|
+
| '/_public/login'
|
|
124
|
+
| '/_public/signup'
|
|
125
|
+
| '/_public/'
|
|
126
|
+
| '/_auth/examples/client-orpc-auth'
|
|
127
|
+
| '/_public/examples/client-orpc'
|
|
128
|
+
fileRoutesById: FileRoutesById
|
|
129
|
+
}
|
|
130
|
+
export interface RootRouteChildren {
|
|
131
|
+
AuthRoute: typeof AuthRouteWithChildren
|
|
132
|
+
PublicRoute: typeof PublicRouteWithChildren
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
declare module '@tanstack/react-router' {
|
|
136
|
+
interface FileRoutesByPath {
|
|
137
|
+
'/_public': {
|
|
138
|
+
id: '/_public'
|
|
139
|
+
path: ''
|
|
140
|
+
fullPath: '/'
|
|
141
|
+
preLoaderRoute: typeof PublicRouteImport
|
|
142
|
+
parentRoute: typeof rootRouteImport
|
|
143
|
+
}
|
|
144
|
+
'/_auth': {
|
|
145
|
+
id: '/_auth'
|
|
146
|
+
path: ''
|
|
147
|
+
fullPath: '/'
|
|
148
|
+
preLoaderRoute: typeof AuthRouteImport
|
|
149
|
+
parentRoute: typeof rootRouteImport
|
|
150
|
+
}
|
|
151
|
+
'/_public/': {
|
|
152
|
+
id: '/_public/'
|
|
153
|
+
path: '/'
|
|
154
|
+
fullPath: '/'
|
|
155
|
+
preLoaderRoute: typeof PublicIndexRouteImport
|
|
156
|
+
parentRoute: typeof PublicRoute
|
|
157
|
+
}
|
|
158
|
+
'/_public/signup': {
|
|
159
|
+
id: '/_public/signup'
|
|
160
|
+
path: '/signup'
|
|
161
|
+
fullPath: '/signup'
|
|
162
|
+
preLoaderRoute: typeof PublicSignupRouteImport
|
|
163
|
+
parentRoute: typeof PublicRoute
|
|
164
|
+
}
|
|
165
|
+
'/_public/login': {
|
|
166
|
+
id: '/_public/login'
|
|
167
|
+
path: '/login'
|
|
168
|
+
fullPath: '/login'
|
|
169
|
+
preLoaderRoute: typeof PublicLoginRouteImport
|
|
170
|
+
parentRoute: typeof PublicRoute
|
|
171
|
+
}
|
|
172
|
+
'/_auth/dashboard': {
|
|
173
|
+
id: '/_auth/dashboard'
|
|
174
|
+
path: '/dashboard'
|
|
175
|
+
fullPath: '/dashboard'
|
|
176
|
+
preLoaderRoute: typeof AuthDashboardRouteImport
|
|
177
|
+
parentRoute: typeof AuthRoute
|
|
178
|
+
}
|
|
179
|
+
'/_auth/account': {
|
|
180
|
+
id: '/_auth/account'
|
|
181
|
+
path: '/account'
|
|
182
|
+
fullPath: '/account'
|
|
183
|
+
preLoaderRoute: typeof AuthAccountRouteImport
|
|
184
|
+
parentRoute: typeof AuthRoute
|
|
185
|
+
}
|
|
186
|
+
'/_public/examples/client-orpc': {
|
|
187
|
+
id: '/_public/examples/client-orpc'
|
|
188
|
+
path: '/examples/client-orpc'
|
|
189
|
+
fullPath: '/examples/client-orpc'
|
|
190
|
+
preLoaderRoute: typeof PublicExamplesClientOrpcRouteImport
|
|
191
|
+
parentRoute: typeof PublicRoute
|
|
192
|
+
}
|
|
193
|
+
'/_auth/examples/client-orpc-auth': {
|
|
194
|
+
id: '/_auth/examples/client-orpc-auth'
|
|
195
|
+
path: '/examples/client-orpc-auth'
|
|
196
|
+
fullPath: '/examples/client-orpc-auth'
|
|
197
|
+
preLoaderRoute: typeof AuthExamplesClientOrpcAuthRouteImport
|
|
198
|
+
parentRoute: typeof AuthRoute
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
interface AuthRouteChildren {
|
|
204
|
+
AuthAccountRoute: typeof AuthAccountRoute
|
|
205
|
+
AuthDashboardRoute: typeof AuthDashboardRoute
|
|
206
|
+
AuthExamplesClientOrpcAuthRoute: typeof AuthExamplesClientOrpcAuthRoute
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const AuthRouteChildren: AuthRouteChildren = {
|
|
210
|
+
AuthAccountRoute: AuthAccountRoute,
|
|
211
|
+
AuthDashboardRoute: AuthDashboardRoute,
|
|
212
|
+
AuthExamplesClientOrpcAuthRoute: AuthExamplesClientOrpcAuthRoute,
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const AuthRouteWithChildren = AuthRoute._addFileChildren(AuthRouteChildren)
|
|
216
|
+
|
|
217
|
+
interface PublicRouteChildren {
|
|
218
|
+
PublicLoginRoute: typeof PublicLoginRoute
|
|
219
|
+
PublicSignupRoute: typeof PublicSignupRoute
|
|
220
|
+
PublicIndexRoute: typeof PublicIndexRoute
|
|
221
|
+
PublicExamplesClientOrpcRoute: typeof PublicExamplesClientOrpcRoute
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const PublicRouteChildren: PublicRouteChildren = {
|
|
225
|
+
PublicLoginRoute: PublicLoginRoute,
|
|
226
|
+
PublicSignupRoute: PublicSignupRoute,
|
|
227
|
+
PublicIndexRoute: PublicIndexRoute,
|
|
228
|
+
PublicExamplesClientOrpcRoute: PublicExamplesClientOrpcRoute,
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const PublicRouteWithChildren =
|
|
232
|
+
PublicRoute._addFileChildren(PublicRouteChildren)
|
|
233
|
+
|
|
234
|
+
const rootRouteChildren: RootRouteChildren = {
|
|
235
|
+
AuthRoute: AuthRouteWithChildren,
|
|
236
|
+
PublicRoute: PublicRouteWithChildren,
|
|
237
|
+
}
|
|
238
|
+
export const routeTree = rootRouteImport
|
|
239
|
+
._addFileChildren(rootRouteChildren)
|
|
240
|
+
._addFileTypes<FileRouteTypes>()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Outlet, createRootRoute } from "@tanstack/react-router";
|
|
2
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
3
|
+
import { Toaster } from "@workspace/ui/components/sonner";
|
|
4
|
+
import { ThemeProvider } from "@workspace/ui/components/theme-provider";
|
|
5
|
+
import { ORPCProvider } from "@workspace/orpc/react";
|
|
6
|
+
import { orpc } from "@/lib/orpc";
|
|
7
|
+
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
|
|
8
|
+
|
|
9
|
+
import "@workspace/ui/globals.css";
|
|
10
|
+
|
|
11
|
+
const queryClient = new QueryClient();
|
|
12
|
+
|
|
13
|
+
export const Route = createRootRoute({
|
|
14
|
+
component: RootLayout,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
function RootLayout() {
|
|
18
|
+
return (
|
|
19
|
+
<ThemeProvider defaultTheme="system" storageKey="theme">
|
|
20
|
+
<QueryClientProvider client={queryClient}>
|
|
21
|
+
<ORPCProvider client={orpc}>
|
|
22
|
+
<Outlet />
|
|
23
|
+
<Toaster />
|
|
24
|
+
<TanStackRouterDevtools />
|
|
25
|
+
</ORPCProvider>
|
|
26
|
+
</QueryClientProvider>
|
|
27
|
+
</ThemeProvider>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
3
|
+
import { useForm } from "@tanstack/react-form";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { toast } from "sonner";
|
|
6
|
+
import { Loader2, User } from "lucide-react";
|
|
7
|
+
|
|
8
|
+
import { Button } from "@workspace/ui/components/button";
|
|
9
|
+
import { Input } from "@workspace/ui/components/input";
|
|
10
|
+
import {
|
|
11
|
+
Card,
|
|
12
|
+
CardContent,
|
|
13
|
+
CardDescription,
|
|
14
|
+
CardFooter,
|
|
15
|
+
CardHeader,
|
|
16
|
+
CardTitle,
|
|
17
|
+
} from "@workspace/ui/components/card";
|
|
18
|
+
import { Field, FieldError, FieldLabel } from "@workspace/ui/components/field";
|
|
19
|
+
import { Avatar, AvatarFallback, AvatarImage } from "@workspace/ui/components/avatar";
|
|
20
|
+
import { Separator } from "@workspace/ui/components/separator";
|
|
21
|
+
|
|
22
|
+
import { authClient } from "@/lib/auth-client";
|
|
23
|
+
|
|
24
|
+
const profileSchema = z.object({
|
|
25
|
+
name: z.string().min(1, "Name is required"),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const passwordSchema = z
|
|
29
|
+
.object({
|
|
30
|
+
currentPassword: z.string().min(1, "Current password is required"),
|
|
31
|
+
newPassword: z.string().min(8, "New password must be at least 8 characters"),
|
|
32
|
+
confirmPassword: z.string().min(1, "Confirm password is required"),
|
|
33
|
+
})
|
|
34
|
+
.refine((data) => data.newPassword === data.confirmPassword, {
|
|
35
|
+
message: "Passwords do not match",
|
|
36
|
+
path: ["confirmPassword"],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export const Route = createFileRoute("/_auth/account")({
|
|
40
|
+
component: AccountPage,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
function AccountPage() {
|
|
44
|
+
const { data: sessionData, isPending } = authClient.useSession();
|
|
45
|
+
const [loading, setLoading] = React.useState(false);
|
|
46
|
+
|
|
47
|
+
const user = sessionData?.user;
|
|
48
|
+
|
|
49
|
+
const profileForm = useForm({
|
|
50
|
+
defaultValues: {
|
|
51
|
+
name: user?.name || "",
|
|
52
|
+
},
|
|
53
|
+
validators: {
|
|
54
|
+
onChange: profileSchema,
|
|
55
|
+
},
|
|
56
|
+
onSubmit: async ({ value }) => {
|
|
57
|
+
setLoading(true);
|
|
58
|
+
const { error } = await authClient.updateUser({
|
|
59
|
+
name: value.name,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (error) {
|
|
63
|
+
toast.error(error.message || "Failed to update profile");
|
|
64
|
+
} else {
|
|
65
|
+
toast.success("Profile updated successfully");
|
|
66
|
+
}
|
|
67
|
+
setLoading(false);
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const passwordForm = useForm({
|
|
72
|
+
defaultValues: {
|
|
73
|
+
currentPassword: "",
|
|
74
|
+
newPassword: "",
|
|
75
|
+
confirmPassword: "",
|
|
76
|
+
},
|
|
77
|
+
validators: {
|
|
78
|
+
onChange: passwordSchema,
|
|
79
|
+
},
|
|
80
|
+
onSubmit: async ({ value }) => {
|
|
81
|
+
setLoading(true);
|
|
82
|
+
const { error } = await authClient.changePassword({
|
|
83
|
+
currentPassword: value.currentPassword,
|
|
84
|
+
newPassword: value.newPassword,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (error) {
|
|
88
|
+
toast.error(error.message || "Failed to change password");
|
|
89
|
+
} else {
|
|
90
|
+
toast.success("Password changed successfully");
|
|
91
|
+
passwordForm.reset();
|
|
92
|
+
}
|
|
93
|
+
setLoading(false);
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (isPending) return <div>Loading...</div>;
|
|
98
|
+
if (!user) return null;
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<div className="container max-w-4xl py-10">
|
|
102
|
+
<div className="flex flex-col gap-8">
|
|
103
|
+
<div>
|
|
104
|
+
<h1 className="text-3xl font-bold">Account Settings</h1>
|
|
105
|
+
<p className="text-muted-foreground">Manage your profile and account preferences.</p>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<Separator />
|
|
109
|
+
|
|
110
|
+
<div className="grid gap-8">
|
|
111
|
+
{/* Profile Section */}
|
|
112
|
+
<Card>
|
|
113
|
+
<CardHeader>
|
|
114
|
+
<CardTitle>Profile</CardTitle>
|
|
115
|
+
<CardDescription>Update your personal information.</CardDescription>
|
|
116
|
+
</CardHeader>
|
|
117
|
+
<CardContent className="space-y-6">
|
|
118
|
+
<div className="flex items-center gap-4">
|
|
119
|
+
<Avatar className="h-20 w-20">
|
|
120
|
+
<AvatarImage src={user.image || ""} />
|
|
121
|
+
<AvatarFallback className="text-2xl">
|
|
122
|
+
{user.name.charAt(0) || <User className="h-10 w-10" />}
|
|
123
|
+
</AvatarFallback>
|
|
124
|
+
</Avatar>
|
|
125
|
+
<div className="space-y-1">
|
|
126
|
+
<p className="text-sm font-medium leading-none">{user.name}</p>
|
|
127
|
+
<p className="text-sm text-muted-foreground">{user.email}</p>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<form
|
|
132
|
+
onSubmit={(e) => {
|
|
133
|
+
e.preventDefault();
|
|
134
|
+
e.stopPropagation();
|
|
135
|
+
profileForm.handleSubmit();
|
|
136
|
+
}}
|
|
137
|
+
className="space-y-4"
|
|
138
|
+
>
|
|
139
|
+
<profileForm.Field
|
|
140
|
+
name="name"
|
|
141
|
+
children={(field) => (
|
|
142
|
+
<Field>
|
|
143
|
+
<FieldLabel htmlFor={field.name}>Display Name</FieldLabel>
|
|
144
|
+
<Input
|
|
145
|
+
id={field.name}
|
|
146
|
+
value={field.state.value}
|
|
147
|
+
onBlur={field.handleBlur}
|
|
148
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
149
|
+
/>
|
|
150
|
+
<FieldError />
|
|
151
|
+
</Field>
|
|
152
|
+
)}
|
|
153
|
+
/>
|
|
154
|
+
<Button type="submit" disabled={loading}>
|
|
155
|
+
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
|
156
|
+
Update Profile
|
|
157
|
+
</Button>
|
|
158
|
+
</form>
|
|
159
|
+
</CardContent>
|
|
160
|
+
</Card>
|
|
161
|
+
|
|
162
|
+
{/* Password Section */}
|
|
163
|
+
<Card>
|
|
164
|
+
<CardHeader>
|
|
165
|
+
<CardTitle>Security</CardTitle>
|
|
166
|
+
<CardDescription>Change your password to keep your account secure.</CardDescription>
|
|
167
|
+
</CardHeader>
|
|
168
|
+
<CardContent>
|
|
169
|
+
<form
|
|
170
|
+
onSubmit={(e) => {
|
|
171
|
+
e.preventDefault();
|
|
172
|
+
e.stopPropagation();
|
|
173
|
+
passwordForm.handleSubmit();
|
|
174
|
+
}}
|
|
175
|
+
className="space-y-4"
|
|
176
|
+
>
|
|
177
|
+
<passwordForm.Field
|
|
178
|
+
name="currentPassword"
|
|
179
|
+
children={(field) => (
|
|
180
|
+
<Field>
|
|
181
|
+
<FieldLabel htmlFor={field.name}>Current Password</FieldLabel>
|
|
182
|
+
<Input
|
|
183
|
+
id={field.name}
|
|
184
|
+
type="password"
|
|
185
|
+
value={field.state.value}
|
|
186
|
+
onBlur={field.handleBlur}
|
|
187
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
188
|
+
/>
|
|
189
|
+
<FieldError />
|
|
190
|
+
</Field>
|
|
191
|
+
)}
|
|
192
|
+
/>
|
|
193
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
194
|
+
<passwordForm.Field
|
|
195
|
+
name="newPassword"
|
|
196
|
+
children={(field) => (
|
|
197
|
+
<Field>
|
|
198
|
+
<FieldLabel htmlFor={field.name}>New Password</FieldLabel>
|
|
199
|
+
<Input
|
|
200
|
+
id={field.name}
|
|
201
|
+
type="password"
|
|
202
|
+
value={field.state.value}
|
|
203
|
+
onBlur={field.handleBlur}
|
|
204
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
205
|
+
/>
|
|
206
|
+
<FieldError />
|
|
207
|
+
</Field>
|
|
208
|
+
)}
|
|
209
|
+
/>
|
|
210
|
+
<passwordForm.Field
|
|
211
|
+
name="confirmPassword"
|
|
212
|
+
children={(field) => (
|
|
213
|
+
<Field>
|
|
214
|
+
<FieldLabel htmlFor={field.name}>Confirm New Password</FieldLabel>
|
|
215
|
+
<Input
|
|
216
|
+
id={field.name}
|
|
217
|
+
type="password"
|
|
218
|
+
value={field.state.value}
|
|
219
|
+
onBlur={field.handleBlur}
|
|
220
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
221
|
+
/>
|
|
222
|
+
<FieldError />
|
|
223
|
+
</Field>
|
|
224
|
+
)}
|
|
225
|
+
/>
|
|
226
|
+
</div>
|
|
227
|
+
<Button type="submit" variant="secondary" disabled={loading}>
|
|
228
|
+
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
|
229
|
+
Change Password
|
|
230
|
+
</Button>
|
|
231
|
+
</form>
|
|
232
|
+
</CardContent>
|
|
233
|
+
</Card>
|
|
234
|
+
|
|
235
|
+
{/* Danger Zone */}
|
|
236
|
+
<Card className="border-destructive/50">
|
|
237
|
+
<CardHeader>
|
|
238
|
+
<CardTitle className="text-destructive">Danger Zone</CardTitle>
|
|
239
|
+
<CardDescription>Permanently delete your account and all data.</CardDescription>
|
|
240
|
+
</CardHeader>
|
|
241
|
+
<CardFooter>
|
|
242
|
+
<Button
|
|
243
|
+
variant="destructive"
|
|
244
|
+
onClick={async () => {
|
|
245
|
+
if (
|
|
246
|
+
confirm(
|
|
247
|
+
"Are you sure you want to delete your account? This action cannot be undone.",
|
|
248
|
+
)
|
|
249
|
+
) {
|
|
250
|
+
const { error } = await authClient.deleteUser();
|
|
251
|
+
if (error) {
|
|
252
|
+
toast.error(error.message || "Failed to delete account");
|
|
253
|
+
} else {
|
|
254
|
+
window.location.reload();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}}
|
|
258
|
+
>
|
|
259
|
+
Delete Account
|
|
260
|
+
</Button>
|
|
261
|
+
</CardFooter>
|
|
262
|
+
</Card>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
);
|
|
267
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import { authClient } from "@/lib/auth-client";
|
|
3
|
+
import { useSecretData } from "@workspace/orpc/react";
|
|
4
|
+
|
|
5
|
+
export const Route = createFileRoute("/_auth/dashboard")({
|
|
6
|
+
component: Dashboard,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
function Dashboard() {
|
|
10
|
+
const { data: sessionData } = authClient.useSession();
|
|
11
|
+
const user = sessionData?.user;
|
|
12
|
+
const { data: secretData, isLoading, error } = useSecretData();
|
|
13
|
+
|
|
14
|
+
if (!user) return null;
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div className="flex min-h-svh p-6">
|
|
18
|
+
<div className="flex max-w-md min-w-0 flex-col gap-4 text-sm leading-loose">
|
|
19
|
+
<div>
|
|
20
|
+
<h1 className="text-2xl font-bold">Dashboard</h1>
|
|
21
|
+
<p>Welcome, {user.name}!</p>
|
|
22
|
+
<p>This is a protected page. Only authenticated users can see this.</p>
|
|
23
|
+
|
|
24
|
+
<div className="mt-6 rounded-lg border bg-gray-50 p-4 dark:bg-zinc-900">
|
|
25
|
+
<h2 className="font-semibold mb-2">Secure oRPC Data:</h2>
|
|
26
|
+
<p className="font-mono text-xs">
|
|
27
|
+
{isLoading ? "Loading secret..." : error ? "Error: " + error.message : secretData?.secret}
|
|
28
|
+
</p>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div className="mt-4 flex gap-2">
|
|
32
|
+
<button
|
|
33
|
+
onClick={async () => {
|
|
34
|
+
await authClient.signOut();
|
|
35
|
+
window.location.reload();
|
|
36
|
+
}}
|
|
37
|
+
className="rounded bg-red-500 px-4 py-2 text-white hover:bg-red-600"
|
|
38
|
+
>
|
|
39
|
+
Sign Out
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import { useSecretData } from "@workspace/orpc/react";
|
|
3
|
+
import { authClient } from "@/lib/auth-client";
|
|
4
|
+
|
|
5
|
+
export const Route = createFileRoute("/_auth/examples/client-orpc-auth")({
|
|
6
|
+
component: ClientORPCAuth,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
function ClientORPCAuth() {
|
|
10
|
+
const { data: sessionData } = authClient.useSession();
|
|
11
|
+
const session = sessionData;
|
|
12
|
+
|
|
13
|
+
const { data, isLoading } = useSecretData();
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className="flex flex-col gap-4">
|
|
17
|
+
<h1 className="text-2xl font-bold">Client + oRPC (Authenticated)</h1>
|
|
18
|
+
<p>This page is protected and fetches secret data on the client using TanStack Query.</p>
|
|
19
|
+
|
|
20
|
+
<div className="rounded-lg border p-4">
|
|
21
|
+
<h2 className="font-semibold">User Session:</h2>
|
|
22
|
+
<pre className="text-xs bg-muted p-2 rounded dark:bg-zinc-900 overflow-auto">{JSON.stringify(session, null, 2)}</pre>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div className="rounded-lg border p-4">
|
|
26
|
+
<h2 className="font-semibold">Secret Data (Client-side):</h2>
|
|
27
|
+
{isLoading ? (
|
|
28
|
+
<p>Loading...</p>
|
|
29
|
+
) : (
|
|
30
|
+
<pre className="text-xs bg-muted p-2 rounded dark:bg-zinc-900 overflow-auto">{JSON.stringify(data, null, 2)}</pre>
|
|
31
|
+
)}
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Outlet, createFileRoute, redirect } from "@tanstack/react-router";
|
|
2
|
+
import { SidebarProvider, SidebarTrigger } from "@workspace/ui/components/sidebar";
|
|
3
|
+
import { AuthSidebar } from "@/components/app-sidebar";
|
|
4
|
+
import { authClient } from "@/lib/auth-client";
|
|
5
|
+
|
|
6
|
+
export const Route = createFileRoute("/_auth")({
|
|
7
|
+
beforeLoad: async ({ location }) => {
|
|
8
|
+
const session = await authClient.getSession();
|
|
9
|
+
if (!session.data) {
|
|
10
|
+
throw redirect({
|
|
11
|
+
to: "/login",
|
|
12
|
+
search: {
|
|
13
|
+
redirect: location.href,
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
component: AuthLayout,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
function AuthLayout() {
|
|
22
|
+
return (
|
|
23
|
+
<SidebarProvider>
|
|
24
|
+
<AuthSidebar />
|
|
25
|
+
<main className="flex flex-1 flex-col overflow-hidden">
|
|
26
|
+
<header className="flex h-16 shrink-0 items-center gap-2 border-b px-4">
|
|
27
|
+
<SidebarTrigger />
|
|
28
|
+
</header>
|
|
29
|
+
<div className="flex-1 overflow-auto p-4">
|
|
30
|
+
<Outlet />
|
|
31
|
+
</div>
|
|
32
|
+
</main>
|
|
33
|
+
</SidebarProvider>
|
|
34
|
+
);
|
|
35
|
+
}
|