create-croissant 0.1.55 → 0.1.57
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/add.js +5 -5
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/template/README.md +10 -3
- package/template/apps/desktop/.vscode/extensions.json +1 -1
- package/template/apps/desktop/README.md +3 -30
- package/template/apps/desktop/index.html +14 -0
- package/template/apps/desktop/package.json +21 -32
- package/template/apps/desktop/public/tauri.svg +6 -0
- package/template/apps/desktop/public/vite.svg +1 -0
- package/template/apps/desktop/src/App.css +116 -0
- package/template/apps/desktop/src/App.tsx +51 -0
- package/template/apps/desktop/src/assets/react.svg +1 -0
- package/template/apps/desktop/src/{renderer/src/components → components}/app-sidebar.tsx +7 -3
- package/template/apps/desktop/src/components/login-form.tsx +160 -0
- package/template/apps/desktop/src/components/search-form.tsx +19 -0
- package/template/apps/desktop/src/components/signup-form.tsx +206 -0
- package/template/apps/desktop/src/components/version-switcher.tsx +54 -0
- package/template/apps/desktop/src/env.d.ts +1 -0
- package/template/apps/desktop/src/lib/auth-client.ts +5 -0
- package/template/apps/desktop/src/lib/orpc.ts +10 -0
- package/template/apps/desktop/src/main.tsx +12 -0
- package/template/apps/desktop/src/{renderer/src/routeTree.gen.ts → routeTree.gen.ts} +21 -0
- package/template/apps/desktop/src/router.tsx +19 -0
- package/template/apps/desktop/src/{renderer/src/routes → routes}/__root.tsx +9 -5
- package/template/apps/desktop/src/{renderer/src/routes → routes}/_auth/account.tsx +18 -10
- package/template/apps/desktop/src/routes/_auth/dashboard.tsx +58 -0
- package/template/apps/desktop/src/{renderer/src/routes → routes}/_auth/examples/client-orpc-auth.tsx +17 -6
- package/template/apps/desktop/src/{renderer/src/routes → routes}/_auth.tsx +2 -14
- package/template/apps/desktop/src/{renderer/src/routes → routes}/_public/examples/client-orpc.tsx +25 -5
- package/template/apps/desktop/src/{renderer/src/routes → routes}/_public/index.tsx +21 -9
- package/template/apps/desktop/src/routes/_public/login.tsx +34 -0
- package/template/apps/desktop/src/routes/_public/signup.tsx +31 -0
- package/template/apps/desktop/src/{renderer/src/routes → routes}/_public.tsx +1 -1
- package/template/apps/desktop/src/vite-env.d.ts +1 -0
- package/template/apps/desktop/src-tauri/Cargo.toml +25 -0
- package/template/apps/desktop/src-tauri/build.rs +3 -0
- package/template/apps/desktop/src-tauri/capabilities/default.json +7 -0
- package/template/apps/desktop/src-tauri/icons/128x128.png +0 -0
- package/template/apps/desktop/src-tauri/icons/128x128@2x.png +0 -0
- package/template/apps/desktop/src-tauri/icons/32x32.png +0 -0
- package/template/apps/desktop/src-tauri/icons/Square107x107Logo.png +0 -0
- package/template/apps/desktop/src-tauri/icons/Square142x142Logo.png +0 -0
- package/template/apps/desktop/src-tauri/icons/Square150x150Logo.png +0 -0
- package/template/apps/desktop/src-tauri/icons/Square284x284Logo.png +0 -0
- package/template/apps/desktop/src-tauri/icons/Square30x30Logo.png +0 -0
- package/template/apps/desktop/src-tauri/icons/Square310x310Logo.png +0 -0
- package/template/apps/desktop/src-tauri/icons/Square44x44Logo.png +0 -0
- package/template/apps/desktop/src-tauri/icons/Square71x71Logo.png +0 -0
- package/template/apps/desktop/src-tauri/icons/Square89x89Logo.png +0 -0
- package/template/apps/desktop/src-tauri/icons/StoreLogo.png +0 -0
- package/template/apps/desktop/src-tauri/icons/icon.icns +0 -0
- package/template/apps/desktop/src-tauri/icons/icon.ico +0 -0
- package/template/apps/desktop/src-tauri/icons/icon.png +0 -0
- package/template/apps/desktop/src-tauri/src/lib.rs +14 -0
- package/template/apps/desktop/src-tauri/src/main.rs +6 -0
- package/template/apps/desktop/src-tauri/tauri.conf.json +35 -0
- package/template/apps/desktop/tsconfig.json +15 -4
- package/template/apps/desktop/tsconfig.node.json +5 -3
- package/template/apps/desktop/vite.config.ts +40 -0
- package/template/apps/mobile/app/(tabs)/_layout.tsx +11 -10
- package/template/apps/mobile/app/(tabs)/explore.tsx +29 -27
- package/template/apps/mobile/app/(tabs)/index.tsx +25 -24
- package/template/apps/mobile/app/_layout.tsx +8 -8
- package/template/apps/mobile/app/modal.tsx +6 -6
- package/template/apps/mobile/components/external-link.tsx +5 -5
- package/template/apps/mobile/components/haptic-tab.tsx +4 -4
- package/template/apps/mobile/components/hello-wave.tsx +5 -4
- package/template/apps/mobile/components/parallax-scroll-view.tsx +15 -13
- package/template/apps/mobile/components/themed-text.tsx +14 -14
- package/template/apps/mobile/components/themed-view.tsx +3 -3
- package/template/apps/mobile/components/ui/collapsible.tsx +14 -13
- package/template/apps/mobile/components/ui/icon-symbol.ios.tsx +4 -4
- package/template/apps/mobile/components/ui/icon-symbol.tsx +9 -9
- package/template/apps/mobile/constants/theme.ts +19 -19
- package/template/apps/mobile/hooks/use-color-scheme.ts +1 -1
- package/template/apps/mobile/hooks/use-color-scheme.web.ts +3 -3
- package/template/apps/mobile/hooks/use-theme-color.ts +4 -4
- package/template/apps/mobile/package.json +7 -7
- package/template/apps/mobile/scripts/reset-project.js +2 -2
- package/template/apps/mobile/tsconfig.json +3 -13
- package/template/apps/platform/package.json +0 -1
- package/template/apps/platform/src/components/login-form.tsx +4 -1
- package/template/apps/platform/src/components/signup-form.tsx +15 -12
- package/template/apps/platform/src/routes/__root.tsx +7 -1
- package/template/apps/platform/src/routes/_public/examples/client-orpc.tsx +12 -3
- package/template/apps/platform/src/routes/_public/examples/ssr-orpc.tsx +2 -1
- package/template/package.json +7 -7
- package/template/packages/orpc/package.json +4 -4
- package/template/pnpm-workspace.yaml +5 -5
- package/template/apps/desktop/.editorconfig +0 -9
- package/template/apps/desktop/.eslintcache +0 -1
- package/template/apps/desktop/.vscode/launch.json +0 -39
- package/template/apps/desktop/.vscode/settings.json +0 -11
- package/template/apps/desktop/build/entitlements.mac.plist +0 -12
- package/template/apps/desktop/build/icon.icns +0 -0
- package/template/apps/desktop/build/icon.ico +0 -0
- package/template/apps/desktop/build/icon.png +0 -0
- package/template/apps/desktop/dev-app-update.yml +0 -3
- package/template/apps/desktop/electron-builder.yml +0 -45
- package/template/apps/desktop/electron.vite.config.ts +0 -26
- package/template/apps/desktop/resources/icon.png +0 -0
- package/template/apps/desktop/src/main/index.ts +0 -88
- package/template/apps/desktop/src/preload/index.d.ts +0 -11
- package/template/apps/desktop/src/preload/index.ts +0 -29
- package/template/apps/desktop/src/renderer/index.html +0 -17
- package/template/apps/desktop/src/renderer/src/assets/base.css +0 -67
- package/template/apps/desktop/src/renderer/src/assets/electron.svg +0 -10
- package/template/apps/desktop/src/renderer/src/assets/main.css +0 -171
- package/template/apps/desktop/src/renderer/src/assets/wavy-lines.svg +0 -25
- package/template/apps/desktop/src/renderer/src/components/Versions.tsx +0 -15
- package/template/apps/desktop/src/renderer/src/components/login-form.tsx +0 -57
- package/template/apps/desktop/src/renderer/src/env.d.ts +0 -6
- package/template/apps/desktop/src/renderer/src/lib/auth-client.ts +0 -5
- package/template/apps/desktop/src/renderer/src/lib/orpc.ts +0 -12
- package/template/apps/desktop/src/renderer/src/main.tsx +0 -22
- package/template/apps/desktop/src/renderer/src/routes/_auth/dashboard.tsx +0 -46
- package/template/apps/desktop/src/renderer/src/routes/_public/login.tsx +0 -16
- package/template/apps/desktop/tsconfig.web.json +0 -19
- package/template/apps/platform/src/lib/auth-client-electron.ts +0 -13
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Label } from "@workspace/ui/components/label";
|
|
2
|
+
import { SidebarGroup, SidebarGroupContent, SidebarInput } from "@workspace/ui/components/sidebar";
|
|
3
|
+
import { SearchIcon } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
export function SearchForm({ ...props }: React.ComponentProps<"form">) {
|
|
6
|
+
return (
|
|
7
|
+
<form {...props}>
|
|
8
|
+
<SidebarGroup className="py-0">
|
|
9
|
+
<SidebarGroupContent className="relative">
|
|
10
|
+
<Label htmlFor="search" className="sr-only">
|
|
11
|
+
Search
|
|
12
|
+
</Label>
|
|
13
|
+
<SidebarInput id="search" placeholder="Search the docs..." className="pl-8" />
|
|
14
|
+
<SearchIcon className="pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none" />
|
|
15
|
+
</SidebarGroupContent>
|
|
16
|
+
</SidebarGroup>
|
|
17
|
+
</form>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { Button } from "@workspace/ui/components/button";
|
|
2
|
+
import {
|
|
3
|
+
Card,
|
|
4
|
+
CardContent,
|
|
5
|
+
CardDescription,
|
|
6
|
+
CardHeader,
|
|
7
|
+
CardTitle,
|
|
8
|
+
} from "@workspace/ui/components/card";
|
|
9
|
+
import {
|
|
10
|
+
Field,
|
|
11
|
+
FieldDescription,
|
|
12
|
+
FieldError,
|
|
13
|
+
FieldGroup,
|
|
14
|
+
FieldLabel,
|
|
15
|
+
} from "@workspace/ui/components/field";
|
|
16
|
+
import { Input } from "@workspace/ui/components/input";
|
|
17
|
+
import { useState } from "react";
|
|
18
|
+
import { Link } from "@tanstack/react-router";
|
|
19
|
+
import { useForm } from "@tanstack/react-form";
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
import { authClient } from "@/lib/auth-client";
|
|
22
|
+
|
|
23
|
+
const signupSchema = z
|
|
24
|
+
.object({
|
|
25
|
+
name: z.string().min(1, "Name is required"),
|
|
26
|
+
email: z.string().email("Invalid email address"),
|
|
27
|
+
password: z.string().min(8, "Password must be at least 8 characters"),
|
|
28
|
+
confirmPassword: z.string().min(1, "Confirm password is required"),
|
|
29
|
+
})
|
|
30
|
+
.refine((data) => data.password === data.confirmPassword, {
|
|
31
|
+
message: "Passwords do not match",
|
|
32
|
+
path: ["confirmPassword"],
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export function SignupForm({ ...props }: React.ComponentProps<typeof Card>) {
|
|
36
|
+
const [loading, setLoading] = useState(false);
|
|
37
|
+
const [error, setError] = useState<string | null>(null);
|
|
38
|
+
|
|
39
|
+
const form = useForm({
|
|
40
|
+
defaultValues: {
|
|
41
|
+
name: "",
|
|
42
|
+
email: "",
|
|
43
|
+
password: "",
|
|
44
|
+
confirmPassword: "",
|
|
45
|
+
},
|
|
46
|
+
validators: {
|
|
47
|
+
onChange: signupSchema,
|
|
48
|
+
},
|
|
49
|
+
onSubmit: async ({ value }) => {
|
|
50
|
+
setLoading(true);
|
|
51
|
+
setError(null);
|
|
52
|
+
const { error: signUpError } = await authClient.signUp.email({
|
|
53
|
+
email: value.email,
|
|
54
|
+
password: value.password,
|
|
55
|
+
name: value.name,
|
|
56
|
+
callbackURL: "/dashboard",
|
|
57
|
+
});
|
|
58
|
+
if (signUpError) {
|
|
59
|
+
setError(signUpError.message || "Failed to sign up");
|
|
60
|
+
}
|
|
61
|
+
setLoading(false);
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<Card {...props}>
|
|
67
|
+
<CardHeader>
|
|
68
|
+
<CardTitle>Create an account</CardTitle>
|
|
69
|
+
<CardDescription>Enter your information below to create your account</CardDescription>
|
|
70
|
+
</CardHeader>
|
|
71
|
+
<CardContent>
|
|
72
|
+
<form
|
|
73
|
+
onSubmit={(e) => {
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
e.stopPropagation();
|
|
76
|
+
form.handleSubmit();
|
|
77
|
+
}}
|
|
78
|
+
>
|
|
79
|
+
<FieldGroup>
|
|
80
|
+
{error && <div className="rounded bg-red-100 p-2 text-sm text-red-600">{error}</div>}
|
|
81
|
+
<form.Field
|
|
82
|
+
name="name"
|
|
83
|
+
children={(field) => {
|
|
84
|
+
const isInvalid = field.state.meta.isTouched && field.state.meta.errors.length > 0;
|
|
85
|
+
return (
|
|
86
|
+
<Field data-invalid={isInvalid}>
|
|
87
|
+
<FieldLabel htmlFor={field.name}>Full Name</FieldLabel>
|
|
88
|
+
<Input
|
|
89
|
+
id={field.name}
|
|
90
|
+
name={field.name}
|
|
91
|
+
type="text"
|
|
92
|
+
placeholder="John Doe"
|
|
93
|
+
value={field.state.value}
|
|
94
|
+
onBlur={field.handleBlur}
|
|
95
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
96
|
+
required
|
|
97
|
+
/>
|
|
98
|
+
{isInvalid && <FieldError errors={field.state.meta.errors} />}
|
|
99
|
+
</Field>
|
|
100
|
+
);
|
|
101
|
+
}}
|
|
102
|
+
/>
|
|
103
|
+
<form.Field
|
|
104
|
+
name="email"
|
|
105
|
+
children={(field) => {
|
|
106
|
+
const isInvalid = field.state.meta.isTouched && field.state.meta.errors.length > 0;
|
|
107
|
+
return (
|
|
108
|
+
<Field data-invalid={isInvalid}>
|
|
109
|
+
<FieldLabel htmlFor={field.name}>Email</FieldLabel>
|
|
110
|
+
<Input
|
|
111
|
+
id={field.name}
|
|
112
|
+
name={field.name}
|
|
113
|
+
type="email"
|
|
114
|
+
placeholder="m@example.com"
|
|
115
|
+
value={field.state.value}
|
|
116
|
+
onBlur={field.handleBlur}
|
|
117
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
118
|
+
required
|
|
119
|
+
/>
|
|
120
|
+
<FieldDescription>
|
|
121
|
+
We'll use this to contact you. We will not share your email with anyone
|
|
122
|
+
else.
|
|
123
|
+
</FieldDescription>
|
|
124
|
+
{isInvalid && <FieldError errors={field.state.meta.errors} />}
|
|
125
|
+
</Field>
|
|
126
|
+
);
|
|
127
|
+
}}
|
|
128
|
+
/>
|
|
129
|
+
<form.Field
|
|
130
|
+
name="password"
|
|
131
|
+
children={(field) => {
|
|
132
|
+
const isInvalid = field.state.meta.isTouched && field.state.meta.errors.length > 0;
|
|
133
|
+
return (
|
|
134
|
+
<Field data-invalid={isInvalid}>
|
|
135
|
+
<FieldLabel htmlFor={field.name}>Password</FieldLabel>
|
|
136
|
+
<Input
|
|
137
|
+
id={field.name}
|
|
138
|
+
name={field.name}
|
|
139
|
+
type="password"
|
|
140
|
+
value={field.state.value}
|
|
141
|
+
onBlur={field.handleBlur}
|
|
142
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
143
|
+
required
|
|
144
|
+
/>
|
|
145
|
+
<FieldDescription>Must be at least 8 characters long.</FieldDescription>
|
|
146
|
+
{isInvalid && <FieldError errors={field.state.meta.errors} />}
|
|
147
|
+
</Field>
|
|
148
|
+
);
|
|
149
|
+
}}
|
|
150
|
+
/>
|
|
151
|
+
<form.Field
|
|
152
|
+
name="confirmPassword"
|
|
153
|
+
children={(field) => {
|
|
154
|
+
const isInvalid = field.state.meta.isTouched && field.state.meta.errors.length > 0;
|
|
155
|
+
return (
|
|
156
|
+
<Field data-invalid={isInvalid}>
|
|
157
|
+
<FieldLabel htmlFor={field.name}>Confirm Password</FieldLabel>
|
|
158
|
+
<Input
|
|
159
|
+
id={field.name}
|
|
160
|
+
name={field.name}
|
|
161
|
+
type="password"
|
|
162
|
+
value={field.state.value}
|
|
163
|
+
onBlur={field.handleBlur}
|
|
164
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
165
|
+
required
|
|
166
|
+
/>
|
|
167
|
+
<FieldDescription>Please confirm your password.</FieldDescription>
|
|
168
|
+
{isInvalid && <FieldError errors={field.state.meta.errors} />}
|
|
169
|
+
</Field>
|
|
170
|
+
);
|
|
171
|
+
}}
|
|
172
|
+
/>
|
|
173
|
+
<FieldGroup>
|
|
174
|
+
<Field>
|
|
175
|
+
<form.Subscribe
|
|
176
|
+
selector={(state) => ({
|
|
177
|
+
canSubmit: state.canSubmit,
|
|
178
|
+
isSubmitting: state.isSubmitting,
|
|
179
|
+
})}
|
|
180
|
+
>
|
|
181
|
+
{(state: { canSubmit: boolean; isSubmitting: boolean }) => (
|
|
182
|
+
<Button
|
|
183
|
+
type="submit"
|
|
184
|
+
disabled={!state.canSubmit || state.isSubmitting || loading}
|
|
185
|
+
>
|
|
186
|
+
{state.isSubmitting || loading ? "Creating..." : "Create Account"}
|
|
187
|
+
</Button>
|
|
188
|
+
)}
|
|
189
|
+
</form.Subscribe>
|
|
190
|
+
<Button variant="outline" type="button" disabled={loading}>
|
|
191
|
+
Sign up with Google
|
|
192
|
+
</Button>
|
|
193
|
+
<FieldDescription className="px-6 text-center">
|
|
194
|
+
Already have an account?{" "}
|
|
195
|
+
<Link to="/login" className="underline">
|
|
196
|
+
Sign in
|
|
197
|
+
</Link>
|
|
198
|
+
</FieldDescription>
|
|
199
|
+
</Field>
|
|
200
|
+
</FieldGroup>
|
|
201
|
+
</FieldGroup>
|
|
202
|
+
</form>
|
|
203
|
+
</CardContent>
|
|
204
|
+
</Card>
|
|
205
|
+
);
|
|
206
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
DropdownMenu,
|
|
7
|
+
DropdownMenuContent,
|
|
8
|
+
DropdownMenuItem,
|
|
9
|
+
DropdownMenuTrigger,
|
|
10
|
+
} from "@workspace/ui/components/dropdown-menu";
|
|
11
|
+
import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "@workspace/ui/components/sidebar";
|
|
12
|
+
import { CheckIcon, ChevronsUpDownIcon, GalleryVerticalEndIcon } from "lucide-react";
|
|
13
|
+
|
|
14
|
+
export function VersionSwitcher({
|
|
15
|
+
versions,
|
|
16
|
+
defaultVersion,
|
|
17
|
+
}: {
|
|
18
|
+
versions: Array<string>;
|
|
19
|
+
defaultVersion: string;
|
|
20
|
+
}) {
|
|
21
|
+
const [selectedVersion, setSelectedVersion] = React.useState(defaultVersion);
|
|
22
|
+
return (
|
|
23
|
+
<SidebarMenu>
|
|
24
|
+
<SidebarMenuItem>
|
|
25
|
+
<DropdownMenu>
|
|
26
|
+
<DropdownMenuTrigger
|
|
27
|
+
render={
|
|
28
|
+
<SidebarMenuButton
|
|
29
|
+
size="lg"
|
|
30
|
+
className="data-open:bg-sidebar-accent data-open:text-sidebar-accent-foreground"
|
|
31
|
+
/>
|
|
32
|
+
}
|
|
33
|
+
>
|
|
34
|
+
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
|
|
35
|
+
<GalleryVerticalEndIcon className="size-4" />
|
|
36
|
+
</div>
|
|
37
|
+
<div className="flex flex-col gap-0.5 leading-none">
|
|
38
|
+
<span className="font-medium">Documentation</span>
|
|
39
|
+
<span className="">v{selectedVersion}</span>
|
|
40
|
+
</div>
|
|
41
|
+
<ChevronsUpDownIcon className="ml-auto" />
|
|
42
|
+
</DropdownMenuTrigger>
|
|
43
|
+
<DropdownMenuContent align="start">
|
|
44
|
+
{versions.map((version) => (
|
|
45
|
+
<DropdownMenuItem key={version} onSelect={() => setSelectedVersion(version)}>
|
|
46
|
+
v{version} {version === selectedVersion && <CheckIcon className="ml-auto" />}
|
|
47
|
+
</DropdownMenuItem>
|
|
48
|
+
))}
|
|
49
|
+
</DropdownMenuContent>
|
|
50
|
+
</DropdownMenu>
|
|
51
|
+
</SidebarMenuItem>
|
|
52
|
+
</SidebarMenu>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createORPCClient } from "@orpc/client";
|
|
2
|
+
import { RPCLink } from "@orpc/client/fetch";
|
|
3
|
+
import type { AppRouter } from "@workspace/orpc/router";
|
|
4
|
+
import type { RouterClient } from "@orpc/server";
|
|
5
|
+
|
|
6
|
+
const link = new RPCLink({
|
|
7
|
+
url: `${import.meta.env.VITE_API_URL || "https://platform.localhost"}/api/rpc`,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const orpc = createORPCClient(link) as RouterClient<AppRouter>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { StrictMode } from "react";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
import { RouterProvider } from "@tanstack/react-router";
|
|
4
|
+
import { getRouter } from "./router";
|
|
5
|
+
|
|
6
|
+
const router = getRouter();
|
|
7
|
+
|
|
8
|
+
createRoot(document.getElementById("root")!).render(
|
|
9
|
+
<StrictMode>
|
|
10
|
+
<RouterProvider router={router} />
|
|
11
|
+
</StrictMode>,
|
|
12
|
+
);
|
|
@@ -12,6 +12,7 @@ import { Route as rootRouteImport } from './routes/__root'
|
|
|
12
12
|
import { Route as PublicRouteImport } from './routes/_public'
|
|
13
13
|
import { Route as AuthRouteImport } from './routes/_auth'
|
|
14
14
|
import { Route as PublicIndexRouteImport } from './routes/_public/index'
|
|
15
|
+
import { Route as PublicSignupRouteImport } from './routes/_public/signup'
|
|
15
16
|
import { Route as PublicLoginRouteImport } from './routes/_public/login'
|
|
16
17
|
import { Route as AuthDashboardRouteImport } from './routes/_auth/dashboard'
|
|
17
18
|
import { Route as AuthAccountRouteImport } from './routes/_auth/account'
|
|
@@ -31,6 +32,11 @@ const PublicIndexRoute = PublicIndexRouteImport.update({
|
|
|
31
32
|
path: '/',
|
|
32
33
|
getParentRoute: () => PublicRoute,
|
|
33
34
|
} as any)
|
|
35
|
+
const PublicSignupRoute = PublicSignupRouteImport.update({
|
|
36
|
+
id: '/signup',
|
|
37
|
+
path: '/signup',
|
|
38
|
+
getParentRoute: () => PublicRoute,
|
|
39
|
+
} as any)
|
|
34
40
|
const PublicLoginRoute = PublicLoginRouteImport.update({
|
|
35
41
|
id: '/login',
|
|
36
42
|
path: '/login',
|
|
@@ -64,6 +70,7 @@ export interface FileRoutesByFullPath {
|
|
|
64
70
|
'/account': typeof AuthAccountRoute
|
|
65
71
|
'/dashboard': typeof AuthDashboardRoute
|
|
66
72
|
'/login': typeof PublicLoginRoute
|
|
73
|
+
'/signup': typeof PublicSignupRoute
|
|
67
74
|
'/examples/client-orpc-auth': typeof AuthExamplesClientOrpcAuthRoute
|
|
68
75
|
'/examples/client-orpc': typeof PublicExamplesClientOrpcRoute
|
|
69
76
|
}
|
|
@@ -72,6 +79,7 @@ export interface FileRoutesByTo {
|
|
|
72
79
|
'/account': typeof AuthAccountRoute
|
|
73
80
|
'/dashboard': typeof AuthDashboardRoute
|
|
74
81
|
'/login': typeof PublicLoginRoute
|
|
82
|
+
'/signup': typeof PublicSignupRoute
|
|
75
83
|
'/examples/client-orpc-auth': typeof AuthExamplesClientOrpcAuthRoute
|
|
76
84
|
'/examples/client-orpc': typeof PublicExamplesClientOrpcRoute
|
|
77
85
|
}
|
|
@@ -82,6 +90,7 @@ export interface FileRoutesById {
|
|
|
82
90
|
'/_auth/account': typeof AuthAccountRoute
|
|
83
91
|
'/_auth/dashboard': typeof AuthDashboardRoute
|
|
84
92
|
'/_public/login': typeof PublicLoginRoute
|
|
93
|
+
'/_public/signup': typeof PublicSignupRoute
|
|
85
94
|
'/_public/': typeof PublicIndexRoute
|
|
86
95
|
'/_auth/examples/client-orpc-auth': typeof AuthExamplesClientOrpcAuthRoute
|
|
87
96
|
'/_public/examples/client-orpc': typeof PublicExamplesClientOrpcRoute
|
|
@@ -93,6 +102,7 @@ export interface FileRouteTypes {
|
|
|
93
102
|
| '/account'
|
|
94
103
|
| '/dashboard'
|
|
95
104
|
| '/login'
|
|
105
|
+
| '/signup'
|
|
96
106
|
| '/examples/client-orpc-auth'
|
|
97
107
|
| '/examples/client-orpc'
|
|
98
108
|
fileRoutesByTo: FileRoutesByTo
|
|
@@ -101,6 +111,7 @@ export interface FileRouteTypes {
|
|
|
101
111
|
| '/account'
|
|
102
112
|
| '/dashboard'
|
|
103
113
|
| '/login'
|
|
114
|
+
| '/signup'
|
|
104
115
|
| '/examples/client-orpc-auth'
|
|
105
116
|
| '/examples/client-orpc'
|
|
106
117
|
id:
|
|
@@ -110,6 +121,7 @@ export interface FileRouteTypes {
|
|
|
110
121
|
| '/_auth/account'
|
|
111
122
|
| '/_auth/dashboard'
|
|
112
123
|
| '/_public/login'
|
|
124
|
+
| '/_public/signup'
|
|
113
125
|
| '/_public/'
|
|
114
126
|
| '/_auth/examples/client-orpc-auth'
|
|
115
127
|
| '/_public/examples/client-orpc'
|
|
@@ -143,6 +155,13 @@ declare module '@tanstack/react-router' {
|
|
|
143
155
|
preLoaderRoute: typeof PublicIndexRouteImport
|
|
144
156
|
parentRoute: typeof PublicRoute
|
|
145
157
|
}
|
|
158
|
+
'/_public/signup': {
|
|
159
|
+
id: '/_public/signup'
|
|
160
|
+
path: '/signup'
|
|
161
|
+
fullPath: '/signup'
|
|
162
|
+
preLoaderRoute: typeof PublicSignupRouteImport
|
|
163
|
+
parentRoute: typeof PublicRoute
|
|
164
|
+
}
|
|
146
165
|
'/_public/login': {
|
|
147
166
|
id: '/_public/login'
|
|
148
167
|
path: '/login'
|
|
@@ -197,12 +216,14 @@ const AuthRouteWithChildren = AuthRoute._addFileChildren(AuthRouteChildren)
|
|
|
197
216
|
|
|
198
217
|
interface PublicRouteChildren {
|
|
199
218
|
PublicLoginRoute: typeof PublicLoginRoute
|
|
219
|
+
PublicSignupRoute: typeof PublicSignupRoute
|
|
200
220
|
PublicIndexRoute: typeof PublicIndexRoute
|
|
201
221
|
PublicExamplesClientOrpcRoute: typeof PublicExamplesClientOrpcRoute
|
|
202
222
|
}
|
|
203
223
|
|
|
204
224
|
const PublicRouteChildren: PublicRouteChildren = {
|
|
205
225
|
PublicLoginRoute: PublicLoginRoute,
|
|
226
|
+
PublicSignupRoute: PublicSignupRoute,
|
|
206
227
|
PublicIndexRoute: PublicIndexRoute,
|
|
207
228
|
PublicExamplesClientOrpcRoute: PublicExamplesClientOrpcRoute,
|
|
208
229
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createRouter as createTanStackRouter } from "@tanstack/react-router";
|
|
2
|
+
import { routeTree } from "./routeTree.gen";
|
|
3
|
+
|
|
4
|
+
export function getRouter() {
|
|
5
|
+
const router = createTanStackRouter({
|
|
6
|
+
routeTree,
|
|
7
|
+
scrollRestoration: true,
|
|
8
|
+
defaultPreload: "intent",
|
|
9
|
+
defaultPreloadStaleTime: 0,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
return router;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
declare module "@tanstack/react-router" {
|
|
16
|
+
interface Register {
|
|
17
|
+
router: ReturnType<typeof getRouter>;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -3,10 +3,15 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
|
3
3
|
import { Toaster } from "@workspace/ui/components/sonner";
|
|
4
4
|
import { ThemeProvider } from "@workspace/ui/components/theme-provider";
|
|
5
5
|
import { ORPCProvider } from "@workspace/orpc/react";
|
|
6
|
-
import { orpc } from "
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
import { orpc } from "@/lib/orpc";
|
|
7
|
+
import {
|
|
8
|
+
Empty,
|
|
9
|
+
EmptyContent,
|
|
10
|
+
EmptyDescription,
|
|
11
|
+
EmptyHeader,
|
|
12
|
+
EmptyTitle,
|
|
13
|
+
} from "@workspace/ui/components/empty";
|
|
14
|
+
import { buttonVariants } from "@workspace/ui/components/button";
|
|
10
15
|
|
|
11
16
|
import "@workspace/ui/globals.css";
|
|
12
17
|
|
|
@@ -40,7 +45,6 @@ function RootLayout() {
|
|
|
40
45
|
<ORPCProvider client={orpc}>
|
|
41
46
|
<Outlet />
|
|
42
47
|
<Toaster />
|
|
43
|
-
<TanStackRouterDevtools />
|
|
44
48
|
</ORPCProvider>
|
|
45
49
|
</QueryClientProvider>
|
|
46
50
|
</ThemeProvider>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import { createFileRoute, redirect } from "@tanstack/react-router";
|
|
3
3
|
import { useForm } from "@tanstack/react-form";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { toast } from "sonner";
|
|
@@ -19,7 +19,7 @@ import { Field, FieldError, FieldLabel } from "@workspace/ui/components/field";
|
|
|
19
19
|
import { Avatar, AvatarFallback, AvatarImage } from "@workspace/ui/components/avatar";
|
|
20
20
|
import { Separator } from "@workspace/ui/components/separator";
|
|
21
21
|
|
|
22
|
-
import { authClient } from "
|
|
22
|
+
import { authClient } from "@/lib/auth-client";
|
|
23
23
|
|
|
24
24
|
const profileSchema = z.object({
|
|
25
25
|
name: z.string().min(1, "Name is required"),
|
|
@@ -37,18 +37,29 @@ const passwordSchema = z
|
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
export const Route = createFileRoute("/_auth/account")({
|
|
40
|
+
beforeLoad: async () => {
|
|
41
|
+
const { data: session } = await authClient.getSession();
|
|
42
|
+
if (!session) {
|
|
43
|
+
throw redirect({
|
|
44
|
+
to: "/login",
|
|
45
|
+
search: {
|
|
46
|
+
redirect: "/account",
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return { session };
|
|
51
|
+
},
|
|
40
52
|
component: AccountPage,
|
|
41
53
|
});
|
|
42
54
|
|
|
43
55
|
function AccountPage() {
|
|
44
|
-
const {
|
|
56
|
+
const { session } = Route.useRouteContext();
|
|
45
57
|
const [loading, setLoading] = React.useState(false);
|
|
46
|
-
|
|
47
|
-
const user = sessionData?.user;
|
|
58
|
+
const user = session.user;
|
|
48
59
|
|
|
49
60
|
const profileForm = useForm({
|
|
50
61
|
defaultValues: {
|
|
51
|
-
name: user
|
|
62
|
+
name: user.name || "",
|
|
52
63
|
},
|
|
53
64
|
validators: {
|
|
54
65
|
onChange: profileSchema,
|
|
@@ -94,9 +105,6 @@ function AccountPage() {
|
|
|
94
105
|
},
|
|
95
106
|
});
|
|
96
107
|
|
|
97
|
-
if (isPending) return <div>Loading...</div>;
|
|
98
|
-
if (!user) return null;
|
|
99
|
-
|
|
100
108
|
return (
|
|
101
109
|
<div className="container max-w-4xl py-10">
|
|
102
110
|
<div className="flex flex-col gap-8">
|
|
@@ -251,7 +259,7 @@ function AccountPage() {
|
|
|
251
259
|
if (error) {
|
|
252
260
|
toast.error(error.message || "Failed to delete account");
|
|
253
261
|
} else {
|
|
254
|
-
window.location.
|
|
262
|
+
window.location.href = "/";
|
|
255
263
|
}
|
|
256
264
|
}
|
|
257
265
|
}}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createFileRoute, redirect } from "@tanstack/react-router";
|
|
2
|
+
import { authClient } from "@/lib/auth-client";
|
|
3
|
+
import { orpc } from "@/lib/orpc";
|
|
4
|
+
|
|
5
|
+
export const Route = createFileRoute("/_auth/dashboard")({
|
|
6
|
+
beforeLoad: async () => {
|
|
7
|
+
const { data: session } = await authClient.getSession();
|
|
8
|
+
if (!session) {
|
|
9
|
+
throw redirect({
|
|
10
|
+
to: "/",
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
return { session };
|
|
14
|
+
},
|
|
15
|
+
loader: async () => {
|
|
16
|
+
try {
|
|
17
|
+
const data = await orpc.getSecretData();
|
|
18
|
+
return { secretData: data.secret };
|
|
19
|
+
} catch (err: any) {
|
|
20
|
+
return { secretData: "Error: " + (err.message || "Unknown error") };
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
component: Dashboard,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
function Dashboard() {
|
|
27
|
+
const { session } = Route.useRouteContext();
|
|
28
|
+
const { secretData } = Route.useLoaderData();
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className="flex min-h-svh p-6">
|
|
32
|
+
<div className="flex max-w-md min-w-0 flex-col gap-4 text-sm leading-loose">
|
|
33
|
+
<div>
|
|
34
|
+
<h1 className="text-2xl font-bold">Dashboard</h1>
|
|
35
|
+
<p>Welcome, {session.user.name}!</p>
|
|
36
|
+
<p>This is a protected page. Only authenticated users can see this.</p>
|
|
37
|
+
|
|
38
|
+
<div className="mt-6 rounded-lg border bg-gray-50 p-4">
|
|
39
|
+
<h2 className="font-semibold mb-2">Secure oRPC Data:</h2>
|
|
40
|
+
<p className="font-mono text-xs">{secretData}</p>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div className="mt-4 flex gap-2">
|
|
44
|
+
<button
|
|
45
|
+
onClick={async () => {
|
|
46
|
+
await authClient.signOut();
|
|
47
|
+
window.location.href = "/";
|
|
48
|
+
}}
|
|
49
|
+
className="rounded bg-red-500 px-4 py-2 text-white hover:bg-red-600"
|
|
50
|
+
>
|
|
51
|
+
Sign Out
|
|
52
|
+
</button>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
package/template/apps/desktop/src/{renderer/src/routes → routes}/_auth/examples/client-orpc-auth.tsx
RENAMED
|
@@ -1,14 +1,25 @@
|
|
|
1
|
-
import { createFileRoute } from "@tanstack/react-router";
|
|
1
|
+
import { createFileRoute, redirect } from "@tanstack/react-router";
|
|
2
|
+
import { authClient } from "@/lib/auth-client";
|
|
2
3
|
import { useSecretData } from "@workspace/orpc/react";
|
|
3
|
-
import { authClient } from "@renderer/lib/auth-client";
|
|
4
4
|
|
|
5
5
|
export const Route = createFileRoute("/_auth/examples/client-orpc-auth")({
|
|
6
|
+
beforeLoad: async () => {
|
|
7
|
+
const { data: session } = await authClient.getSession();
|
|
8
|
+
if (!session) {
|
|
9
|
+
throw redirect({
|
|
10
|
+
to: "/login",
|
|
11
|
+
search: {
|
|
12
|
+
redirect: "/examples/client-orpc-auth",
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return { session };
|
|
17
|
+
},
|
|
6
18
|
component: ClientORPCAuth,
|
|
7
19
|
});
|
|
8
20
|
|
|
9
21
|
function ClientORPCAuth() {
|
|
10
|
-
const {
|
|
11
|
-
const session = sessionData;
|
|
22
|
+
const { session } = Route.useRouteContext();
|
|
12
23
|
|
|
13
24
|
const { data, isLoading } = useSecretData();
|
|
14
25
|
|
|
@@ -19,7 +30,7 @@ function ClientORPCAuth() {
|
|
|
19
30
|
|
|
20
31
|
<div className="rounded-lg border p-4">
|
|
21
32
|
<h2 className="font-semibold">User Session:</h2>
|
|
22
|
-
<pre className="text-xs bg-muted p-2 rounded
|
|
33
|
+
<pre className="text-xs bg-muted p-2 rounded">{JSON.stringify(session, null, 2)}</pre>
|
|
23
34
|
</div>
|
|
24
35
|
|
|
25
36
|
<div className="rounded-lg border p-4">
|
|
@@ -27,7 +38,7 @@ function ClientORPCAuth() {
|
|
|
27
38
|
{isLoading ? (
|
|
28
39
|
<p>Loading...</p>
|
|
29
40
|
) : (
|
|
30
|
-
<pre className="text-xs bg-muted p-2 rounded
|
|
41
|
+
<pre className="text-xs bg-muted p-2 rounded">{JSON.stringify(data, null, 2)}</pre>
|
|
31
42
|
)}
|
|
32
43
|
</div>
|
|
33
44
|
</div>
|
|
@@ -1,20 +1,8 @@
|
|
|
1
|
-
import { Outlet, createFileRoute
|
|
1
|
+
import { Outlet, createFileRoute } from "@tanstack/react-router";
|
|
2
2
|
import { SidebarProvider, SidebarTrigger } from "@workspace/ui/components/sidebar";
|
|
3
|
-
import { AuthSidebar } from "
|
|
4
|
-
import { authClient } from "@renderer/lib/auth-client";
|
|
3
|
+
import { AuthSidebar } from "@/components/app-sidebar";
|
|
5
4
|
|
|
6
5
|
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
6
|
component: AuthLayout,
|
|
19
7
|
});
|
|
20
8
|
|