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.
Files changed (61) 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/apps/mobile/.vscode/extensions.json +1 -0
  24. package/template/apps/mobile/.vscode/settings.json +7 -0
  25. package/template/apps/mobile/README.md +50 -0
  26. package/template/apps/mobile/app/(tabs)/_layout.tsx +35 -0
  27. package/template/apps/mobile/app/(tabs)/explore.tsx +112 -0
  28. package/template/apps/mobile/app/(tabs)/index.tsx +98 -0
  29. package/template/apps/mobile/app/_layout.tsx +24 -0
  30. package/template/apps/mobile/app/modal.tsx +29 -0
  31. package/template/apps/mobile/app.json +48 -0
  32. package/template/apps/mobile/assets/images/android-icon-background.png +0 -0
  33. package/template/apps/mobile/assets/images/android-icon-foreground.png +0 -0
  34. package/template/apps/mobile/assets/images/android-icon-monochrome.png +0 -0
  35. package/template/apps/mobile/assets/images/favicon.png +0 -0
  36. package/template/apps/mobile/assets/images/icon.png +0 -0
  37. package/template/apps/mobile/assets/images/partial-react-logo.png +0 -0
  38. package/template/apps/mobile/assets/images/react-logo.png +0 -0
  39. package/template/apps/mobile/assets/images/react-logo@2x.png +0 -0
  40. package/template/apps/mobile/assets/images/react-logo@3x.png +0 -0
  41. package/template/apps/mobile/assets/images/splash-icon.png +0 -0
  42. package/template/apps/mobile/components/external-link.tsx +25 -0
  43. package/template/apps/mobile/components/haptic-tab.tsx +18 -0
  44. package/template/apps/mobile/components/hello-wave.tsx +19 -0
  45. package/template/apps/mobile/components/parallax-scroll-view.tsx +79 -0
  46. package/template/apps/mobile/components/themed-text.tsx +60 -0
  47. package/template/apps/mobile/components/themed-view.tsx +14 -0
  48. package/template/apps/mobile/components/ui/collapsible.tsx +45 -0
  49. package/template/apps/mobile/components/ui/icon-symbol.ios.tsx +32 -0
  50. package/template/apps/mobile/components/ui/icon-symbol.tsx +41 -0
  51. package/template/apps/mobile/constants/theme.ts +53 -0
  52. package/template/apps/mobile/hooks/use-color-scheme.ts +1 -0
  53. package/template/apps/mobile/hooks/use-color-scheme.web.ts +21 -0
  54. package/template/apps/mobile/hooks/use-theme-color.ts +21 -0
  55. package/template/apps/mobile/package.json +45 -0
  56. package/template/apps/mobile/scripts/reset-project.js +112 -0
  57. package/template/apps/mobile/tsconfig.json +20 -0
  58. package/template/package.json +14 -8
  59. package/template/packages/ui/package.json +4 -2
  60. package/template/pnpm-workspace.yaml +8 -0
  61. package/template/apps/desktop/src/renderer/src/App.tsx +0 -35
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-croissant",
3
- "version": "0.1.48",
3
+ "version": "0.1.50",
4
4
  "description": "Scaffold a new project using the Croissant Stack",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,6 +1,8 @@
1
1
  import { resolve } from "node:path";
2
2
  import { defineConfig } from "electron-vite";
3
3
  import react from "@vitejs/plugin-react";
4
+ import { tanstackRouter } from "@tanstack/router-plugin/vite";
5
+ import tailwindcss from "@tailwindcss/vite";
4
6
 
