bosia 1.1.6 → 1.2.1
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/LICENSE +21 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/ambient.d.ts +3 -0
- package/src/cli/sync.ts +14 -1
- package/src/core/appHtml.ts +52 -0
- package/src/core/build.ts +4 -3
- package/src/core/client/App.svelte +82 -12
- package/src/core/client/TitleProbe.svelte +15 -0
- package/src/core/client/router.svelte.ts +1 -2
- package/src/core/client/titleMemory.svelte.ts +64 -0
- package/src/core/headScan.ts +132 -0
- package/src/core/html.ts +103 -45
- package/src/core/renderer.ts +190 -91
- package/src/core/routeFile.ts +15 -2
- package/src/core/scanner.ts +28 -31
- package/src/core/server.ts +95 -80
- package/src/lib/utils.ts +1 -8
- package/templates/default/package.json +2 -2
- package/templates/demo/package.json +2 -2
- package/templates/shop/package.json +2 -2
- package/templates/shop/src/routes/(private)/dashboard/files/+page.svelte +9 -0
- package/templates/shop/src/routes/(private)/dashboard/orders/+page.svelte +9 -0
- package/templates/shop/src/routes/(private)/dashboard/orders/pending/+page.svelte +9 -0
- package/templates/shop/src/routes/(private)/dashboard/orders/refunds/+page.svelte +9 -0
- package/templates/shop/src/routes/(private)/dashboard/products/+page.svelte +9 -0
- package/templates/shop/src/routes/(private)/dashboard/profile/+page.svelte +9 -0
- package/templates/shop/src/routes/(private)/dashboard/settings/+page.svelte +9 -0
- package/templates/shop/src/routes/(private)/dashboard/users/+page.svelte +9 -0
- package/templates/shop/src/routes/(public)/+page.svelte +2 -2
- package/templates/store/package.json +2 -2
- package/templates/store/src/routes/(private)/dashboard/files/+page.svelte +9 -0
- package/templates/store/src/routes/(private)/dashboard/orders/+page.svelte +9 -0
- package/templates/store/src/routes/(private)/dashboard/orders/pending/+page.svelte +9 -0
- package/templates/store/src/routes/(private)/dashboard/orders/refunds/+page.svelte +9 -0
- package/templates/store/src/routes/(private)/dashboard/products/+page.svelte +9 -0
- package/templates/store/src/routes/(private)/dashboard/profile/+page.svelte +9 -0
- package/templates/store/src/routes/(private)/dashboard/settings/+page.svelte +9 -0
- package/templates/store/src/routes/(private)/dashboard/users/+page.svelte +9 -0
- package/templates/store/src/routes/(public)/+page.svelte +2 -2
package/src/core/server.ts
CHANGED
|
@@ -404,33 +404,39 @@ async function resolve(event: RequestEvent): Promise<Response> {
|
|
|
404
404
|
)
|
|
405
405
|
: undefined;
|
|
406
406
|
const runLoad = async () => {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
try {
|
|
412
|
-
const meta = await loadMetadata(
|
|
407
|
+
// metadata() runs alongside the loaders; the page load() gets its
|
|
408
|
+
// `data`, as on SSR. A failure stays non-fatal here.
|
|
409
|
+
const metaP = pageMatch
|
|
410
|
+
? loadMetadata(
|
|
413
411
|
pageMatch.route,
|
|
414
412
|
pageMatch.params,
|
|
415
413
|
routeUrl,
|
|
416
414
|
locals,
|
|
417
415
|
cookies,
|
|
418
416
|
request,
|
|
419
|
-
)
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
417
|
+
).catch(() => null)
|
|
418
|
+
: Promise.resolve(null);
|
|
419
|
+
const data = await loadRouteData(
|
|
420
|
+
routeUrl,
|
|
421
|
+
locals,
|
|
422
|
+
request,
|
|
423
|
+
cookies,
|
|
424
|
+
metaP.then((m) => m?.data ?? null),
|
|
425
|
+
pageMatch,
|
|
426
|
+
mask,
|
|
427
|
+
);
|
|
428
|
+
const meta = await metaP;
|
|
429
|
+
// Explicit whitelist, not a spread: `metadata.data` feeds load() on the
|
|
430
|
+
// server and may hold secrets — it must not reach the client.
|
|
431
|
+
const metadata = meta
|
|
432
|
+
? {
|
|
433
|
+
title: meta.title,
|
|
434
|
+
description: meta.description,
|
|
435
|
+
meta: meta.meta,
|
|
436
|
+
link: meta.link,
|
|
437
|
+
lang: meta.lang,
|
|
438
|
+
}
|
|
439
|
+
: null;
|
|
434
440
|
|
|
435
441
|
return { data, metadata, cookiesAccessed: (cookies as CookieJar).accessed };
|
|
436
442
|
};
|
|
@@ -1041,6 +1047,69 @@ if (_xfoDisabled) {
|
|
|
1041
1047
|
console.log("🪟 X-Frame-Options disabled (DISABLE_X_FRAME_OPTIONS=true)");
|
|
1042
1048
|
}
|
|
1043
1049
|
|
|
1050
|
+
// Security headers, CSP, CORS and the request's Set-Cookie headers, applied to
|
|
1051
|
+
// every response handleRequest hands back — resolved ones and the ones a hook
|
|
1052
|
+
// threw (redirect / error()).
|
|
1053
|
+
function finalizeResponse(
|
|
1054
|
+
response: Response,
|
|
1055
|
+
request: Request,
|
|
1056
|
+
nonce: string,
|
|
1057
|
+
cookieJar: CookieJar,
|
|
1058
|
+
): Response {
|
|
1059
|
+
// Set on the response itself when its headers are mutable, which every
|
|
1060
|
+
// Response the framework builds has. Copying them and rebuilding the
|
|
1061
|
+
// Response cost a Headers copy plus a body re-wrap on every request.
|
|
1062
|
+
// Responses with immutable headers (`fetch()`, `Response.redirect()`)
|
|
1063
|
+
// still get the copy.
|
|
1064
|
+
// On Workers (compressionOn is false only there) an already-encoded body
|
|
1065
|
+
// is rebuilt as before, since the rebuild is what marks it PRECOMPRESSED.
|
|
1066
|
+
let mutable: Headers | null = null;
|
|
1067
|
+
if (compressionOn || !response.headers.has("content-encoding")) {
|
|
1068
|
+
try {
|
|
1069
|
+
response.headers.set("X-Content-Type-Options", SECURITY_HEADERS["X-Content-Type-Options"]!);
|
|
1070
|
+
mutable = response.headers;
|
|
1071
|
+
} catch {
|
|
1072
|
+
// immutable — copied below
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
const inPlace = mutable !== null;
|
|
1076
|
+
const headers = mutable ?? new Headers(response.headers);
|
|
1077
|
+
// A handle can mark a response (e.g. a proxied embeddable preview) to opt
|
|
1078
|
+
// out of the frame guard. Strip the internal marker so it never ships, and
|
|
1079
|
+
// skip only X-Frame-Options for that response — other security headers stay.
|
|
1080
|
+
const skipFrameGuard = headers.has(NO_FRAME_GUARD_HEADER);
|
|
1081
|
+
headers.delete(NO_FRAME_GUARD_HEADER);
|
|
1082
|
+
for (const [k, v] of Object.entries(SECURITY_HEADERS)) {
|
|
1083
|
+
if (skipFrameGuard && k === "X-Frame-Options") continue;
|
|
1084
|
+
headers.set(k, v);
|
|
1085
|
+
}
|
|
1086
|
+
const cspHeader = buildCspHeader(nonce);
|
|
1087
|
+
if (cspHeader) headers.set("Content-Security-Policy", cspHeader);
|
|
1088
|
+
// Apply CORS headers for allowed origins. `Vary: Origin` is set whenever
|
|
1089
|
+
// CORS is configured — even on responses to non-allowed origins — so
|
|
1090
|
+
// downstream caches (CDNs, browser HTTP cache) key on the Origin header
|
|
1091
|
+
// instead of serving an Access-Control-Allow-Origin response across origins.
|
|
1092
|
+
if (CORS_CONFIG) {
|
|
1093
|
+
applyCorsVary(headers);
|
|
1094
|
+
const corsHeaders = getCorsHeaders(request, CORS_CONFIG);
|
|
1095
|
+
if (corsHeaders) {
|
|
1096
|
+
for (const [k, v] of Object.entries(corsHeaders)) headers.set(k, v);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
// Apply any Set-Cookie headers accumulated during the request
|
|
1100
|
+
for (const cookie of cookieJar.outgoing) headers.append("Set-Cookie", cookie);
|
|
1101
|
+
if (inPlace) return response;
|
|
1102
|
+
return new Response(response.body, {
|
|
1103
|
+
// A Content-Encoding here means the body is already compressed (cache
|
|
1104
|
+
// hit, compress()); rebuilding drops that flag and Workers would
|
|
1105
|
+
// compress it again.
|
|
1106
|
+
...(headers.has("content-encoding") ? PRECOMPRESSED : {}),
|
|
1107
|
+
status: response.status,
|
|
1108
|
+
statusText: response.statusText,
|
|
1109
|
+
headers,
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1044
1113
|
async function handleRequest(request: Request, url: URL): Promise<Response> {
|
|
1045
1114
|
// Behind a trusted proxy the inbound `Host`/scheme is the proxy's inner hop
|
|
1046
1115
|
// (e.g. `localhost:PORT` over plain HTTP), so `url` built from `request.url`
|
|
@@ -1188,58 +1257,7 @@ async function handleRequest(request: Request, url: URL): Promise<Response> {
|
|
|
1188
1257
|
}
|
|
1189
1258
|
}
|
|
1190
1259
|
|
|
1191
|
-
|
|
1192
|
-
// Response the framework builds has. Copying them and rebuilding the
|
|
1193
|
-
// Response cost a Headers copy plus a body re-wrap on every request.
|
|
1194
|
-
// Responses with immutable headers (`fetch()`, `Response.redirect()`)
|
|
1195
|
-
// still get the copy.
|
|
1196
|
-
// On Workers (compressionOn is false only there) an already-encoded body
|
|
1197
|
-
// is rebuilt as before, since the rebuild is what marks it PRECOMPRESSED.
|
|
1198
|
-
let mutable: Headers | null = null;
|
|
1199
|
-
if (compressionOn || !response.headers.has("content-encoding")) {
|
|
1200
|
-
try {
|
|
1201
|
-
response.headers.set("X-Content-Type-Options", SECURITY_HEADERS["X-Content-Type-Options"]!);
|
|
1202
|
-
mutable = response.headers;
|
|
1203
|
-
} catch {
|
|
1204
|
-
// immutable — copied below
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
const inPlace = mutable !== null;
|
|
1208
|
-
const headers = mutable ?? new Headers(response.headers);
|
|
1209
|
-
// A handle can mark a response (e.g. a proxied embeddable preview) to opt
|
|
1210
|
-
// out of the frame guard. Strip the internal marker so it never ships, and
|
|
1211
|
-
// skip only X-Frame-Options for that response — other security headers stay.
|
|
1212
|
-
const skipFrameGuard = headers.has(NO_FRAME_GUARD_HEADER);
|
|
1213
|
-
headers.delete(NO_FRAME_GUARD_HEADER);
|
|
1214
|
-
for (const [k, v] of Object.entries(SECURITY_HEADERS)) {
|
|
1215
|
-
if (skipFrameGuard && k === "X-Frame-Options") continue;
|
|
1216
|
-
headers.set(k, v);
|
|
1217
|
-
}
|
|
1218
|
-
const cspHeader = buildCspHeader(nonce);
|
|
1219
|
-
if (cspHeader) headers.set("Content-Security-Policy", cspHeader);
|
|
1220
|
-
// Apply CORS headers for allowed origins. `Vary: Origin` is set whenever
|
|
1221
|
-
// CORS is configured — even on responses to non-allowed origins — so
|
|
1222
|
-
// downstream caches (CDNs, browser HTTP cache) key on the Origin header
|
|
1223
|
-
// instead of serving an Access-Control-Allow-Origin response across origins.
|
|
1224
|
-
if (CORS_CONFIG) {
|
|
1225
|
-
applyCorsVary(headers);
|
|
1226
|
-
const corsHeaders = getCorsHeaders(request, CORS_CONFIG);
|
|
1227
|
-
if (corsHeaders) {
|
|
1228
|
-
for (const [k, v] of Object.entries(corsHeaders)) headers.set(k, v);
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
// Apply any Set-Cookie headers accumulated during the request
|
|
1232
|
-
for (const cookie of cookieJar.outgoing) headers.append("Set-Cookie", cookie);
|
|
1233
|
-
if (inPlace) return response;
|
|
1234
|
-
return new Response(response.body, {
|
|
1235
|
-
// A Content-Encoding here means the body is already compressed (cache
|
|
1236
|
-
// hit, compress()); rebuilding drops that flag and Workers would
|
|
1237
|
-
// compress it again.
|
|
1238
|
-
...(headers.has("content-encoding") ? PRECOMPRESSED : {}),
|
|
1239
|
-
status: response.status,
|
|
1240
|
-
statusText: response.statusText,
|
|
1241
|
-
headers,
|
|
1242
|
-
});
|
|
1260
|
+
return finalizeResponse(response, request, nonce, cookieJar);
|
|
1243
1261
|
} catch (err) {
|
|
1244
1262
|
// `throw redirect()` / `throw error()` from a hook lands here — the same
|
|
1245
1263
|
// escape hatch loaders have always had. Without these branches both fall
|
|
@@ -1273,13 +1291,10 @@ async function handleRequest(request: Request, url: URL): Promise<Response> {
|
|
|
1273
1291
|
undefined,
|
|
1274
1292
|
nonce,
|
|
1275
1293
|
);
|
|
1276
|
-
//
|
|
1277
|
-
// Set-Cookie
|
|
1278
|
-
//
|
|
1279
|
-
|
|
1280
|
-
for (const cookie of cookieJar.outgoing) out.headers.append("Set-Cookie", cookie);
|
|
1281
|
-
}
|
|
1282
|
-
return out;
|
|
1294
|
+
// The same finishing pass as a resolved response: security headers,
|
|
1295
|
+
// CSP, CORS, and the jar's Set-Cookie — a hook that expires the session
|
|
1296
|
+
// before throwing must not lose the cookie that does it.
|
|
1297
|
+
return cookieJar ? finalizeResponse(out, request, nonce, cookieJar) : out;
|
|
1283
1298
|
}
|
|
1284
1299
|
if (isDev) console.error("Unhandled request error:", err);
|
|
1285
1300
|
else console.error("Unhandled request error:", (err as Error).message ?? err);
|
package/src/lib/utils.ts
CHANGED
|
@@ -3,14 +3,7 @@ import { twMerge } from "tailwind-merge";
|
|
|
3
3
|
type ClassDictionary = Record<string, any>;
|
|
4
4
|
type ClassArray = ClassValue[];
|
|
5
5
|
type ClassValue =
|
|
6
|
-
|
|
|
7
|
-
| ClassDictionary
|
|
8
|
-
| string
|
|
9
|
-
| number
|
|
10
|
-
| bigint
|
|
11
|
-
| null
|
|
12
|
-
| boolean
|
|
13
|
-
| undefined;
|
|
6
|
+
ClassArray | ClassDictionary | string | number | bigint | null | boolean | undefined;
|
|
14
7
|
|
|
15
8
|
function clsx(...inputs: ClassValue[]): string {
|
|
16
9
|
let str = "";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!-- EDIT THIS FILE: uploaded files (the file-upload feature). -->
|
|
2
|
+
<svelte:head>
|
|
3
|
+
<title>Files</title>
|
|
4
|
+
</svelte:head>
|
|
5
|
+
|
|
6
|
+
<div class="flex flex-col gap-4">
|
|
7
|
+
<h1 class="text-2xl font-bold tracking-tight">Files</h1>
|
|
8
|
+
<p class="text-muted-foreground text-sm">Nothing here yet. Build this page when you need it.</p>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!-- EDIT THIS FILE: list orders and open their details (OrderService in src/features/shop). -->
|
|
2
|
+
<svelte:head>
|
|
3
|
+
<title>Orders</title>
|
|
4
|
+
</svelte:head>
|
|
5
|
+
|
|
6
|
+
<div class="flex flex-col gap-4">
|
|
7
|
+
<h1 class="text-2xl font-bold tracking-tight">Orders</h1>
|
|
8
|
+
<p class="text-muted-foreground text-sm">Nothing here yet. Build this page when you need it.</p>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!-- EDIT THIS FILE: orders waiting to be paid or shipped. -->
|
|
2
|
+
<svelte:head>
|
|
3
|
+
<title>Pending orders</title>
|
|
4
|
+
</svelte:head>
|
|
5
|
+
|
|
6
|
+
<div class="flex flex-col gap-4">
|
|
7
|
+
<h1 class="text-2xl font-bold tracking-tight">Pending orders</h1>
|
|
8
|
+
<p class="text-muted-foreground text-sm">Nothing here yet. Build this page when you need it.</p>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!-- EDIT THIS FILE: refund requests and refunded orders. -->
|
|
2
|
+
<svelte:head>
|
|
3
|
+
<title>Refunds</title>
|
|
4
|
+
</svelte:head>
|
|
5
|
+
|
|
6
|
+
<div class="flex flex-col gap-4">
|
|
7
|
+
<h1 class="text-2xl font-bold tracking-tight">Refunds</h1>
|
|
8
|
+
<p class="text-muted-foreground text-sm">Nothing here yet. Build this page when you need it.</p>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!-- EDIT THIS FILE: list, create and edit products (ProductService in src/features/shop). -->
|
|
2
|
+
<svelte:head>
|
|
3
|
+
<title>Products</title>
|
|
4
|
+
</svelte:head>
|
|
5
|
+
|
|
6
|
+
<div class="flex flex-col gap-4">
|
|
7
|
+
<h1 class="text-2xl font-bold tracking-tight">Products</h1>
|
|
8
|
+
<p class="text-muted-foreground text-sm">Nothing here yet. Build this page when you need it.</p>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!-- EDIT THIS FILE: the signed-in user's name, email and password. -->
|
|
2
|
+
<svelte:head>
|
|
3
|
+
<title>Profile</title>
|
|
4
|
+
</svelte:head>
|
|
5
|
+
|
|
6
|
+
<div class="flex flex-col gap-4">
|
|
7
|
+
<h1 class="text-2xl font-bold tracking-tight">Profile</h1>
|
|
8
|
+
<p class="text-muted-foreground text-sm">Nothing here yet. Build this page when you need it.</p>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!-- EDIT THIS FILE: store settings. -->
|
|
2
|
+
<svelte:head>
|
|
3
|
+
<title>Settings</title>
|
|
4
|
+
</svelte:head>
|
|
5
|
+
|
|
6
|
+
<div class="flex flex-col gap-4">
|
|
7
|
+
<h1 class="text-2xl font-bold tracking-tight">Settings</h1>
|
|
8
|
+
<p class="text-muted-foreground text-sm">Nothing here yet. Build this page when you need it.</p>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!-- EDIT THIS FILE: users and their roles (the auth and rbac features). -->
|
|
2
|
+
<svelte:head>
|
|
3
|
+
<title>Users</title>
|
|
4
|
+
</svelte:head>
|
|
5
|
+
|
|
6
|
+
<div class="flex flex-col gap-4">
|
|
7
|
+
<h1 class="text-2xl font-bold tracking-tight">Users</h1>
|
|
8
|
+
<p class="text-muted-foreground text-sm">Nothing here yet. Build this page when you need it.</p>
|
|
9
|
+
</div>
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
|
|
18
18
|
<div class="mt-4 flex gap-3">
|
|
19
19
|
<a
|
|
20
|
-
href="/products"
|
|
20
|
+
href="/dashboard/products"
|
|
21
21
|
class="bg-primary text-primary-foreground hover:bg-primary/90 rounded-md px-4 py-2 text-sm font-medium transition-colors"
|
|
22
22
|
>
|
|
23
|
-
|
|
23
|
+
Manage products
|
|
24
24
|
</a>
|
|
25
25
|
<a
|
|
26
26
|
href="/login"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!-- EDIT THIS FILE: uploaded files (the file-upload feature). -->
|
|
2
|
+
<svelte:head>
|
|
3
|
+
<title>Files</title>
|
|
4
|
+
</svelte:head>
|
|
5
|
+
|
|
6
|
+
<div class="flex flex-col gap-4">
|
|
7
|
+
<h1 class="text-2xl font-bold tracking-tight">Files</h1>
|
|
8
|
+
<p class="text-muted-foreground text-sm">Nothing here yet. Build this page when you need it.</p>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!-- EDIT THIS FILE: list orders and open their details (OrderService in src/features/shop). -->
|
|
2
|
+
<svelte:head>
|
|
3
|
+
<title>Orders</title>
|
|
4
|
+
</svelte:head>
|
|
5
|
+
|
|
6
|
+
<div class="flex flex-col gap-4">
|
|
7
|
+
<h1 class="text-2xl font-bold tracking-tight">Orders</h1>
|
|
8
|
+
<p class="text-muted-foreground text-sm">Nothing here yet. Build this page when you need it.</p>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!-- EDIT THIS FILE: orders waiting to be paid or shipped. -->
|
|
2
|
+
<svelte:head>
|
|
3
|
+
<title>Pending orders</title>
|
|
4
|
+
</svelte:head>
|
|
5
|
+
|
|
6
|
+
<div class="flex flex-col gap-4">
|
|
7
|
+
<h1 class="text-2xl font-bold tracking-tight">Pending orders</h1>
|
|
8
|
+
<p class="text-muted-foreground text-sm">Nothing here yet. Build this page when you need it.</p>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!-- EDIT THIS FILE: refund requests and refunded orders. -->
|
|
2
|
+
<svelte:head>
|
|
3
|
+
<title>Refunds</title>
|
|
4
|
+
</svelte:head>
|
|
5
|
+
|
|
6
|
+
<div class="flex flex-col gap-4">
|
|
7
|
+
<h1 class="text-2xl font-bold tracking-tight">Refunds</h1>
|
|
8
|
+
<p class="text-muted-foreground text-sm">Nothing here yet. Build this page when you need it.</p>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!-- EDIT THIS FILE: list, create and edit products (ProductService in src/features/shop). -->
|
|
2
|
+
<svelte:head>
|
|
3
|
+
<title>Products</title>
|
|
4
|
+
</svelte:head>
|
|
5
|
+
|
|
6
|
+
<div class="flex flex-col gap-4">
|
|
7
|
+
<h1 class="text-2xl font-bold tracking-tight">Products</h1>
|
|
8
|
+
<p class="text-muted-foreground text-sm">Nothing here yet. Build this page when you need it.</p>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!-- EDIT THIS FILE: the signed-in user's name, email and password. -->
|
|
2
|
+
<svelte:head>
|
|
3
|
+
<title>Profile</title>
|
|
4
|
+
</svelte:head>
|
|
5
|
+
|
|
6
|
+
<div class="flex flex-col gap-4">
|
|
7
|
+
<h1 class="text-2xl font-bold tracking-tight">Profile</h1>
|
|
8
|
+
<p class="text-muted-foreground text-sm">Nothing here yet. Build this page when you need it.</p>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!-- EDIT THIS FILE: store settings. -->
|
|
2
|
+
<svelte:head>
|
|
3
|
+
<title>Settings</title>
|
|
4
|
+
</svelte:head>
|
|
5
|
+
|
|
6
|
+
<div class="flex flex-col gap-4">
|
|
7
|
+
<h1 class="text-2xl font-bold tracking-tight">Settings</h1>
|
|
8
|
+
<p class="text-muted-foreground text-sm">Nothing here yet. Build this page when you need it.</p>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!-- EDIT THIS FILE: users and their roles (the auth and rbac features). -->
|
|
2
|
+
<svelte:head>
|
|
3
|
+
<title>Users</title>
|
|
4
|
+
</svelte:head>
|
|
5
|
+
|
|
6
|
+
<div class="flex flex-col gap-4">
|
|
7
|
+
<h1 class="text-2xl font-bold tracking-tight">Users</h1>
|
|
8
|
+
<p class="text-muted-foreground text-sm">Nothing here yet. Build this page when you need it.</p>
|
|
9
|
+
</div>
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
|
|
18
18
|
<div class="mt-4 flex gap-3">
|
|
19
19
|
<a
|
|
20
|
-
href="/products"
|
|
20
|
+
href="/dashboard/products"
|
|
21
21
|
class="bg-primary text-primary-foreground hover:bg-primary/90 rounded-md px-4 py-2 text-sm font-medium transition-colors"
|
|
22
22
|
>
|
|
23
|
-
|
|
23
|
+
Manage products
|
|
24
24
|
</a>
|
|
25
25
|
<a
|
|
26
26
|
href="/login"
|