bosia 0.2.2 → 0.3.0
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/README.md +39 -39
- package/package.json +56 -53
- package/src/ambient.d.ts +31 -0
- package/src/cli/add.ts +120 -114
- package/src/cli/build.ts +10 -10
- package/src/cli/create.ts +142 -137
- package/src/cli/dev.ts +8 -8
- package/src/cli/feat.ts +291 -132
- package/src/cli/index.ts +51 -42
- package/src/cli/registry.ts +136 -115
- package/src/cli/start.ts +17 -17
- package/src/cli/test.ts +25 -0
- package/src/core/build.ts +72 -56
- package/src/core/client/App.svelte +177 -153
- package/src/core/client/appState.svelte.ts +57 -0
- package/src/core/client/enhance.ts +112 -0
- package/src/core/client/hydrate.ts +97 -65
- package/src/core/client/prefetch.ts +101 -94
- package/src/core/client/router.svelte.ts +64 -51
- package/src/core/cookies.ts +70 -66
- package/src/core/cors.ts +44 -35
- package/src/core/csrf.ts +38 -38
- package/src/core/dedup.ts +17 -17
- package/src/core/dev.ts +165 -168
- package/src/core/env.ts +155 -128
- package/src/core/envCodegen.ts +73 -73
- package/src/core/errors.ts +48 -49
- package/src/core/hooks.ts +50 -50
- package/src/core/html.ts +192 -139
- package/src/core/matcher.ts +130 -121
- package/src/core/paths.ts +8 -10
- package/src/core/plugin.ts +113 -107
- package/src/core/prerender.ts +191 -118
- package/src/core/renderer.ts +359 -265
- package/src/core/routeFile.ts +140 -127
- package/src/core/routeTypes.ts +144 -83
- package/src/core/scanner.ts +125 -95
- package/src/core/server.ts +543 -370
- package/src/core/types.ts +25 -20
- package/src/lib/client.ts +12 -0
- package/src/lib/index.ts +8 -8
- package/src/lib/utils.ts +44 -30
- package/templates/default/.prettierignore +5 -0
- package/templates/default/.prettierrc.json +9 -0
- package/templates/default/README.md +5 -5
- package/templates/default/package.json +22 -18
- package/templates/default/src/app.css +80 -80
- package/templates/default/src/app.d.ts +3 -3
- package/templates/default/src/routes/+error.svelte +7 -10
- package/templates/default/src/routes/+layout.svelte +2 -2
- package/templates/default/src/routes/+page.svelte +31 -29
- package/templates/default/src/routes/about/+page.svelte +3 -3
- package/templates/default/tsconfig.json +20 -20
- package/templates/demo/.prettierignore +5 -0
- package/templates/demo/.prettierrc.json +9 -0
- package/templates/demo/README.md +9 -9
- package/templates/demo/package.json +22 -17
- package/templates/demo/src/app.css +80 -80
- package/templates/demo/src/app.d.ts +3 -3
- package/templates/demo/src/hooks.server.ts +9 -9
- package/templates/demo/src/routes/(public)/+layout.svelte +45 -23
- package/templates/demo/src/routes/(public)/+page.svelte +96 -67
- package/templates/demo/src/routes/(public)/about/+page.svelte +13 -25
- package/templates/demo/src/routes/(public)/all/[...catchall]/+page.svelte +24 -28
- package/templates/demo/src/routes/(public)/blog/+page.svelte +55 -46
- package/templates/demo/src/routes/(public)/blog/[slug]/+page.server.ts +36 -38
- package/templates/demo/src/routes/(public)/blog/[slug]/+page.svelte +60 -42
- package/templates/demo/src/routes/+error.svelte +10 -7
- package/templates/demo/src/routes/+layout.server.ts +4 -4
- package/templates/demo/src/routes/+layout.svelte +2 -2
- package/templates/demo/src/routes/actions-test/+page.server.ts +16 -16
- package/templates/demo/src/routes/actions-test/+page.svelte +49 -49
- package/templates/demo/src/routes/api/hello/+server.ts +25 -25
- package/templates/demo/tsconfig.json +20 -20
- package/templates/todo/.prettierignore +5 -0
- package/templates/todo/.prettierrc.json +9 -0
- package/templates/todo/README.md +9 -9
- package/templates/todo/package.json +22 -17
- package/templates/todo/src/app.css +80 -80
- package/templates/todo/src/app.d.ts +7 -7
- package/templates/todo/src/hooks.server.ts +9 -9
- package/templates/todo/src/routes/+error.svelte +10 -7
- package/templates/todo/src/routes/+layout.server.ts +4 -4
- package/templates/todo/src/routes/+layout.svelte +2 -2
- package/templates/todo/src/routes/+page.svelte +44 -44
- package/templates/todo/template.json +1 -1
- package/templates/todo/tsconfig.json +20 -20
|
@@ -1,31 +1,19 @@
|
|
|
1
1
|
<svelte:head>
|
|
2
|
-
|
|
2
|
+
<title>About | {{ PROJECT_NAME }}</title>
|
|
3
3
|
</svelte:head>
|
|
4
4
|
|
|
5
5
|
<div class="space-y-6 max-w-2xl">
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
<h1 class="text-4xl font-bold tracking-tight">About {{ PROJECT_NAME }}</h1>
|
|
7
|
+
<p class="text-muted-foreground text-lg">
|
|
8
|
+
A minimalist fullstack framework built on Bun, ElysiaJS, and Svelte 5.
|
|
9
|
+
</p>
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"Dynamic params [slug] and catch-all [...rest]",
|
|
20
|
-
"Server loaders with parent() data threading",
|
|
21
|
-
"Hooks — sequence() middleware for auth, logging, etc.",
|
|
22
|
-
"Component registry — bosia add button",
|
|
23
|
-
"Feature registry — bosia feat login",
|
|
24
|
-
] as item}
|
|
25
|
-
<li class="flex items-start gap-2">
|
|
26
|
-
<span class="text-primary mt-0.5">✓</span>
|
|
27
|
-
<span>{item}</span>
|
|
28
|
-
</li>
|
|
29
|
-
{/each}
|
|
30
|
-
</ul>
|
|
11
|
+
<ul class="space-y-2 text-foreground">
|
|
12
|
+
{#each ["Bun runtime — fast builds, native TypeScript", "ElysiaJS — HTTP server with type-safe routing", "Svelte 5 Runes — fine-grained reactivity", "Isomorphic SSR with client hydration", "File-based routing (SvelteKit-compatible conventions)", "Nested layouts and route groups (public), (auth), (admin)", "Dynamic params [slug] and catch-all [...rest]", "Server loaders with parent() data threading", "Hooks — sequence() middleware for auth, logging, etc.", "Component registry — bosia add button", "Feature registry — bosia feat login"] as item}
|
|
13
|
+
<li class="flex items-start gap-2">
|
|
14
|
+
<span class="text-primary mt-0.5">✓</span>
|
|
15
|
+
<span>{item}</span>
|
|
16
|
+
</li>
|
|
17
|
+
{/each}
|
|
18
|
+
</ul>
|
|
31
19
|
</div>
|
|
@@ -1,38 +1,34 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
((data as any)?.params?.catchall ?? "")
|
|
5
|
-
.split("/")
|
|
6
|
-
.filter(Boolean)
|
|
7
|
-
);
|
|
2
|
+
let { data = {} }: { data?: Record<string, any> } = $props();
|
|
3
|
+
const segments = $derived(((data as any)?.params?.catchall ?? "").split("/").filter(Boolean));
|
|
8
4
|
</script>
|
|
9
5
|
|
|
10
6
|
<svelte:head>
|
|
11
|
-
|
|
7
|
+
<title>Catch-all Demo | {{ PROJECT_NAME }}</title>
|
|
12
8
|
</svelte:head>
|
|
13
9
|
|
|
14
10
|
<div class="flex flex-col items-center justify-center py-24 text-center space-y-6">
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
<h1 class="text-3xl font-bold">Catch-all Route Demo</h1>
|
|
12
|
+
<p class="text-muted-foreground text-sm">
|
|
13
|
+
This page matches <code class="bg-muted rounded px-1 font-mono">/all/[...catchall]</code>
|
|
14
|
+
</p>
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
16
|
+
{#if segments.length > 0}
|
|
17
|
+
<div class="mt-2 space-y-1">
|
|
18
|
+
<p class="text-sm text-muted-foreground">Captured segments:</p>
|
|
19
|
+
<div class="flex gap-2 flex-wrap justify-center">
|
|
20
|
+
{#each segments as segment, i}
|
|
21
|
+
<span class="bg-muted rounded px-2 py-1 font-mono text-sm">
|
|
22
|
+
[{i}] {segment}
|
|
23
|
+
</span>
|
|
24
|
+
{/each}
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
{:else}
|
|
28
|
+
<p class="text-muted-foreground text-sm italic">No segments captured</p>
|
|
29
|
+
{/if}
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
<a href="/" class="mt-4 rounded-md border px-4 py-2 text-sm hover:bg-muted transition-colors">
|
|
32
|
+
Go Home
|
|
33
|
+
</a>
|
|
38
34
|
</div>
|
|
@@ -1,55 +1,64 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
2
|
+
const posts = [
|
|
3
|
+
{
|
|
4
|
+
slug: "hello-world",
|
|
5
|
+
title: "Hello, World!",
|
|
6
|
+
date: "2026-03-05",
|
|
7
|
+
excerpt: "The first post in the demo — a quick intro to the framework.",
|
|
8
|
+
tags: ["intro", "bosia"],
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
slug: "route-groups",
|
|
12
|
+
title: "Route Groups Explained",
|
|
13
|
+
date: "2026-03-04",
|
|
14
|
+
excerpt:
|
|
15
|
+
"How (public), (auth), (admin) groups work — invisible in URLs, share layouts.",
|
|
16
|
+
tags: ["routing", "layouts"],
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
slug: "dynamic-params",
|
|
20
|
+
title: "Dynamic Params with [slug]",
|
|
21
|
+
date: "2026-03-03",
|
|
22
|
+
excerpt: "Using [slug] segments to match any value and pass it to server loaders.",
|
|
23
|
+
tags: ["routing", "dynamic"],
|
|
24
|
+
},
|
|
25
|
+
];
|
|
25
26
|
</script>
|
|
26
27
|
|
|
27
28
|
<svelte:head>
|
|
28
|
-
|
|
29
|
+
<title>Blog | {{ PROJECT_NAME }}</title>
|
|
29
30
|
</svelte:head>
|
|
30
31
|
|
|
31
32
|
<div class="space-y-8">
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
<div class="space-y-2">
|
|
34
|
+
<h1 class="text-4xl font-bold tracking-tight">Blog</h1>
|
|
35
|
+
<p class="text-muted-foreground">Routing patterns and framework internals.</p>
|
|
36
|
+
</div>
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
38
|
+
<div class="grid gap-4" data-bosia-preload="hover">
|
|
39
|
+
{#each posts as post}
|
|
40
|
+
<a
|
|
41
|
+
href="/blog/{post.slug}"
|
|
42
|
+
class="group block rounded-lg border bg-card p-5 hover:border-primary transition-colors"
|
|
43
|
+
>
|
|
44
|
+
<div class="flex items-start justify-between gap-4">
|
|
45
|
+
<div class="space-y-1">
|
|
46
|
+
<p class="font-semibold group-hover:text-primary transition-colors">
|
|
47
|
+
{post.title}
|
|
48
|
+
</p>
|
|
49
|
+
<p class="text-xs text-muted-foreground font-mono">{post.date}</p>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="flex gap-1 shrink-0">
|
|
52
|
+
{#each post.tags as tag}
|
|
53
|
+
<span
|
|
54
|
+
class="rounded-full bg-secondary px-2 py-0.5 text-xs text-secondary-foreground"
|
|
55
|
+
>{tag}</span
|
|
56
|
+
>
|
|
57
|
+
{/each}
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
<p class="mt-2 text-sm text-muted-foreground">{post.excerpt}</p>
|
|
61
|
+
</a>
|
|
62
|
+
{/each}
|
|
63
|
+
</div>
|
|
55
64
|
</div>
|
|
@@ -1,62 +1,60 @@
|
|
|
1
1
|
import type { LoadEvent, MetadataEvent } from "bosia";
|
|
2
2
|
|
|
3
3
|
const posts: Record<string, { title: string; date: string; tags: string[]; content: string }> = {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
"hello-world": {
|
|
5
|
+
title: "Hello, World!",
|
|
6
|
+
date: "2026-03-05",
|
|
7
|
+
tags: ["intro", "bosia"],
|
|
8
|
+
content: `Welcome to Bosia! This page was loaded by a +page.server.ts file.
|
|
9
9
|
|
|
10
10
|
The slug param was extracted from the URL by the route matcher and passed to the load() function as params.slug.
|
|
11
11
|
|
|
12
12
|
This is standard SvelteKit-compatible server loading.`,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
},
|
|
14
|
+
"route-groups": {
|
|
15
|
+
title: "Route Groups Explained",
|
|
16
|
+
date: "2026-03-04",
|
|
17
|
+
tags: ["routing", "layouts"],
|
|
18
|
+
content: `Route groups like (public), (auth), and (admin) are directory names that are invisible in the URL.
|
|
19
19
|
|
|
20
20
|
They let you share layouts across a set of routes without adding a URL segment. A directory named (public) applies its +layout.svelte to all routes inside it, but /public never appears in the browser URL.
|
|
21
21
|
|
|
22
22
|
This page lives at routes/(public)/blog/[slug]/+page.svelte but is served at /blog/route-groups.`,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
},
|
|
24
|
+
"dynamic-params": {
|
|
25
|
+
title: "Dynamic Params with [slug]",
|
|
26
|
+
date: "2026-03-03",
|
|
27
|
+
tags: ["routing", "dynamic"],
|
|
28
|
+
content: `A directory named [slug] creates a dynamic route segment that matches any URL value.
|
|
29
29
|
|
|
30
30
|
The matched value is available as params.slug inside +page.server.ts load() and inside the page component via data.params.slug.
|
|
31
31
|
|
|
32
32
|
The route matcher uses 3-pass priority: exact matches first, then dynamic segments, then catch-all routes.`,
|
|
33
|
-
|
|
33
|
+
},
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
export function metadata({ params }: MetadataEvent) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
data: { post },
|
|
47
|
-
};
|
|
37
|
+
// In production this would be a DB query for the post
|
|
38
|
+
const post = posts[params.slug] ?? null;
|
|
39
|
+
return {
|
|
40
|
+
title: post ? `${post.title} — Blog` : `Post not found`,
|
|
41
|
+
description: post ? `A blog post about ${params.slug}` : undefined,
|
|
42
|
+
meta: post ? [{ property: "og:title", content: post.title }] : [],
|
|
43
|
+
// Pass fetched post to load() — avoids duplicate query
|
|
44
|
+
data: { post },
|
|
45
|
+
};
|
|
48
46
|
}
|
|
49
47
|
|
|
50
48
|
export async function load({ params, parent, metadata }: LoadEvent) {
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
// parent() gives us data from +layout.server.ts (appName, requestTime)
|
|
50
|
+
const parentData = await parent();
|
|
53
51
|
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
// Reuse post from metadata() — no duplicate DB query
|
|
53
|
+
const post = metadata?.post ?? posts[params.slug] ?? null;
|
|
56
54
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
return {
|
|
56
|
+
post,
|
|
57
|
+
slug: params.slug,
|
|
58
|
+
appName: parentData.appName as string,
|
|
59
|
+
};
|
|
62
60
|
}
|
|
@@ -1,53 +1,71 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
2
|
+
import type { PageData } from "./$types";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
let { data }: { data: PageData } = $props();
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const post = $derived(data.post);
|
|
7
|
+
const slug = $derived(data.slug ?? data.params.slug ?? "");
|
|
8
8
|
</script>
|
|
9
9
|
|
|
10
10
|
<svelte:head>
|
|
11
|
-
|
|
11
|
+
<title>{post ? post.title : "Post Not Found"} | {{ PROJECT_NAME }}</title>
|
|
12
12
|
</svelte:head>
|
|
13
13
|
|
|
14
14
|
{#if post}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
15
|
+
<article class="space-y-6 max-w-2xl">
|
|
16
|
+
<a
|
|
17
|
+
href="/blog"
|
|
18
|
+
class="text-sm text-muted-foreground hover:text-foreground transition-colors">← Blog</a
|
|
19
|
+
>
|
|
20
|
+
|
|
21
|
+
<div class="space-y-2">
|
|
22
|
+
<div class="flex flex-wrap gap-1">
|
|
23
|
+
{#each post.tags as tag}
|
|
24
|
+
<span
|
|
25
|
+
class="rounded-full bg-secondary px-2 py-0.5 text-xs text-secondary-foreground"
|
|
26
|
+
>{tag}</span
|
|
27
|
+
>
|
|
28
|
+
{/each}
|
|
29
|
+
</div>
|
|
30
|
+
<h1 class="text-4xl font-bold tracking-tight">{post.title}</h1>
|
|
31
|
+
<p class="text-sm text-muted-foreground font-mono">{post.date}</p>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<hr class="border-border" />
|
|
35
|
+
|
|
36
|
+
<div class="space-y-4">
|
|
37
|
+
{#each post.content.split("\n\n") as paragraph}
|
|
38
|
+
<p class="text-foreground leading-relaxed">{paragraph}</p>
|
|
39
|
+
{/each}
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<hr class="border-border" />
|
|
43
|
+
|
|
44
|
+
<!-- Route debug box — demonstrates params flowing from server to client -->
|
|
45
|
+
<div class="rounded-lg border bg-muted/40 p-4 space-y-1 text-sm font-mono">
|
|
46
|
+
<p
|
|
47
|
+
class="font-sans text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-2"
|
|
48
|
+
>
|
|
49
|
+
Route debug
|
|
50
|
+
</p>
|
|
51
|
+
<p><span class="text-muted-foreground">pattern: </span>/blog/[slug]</p>
|
|
52
|
+
<p>
|
|
53
|
+
<span class="text-muted-foreground">params.slug: </span><span
|
|
54
|
+
class="text-primary font-semibold">{slug}</span
|
|
55
|
+
>
|
|
56
|
+
</p>
|
|
57
|
+
<p><span class="text-muted-foreground">loaded by: </span>+page.server.ts</p>
|
|
58
|
+
<p><span class="text-muted-foreground">parent data: </span>{data.appName}</p>
|
|
59
|
+
</div>
|
|
60
|
+
</article>
|
|
47
61
|
{:else}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
62
|
+
<div class="flex flex-col items-center justify-center py-20 text-center space-y-4">
|
|
63
|
+
<p class="text-7xl font-bold text-destructive">404</p>
|
|
64
|
+
<p class="text-xl text-muted-foreground">
|
|
65
|
+
Post "<span class="font-mono">{slug}</span>" not found.
|
|
66
|
+
</p>
|
|
67
|
+
<a href="/blog" class="rounded-md border px-4 py-2 text-sm hover:bg-muted transition-colors"
|
|
68
|
+
>Back to Blog</a
|
|
69
|
+
>
|
|
70
|
+
</div>
|
|
53
71
|
{/if}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
2
|
+
let { error }: { error: { status: number; message: string } } = $props();
|
|
3
3
|
</script>
|
|
4
4
|
|
|
5
5
|
<svelte:head>
|
|
6
|
-
|
|
6
|
+
<title>{error.status} — {error.message}</title>
|
|
7
7
|
</svelte:head>
|
|
8
8
|
|
|
9
9
|
<div class="min-h-screen flex flex-col items-center justify-center gap-4 text-center px-4">
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
<p class="text-8xl font-bold text-gray-200">{error.status}</p>
|
|
11
|
+
<p class="text-2xl font-semibold text-gray-700">{error.message}</p>
|
|
12
|
+
<a
|
|
13
|
+
href="/"
|
|
14
|
+
class="mt-4 px-5 py-2 rounded-lg bg-gray-900 text-white text-sm hover:bg-gray-700 transition-colors"
|
|
15
|
+
>
|
|
16
|
+
Go home
|
|
17
|
+
</a>
|
|
15
18
|
</div>
|
|
@@ -3,8 +3,8 @@ import type { LoadEvent } from "bosia";
|
|
|
3
3
|
// Data returned here is available to all child loaders via parent()
|
|
4
4
|
// and to all layouts via the `data` prop.
|
|
5
5
|
export async function load({ locals }: LoadEvent) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
return {
|
|
7
|
+
appName: "{{PROJECT_NAME}}",
|
|
8
|
+
requestTime: (locals.requestTime as number | null) ?? null,
|
|
9
|
+
};
|
|
10
10
|
}
|
|
@@ -2,27 +2,27 @@ import { fail, redirect } from "bosia";
|
|
|
2
2
|
import type { RequestEvent } from "bosia";
|
|
3
3
|
|
|
4
4
|
export async function load() {
|
|
5
|
-
|
|
5
|
+
return { greeting: "Test form actions" };
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export const actions = {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
default: async ({ request }: RequestEvent) => {
|
|
10
|
+
const data = await request.formData();
|
|
11
|
+
const email = data.get("email") as string;
|
|
12
|
+
const name = data.get("name") as string;
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
const errors: Record<string, string> = {};
|
|
15
|
+
if (!email) errors.email = "Email is required";
|
|
16
|
+
if (!name) errors.name = "Name is required";
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
if (Object.keys(errors).length > 0) {
|
|
19
|
+
return fail(400, { email, name, errors });
|
|
20
|
+
}
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
return { success: true, email, name };
|
|
23
|
+
},
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
reset: async () => {
|
|
26
|
+
return { cleared: true };
|
|
27
|
+
},
|
|
28
28
|
};
|