5
7
  export default defineConfig({
6
8
  main: {},
@@ -8,9 +10,17 @@ export default defineConfig({
8
10
  renderer: {
9
11
  resolve: {
10
12
  alias: {
13
+ "@": resolve("src/renderer/src"),
11
14
  "@renderer": resolve("src/renderer/src"),
12
15
  },
13
16
  },
14
- plugins: [react()],
17
+ plugins: [
18
+ tanstackRouter({
19
+ routesDirectory: resolve("src/renderer/src/routes"),
20
+ generatedRouteTree: resolve("src/renderer/src/routeTree.gen.ts"),
21
+ }),
22
+ tailwindcss(),
23
+ react(),
24
+ ],
15
25
  },
16
26
  });
@@ -21,10 +21,28 @@
21
21
  "dependencies": {
22
22
  "@electron-toolkit/preload": "^3.0.2",
23
23
  "@electron-toolkit/utils": "^4.0.0",
24
- "electron-updater": "^6.3.9"
24
+ "@orpc/client": "^1.14.3",
25
+ "@orpc/server": "^1.14.3",
26
+ "@orpc/tanstack-query": "^1.14.3",
27
+ "@tailwindcss/vite": "^4.2.4",
28
+ "@tanstack/react-form": "^1.32.0",
29
+ "@tanstack/react-query": "^5.100.10",
30
+ "@tanstack/react-router": "^1.169.2",
31
+ "@workspace/auth": "workspace:*",
32
+ "@workspace/orpc": "workspace:*",
33
+ "@workspace/ui": "workspace:*",
34
+ "better-auth": "^1.6.11",
35
+ "electron-updater": "^6.3.9",
36
+ "lucide-react": "^1.14.0",
37
+ "react": "19.2.5",
38
+ "react-dom": "19.2.5",
39
+ "sonner": "^2.0.7",
40
+ "tailwindcss": "^4.2.4"
25
41
  },
26
42
  "devDependencies": {
27
43
  "@electron-toolkit/tsconfig": "^2.0.0",
44
+ "@tanstack/react-router-devtools": "^1.166.13",
45
+ "@tanstack/router-plugin": "^1.167.35",
28
46
  "@types/node": "^22.19.1",
29
47
  "@vitejs/plugin-react": "^5.1.1",
30
48
  "@workspace/config-typescript": "workspace:*",
@@ -6,7 +6,7 @@
6
6
  <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
7
7
  <meta
8
8
  http-equiv="Content-Security-Policy"
9
- content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
9
+ content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https://platform.localhost"
10
10
  />
11
11
  </head>
12
12
 
@@ -0,0 +1,186 @@
1
+ import * as React from "react";
2
+ import { Link } from "@tanstack/react-router";
3
+ import { LogOut, Settings, User } from "lucide-react";
4
+
5
+ import {
6
+ Sidebar,
7
+ SidebarContent,
8
+ SidebarFooter,
9
+ SidebarGroup,
10
+ SidebarGroupContent,
11
+ SidebarGroupLabel,
12
+ SidebarHeader,
13
+ SidebarMenu,
14
+ SidebarMenuButton,
15
+ SidebarMenuItem,
16
+ SidebarRail,
17
+ } from "@workspace/ui/components/sidebar";
18
+ import { Avatar, AvatarFallback, AvatarImage } from "@workspace/ui/components/avatar";
19
+ import { ModeToggle } from "@workspace/ui/components/mode-toggle";
20
+
21
+ import { authClient } from "@/lib/auth-client";
22
+
23
+ // This is sample data.
24
+ export const authNavItems = [
25
+ {
26
+ title: "Dashboard",
27
+ items: [
28
+ {
29
+ title: "Overview",
30
+ url: "/dashboard",
31
+ },
32
+ {
33
+ title: "Account",
34
+ url: "/account",
35
+ },
36
+ ],
37
+ },
38
+ {
39
+ title: "Examples",
40
+ items: [
41
+ {
42
+ title: "Client + oRPC (Auth)",
43
+ url: "/examples/client-orpc-auth",
44
+ },
45
+ ],
46
+ },
47
+ ];
48
+
49
+ export const publicNavItems = [
50
+ {
51
+ title: "Welcome",
52
+ items: [
53
+ {
54
+ title: "Home",
55
+ url: "/",
56
+ },
57
+ {
58
+ title: "Login",
59
+ url: "/login",
60
+ },
61
+ {
62
+ title: "Sign Up",
63
+ url: "/signup",
64
+ },
65
+ ],
66
+ },
67
+ {
68
+ title: "Examples",
69
+ items: [
70
+ {
71
+ title: "Client + oRPC",
72
+ url: "/examples/client-orpc",
73
+ },
74
+ ],
75
+ },
76
+ ];
77
+
78
+ interface AppSidebarProps extends React.ComponentProps<typeof Sidebar> {
79
+ items?: Array<{
80
+ title: string;
81
+ items: Array<{
82
+ title: string;
83
+ url: string;
84
+ }>;
85
+ }>;
86
+ }
87
+
88
+ export function AuthSidebar(props: React.ComponentProps<typeof Sidebar>) {
89
+ return <AppSidebar items={authNavItems} {...props} />;
90
+ }
91
+
92
+ export function PublicSidebar(props: React.ComponentProps<typeof Sidebar>) {
93
+ return <AppSidebar items={publicNavItems} {...props} />;
94
+ }
95
+
96
+ export function AppSidebar({ items = authNavItems, ...props }: AppSidebarProps) {
97
+ const { data: session } = authClient.useSession();
98
+
99
+ const user = session?.user || null;
100
+
101
+ return (
102
+ <Sidebar {...props}>
103
+ <SidebarHeader>
104
+ <div className="flex items-center justify-between px-4 py-2">
105
+ <span className="font-bold">Croissant Desktop</span>
106
+ <ModeToggle />
107
+ </div>
108
+ </SidebarHeader>
109
+ <SidebarContent>
110
+ {items.map((item) => (
111
+ <SidebarGroup key={item.title}>
112
+ <SidebarGroupLabel>{item.title}</SidebarGroupLabel>
113
+ <SidebarGroupContent>
114
+ <SidebarMenu>
115
+ {item.items.map((subItem) => (
116
+ <SidebarMenuItem key={subItem.title}>
117
+ <SidebarMenuButton
118
+ render={
119
+ <Link to={subItem.url} activeProps={{ className: "bg-sidebar-accent" }} />
120
+ }
121
+ >
122
+ {subItem.title}
123
+ </SidebarMenuButton>
124
+ </SidebarMenuItem>
125
+ ))}
126
+ </SidebarMenu>
127
+ </SidebarGroupContent>
128
+ </SidebarGroup>
129
+ ))}
130
+ </SidebarContent>
131
+ <SidebarFooter>
132
+ <SidebarMenu>
133
+ <SidebarMenuItem>
134
+ {user ? (
135
+ <SidebarMenuButton
136
+ size="lg"
137
+ render={<div />}
138
+ className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
139
+ >
140
+ <div className="flex items-center gap-2 w-full">
141
+ <Avatar className="h-8 w-8 rounded-lg">
142
+ <AvatarImage src={user.image || ""} alt={user.name} />
143
+ <AvatarFallback className="rounded-lg">
144
+ {user.name.charAt(0) || "U"}
145
+ </AvatarFallback>
146
+ </Avatar>
147
+ <div className="grid flex-1 text-left text-sm leading-tight">
148
+ <span className="truncate font-semibold">{user.name}</span>
149
+ <span className="truncate text-xs">{user.email}</span>
150
+ </div>
151
+ <div className="flex items-center gap-1 ml-auto">
152
+ <SidebarMenuButton
153
+ render={<Link to="/account" />}
154
+ className="p-1 h-auto w-auto rounded hover:bg-sidebar-accent-foreground/10"
155
+ title="Account Settings"
156
+ >
157
+ <Settings className="h-4 w-4" />
158
+ </SidebarMenuButton>
159
+ <SidebarMenuButton
160
+ onClick={async (e) => {
161
+ e.preventDefault();
162
+ e.stopPropagation();
163
+ await authClient.signOut();
164
+ window.location.reload();
165
+ }}
166
+ className="p-1 h-auto w-auto rounded hover:bg-sidebar-accent-foreground/10"
167
+ title="Sign Out"
168
+ >
169
+ <LogOut className="h-4 w-4" />
170
+ </SidebarMenuButton>
171
+ </div>
172
+ </div>
173
+ </SidebarMenuButton>
174
+ ) : (
175
+ <SidebarMenuButton render={<Link to="/login" className="flex items-center gap-2" />}>
176
+ <User className="h-4 w-4" />
177
+ <span>Sign In</span>
178
+ </SidebarMenuButton>
179
+ )}
180
+ </SidebarMenuItem>
181
+ </SidebarMenu>
182
+ </SidebarFooter>
183
+ <SidebarRail />
184
+ </Sidebar>
185
+ );
186
+ }
@@ -0,0 +1,154 @@
1
+ import { cn } from "@workspace/ui/lib/utils";
2
+ import { Button } from "@workspace/ui/components/button";
3
+ import {
4
+ Card,
5
+ CardContent,
6
+ CardDescription,
7
+ CardHeader,
8
+ CardTitle,
9
+ } from "@workspace/ui/components/card";
10
+ import {
11
+ Field,
12
+ FieldDescription,
13
+ FieldError,
14
+ FieldGroup,
15
+ FieldLabel,
16
+ } from "@workspace/ui/components/field";
17
+ import { Input } from "@workspace/ui/components/input";
18
+ import { useState } from "react";
19
+ import { Link, useNavigate } from "@tanstack/react-router";
20
+ import { useForm } from "@tanstack/react-form";
21
+ import { z } from "zod";
22
+
23
+ import { authClient } from "@/lib/auth-client";
24
+
25
+ const loginSchema = z.object({
26
+ email: z.string().email("Invalid email address"),
27
+ password: z.string().min(8, "Password must be at least 8 characters"),
28
+ });
29
+
30
+ export function LoginForm({ className, ...props }: React.ComponentProps<"div">) {
31
+ const [loading, setLoading] = useState(false);
32
+ const [error, setError] = useState<string | null>(null);
33
+ const navigate = useNavigate();
34
+
35
+ const form = useForm({
36
+ defaultValues: {
37
+ email: "",
38
+ password: "",
39
+ },
40
+ validators: {
41
+ onChange: loginSchema,
42
+ },
43
+ onSubmit: async ({ value }) => {
44
+ setLoading(true);
45
+ setError(null);
46
+ const { error: signInError } = await authClient.signIn.email({
47
+ email: value.email,
48
+ password: value.password,
49
+ });
50
+ if (signInError) {
51
+ setError(signInError.message || "Failed to sign in");
52
+ } else {
53
+ navigate({ to: "/dashboard" });
54
+ }
55
+ setLoading(false);
56
+ },
57
+ });
58
+
59
+ return (
60
+ <div className={cn("flex flex-col gap-6", className)} {...props}>
61
+ <Card>
62
+ <CardHeader>
63
+ <CardTitle>Login to your account</CardTitle>
64
+ <CardDescription>Enter your email below to login to your account</CardDescription>
65
+ </CardHeader>
66
+ <CardContent>
67
+ <form
68
+ onSubmit={(e) => {
69
+ e.preventDefault();
70
+ e.stopPropagation();
71
+ form.handleSubmit();
72
+ }}
73
+ >
74
+ <FieldGroup>
75
+ {error && <div className="rounded bg-red-100 p-2 text-sm text-red-600">{error}</div>}
76
+ <form.Field
77
+ name="email"
78
+ children={(field) => {
79
+ const isInvalid =
80
+ field.state.meta.isTouched && field.state.meta.errors.length > 0;
81
+ return (
82
+ <Field data-invalid={isInvalid}>
83
+ <FieldLabel htmlFor={field.name}>Email</FieldLabel>
84
+ <Input
85
+ id={field.name}
86
+ name={field.name}
87
+ type="email"
88
+ placeholder="m@example.com"
89
+ value={field.state.value}
90
+ onBlur={field.handleBlur}
91
+ onChange={(e) => field.handleChange(e.target.value)}
92
+ required
93
+ />
94
+ {isInvalid && <FieldError errors={field.state.meta.errors} />}
95
+ </Field>
96
+ );
97
+ }}
98
+ />
99
+ <form.Field
100
+ name="password"
101
+ children={(field) => {
102
+ const isInvalid =
103
+ field.state.meta.isTouched && field.state.meta.errors.length > 0;
104
+ return (
105
+ <Field data-invalid={isInvalid}>
106
+ <div className="flex items-center">
107
+ <FieldLabel htmlFor={field.name}>Password</FieldLabel>
108
+ <a
109
+ href="#"
110
+ className="ml-auto inline-block text-sm underline-offset-4 hover:underline"
111
+ >
112
+ Forgot your password?
113
+ </a>
114
+ </div>
115
+ <Input
116
+ id={field.name}
117
+ name={field.name}
118
+ type="password"
119
+ value={field.state.value}
120
+ onBlur={field.handleBlur}
121
+ onChange={(e) => field.handleChange(e.target.value)}
122
+ required
123
+ />
124
+ {isInvalid && <FieldError errors={field.state.meta.errors} />}
125
+ </Field>
126
+ );
127
+ }}
128
+ />
129
+ <Field>
130
+ <form.Subscribe
131
+ selector={(state) => [state.canSubmit, state.isSubmitting]}
132
+ children={([canSubmit, isSubmitting]) => (
133
+ <Button type="submit" disabled={!canSubmit || isSubmitting || loading}>
134
+ {isSubmitting || loading ? "Logging in..." : "Login"}
135
+ </Button>
136
+ )}
137
+ />
138
+ <Button variant="outline" type="button" disabled={loading}>
139
+ Login with Google
140
+ </Button>
141
+ <FieldDescription className="text-center">
142
+ Don&apos;t have an account?{" "}
143
+ <Link to="/signup" className="underline">
144
+ Sign up
145
+ </Link>
146
+ </FieldDescription>
147
+ </Field>
148
+ </FieldGroup>
149
+ </form>
150
+ </CardContent>
151
+ </Card>
152
+ </div>
153
+ );
154
+ }
@@ -0,0 +1,201 @@
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, useNavigate } 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
+ const navigate = useNavigate();
39
+
40
+ const form = useForm({
41
+ defaultValues: {
42
+ name: "",
43
+ email: "",
44
+ password: "",
45
+ confirmPassword: "",
46
+ },
47
+ validators: {
48
+ onChange: signupSchema,
49
+ },
50
+ onSubmit: async ({ value }) => {
51
+ setLoading(true);
52
+ setError(null);
53
+ const { error: signUpError } = await authClient.signUp.email({
54
+ email: value.email,
55
+ password: value.password,
56
+ name: value.name,
57
+ });
58
+ if (signUpError) {
59
+ setError(signUpError.message || "Failed to sign up");
60
+ } else {
61
+ navigate({ to: "/dashboard" });
62
+ }
63
+ setLoading(false);
64
+ },
65
+ });
66
+
67
+ return (
68
+ <Card {...props}>
69
+ <CardHeader>
70
+ <CardTitle>Create an account</CardTitle>
71
+ <CardDescription>Enter your information below to create your account</CardDescription>
72
+ </CardHeader>
73
+ <CardContent>
74
+ <form
75
+ onSubmit={(e) => {
76
+ e.preventDefault();
77
+ e.stopPropagation();
78
+ form.handleSubmit();
79
+ }}
80
+ >
81
+ <FieldGroup>
82
+ {error && <div className="rounded bg-red-100 p-2 text-sm text-red-600">{error}</div>}
83
+ <form.Field
84
+ name="name"
85
+ children={(field) => {
86
+ const isInvalid = field.state.meta.isTouched && field.state.meta.errors.length > 0;
87
+ return (
88
+ <Field data-invalid={isInvalid}>
89
+ <FieldLabel htmlFor={field.name}>Full Name</FieldLabel>
90
+ <Input
91
+ id={field.name}
92
+ name={field.name}
93
+ type="text"
94
+ placeholder="John Doe"
95
+ value={field.state.value}
96
+ onBlur={field.handleBlur}
97
+ onChange={(e) => field.handleChange(e.target.value)}
98
+ required
99
+ />
100
+ {isInvalid && <FieldError errors={field.state.meta.errors} />}
101
+ </Field>
102
+ );
103
+ }}
104
+ />
105
+ <form.Field
106
+ name="email"
107
+ children={(field) => {
108
+ const isInvalid = field.state.meta.isTouched && field.state.meta.errors.length > 0;
109
+ return (
110
+ <Field data-invalid={isInvalid}>
111
+ <FieldLabel htmlFor={field.name}>Email</FieldLabel>
112
+ <Input
113
+ id={field.name}
114
+ name={field.name}
115
+ type="email"
116
+ placeholder="m@example.com"
117
+ value={field.state.value}
118
+ onBlur={field.handleBlur}
119
+ onChange={(e) => field.handleChange(e.target.value)}
120
+ required
121
+ />
122
+ <FieldDescription>
123
+ We&apos;ll use this to contact you. We will not share your email with anyone
124
+ else.
125
+ </FieldDescription>
126
+ {isInvalid && <FieldError errors={field.state.meta.errors} />}
127
+ </Field>
128
+ );
129
+ }}
130
+ />
131
+ <form.Field
132
+ name="password"
133
+ children={(field) => {
134
+ const isInvalid = field.state.meta.isTouched && field.state.meta.errors.length > 0;
135
+ return (
136
+ <Field data-invalid={isInvalid}>
137
+ <FieldLabel htmlFor={field.name}>Password</FieldLabel>
138
+ <Input
139
+ id={field.name}
140
+ name={field.name}
141
+ type="password"
142
+ value={field.state.value}
143
+ onBlur={field.handleBlur}
144
+ onChange={(e) => field.handleChange(e.target.value)}
145
+ required
146
+ />
147
+ <FieldDescription>Must be at least 8 characters long.</FieldDescription>
148
+ {isInvalid && <FieldError errors={field.state.meta.errors} />}
149
+ </Field>
150
+ );
151
+ }}
152
+ />
153
+ <form.Field
154
+ name="confirmPassword"
155
+ children={(field) => {
156
+ const isInvalid = field.state.meta.isTouched && field.state.meta.errors.length > 0;
157
+ return (
158
+ <Field data-invalid={isInvalid}>
159
+ <FieldLabel htmlFor={field.name}>Confirm Password</FieldLabel>
160
+ <Input
161
+ id={field.name}
162
+ name={field.name}
163
+ type="password"
164
+ value={field.state.value}
165
+ onBlur={field.handleBlur}
166
+ onChange={(e) => field.handleChange(e.target.value)}
167
+ required
168
+ />
169
+ <FieldDescription>Please confirm your password.</FieldDescription>
170
+ {isInvalid && <FieldError errors={field.state.meta.errors} />}
171
+ </Field>
172
+ );
173
+ }}
174
+ />
175
+ <FieldGroup>
176
+ <Field>
177
+ <form.Subscribe
178
+ selector={(state) => [state.canSubmit, state.isSubmitting]}
179
+ children={([canSubmit, isSubmitting]) => (
180
+ <Button type="submit" disabled={!canSubmit || isSubmitting || loading}>
181
+ {isSubmitting || loading ? "Creating..." : "Create Account"}
182
+ </Button>
183
+ )}
184
+ />
185
+ <Button variant="outline" type="button" disabled={loading}>
186
+ Sign up with Google
187
+ </Button>
188
+ <FieldDescription className="px-6 text-center">
189
+ Already have an account?{" "}
190
+ <Link to="/login" className="underline">
191
+ Sign in
192
+ </Link>
193
+ </FieldDescription>
194
+ </Field>
195
+ </FieldGroup>
196
+ </FieldGroup>
197
+ </form>
198
+ </CardContent>
199
+ </Card>
200
+ );
201
+ }
@@ -0,0 +1,3 @@
1
+ import { createAuthClient } from "better-auth/react";
2
+
3
+ export const authClient = createAuthClient();
@@ -0,0 +1,12 @@
1
+ import { createORPCClient } from "@orpc/client";
2
+ import { RPCLink } from "@orpc/client/fetch";
3
+ import { RouterClient } from "@orpc/server";
4
+ import { router } from "@workspace/orpc/router";
5
+
6
+ const link = new RPCLink({
7
+ // In a real desktop app, this might point to a local or remote server
8
+ // For now, we assume it's the same origin as the platform's API
9
+ url: `https://platform.localhost/api/rpc`,
10
+ });
11
+
12
+ export const orpc: RouterClient<typeof router> = createORPCClient(link)
@@ -1,11 +1,22 @@
1
- import "./assets/main.css";
2
-
3
1
  import { StrictMode } from "react";
4
2
  import { createRoot } from "react-dom/client";
5
- import App from "./App";
3
+ import { RouterProvider, createRouter } from "@tanstack/react-router";
4
+
5
+ // Import the generated route tree
6
+ import { routeTree } from "./routeTree.gen";
7
+
8
+ // Create a new router instance
9
+ const router = createRouter({ routeTree });
10
+
11
+ // Register the router instance for type safety
12
+ declare module "@tanstack/react-router" {
13
+ interface Register {
14
+ router: typeof router;
15
+ }
16
+ }
6
17
 
7
18
  createRoot(document.getElementById("root")!).render(
8
19
  <StrictMode>
9
- <App />
20
+ <RouterProvider router={router} />
10
21
  </StrictMode>,
11
22
  );