Enhance user routing and layout management. Added support for user-specific routes, including dynamic user ID handling and updated component imports. Improved type definitions and user data mapping in hooks for better user experience.
This commit is contained in:
@@ -7,16 +7,20 @@ export const nodes = [
|
|||||||
() => import('./nodes/3'),
|
() => import('./nodes/3'),
|
||||||
() => import('./nodes/4'),
|
() => import('./nodes/4'),
|
||||||
() => import('./nodes/5'),
|
() => import('./nodes/5'),
|
||||||
() => import('./nodes/6')
|
() => import('./nodes/6'),
|
||||||
|
() => import('./nodes/7'),
|
||||||
|
() => import('./nodes/8')
|
||||||
];
|
];
|
||||||
|
|
||||||
export const server_loads = [0];
|
export const server_loads = [0];
|
||||||
|
|
||||||
export const dictionary = {
|
export const dictionary = {
|
||||||
"/": [3],
|
"/": [3],
|
||||||
"/login": [~6],
|
"/login": [~8],
|
||||||
"/logout": [~4],
|
"/logout": [~4],
|
||||||
"/[site]": [~5,[2]]
|
"/users": [~5],
|
||||||
|
"/users/[id]": [~6],
|
||||||
|
"/[site]": [~7,[2]]
|
||||||
};
|
};
|
||||||
|
|
||||||
export const hooks = {
|
export const hooks = {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export { default as component } from "../../../../src/routes/[site]/+page.svelte";
|
export { default as component } from "../../../../src/routes/users/+page.svelte";
|
||||||
@@ -1 +1 @@
|
|||||||
export { default as component } from "../../../../src/routes/login/+page.svelte";
|
export { default as component } from "../../../../src/routes/users/[id]/+page.svelte";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as component } from "../../../../src/routes/[site]/+page.svelte";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as component } from "../../../../src/routes/login/+page.svelte";
|
||||||
@@ -24,7 +24,7 @@ export const options = {
|
|||||||
app: ({ head, body, assets, nonce, env }) => "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <link rel=\"icon\" href=\"" + assets + "/favicon.png\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=5, viewport-fit=cover\" />\n " + head + "\n </head>\n <body data-sveltekit-preload-data=\"hover\">\n <div style=\"display: contents\">" + body + "</div>\n </body>\n</html>\n",
|
app: ({ head, body, assets, nonce, env }) => "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <link rel=\"icon\" href=\"" + assets + "/favicon.png\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=5, viewport-fit=cover\" />\n " + head + "\n </head>\n <body data-sveltekit-preload-data=\"hover\">\n <div style=\"display: contents\">" + body + "</div>\n </body>\n</html>\n",
|
||||||
error: ({ status, message }) => "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>" + message + "</title>\n\n\t\t<style>\n\t\t\tbody {\n\t\t\t\t--bg: white;\n\t\t\t\t--fg: #222;\n\t\t\t\t--divider: #ccc;\n\t\t\t\tbackground: var(--bg);\n\t\t\t\tcolor: var(--fg);\n\t\t\t\tfont-family:\n\t\t\t\t\tsystem-ui,\n\t\t\t\t\t-apple-system,\n\t\t\t\t\tBlinkMacSystemFont,\n\t\t\t\t\t'Segoe UI',\n\t\t\t\t\tRoboto,\n\t\t\t\t\tOxygen,\n\t\t\t\t\tUbuntu,\n\t\t\t\t\tCantarell,\n\t\t\t\t\t'Open Sans',\n\t\t\t\t\t'Helvetica Neue',\n\t\t\t\t\tsans-serif;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\theight: 100vh;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t.error {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tmax-width: 32rem;\n\t\t\t\tmargin: 0 1rem;\n\t\t\t}\n\n\t\t\t.status {\n\t\t\t\tfont-weight: 200;\n\t\t\t\tfont-size: 3rem;\n\t\t\t\tline-height: 1;\n\t\t\t\tposition: relative;\n\t\t\t\ttop: -0.05rem;\n\t\t\t}\n\n\t\t\t.message {\n\t\t\t\tborder-left: 1px solid var(--divider);\n\t\t\t\tpadding: 0 0 0 1rem;\n\t\t\t\tmargin: 0 0 0 1rem;\n\t\t\t\tmin-height: 2.5rem;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t}\n\n\t\t\t.message h1 {\n\t\t\t\tfont-weight: 400;\n\t\t\t\tfont-size: 1em;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\tbody {\n\t\t\t\t\t--bg: #222;\n\t\t\t\t\t--fg: #ddd;\n\t\t\t\t\t--divider: #666;\n\t\t\t\t}\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div class=\"error\">\n\t\t\t<span class=\"status\">" + status + "</span>\n\t\t\t<div class=\"message\">\n\t\t\t\t<h1>" + message + "</h1>\n\t\t\t</div>\n\t\t</div>\n\t</body>\n</html>\n"
|
error: ({ status, message }) => "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>" + message + "</title>\n\n\t\t<style>\n\t\t\tbody {\n\t\t\t\t--bg: white;\n\t\t\t\t--fg: #222;\n\t\t\t\t--divider: #ccc;\n\t\t\t\tbackground: var(--bg);\n\t\t\t\tcolor: var(--fg);\n\t\t\t\tfont-family:\n\t\t\t\t\tsystem-ui,\n\t\t\t\t\t-apple-system,\n\t\t\t\t\tBlinkMacSystemFont,\n\t\t\t\t\t'Segoe UI',\n\t\t\t\t\tRoboto,\n\t\t\t\t\tOxygen,\n\t\t\t\t\tUbuntu,\n\t\t\t\t\tCantarell,\n\t\t\t\t\t'Open Sans',\n\t\t\t\t\t'Helvetica Neue',\n\t\t\t\t\tsans-serif;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\theight: 100vh;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t.error {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tmax-width: 32rem;\n\t\t\t\tmargin: 0 1rem;\n\t\t\t}\n\n\t\t\t.status {\n\t\t\t\tfont-weight: 200;\n\t\t\t\tfont-size: 3rem;\n\t\t\t\tline-height: 1;\n\t\t\t\tposition: relative;\n\t\t\t\ttop: -0.05rem;\n\t\t\t}\n\n\t\t\t.message {\n\t\t\t\tborder-left: 1px solid var(--divider);\n\t\t\t\tpadding: 0 0 0 1rem;\n\t\t\t\tmargin: 0 0 0 1rem;\n\t\t\t\tmin-height: 2.5rem;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t}\n\n\t\t\t.message h1 {\n\t\t\t\tfont-weight: 400;\n\t\t\t\tfont-size: 1em;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\tbody {\n\t\t\t\t\t--bg: #222;\n\t\t\t\t\t--fg: #ddd;\n\t\t\t\t\t--divider: #666;\n\t\t\t\t}\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div class=\"error\">\n\t\t\t<span class=\"status\">" + status + "</span>\n\t\t\t<div class=\"message\">\n\t\t\t\t<h1>" + message + "</h1>\n\t\t\t</div>\n\t\t</div>\n\t</body>\n</html>\n"
|
||||||
},
|
},
|
||||||
version_hash: "xmp3r"
|
version_hash: "1untsbf"
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function get_hooks() {
|
export async function get_hooks() {
|
||||||
|
|||||||
Vendored
+6
-3
@@ -27,19 +27,22 @@ export {};
|
|||||||
|
|
||||||
declare module "$app/types" {
|
declare module "$app/types" {
|
||||||
export interface AppTypes {
|
export interface AppTypes {
|
||||||
RouteId(): "/" | "/api" | "/api/darkmode" | "/login" | "/logout" | "/[site]";
|
RouteId(): "/" | "/api" | "/api/darkmode" | "/login" | "/logout" | "/users" | "/users/[id]" | "/[site]";
|
||||||
RouteParams(): {
|
RouteParams(): {
|
||||||
|
"/users/[id]": { id: string };
|
||||||
"/[site]": { site: string }
|
"/[site]": { site: string }
|
||||||
};
|
};
|
||||||
LayoutParams(): {
|
LayoutParams(): {
|
||||||
"/": { site?: string };
|
"/": { id?: string; site?: string };
|
||||||
"/api": Record<string, never>;
|
"/api": Record<string, never>;
|
||||||
"/api/darkmode": Record<string, never>;
|
"/api/darkmode": Record<string, never>;
|
||||||
"/login": Record<string, never>;
|
"/login": Record<string, never>;
|
||||||
"/logout": Record<string, never>;
|
"/logout": Record<string, never>;
|
||||||
|
"/users": { id?: string };
|
||||||
|
"/users/[id]": { id: string };
|
||||||
"/[site]": { site: string }
|
"/[site]": { site: string }
|
||||||
};
|
};
|
||||||
Pathname(): "/" | "/api/darkmode" | "/login" | "/logout" | `/${string}` & {};
|
Pathname(): "/" | "/api/darkmode" | "/login" | "/logout" | "/users" | `/users/${string}` & {} | `/${string}` & {};
|
||||||
ResolvedPathname(): `${"" | `/${string}`}${ReturnType<AppTypes['Pathname']>}`;
|
ResolvedPathname(): `${"" | `/${string}`}${ReturnType<AppTypes['Pathname']>}`;
|
||||||
Asset(): string & {};
|
Asset(): string & {};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,14 @@
|
|||||||
"src/routes/logout/+page.server.ts",
|
"src/routes/logout/+page.server.ts",
|
||||||
"src/routes/+layout.server.ts"
|
"src/routes/+layout.server.ts"
|
||||||
],
|
],
|
||||||
|
"/users": [
|
||||||
|
"src/routes/users/+page.server.ts",
|
||||||
|
"src/routes/+layout.server.ts"
|
||||||
|
],
|
||||||
|
"/users/[id]": [
|
||||||
|
"src/routes/users/[id]/+page.server.ts",
|
||||||
|
"src/routes/+layout.server.ts"
|
||||||
|
],
|
||||||
"/[site]": [
|
"/[site]": [
|
||||||
"src/routes/[site]/+page.server.ts",
|
"src/routes/[site]/+page.server.ts",
|
||||||
"src/routes/+layout.server.ts",
|
"src/routes/+layout.server.ts",
|
||||||
|
|||||||
+2
-2
@@ -12,8 +12,8 @@ type EnsureDefined<T> = T extends null | undefined ? {} : T;
|
|||||||
type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;
|
type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;
|
||||||
export type Snapshot<T = any> = Kit.Snapshot<T>;
|
export type Snapshot<T = any> = Kit.Snapshot<T>;
|
||||||
type PageParentData = EnsureDefined<LayoutData>;
|
type PageParentData = EnsureDefined<LayoutData>;
|
||||||
type LayoutRouteId = RouteId | "/" | "/logout" | "/[site]" | "/login" | null
|
type LayoutRouteId = RouteId | "/" | "/logout" | "/users" | "/users/[id]" | "/[site]" | "/login" | null
|
||||||
type LayoutParams = RouteParams & { site?: string }
|
type LayoutParams = RouteParams & { id?: string; site?: string }
|
||||||
type LayoutServerParentData = EnsureDefined<{}>;
|
type LayoutServerParentData = EnsureDefined<{}>;
|
||||||
type LayoutParentData = EnsureDefined<{}>;
|
type LayoutParentData = EnsureDefined<{}>;
|
||||||
|
|
||||||
|
|||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
import type * as Kit from '@sveltejs/kit';
|
||||||
|
|
||||||
|
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
|
||||||
|
// @ts-ignore
|
||||||
|
type MatcherParam<M> = M extends (param : string) => param is infer U ? U extends string ? U : string : string;
|
||||||
|
type RouteParams = { };
|
||||||
|
type RouteId = '/users';
|
||||||
|
type MaybeWithVoid<T> = {} extends T ? T | void : T;
|
||||||
|
export type RequiredKeys<T> = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T];
|
||||||
|
type OutputDataShape<T> = MaybeWithVoid<Omit<App.PageData, RequiredKeys<T>> & Partial<Pick<App.PageData, keyof T & keyof App.PageData>> & Record<string, any>>
|
||||||
|
type EnsureDefined<T> = T extends null | undefined ? {} : T;
|
||||||
|
type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;
|
||||||
|
export type Snapshot<T = any> = Kit.Snapshot<T>;
|
||||||
|
type PageServerParentData = EnsureDefined<import('../$types.js').LayoutServerData>;
|
||||||
|
type PageParentData = EnsureDefined<import('../$types.js').LayoutData>;
|
||||||
|
|
||||||
|
export type PageServerLoad<OutputData extends OutputDataShape<PageServerParentData> = OutputDataShape<PageServerParentData>> = Kit.ServerLoad<RouteParams, PageServerParentData, OutputData, RouteId>;
|
||||||
|
export type PageServerLoadEvent = Parameters<PageServerLoad>[0];
|
||||||
|
export type ActionData = unknown;
|
||||||
|
export type PageServerData = Expand<OptionalUnion<EnsureDefined<Kit.LoadProperties<Awaited<ReturnType<typeof import('./proxy+page.server.js').load>>>>>>;
|
||||||
|
export type PageData = Expand<Omit<PageParentData, keyof PageServerData> & EnsureDefined<PageServerData>>;
|
||||||
|
export type Action<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Action<RouteParams, OutputData, RouteId>
|
||||||
|
export type Actions<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Actions<RouteParams, OutputData, RouteId>
|
||||||
|
export type PageProps = { params: RouteParams; data: PageData; form: ActionData }
|
||||||
|
export type RequestEvent = Kit.RequestEvent<RouteParams, RouteId>;
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import type * as Kit from '@sveltejs/kit';
|
||||||
|
|
||||||
|
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
|
||||||
|
// @ts-ignore
|
||||||
|
type MatcherParam<M> = M extends (param : string) => param is infer U ? U extends string ? U : string : string;
|
||||||
|
type RouteParams = { id: string };
|
||||||
|
type RouteId = '/users/[id]';
|
||||||
|
type MaybeWithVoid<T> = {} extends T ? T | void : T;
|
||||||
|
export type RequiredKeys<T> = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T];
|
||||||
|
type OutputDataShape<T> = MaybeWithVoid<Omit<App.PageData, RequiredKeys<T>> & Partial<Pick<App.PageData, keyof T & keyof App.PageData>> & Record<string, any>>
|
||||||
|
type EnsureDefined<T> = T extends null | undefined ? {} : T;
|
||||||
|
type OptionalUnion<U extends Record<string, any>, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;
|
||||||
|
export type Snapshot<T = any> = Kit.Snapshot<T>;
|
||||||
|
type PageServerParentData = EnsureDefined<import('../../$types.js').LayoutServerData>;
|
||||||
|
type PageParentData = EnsureDefined<import('../../$types.js').LayoutData>;
|
||||||
|
|
||||||
|
export type EntryGenerator = () => Promise<Array<RouteParams>> | Array<RouteParams>;
|
||||||
|
export type PageServerLoad<OutputData extends OutputDataShape<PageServerParentData> = OutputDataShape<PageServerParentData>> = Kit.ServerLoad<RouteParams, PageServerParentData, OutputData, RouteId>;
|
||||||
|
export type PageServerLoadEvent = Parameters<PageServerLoad>[0];
|
||||||
|
export type ActionData = unknown;
|
||||||
|
export type PageServerData = Expand<OptionalUnion<EnsureDefined<Kit.LoadProperties<Awaited<ReturnType<typeof import('./proxy+page.server.js').load>>>>>>;
|
||||||
|
export type PageData = Expand<Omit<PageParentData, keyof PageServerData> & EnsureDefined<PageServerData>>;
|
||||||
|
export type Action<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Action<RouteParams, OutputData, RouteId>
|
||||||
|
export type Actions<OutputData extends Record<string, any> | void = Record<string, any> | void> = Kit.Actions<RouteParams, OutputData, RouteId>
|
||||||
|
export type PageProps = { params: RouteParams; data: PageData; form: ActionData }
|
||||||
|
export type RequestEvent = Kit.RequestEvent<RouteParams, RouteId>;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { error } from '@sveltejs/kit';
|
||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
import { mapUserRecord } from '$lib/user-from-record';
|
||||||
|
|
||||||
|
export const load = async ({ params, locals }: Parameters<PageServerLoad>[0]) => {
|
||||||
|
const id = params.id;
|
||||||
|
if (!id) throw error(404, 'User not found');
|
||||||
|
try {
|
||||||
|
const record = (await locals.pb.collection('users').getOne(id)) as Record<string, unknown>;
|
||||||
|
return { user: mapUserRecord(record) };
|
||||||
|
} catch {
|
||||||
|
throw error(404, 'User not found');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
import { mapUserRecord } from '$lib/user-from-record';
|
||||||
|
|
||||||
|
export const load = async ({ locals }: Parameters<PageServerLoad>[0]) => {
|
||||||
|
const list = await locals.pb.collection('users').getFullList();
|
||||||
|
const users = list.map((r) => mapUserRecord(r as Record<string, unknown>));
|
||||||
|
return { users };
|
||||||
|
};
|
||||||
Vendored
+11
-1
@@ -5,7 +5,17 @@ declare global {
|
|||||||
namespace App {
|
namespace App {
|
||||||
interface Locals {
|
interface Locals {
|
||||||
pb: PocketBase;
|
pb: PocketBase;
|
||||||
user: { id: string; email?: string; name?: string; avatar?: string; darkmode: boolean } | null;
|
user: {
|
||||||
|
id: string;
|
||||||
|
email?: string;
|
||||||
|
name?: string;
|
||||||
|
avatar?: string;
|
||||||
|
darkmode: boolean;
|
||||||
|
prism?: boolean;
|
||||||
|
tasgrid?: boolean;
|
||||||
|
stackq?: boolean;
|
||||||
|
hrm?: boolean;
|
||||||
|
} | null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-9
@@ -1,6 +1,7 @@
|
|||||||
import { redirect } from '@sveltejs/kit';
|
import { redirect } from '@sveltejs/kit';
|
||||||
import PocketBase from 'pocketbase';
|
import PocketBase from 'pocketbase';
|
||||||
import type { Handle } from '@sveltejs/kit';
|
import type { Handle } from '@sveltejs/kit';
|
||||||
|
import { mapUserRecord } from '$lib/user-from-record';
|
||||||
|
|
||||||
const POCKETBASE_URL =
|
const POCKETBASE_URL =
|
||||||
process.env.VITE_POCKETBASE_URL ?? 'https://pocketbase.ccllc.pro';
|
process.env.VITE_POCKETBASE_URL ?? 'https://pocketbase.ccllc.pro';
|
||||||
@@ -28,15 +29,16 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const record = event.locals.pb.authStore.record;
|
const record = event.locals.pb.authStore.record;
|
||||||
event.locals.user = record
|
if (!record) {
|
||||||
? {
|
event.locals.user = null;
|
||||||
id: record.id,
|
} else {
|
||||||
email: record.email,
|
try {
|
||||||
name: record.name,
|
const full = (await event.locals.pb.collection('users').getOne(record.id)) as Record<string, unknown>;
|
||||||
avatar: record.avatar,
|
event.locals.user = mapUserRecord(full);
|
||||||
darkmode: !!record.darkmode
|
} catch {
|
||||||
}
|
event.locals.user = mapUserRecord(record as Record<string, unknown>);
|
||||||
: null;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!event.locals.user && !isPublicRoute(event.url.pathname)) {
|
if (!event.locals.user && !isPublicRoute(event.url.pathname)) {
|
||||||
const redirectTo = event.url.pathname + event.url.search;
|
const redirectTo = event.url.pathname + event.url.search;
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Map a PocketBase users collection record to our app user shape.
|
||||||
|
* Handles multiple possible field names (PRISM, prism, TasGrid, etc.).
|
||||||
|
*/
|
||||||
|
|
||||||
|
function bool(r: Record<string, unknown>, ...keys: string[]): boolean {
|
||||||
|
for (const k of keys) {
|
||||||
|
const v = r[k];
|
||||||
|
if (v === true || v === 'true') return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserFromRecord = NonNullable<App.Locals['user']>;
|
||||||
|
|
||||||
|
export function mapUserRecord(r: Record<string, unknown>): UserFromRecord {
|
||||||
|
return {
|
||||||
|
id: r.id as string,
|
||||||
|
email: r.email as string | undefined,
|
||||||
|
name: r.name as string | undefined,
|
||||||
|
avatar: r.avatar as string | undefined,
|
||||||
|
darkmode: bool(r, 'darkmode'),
|
||||||
|
prism: bool(r, 'PRISM', 'prism', 'Prism'),
|
||||||
|
tasgrid: bool(r, 'TasGrid', 'tasgrid'),
|
||||||
|
stackq: bool(r, 'Stackq', 'stackq'),
|
||||||
|
hrm: bool(r, 'HRM', 'hrm', 'Hrm')
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,9 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import { sites, siteSlugs } from '$lib/sites';
|
import { sites, siteSlugs, type SiteSlug } from '$lib/sites';
|
||||||
|
|
||||||
let { data }: { data: Record<string, unknown> } = $props();
|
let { data }: { data: App.PageData } = $props();
|
||||||
|
|
||||||
|
const visibleSlugs = $derived(
|
||||||
|
data.user
|
||||||
|
? (siteSlugs.filter((slug) => data.user && data.user[slug] === true) as SiteSlug[])
|
||||||
|
: []
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if browser}
|
{#if browser}
|
||||||
@@ -18,7 +24,7 @@
|
|||||||
<main class="flex min-h-0 flex-1 flex-col items-center justify-center gap-4 p-4 sm:gap-6 sm:p-6">
|
<main class="flex min-h-0 flex-1 flex-col items-center justify-center gap-4 p-4 sm:gap-6 sm:p-6">
|
||||||
<h1 class="text-2xl font-semibold">Base</h1>
|
<h1 class="text-2xl font-semibold">Base</h1>
|
||||||
<div class="flex w-full max-w-xs flex-col gap-3">
|
<div class="flex w-full max-w-xs flex-col gap-3">
|
||||||
{#each siteSlugs as slug}
|
{#each visibleSlugs as slug}
|
||||||
{@const site = sites[slug]}
|
{@const site = sites[slug]}
|
||||||
<a href="/{slug}" class="block">
|
<a href="/{slug}" class="block">
|
||||||
<Button class="h-16 w-full min-h-16" variant="outline">{site.title}</Button>
|
<Button class="h-16 w-full min-h-16" variant="outline">{site.title}</Button>
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import { sites, siteSlugs } from '$lib/sites';
|
import { sites, siteSlugs, type SiteSlug } from '$lib/sites';
|
||||||
|
|
||||||
let { data: _data }: { data: Record<string, unknown> } = $props();
|
let { data }: { data: App.PageData } = $props();
|
||||||
// `_data` is accepted to mirror Stackq's pattern (unused for now).
|
|
||||||
|
const visibleSlugs = $derived(
|
||||||
|
data.user
|
||||||
|
? (siteSlugs.filter((slug) => data.user && data.user[slug] === true) as SiteSlug[])
|
||||||
|
: []
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="flex min-h-0 flex-1 flex-col items-center justify-center gap-4 p-4 sm:gap-6 sm:p-6">
|
<main class="flex min-h-0 flex-1 flex-col items-center justify-center gap-4 p-4 sm:gap-6 sm:p-6">
|
||||||
<h1 class="w-full max-w-xs shrink-0 text-center text-2xl font-semibold">Base</h1>
|
<h1 class="w-full max-w-xs shrink-0 text-center text-2xl font-semibold">Base</h1>
|
||||||
<div class="flex w-full max-w-xs shrink-0 flex-col gap-3">
|
<div class="flex w-full max-w-xs shrink-0 flex-col gap-3">
|
||||||
{#each siteSlugs as slug}
|
{#each visibleSlugs as slug}
|
||||||
{@const site = sites[slug]}
|
{@const site = sites[slug]}
|
||||||
<a href="/{slug}" class="block">
|
<a href="/{slug}" class="block">
|
||||||
<Button class="h-16 min-h-16 w-full" variant="outline">
|
<Button class="h-16 min-h-16 w-full" variant="outline">
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
import { mapUserRecord } from '$lib/user-from-record';
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ locals }) => {
|
||||||
|
const list = await locals.pb.collection('users').getFullList();
|
||||||
|
const users = list.map((r) => mapUserRecord(r as Record<string, unknown>));
|
||||||
|
return { users };
|
||||||
|
};
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import * as Card from '$lib/components/ui/card';
|
||||||
|
import { Button } from '$lib/components/ui/button';
|
||||||
|
import type { UserFromRecord } from '$lib/user-from-record';
|
||||||
|
|
||||||
|
let { data }: { data: { users: UserFromRecord[] } } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Users — Base</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<main class="flex min-h-0 flex-1 flex-col gap-4 p-4 sm:gap-6 sm:p-6">
|
||||||
|
<h1 class="text-2xl font-semibold">Users</h1>
|
||||||
|
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{#each data.users as user (user.id)}
|
||||||
|
<Card.Root class="flex flex-col">
|
||||||
|
<Card.Header class="flex flex-row items-center gap-3">
|
||||||
|
<div class="flex size-10 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground text-sm font-medium">
|
||||||
|
{user.name
|
||||||
|
? String(user.name).slice(0, 2).toUpperCase()
|
||||||
|
: user.email
|
||||||
|
? String(user.email).slice(0, 2).toUpperCase()
|
||||||
|
: '?'}
|
||||||
|
</div>
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<Card.Title class="truncate">{user.name ?? user.email ?? user.id}</Card.Title>
|
||||||
|
{#if user.email && user.name}
|
||||||
|
<Card.Description class="truncate text-xs">{user.email}</Card.Description>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</Card.Header>
|
||||||
|
<Card.Content class="flex-1">
|
||||||
|
<p class="text-muted-foreground text-sm">
|
||||||
|
Prism {user.prism ? '✓' : '—'} · TasGrid {user.tasgrid ? '✓' : '—'} · Stackq {user.stackq ? '✓' : '—'} · HRM {user.hrm ? '✓' : '—'}
|
||||||
|
</p>
|
||||||
|
</Card.Content>
|
||||||
|
<Card.Footer>
|
||||||
|
<a href="/users/{user.id}" class="block">
|
||||||
|
<Button variant="outline" class="w-full">View</Button>
|
||||||
|
</a>
|
||||||
|
</Card.Footer>
|
||||||
|
</Card.Root>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { error } from '@sveltejs/kit';
|
||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
import { mapUserRecord } from '$lib/user-from-record';
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ params, locals }) => {
|
||||||
|
const id = params.id;
|
||||||
|
if (!id) throw error(404, 'User not found');
|
||||||
|
try {
|
||||||
|
const record = (await locals.pb.collection('users').getOne(id)) as Record<string, unknown>;
|
||||||
|
return { user: mapUserRecord(record) };
|
||||||
|
} catch {
|
||||||
|
throw error(404, 'User not found');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import * as Card from '$lib/components/ui/card';
|
||||||
|
import { Button } from '$lib/components/ui/button';
|
||||||
|
import type { UserFromRecord } from '$lib/user-from-record';
|
||||||
|
|
||||||
|
let { data }: { data: { user: UserFromRecord } } = $props();
|
||||||
|
const user = data.user;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>{user.name ?? user.email ?? 'User'} — Base</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<main class="flex min-h-0 flex-1 flex-col gap-4 p-4 sm:gap-6 sm:p-6">
|
||||||
|
<a href="/users" class="self-start">
|
||||||
|
<Button variant="ghost" size="sm">← Users</Button>
|
||||||
|
</a>
|
||||||
|
<Card.Root class="max-w-md">
|
||||||
|
<Card.Header>
|
||||||
|
<Card.Title>{user.name ?? user.email ?? user.id}</Card.Title>
|
||||||
|
{#if user.email}
|
||||||
|
<Card.Description>{user.email}</Card.Description>
|
||||||
|
{/if}
|
||||||
|
</Card.Header>
|
||||||
|
<Card.Content class="flex flex-col gap-2">
|
||||||
|
<p class="text-muted-foreground text-sm">
|
||||||
|
<span class="font-medium text-foreground">Access:</span><br />
|
||||||
|
Prism {user.prism ? '✓' : '—'} · TasGrid {user.tasgrid ? '✓' : '—'} · Stackq {user.stackq ? '✓' : '—'} · HRM {user.hrm ? '✓' : '—'}
|
||||||
|
</p>
|
||||||
|
<p class="text-muted-foreground text-sm">
|
||||||
|
<span class="font-medium text-foreground">Dark mode:</span> {user.darkmode ? 'On' : 'Off'}
|
||||||
|
</p>
|
||||||
|
</Card.Content>
|
||||||
|
</Card.Root>
|
||||||
|
</main>
|
||||||
Reference in New Issue
Block a user