create-croissant 0.1.49 → 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.
Files changed (25) hide show
  1. package/package.json +1 -1
  2. package/template/apps/desktop/electron.vite.config.ts +11 -1
  3. package/template/apps/desktop/package.json +19 -1
  4. package/template/apps/desktop/src/renderer/index.html +1 -1
  5. package/template/apps/desktop/src/renderer/src/components/app-sidebar.tsx +186 -0
  6. package/template/apps/desktop/src/renderer/src/components/login-form.tsx +154 -0
  7. package/template/apps/desktop/src/renderer/src/components/signup-form.tsx +201 -0
  8. package/template/apps/desktop/src/renderer/src/lib/auth-client.ts +3 -0
  9. package/template/apps/desktop/src/renderer/src/lib/orpc.ts +12 -0
  10. package/template/apps/desktop/src/renderer/src/main.tsx +15 -4
  11. package/template/apps/desktop/src/renderer/src/routeTree.gen.ts +240 -0
  12. package/template/apps/desktop/src/renderer/src/routes/__root.tsx +29 -0
  13. package/template/apps/desktop/src/renderer/src/routes/_auth/account.tsx +267 -0
  14. package/template/apps/desktop/src/renderer/src/routes/_auth/dashboard.tsx +46 -0
  15. package/template/apps/desktop/src/renderer/src/routes/_auth/examples/client-orpc-auth.tsx +35 -0
  16. package/template/apps/desktop/src/renderer/src/routes/_auth.tsx +35 -0
  17. package/template/apps/desktop/src/renderer/src/routes/_public/examples/client-orpc.tsx +306 -0
  18. package/template/apps/desktop/src/renderer/src/routes/_public/index.tsx +54 -0
  19. package/template/apps/desktop/src/renderer/src/routes/_public/login.tsx +16 -0
  20. package/template/apps/desktop/src/renderer/src/routes/_public/signup.tsx +16 -0
  21. package/template/apps/desktop/src/renderer/src/routes/_public.tsx +23 -0
  22. package/template/apps/desktop/tsconfig.web.json +1 -0
  23. package/template/package.json +3 -0
  24. package/template/packages/ui/package.json +4 -2
  25. 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
+ }