create-croissant 0.1.44 → 0.1.45
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/.expo/README.md +13 -0
- package/template/.expo/devices.json +3 -0
- package/template/apps/desktop/.eslintcache +1 -0
- package/template/apps/mobile/app/(tabs)/_layout.tsx +21 -14
- package/template/apps/mobile/app/(tabs)/account.tsx +147 -0
- package/template/apps/mobile/app/(tabs)/explore.tsx +334 -104
- package/template/apps/mobile/app/(tabs)/index.tsx +99 -97
- package/template/apps/mobile/app/_layout.tsx +26 -7
- package/template/apps/mobile/app/index.tsx +136 -0
- package/template/apps/mobile/app/login.tsx +135 -0
- package/template/apps/mobile/app/signup.tsx +144 -0
- package/template/apps/mobile/app.json +3 -3
- package/template/apps/mobile/components/ui/button.tsx +86 -0
- package/template/apps/mobile/components/ui/input.tsx +56 -0
- package/template/apps/mobile/lib/orpc.ts +23 -0
- package/template/apps/mobile/package.json +13 -1
- package/template/apps/mobile/tsconfig.json +4 -1
- package/template/apps/platform/package.json +2 -1
- package/template/apps/platform/src/components/login-form.tsx +5 -4
- package/template/apps/platform/src/components/signup-form.tsx +12 -16
- package/template/apps/platform/src/routes/__root.tsx +6 -2
- package/template/apps/platform/src/routes/_auth/account.tsx +13 -17
- package/template/apps/platform/src/routes/_auth/examples/client-orpc-auth.tsx +2 -6
- package/template/apps/platform/src/routes/_public/examples/client-orpc.tsx +16 -29
- package/template/apps/platform/src/routes/_public/examples/ssr-orpc.tsx +10 -14
- package/template/apps/platform/src/routes/api/auth/$.ts +23 -2
- package/template/apps/platform/src/routes/api/rpc.$.ts +18 -0
- package/template/package.json +2 -2
- package/template/packages/orpc/package.json +7 -1
- package/template/packages/orpc/src/lib/planets.ts +18 -18
- package/template/packages/orpc/src/lib/router.ts +3 -3
- package/template/packages/orpc/src/react/context.tsx +23 -0
- package/template/packages/orpc/src/react/general.ts +29 -0
- package/template/packages/orpc/src/react/index.ts +3 -0
- package/template/packages/orpc/src/react/planets.ts +90 -0
- package/template/tsconfig.json +2 -1
- package/template/apps/mobile/app/modal.tsx +0 -29
|
@@ -18,12 +18,13 @@ import { Input } from "@workspace/ui/components/input";
|
|
|
18
18
|
import { useState } from "react";
|
|
19
19
|
import { Link } from "@tanstack/react-router";
|
|
20
20
|
import { useForm } from "@tanstack/react-form";
|
|
21
|
-
import {
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
|
|
22
23
|
import { authClient } from "@/lib/auth-client";
|
|
23
24
|
|
|
24
|
-
const loginSchema =
|
|
25
|
-
email:
|
|
26
|
-
password:
|
|
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"),
|
|
27
28
|
});
|
|
28
29
|
|
|
29
30
|
export function LoginForm({ className, ...props }: React.ComponentProps<"div">) {
|
|
@@ -17,24 +17,20 @@ import { Input } from "@workspace/ui/components/input";
|
|
|
17
17
|
import { useState } from "react";
|
|
18
18
|
import { Link } from "@tanstack/react-router";
|
|
19
19
|
import { useForm } from "@tanstack/react-form";
|
|
20
|
-
import {
|
|
20
|
+
import { z } from "zod";
|
|
21
21
|
import { authClient } from "@/lib/auth-client";
|
|
22
22
|
|
|
23
|
-
const signupSchema =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
return true;
|
|
37
|
-
});
|
|
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
|
+
});
|
|
38
34
|
|
|
39
35
|
export function SignupForm({ ...props }: React.ComponentProps<typeof Card>) {
|
|
40
36
|
const [loading, setLoading] = useState(false);
|
|
@@ -2,6 +2,8 @@ import { HeadContent, Scripts, createRootRoute } from "@tanstack/react-router";
|
|
|
2
2
|
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
|
+
import { ORPCProvider } from "@workspace/orpc/react";
|
|
6
|
+
import { orpc } from "@/lib/orpc";
|
|
5
7
|
|
|
6
8
|
import appCss from "@workspace/ui/globals.css?url";
|
|
7
9
|
|
|
@@ -40,8 +42,10 @@ function RootDocument({ children }: { children: React.ReactNode }) {
|
|
|
40
42
|
<body>
|
|
41
43
|
<ThemeProvider defaultTheme="system" storageKey="theme">
|
|
42
44
|
<QueryClientProvider client={queryClient}>
|
|
43
|
-
{
|
|
44
|
-
|
|
45
|
+
<ORPCProvider client={orpc}>
|
|
46
|
+
{children}
|
|
47
|
+
<Toaster />
|
|
48
|
+
</ORPCProvider>
|
|
45
49
|
</QueryClientProvider>
|
|
46
50
|
</ThemeProvider>
|
|
47
51
|
<Scripts />
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
|
3
3
|
import { useForm } from "@tanstack/react-form";
|
|
4
|
+
import { z } from "zod";
|
|
4
5
|
import { toast } from "sonner";
|
|
5
6
|
import { Loader2, User } from "lucide-react";
|
|
6
|
-
import { type } from "arktype";
|
|
7
7
|
|
|
8
8
|
import { Button } from "@workspace/ui/components/button";
|
|
9
9
|
import { Input } from "@workspace/ui/components/input";
|
|
@@ -22,24 +22,20 @@ import { Separator } from "@workspace/ui/components/separator";
|
|
|
22
22
|
import { authClient } from "@/lib/auth-client";
|
|
23
23
|
import { getSessionFn } from "@/lib/auth-utils";
|
|
24
24
|
|
|
25
|
-
const profileSchema =
|
|
26
|
-
name:
|
|
25
|
+
const profileSchema = z.object({
|
|
26
|
+
name: z.string().min(1, "Name is required"),
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
const passwordSchema =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
return true;
|
|
42
|
-
});
|
|
29
|
+
const passwordSchema = z
|
|
30
|
+
.object({
|
|
31
|
+
currentPassword: z.string().min(1, "Current password is required"),
|
|
32
|
+
newPassword: z.string().min(8, "New password must be at least 8 characters"),
|
|
33
|
+
confirmPassword: z.string().min(1, "Confirm password is required"),
|
|
34
|
+
})
|
|
35
|
+
.refine((data) => data.newPassword === data.confirmPassword, {
|
|
36
|
+
message: "Passwords do not match",
|
|
37
|
+
path: ["confirmPassword"],
|
|
38
|
+
});
|
|
43
39
|
|
|
44
40
|
export const Route = createFileRoute("/_auth/account")({
|
|
45
41
|
beforeLoad: async () => {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
|
2
|
-
import { useQuery } from "@tanstack/react-query";
|
|
3
2
|
import { getSessionFn } from "@/lib/auth-utils";
|
|
4
|
-
import {
|
|
3
|
+
import { useSecretData } from "@workspace/orpc/react";
|
|
5
4
|
|
|
6
5
|
export const Route = createFileRoute("/_auth/examples/client-orpc-auth")({
|
|
7
6
|
beforeLoad: async () => {
|
|
@@ -22,10 +21,7 @@ export const Route = createFileRoute("/_auth/examples/client-orpc-auth")({
|
|
|
22
21
|
function ClientORPCAuth() {
|
|
23
22
|
const { session } = Route.useRouteContext();
|
|
24
23
|
|
|
25
|
-
const { data, isLoading } =
|
|
26
|
-
queryKey: ["secret-data"],
|
|
27
|
-
queryFn: () => orpc.getSecretData(),
|
|
28
|
-
});
|
|
24
|
+
const { data, isLoading } = useSecretData();
|
|
29
25
|
|
|
30
26
|
return (
|
|
31
27
|
<div className="flex flex-col gap-4">
|
|
@@ -2,9 +2,9 @@ import * as React from "react";
|
|
|
2
2
|
import { createFileRoute } from "@tanstack/react-router";
|
|
3
3
|
import { Check, Pencil, Plus, Trash2 } from "lucide-react";
|
|
4
4
|
import { toast } from "sonner";
|
|
5
|
-
import {
|
|
5
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
6
6
|
import { useForm } from "@tanstack/react-form";
|
|
7
|
-
import {
|
|
7
|
+
import { z } from "zod";
|
|
8
8
|
|
|
9
9
|
import { Button } from "@workspace/ui/components/button";
|
|
10
10
|
import { Input } from "@workspace/ui/components/input";
|
|
@@ -12,25 +12,21 @@ import { Field, FieldError, FieldLabel } from "@workspace/ui/components/field";
|
|
|
12
12
|
|
|
13
13
|
import type { router } from "@workspace/orpc/router";
|
|
14
14
|
import type { InferRouterInputs, InferRouterOutputs } from "@orpc/server";
|
|
15
|
-
import {
|
|
15
|
+
import { usePlanets, useCreatePlanet, useUpdatePlanet, useDeletePlanet } from "@workspace/orpc/react";
|
|
16
16
|
|
|
17
17
|
type Inputs = InferRouterInputs<typeof router>;
|
|
18
18
|
type Outputs = InferRouterOutputs<typeof router>;
|
|
19
19
|
type Planet = Outputs["planets"]["getPlanets"][number];
|
|
20
20
|
|
|
21
|
-
const planetSchema =
|
|
22
|
-
name:
|
|
23
|
-
description:
|
|
24
|
-
distance:
|
|
25
|
-
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
if (isNaN(parseFloat(data.diameter))) {
|
|
31
|
-
ctx.error({ message: "Must be a number", path: ["diameter"] });
|
|
32
|
-
}
|
|
33
|
-
return true;
|
|
21
|
+
const planetSchema = z.object({
|
|
22
|
+
name: z.string().min(1),
|
|
23
|
+
description: z.string(),
|
|
24
|
+
distance: z.string().refine((val) => !isNaN(parseFloat(val)), {
|
|
25
|
+
message: "Must be a number",
|
|
26
|
+
}),
|
|
27
|
+
diameter: z.string().refine((val) => !isNaN(parseFloat(val)), {
|
|
28
|
+
message: "Must be a number",
|
|
29
|
+
}),
|
|
34
30
|
});
|
|
35
31
|
|
|
36
32
|
export const Route = createFileRoute("/_public/examples/client-orpc")({
|
|
@@ -52,10 +48,7 @@ function ClientORPC() {
|
|
|
52
48
|
const queryClient = useQueryClient();
|
|
53
49
|
const [editingId, setEditingId] = React.useState<number | null>(null);
|
|
54
50
|
|
|
55
|
-
const { data: planets = [], isLoading } =
|
|
56
|
-
queryKey: ["planets"],
|
|
57
|
-
queryFn: () => orpc.planets.getPlanets(),
|
|
58
|
-
});
|
|
51
|
+
const { data: planets = [], isLoading } = usePlanets();
|
|
59
52
|
|
|
60
53
|
const form = useForm({
|
|
61
54
|
defaultValues: {
|
|
@@ -94,10 +87,8 @@ function ClientORPC() {
|
|
|
94
87
|
setEditingId(null);
|
|
95
88
|
};
|
|
96
89
|
|
|
97
|
-
const createMutation =
|
|
98
|
-
mutationFn: (input: Inputs["planets"]["createPlanet"]) => orpc.planets.createPlanet(input),
|
|
90
|
+
const createMutation = useCreatePlanet({
|
|
99
91
|
onSuccess: () => {
|
|
100
|
-
queryClient.invalidateQueries({ queryKey: ["planets"] });
|
|
101
92
|
resetForm();
|
|
102
93
|
toast.success("Planet added successfully");
|
|
103
94
|
},
|
|
@@ -106,10 +97,8 @@ function ClientORPC() {
|
|
|
106
97
|
},
|
|
107
98
|
});
|
|
108
99
|
|
|
109
|
-
const updateMutation =
|
|
110
|
-
mutationFn: (input: Inputs["planets"]["updatePlanet"]) => orpc.planets.updatePlanet(input),
|
|
100
|
+
const updateMutation = useUpdatePlanet({
|
|
111
101
|
onSuccess: () => {
|
|
112
|
-
queryClient.invalidateQueries({ queryKey: ["planets"] });
|
|
113
102
|
resetForm();
|
|
114
103
|
toast.success("Planet updated successfully");
|
|
115
104
|
},
|
|
@@ -118,10 +107,8 @@ function ClientORPC() {
|
|
|
118
107
|
},
|
|
119
108
|
});
|
|
120
109
|
|
|
121
|
-
const deleteMutation =
|
|
122
|
-
mutationFn: (input: Inputs["planets"]["deletePlanet"]) => orpc.planets.deletePlanet(input),
|
|
110
|
+
const deleteMutation = useDeletePlanet({
|
|
123
111
|
onSuccess: () => {
|
|
124
|
-
queryClient.invalidateQueries({ queryKey: ["planets"] });
|
|
125
112
|
toast.success("Planet deleted successfully");
|
|
126
113
|
},
|
|
127
114
|
onError: (err) => {
|
|
@@ -4,7 +4,7 @@ import { createServerFn } from "@tanstack/react-start";
|
|
|
4
4
|
import { Check, Pencil, Plus, Trash2 } from "lucide-react";
|
|
5
5
|
import { toast } from "sonner";
|
|
6
6
|
import { useForm } from "@tanstack/react-form";
|
|
7
|
-
import {
|
|
7
|
+
import { z } from "zod";
|
|
8
8
|
|
|
9
9
|
import { Button } from "@workspace/ui/components/button";
|
|
10
10
|
import { Input } from "@workspace/ui/components/input";
|
|
@@ -17,19 +17,15 @@ import { orpc } from "@/lib/orpc";
|
|
|
17
17
|
type Outputs = InferRouterOutputs<typeof router>;
|
|
18
18
|
type Planet = Outputs["planets"]["getPlanets"][number];
|
|
19
19
|
|
|
20
|
-
const planetSchema =
|
|
21
|
-
name:
|
|
22
|
-
description:
|
|
23
|
-
distance:
|
|
24
|
-
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
if (isNaN(parseFloat(data.diameter))) {
|
|
30
|
-
ctx.error({ message: "Must be a number", path: ["diameter"] });
|
|
31
|
-
}
|
|
32
|
-
return true;
|
|
20
|
+
const planetSchema = z.object({
|
|
21
|
+
name: z.string().min(1),
|
|
22
|
+
description: z.string(),
|
|
23
|
+
distance: z.string().refine((val) => !isNaN(parseFloat(val)), {
|
|
24
|
+
message: "Must be a number",
|
|
25
|
+
}),
|
|
26
|
+
diameter: z.string().refine((val) => !isNaN(parseFloat(val)), {
|
|
27
|
+
message: "Must be a number",
|
|
28
|
+
}),
|
|
33
29
|
});
|
|
34
30
|
|
|
35
31
|
const getPlanets = createServerFn({ method: "GET" }).handler(async () => {
|
|
@@ -1,14 +1,35 @@
|
|
|
1
1
|
import { auth } from "@workspace/auth/lib/auth";
|
|
2
2
|
import { createFileRoute } from "@tanstack/react-router";
|
|
3
3
|
|
|
4
|
+
const CORS_HEADERS = {
|
|
5
|
+
"Access-Control-Allow-Origin": "http://localhost:8081",
|
|
6
|
+
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
7
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
8
|
+
"Access-Control-Allow-Credentials": "true",
|
|
9
|
+
};
|
|
10
|
+
|
|
4
11
|
export const Route = createFileRoute("/api/auth/$")({
|
|
5
12
|
server: {
|
|
6
13
|
handlers: {
|
|
7
14
|
GET: async ({ request }: { request: Request }) => {
|
|
8
|
-
|
|
15
|
+
const response = await auth.handler(request);
|
|
16
|
+
Object.entries(CORS_HEADERS).forEach(([key, value]) => {
|
|
17
|
+
response.headers.set(key, value);
|
|
18
|
+
});
|
|
19
|
+
return response;
|
|
9
20
|
},
|
|
10
21
|
POST: async ({ request }: { request: Request }) => {
|
|
11
|
-
|
|
22
|
+
const response = await auth.handler(request);
|
|
23
|
+
Object.entries(CORS_HEADERS).forEach(([key, value]) => {
|
|
24
|
+
response.headers.set(key, value);
|
|
25
|
+
});
|
|
26
|
+
return response;
|
|
27
|
+
},
|
|
28
|
+
OPTIONS: async () => {
|
|
29
|
+
return new Response(null, {
|
|
30
|
+
status: 204,
|
|
31
|
+
headers: CORS_HEADERS,
|
|
32
|
+
});
|
|
12
33
|
},
|
|
13
34
|
},
|
|
14
35
|
},
|
|
@@ -27,8 +27,26 @@ export const Route = createFileRoute("/api/rpc/$")({
|
|
|
27
27
|
},
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
+
if (response) {
|
|
31
|
+
response.headers.set("Access-Control-Allow-Origin", "http://localhost:8081");
|
|
32
|
+
response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
|
33
|
+
response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
34
|
+
response.headers.set("Access-Control-Allow-Credentials", "true");
|
|
35
|
+
}
|
|
36
|
+
|
|
30
37
|
return response ?? new Response("Not Found", { status: 404 });
|
|
31
38
|
},
|
|
39
|
+
OPTIONS: async () => {
|
|
40
|
+
return new Response(null, {
|
|
41
|
+
status: 204,
|
|
42
|
+
headers: {
|
|
43
|
+
"Access-Control-Allow-Origin": "http://localhost:8081",
|
|
44
|
+
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
45
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
46
|
+
"Access-Control-Allow-Credentials": "true",
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
},
|
|
32
50
|
},
|
|
33
51
|
},
|
|
34
52
|
});
|
package/template/package.json
CHANGED
|
@@ -31,13 +31,13 @@
|
|
|
31
31
|
"@better-auth/core": "^1.6.9",
|
|
32
32
|
"@types/react": "^19.2.7",
|
|
33
33
|
"@types/react-dom": "^19.2.3",
|
|
34
|
-
"arktype": "^2.2.0",
|
|
35
34
|
"husky": "^9.1.7",
|
|
36
35
|
"oxfmt": "^0.46.0",
|
|
37
36
|
"oxlint": "^1.61.0",
|
|
38
37
|
"turbo": "^2.9.6",
|
|
39
38
|
"typescript": "6.0.3",
|
|
40
|
-
"vite": "^8.0.10"
|
|
39
|
+
"vite": "^8.0.10",
|
|
40
|
+
"zod": "4.3.6"
|
|
41
41
|
},
|
|
42
42
|
"overrides": {
|
|
43
43
|
"@noble/ciphers": "2.2.0",
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
|
-
"./router": "./src/lib/router.ts"
|
|
7
|
+
"./router": "./src/lib/router.ts",
|
|
8
|
+
"./react": "./src/react/index.ts"
|
|
8
9
|
},
|
|
9
10
|
"scripts": {
|
|
10
11
|
"typecheck": "tsc --noEmit"
|
|
@@ -12,9 +13,14 @@
|
|
|
12
13
|
"dependencies": {
|
|
13
14
|
"@orpc/client": "^1.14.0",
|
|
14
15
|
"@orpc/server": "^1.14.0",
|
|
16
|
+
"@orpc/tanstack-query": "^1.14.0",
|
|
15
17
|
"@workspace/auth": "*",
|
|
16
18
|
"@workspace/db": "*"
|
|
17
19
|
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"@tanstack/react-query": "^5.100.5",
|
|
22
|
+
"react": ">=18"
|
|
23
|
+
},
|
|
18
24
|
"devDependencies": {
|
|
19
25
|
"@types/node": "^22.19.1",
|
|
20
26
|
"@workspace/config-typescript": "*"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ORPCError, os } from "@orpc/server";
|
|
2
2
|
import { db, schema } from "@workspace/db";
|
|
3
3
|
import { eq } from "drizzle-orm";
|
|
4
|
-
import {
|
|
4
|
+
import { z } from "zod";
|
|
5
5
|
import type { RPCContext } from "./router";
|
|
6
6
|
|
|
7
7
|
const { planets } = schema;
|
|
@@ -16,13 +16,13 @@ export const planetRouter = o.router({
|
|
|
16
16
|
|
|
17
17
|
createPlanet: o
|
|
18
18
|
.input(
|
|
19
|
-
|
|
20
|
-
name:
|
|
21
|
-
|
|
22
|
-
distanceFromSun:
|
|
23
|
-
diameter:
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
z.object({
|
|
20
|
+
name: z.string().min(1),
|
|
21
|
+
description: z.string().optional(),
|
|
22
|
+
distanceFromSun: z.number(),
|
|
23
|
+
diameter: z.number(),
|
|
24
|
+
hasRings: z.boolean().optional(),
|
|
25
|
+
atmosphere: z.string().optional(),
|
|
26
26
|
}),
|
|
27
27
|
)
|
|
28
28
|
.handler(async ({ input }) => {
|
|
@@ -32,14 +32,14 @@ export const planetRouter = o.router({
|
|
|
32
32
|
|
|
33
33
|
updatePlanet: o
|
|
34
34
|
.input(
|
|
35
|
-
|
|
36
|
-
id:
|
|
37
|
-
name:
|
|
38
|
-
|
|
39
|
-
distanceFromSun:
|
|
40
|
-
diameter:
|
|
41
|
-
hasRings:
|
|
42
|
-
|
|
35
|
+
z.object({
|
|
36
|
+
id: z.number(),
|
|
37
|
+
name: z.string().min(1),
|
|
38
|
+
description: z.string().optional(),
|
|
39
|
+
distanceFromSun: z.number(),
|
|
40
|
+
diameter: z.number(),
|
|
41
|
+
hasRings: z.boolean(),
|
|
42
|
+
atmosphere: z.string().optional(),
|
|
43
43
|
}),
|
|
44
44
|
)
|
|
45
45
|
.handler(async ({ input }) => {
|
|
@@ -59,8 +59,8 @@ export const planetRouter = o.router({
|
|
|
59
59
|
|
|
60
60
|
deletePlanet: o
|
|
61
61
|
.input(
|
|
62
|
-
|
|
63
|
-
id:
|
|
62
|
+
z.object({
|
|
63
|
+
id: z.number(),
|
|
64
64
|
}),
|
|
65
65
|
)
|
|
66
66
|
.handler(async ({ input }) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ORPCError, os } from "@orpc/server";
|
|
2
|
-
import {
|
|
2
|
+
import { z } from "zod";
|
|
3
3
|
import { planetRouter } from "./planets";
|
|
4
4
|
import type { Session } from "@workspace/auth/lib/auth";
|
|
5
5
|
|
|
@@ -14,8 +14,8 @@ export const router = o.router({
|
|
|
14
14
|
|
|
15
15
|
hello: o
|
|
16
16
|
.input(
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
z.object({
|
|
18
|
+
name: z.string().optional(),
|
|
19
19
|
}),
|
|
20
20
|
)
|
|
21
21
|
.handler(({ input }) => {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { RouterClient } from "@orpc/server";
|
|
3
|
+
import type { AppRouter } from "../lib/router";
|
|
4
|
+
|
|
5
|
+
const ORPCContext = React.createContext<RouterClient<AppRouter> | null>(null);
|
|
6
|
+
|
|
7
|
+
export function ORPCProvider({
|
|
8
|
+
client,
|
|
9
|
+
children,
|
|
10
|
+
}: {
|
|
11
|
+
client: RouterClient<AppRouter>;
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
}) {
|
|
14
|
+
return <ORPCContext.Provider value={client}>{children}</ORPCContext.Provider>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function useORPC() {
|
|
18
|
+
const context = React.useContext(ORPCContext);
|
|
19
|
+
if (!context) {
|
|
20
|
+
throw new Error("useORPC must be used within an ORPCProvider");
|
|
21
|
+
}
|
|
22
|
+
return context;
|
|
23
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useQuery, type UseQueryOptions } from "@tanstack/react-query";
|
|
2
|
+
import type { AppRouter } from "../lib/router";
|
|
3
|
+
import type { InferRouterOutputs } from "@orpc/server";
|
|
4
|
+
import { useORPC } from "./context";
|
|
5
|
+
|
|
6
|
+
type Outputs = InferRouterOutputs<AppRouter>;
|
|
7
|
+
|
|
8
|
+
export function useSecretData(
|
|
9
|
+
options?: Omit<UseQueryOptions<Outputs["getSecretData"]>, "queryKey" | "queryFn">,
|
|
10
|
+
) {
|
|
11
|
+
const orpc = useORPC();
|
|
12
|
+
return useQuery({
|
|
13
|
+
...options,
|
|
14
|
+
queryKey: ["secret-data"],
|
|
15
|
+
queryFn: () => orpc.getSecretData(),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function useHello(
|
|
20
|
+
name?: string,
|
|
21
|
+
options?: Omit<UseQueryOptions<Outputs["hello"]>, "queryKey" | "queryFn">,
|
|
22
|
+
) {
|
|
23
|
+
const orpc = useORPC();
|
|
24
|
+
return useQuery({
|
|
25
|
+
...options,
|
|
26
|
+
queryKey: ["hello", name],
|
|
27
|
+
queryFn: () => orpc.hello({ name }),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useMutation,
|
|
3
|
+
useQuery,
|
|
4
|
+
useQueryClient,
|
|
5
|
+
type UseMutationOptions,
|
|
6
|
+
type UseQueryOptions,
|
|
7
|
+
} from "@tanstack/react-query";
|
|
8
|
+
import type { AppRouter } from "../lib/router";
|
|
9
|
+
import type { InferRouterInputs, InferRouterOutputs } from "@orpc/server";
|
|
10
|
+
import { useORPC } from "./context";
|
|
11
|
+
|
|
12
|
+
type Inputs = InferRouterInputs<AppRouter>;
|
|
13
|
+
type Outputs = InferRouterOutputs<AppRouter>;
|
|
14
|
+
|
|
15
|
+
export function usePlanets(
|
|
16
|
+
options?: Omit<UseQueryOptions<Outputs["planets"]["getPlanets"]>, "queryKey" | "queryFn">,
|
|
17
|
+
) {
|
|
18
|
+
const orpc = useORPC();
|
|
19
|
+
return useQuery({
|
|
20
|
+
...options,
|
|
21
|
+
queryKey: ["planets"],
|
|
22
|
+
queryFn: () => orpc.planets.getPlanets(),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function useCreatePlanet(
|
|
27
|
+
options?: Omit<
|
|
28
|
+
UseMutationOptions<
|
|
29
|
+
Outputs["planets"]["createPlanet"],
|
|
30
|
+
Error,
|
|
31
|
+
Inputs["planets"]["createPlanet"]
|
|
32
|
+
>,
|
|
33
|
+
"mutationFn"
|
|
34
|
+
>,
|
|
35
|
+
) {
|
|
36
|
+
const orpc = useORPC();
|
|
37
|
+
const queryClient = useQueryClient();
|
|
38
|
+
return useMutation({
|
|
39
|
+
...options,
|
|
40
|
+
mutationFn: (input: Inputs["planets"]["createPlanet"]) => orpc.planets.createPlanet(input),
|
|
41
|
+
onSuccess: (data, variables, context, extra) => {
|
|
42
|
+
queryClient.invalidateQueries({ queryKey: ["planets"] });
|
|
43
|
+
options?.onSuccess?.(data, variables, context, extra);
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function useUpdatePlanet(
|
|
49
|
+
options?: Omit<
|
|
50
|
+
UseMutationOptions<
|
|
51
|
+
Outputs["planets"]["updatePlanet"],
|
|
52
|
+
Error,
|
|
53
|
+
Inputs["planets"]["updatePlanet"]
|
|
54
|
+
>,
|
|
55
|
+
"mutationFn"
|
|
56
|
+
>,
|
|
57
|
+
) {
|
|
58
|
+
const orpc = useORPC();
|
|
59
|
+
const queryClient = useQueryClient();
|
|
60
|
+
return useMutation({
|
|
61
|
+
...options,
|
|
62
|
+
mutationFn: (input: Inputs["planets"]["updatePlanet"]) => orpc.planets.updatePlanet(input),
|
|
63
|
+
onSuccess: (data, variables, context, extra) => {
|
|
64
|
+
queryClient.invalidateQueries({ queryKey: ["planets"] });
|
|
65
|
+
options?.onSuccess?.(data, variables, context, extra);
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function useDeletePlanet(
|
|
71
|
+
options?: Omit<
|
|
72
|
+
UseMutationOptions<
|
|
73
|
+
Outputs["planets"]["deletePlanet"],
|
|
74
|
+
Error,
|
|
75
|
+
Inputs["planets"]["deletePlanet"]
|
|
76
|
+
>,
|
|
77
|
+
"mutationFn"
|
|
78
|
+
>,
|
|
79
|
+
) {
|
|
80
|
+
const orpc = useORPC();
|
|
81
|
+
const queryClient = useQueryClient();
|
|
82
|
+
return useMutation({
|
|
83
|
+
...options,
|
|
84
|
+
mutationFn: (input: Inputs["planets"]["deletePlanet"]) => orpc.planets.deletePlanet(input),
|
|
85
|
+
onSuccess: (data, variables, context, extra) => {
|
|
86
|
+
queryClient.invalidateQueries({ queryKey: ["planets"] });
|
|
87
|
+
options?.onSuccess?.(data, variables, context, extra);
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
package/template/tsconfig.json
CHANGED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { Link } from "expo-router";
|
|
2
|
-
import { StyleSheet } from "react-native";
|
|
3
|
-
|
|
4
|
-
import { ThemedText } from "@/components/themed-text";
|
|
5
|
-
import { ThemedView } from "@/components/themed-view";
|
|
6
|
-
|
|
7
|
-
export default function ModalScreen() {
|
|
8
|
-
return (
|
|
9
|
-
<ThemedView style={styles.container}>
|
|
10
|
-
<ThemedText type="title">This is a modal</ThemedText>
|
|
11
|
-
<Link href="/" dismissTo style={styles.link}>
|
|
12
|
-
<ThemedText type="link">Go to home screen</ThemedText>
|
|
13
|
-
</Link>
|
|
14
|
-
</ThemedView>
|
|
15
|
-
);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const styles = StyleSheet.create({
|
|
19
|
-
container: {
|
|
20
|
-
flex: 1,
|
|
21
|
-
alignItems: "center",
|
|
22
|
-
justifyContent: "center",
|
|
23
|
-
padding: 20,
|
|
24
|
-
},
|
|
25
|
-
link: {
|
|
26
|
-
marginTop: 15,
|
|
27
|
-
paddingVertical: 15,
|
|
28
|
-
},
|
|
29
|
-
});
|