create-croissant 0.1.23 → 0.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/apps/web/src/components/app-sidebar.tsx +8 -8
- package/template/apps/web/src/routes/__root.tsx +1 -1
- package/template/apps/web/src/routes/_auth/examples/ssr-orpc-auth.tsx +11 -8
- package/template/apps/web/src/routes/_public/examples/client-orpc.tsx +11 -0
- package/template/apps/web/src/routes/_public/examples/isr.tsx +27 -11
- package/template/apps/web/src/routes/_public/examples/ssr-orpc.tsx +19 -4
- package/template/apps/web/src/routes/_public/index.tsx +39 -11
- package/template/apps/web/src/routes/_public/login.tsx +15 -0
- package/template/apps/web/src/routes/_public/signup.tsx +11 -0
- package/template/packages/ui/src/components/sidebar.tsx +11 -1
package/package.json
CHANGED
|
@@ -114,7 +114,7 @@ export function AppSidebar({ items = authNavItems, ...props }: AppSidebarProps)
|
|
|
114
114
|
<Sidebar {...props}>
|
|
115
115
|
<SidebarHeader>
|
|
116
116
|
<div className="flex items-center gap-2 px-4 py-2">
|
|
117
|
-
<span className="font-bold">
|
|
117
|
+
<span className="font-bold">Croissant Stack</span>
|
|
118
118
|
</div>
|
|
119
119
|
</SidebarHeader>
|
|
120
120
|
<SidebarContent>
|
|
@@ -163,25 +163,25 @@ export function AppSidebar({ items = authNavItems, ...props }: AppSidebarProps)
|
|
|
163
163
|
<span className="truncate text-xs">{user.email}</span>
|
|
164
164
|
</div>
|
|
165
165
|
<div className="flex items-center gap-1 ml-auto">
|
|
166
|
-
<
|
|
167
|
-
to="/account"
|
|
168
|
-
className="p-1 rounded hover:bg-sidebar-accent-foreground/10"
|
|
166
|
+
<SidebarMenuButton
|
|
167
|
+
render={<Link to="/account" />}
|
|
168
|
+
className="p-1 h-auto w-auto rounded hover:bg-sidebar-accent-foreground/10"
|
|
169
169
|
title="Account Settings"
|
|
170
170
|
>
|
|
171
171
|
<Settings className="h-4 w-4" />
|
|
172
|
-
</
|
|
173
|
-
<
|
|
172
|
+
</SidebarMenuButton>
|
|
173
|
+
<SidebarMenuButton
|
|
174
174
|
onClick={async (e) => {
|
|
175
175
|
e.preventDefault()
|
|
176
176
|
e.stopPropagation()
|
|
177
177
|
await authClient.signOut()
|
|
178
178
|
window.location.reload()
|
|
179
179
|
}}
|
|
180
|
-
className="p-1 rounded hover:bg-sidebar-accent-foreground/10"
|
|
180
|
+
className="p-1 h-auto w-auto rounded hover:bg-sidebar-accent-foreground/10"
|
|
181
181
|
title="Sign Out"
|
|
182
182
|
>
|
|
183
183
|
<LogOut className="h-4 w-4" />
|
|
184
|
-
</
|
|
184
|
+
</SidebarMenuButton>
|
|
185
185
|
</div>
|
|
186
186
|
</div>
|
|
187
187
|
</SidebarMenuButton>
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { createFileRoute, redirect } from "@tanstack/react-router"
|
|
2
|
+
import { createServerFn } from "@tanstack/react-start"
|
|
2
3
|
import { getSessionFn } from "@/lib/auth-utils"
|
|
3
4
|
import { orpc } from "@/lib/orpc"
|
|
4
5
|
|
|
6
|
+
const getSecretData = createServerFn({ method: "GET" }).handler(async () => {
|
|
7
|
+
try {
|
|
8
|
+
const secretData = await orpc.getSecretData()
|
|
9
|
+
return { secretData }
|
|
10
|
+
} catch (err) {
|
|
11
|
+
return { secretData: null, error: "Failed to fetch secret data" }
|
|
12
|
+
}
|
|
13
|
+
})
|
|
14
|
+
|
|
5
15
|
export const Route = createFileRoute("/_auth/examples/ssr-orpc-auth")({
|
|
6
16
|
beforeLoad: async () => {
|
|
7
17
|
const session = await getSessionFn()
|
|
@@ -15,14 +25,7 @@ export const Route = createFileRoute("/_auth/examples/ssr-orpc-auth")({
|
|
|
15
25
|
}
|
|
16
26
|
return { session }
|
|
17
27
|
},
|
|
18
|
-
loader:
|
|
19
|
-
try {
|
|
20
|
-
const secretData = await orpc.getSecretData()
|
|
21
|
-
return { secretData }
|
|
22
|
-
} catch (err) {
|
|
23
|
-
return { secretData: null, error: "Failed to fetch secret data" }
|
|
24
|
-
}
|
|
25
|
-
},
|
|
28
|
+
loader: () => getSecretData(),
|
|
26
29
|
component: SSRORPCAuth,
|
|
27
30
|
})
|
|
28
31
|
|
|
@@ -30,6 +30,17 @@ const planetSchema = z.object({
|
|
|
30
30
|
})
|
|
31
31
|
|
|
32
32
|
export const Route = createFileRoute("/_public/examples/client-orpc")({
|
|
33
|
+
head: () => ({
|
|
34
|
+
meta: [
|
|
35
|
+
{
|
|
36
|
+
title: "Client + oRPC Example | Croissant Stack",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "description",
|
|
40
|
+
content: "Explore client-side data fetching and mutations with oRPC in Croissant Stack.",
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
}),
|
|
33
44
|
component: ClientORPC,
|
|
34
45
|
})
|
|
35
46
|
|
|
@@ -1,18 +1,34 @@
|
|
|
1
1
|
import { createFileRoute } from "@tanstack/react-router"
|
|
2
|
+
import { createServerFn } from "@tanstack/react-start"
|
|
2
3
|
import { orpc } from "@/lib/orpc"
|
|
3
4
|
|
|
5
|
+
const getISRData = createServerFn({ method: "GET" }).handler(async () => {
|
|
6
|
+
// In a real ISR scenario, this would be cached on the server
|
|
7
|
+
// For this example, we'll fetch planets via oRPC
|
|
8
|
+
const planets = await orpc.planets.getPlanets()
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
generatedAt: new Date().toISOString(),
|
|
12
|
+
planets,
|
|
13
|
+
message:
|
|
14
|
+
"This page is an example of ISR. In a production build with proper configuration, this data would be cached and updated in the background.",
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
|
|
4
18
|
export const Route = createFileRoute("/_public/examples/isr")({
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
head: () => ({
|
|
20
|
+
meta: [
|
|
21
|
+
{
|
|
22
|
+
title: "Incremental Static Regeneration (ISR) Example | Croissant Stack",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "description",
|
|
26
|
+
content:
|
|
27
|
+
"Experience high-performance page loads with ISR in Croissant Stack.",
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
}),
|
|
31
|
+
loader: () => getISRData(),
|
|
16
32
|
headers: () => ({
|
|
17
33
|
// Cache at CDN for 10 seconds, allow stale content for up to 1 minute
|
|
18
34
|
"Cache-Control": "public, max-age=10, s-maxage=10, stale-while-revalidate=60",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from "react"
|
|
2
2
|
import { createFileRoute, useRouter } from "@tanstack/react-router"
|
|
3
|
+
import { createServerFn } from "@tanstack/react-start"
|
|
3
4
|
import { Check, Pencil, Plus, Trash2 } from "lucide-react"
|
|
4
5
|
import { toast } from "sonner"
|
|
5
6
|
import { useForm } from "@tanstack/react-form"
|
|
@@ -27,11 +28,25 @@ const planetSchema = z.object({
|
|
|
27
28
|
diameter: z.string().refine((val) => !isNaN(parseFloat(val)), "Must be a number"),
|
|
28
29
|
})
|
|
29
30
|
|
|
31
|
+
const getPlanets = createServerFn({ method: "GET" }).handler(async () => {
|
|
32
|
+
const planets = await orpc.planets.getPlanets()
|
|
33
|
+
return { planets }
|
|
34
|
+
})
|
|
35
|
+
|
|
30
36
|
export const Route = createFileRoute("/_public/examples/ssr-orpc")({
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
head: () => ({
|
|
38
|
+
meta: [
|
|
39
|
+
{
|
|
40
|
+
title: "SSR + oRPC Example | Croissant Stack",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "description",
|
|
44
|
+
content:
|
|
45
|
+
"Learn how to use Server-Side Rendering (SSR) with oRPC in Croissant Stack.",
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
}),
|
|
49
|
+
loader: () => getPlanets(),
|
|
35
50
|
component: SSRORPC,
|
|
36
51
|
})
|
|
37
52
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Link, createFileRoute } from "@tanstack/react-router"
|
|
2
|
+
import { createServerFn } from "@tanstack/react-start"
|
|
2
3
|
import { Button } from "@workspace/ui/components/button"
|
|
3
4
|
import type { router } from "@workspace/orpc/router"
|
|
4
5
|
import type { InferRouterOutputs } from "@orpc/server"
|
|
@@ -7,19 +8,46 @@ import { orpc } from "@/lib/orpc"
|
|
|
7
8
|
type Outputs = InferRouterOutputs<typeof router>
|
|
8
9
|
type Planet = Outputs["planets"]["getPlanets"][number]
|
|
9
10
|
|
|
11
|
+
const getHomeData = createServerFn({ method: "GET" }).handler(async () => {
|
|
12
|
+
const [helloRes, planets] = await Promise.all([
|
|
13
|
+
orpc.hello({ name: "Croissant Stack" }),
|
|
14
|
+
orpc.planets.getPlanets(),
|
|
15
|
+
])
|
|
16
|
+
return {
|
|
17
|
+
message: helloRes.message,
|
|
18
|
+
planets,
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
10
22
|
export const Route = createFileRoute("/_public/")({
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
head: () => ({
|
|
24
|
+
meta: [
|
|
25
|
+
{
|
|
26
|
+
title: "Croissant Stack - The Ultimate TanStack Starter",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "description",
|
|
30
|
+
content:
|
|
31
|
+
"Build full-stack applications faster with Croissant Stack. Featuring TanStack Start, oRPC, and Better Auth.",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
property: "og:title",
|
|
35
|
+
content: "Croissant Stack - The Ultimate TanStack Starter",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
property: "og:description",
|
|
39
|
+
content: "Build full-stack applications faster with Croissant Stack.",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
property: "og:image",
|
|
43
|
+
content: "/og-image.png",
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
}),
|
|
47
|
+
loader: () => getHomeData(),
|
|
21
48
|
headers: () => ({
|
|
22
|
-
"Cache-Control":
|
|
49
|
+
"Cache-Control":
|
|
50
|
+
"public, max-age=10, s-maxage=10, stale-while-revalidate=60",
|
|
23
51
|
}),
|
|
24
52
|
component: App,
|
|
25
53
|
})
|
|
@@ -2,6 +2,21 @@ import { createFileRoute } from "@tanstack/react-router"
|
|
|
2
2
|
import { LoginForm } from "@/components/login-form"
|
|
3
3
|
|
|
4
4
|
export const Route = createFileRoute("/_public/login")({
|
|
5
|
+
head: () => ({
|
|
6
|
+
meta: [
|
|
7
|
+
{
|
|
8
|
+
title: "Sign In | Croissant Stack",
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
name: "description",
|
|
12
|
+
content: "Sign in to your Croissant Stack account.",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: "robots",
|
|
16
|
+
content: "noindex, follow",
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
}),
|
|
5
20
|
headers: () => ({
|
|
6
21
|
"Cache-Control": "public, max-age=3600, s-maxage=3600, stale-while-revalidate=86400",
|
|
7
22
|
}),
|
|
@@ -2,6 +2,17 @@ import { createFileRoute } from "@tanstack/react-router"
|
|
|
2
2
|
import { SignupForm } from "@/components/signup-form"
|
|
3
3
|
|
|
4
4
|
export const Route = createFileRoute("/_public/signup")({
|
|
5
|
+
head: () => ({
|
|
6
|
+
meta: [
|
|
7
|
+
{
|
|
8
|
+
title: "Create an Account | Croissant Stack",
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
name: "description",
|
|
12
|
+
content: "Join Croissant Stack today. Create an account to start building with the best stack.",
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
}),
|
|
5
16
|
headers: () => ({
|
|
6
17
|
"Cache-Control": "public, max-age=3600, s-maxage=3600, stale-while-revalidate=86400",
|
|
7
18
|
}),
|
|
@@ -506,18 +506,28 @@ function SidebarMenuButton({
|
|
|
506
506
|
size = "default",
|
|
507
507
|
tooltip,
|
|
508
508
|
className,
|
|
509
|
+
onClick,
|
|
509
510
|
...props
|
|
510
511
|
}: useRender.ComponentProps<"button"> &
|
|
511
512
|
React.ComponentProps<"button"> & {
|
|
512
513
|
isActive?: boolean
|
|
513
514
|
tooltip?: string | React.ComponentProps<typeof TooltipContent>
|
|
514
515
|
} & VariantProps<typeof sidebarMenuButtonVariants>) {
|
|
515
|
-
const { isMobile, state } = useSidebar()
|
|
516
|
+
const { isMobile, state, setOpenMobile } = useSidebar()
|
|
517
|
+
|
|
518
|
+
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
519
|
+
if (isMobile) {
|
|
520
|
+
setOpenMobile(false)
|
|
521
|
+
}
|
|
522
|
+
onClick?.(event)
|
|
523
|
+
}
|
|
524
|
+
|
|
516
525
|
const comp = useRender({
|
|
517
526
|
defaultTagName: "button",
|
|
518
527
|
props: mergeProps<"button">(
|
|
519
528
|
{
|
|
520
529
|
className: cn(sidebarMenuButtonVariants({ variant, size }), className),
|
|
530
|
+
onClick: handleClick,
|
|
521
531
|
},
|
|
522
532
|
props
|
|
523
533
|
),
|