mirror of
https://github.com/bartvdbraak/omnidash.git
synced 2025-10-25 21:59:09 +00:00
feat: added particle and reformatting
This commit is contained in:
parent
5158767019
commit
32b6bf7582
147 changed files with 1186 additions and 922 deletions
|
|
@ -4,7 +4,7 @@ import type { Actions } from './$types';
|
|||
export const actions = {
|
||||
default: async ({ request, locals }: { request: Request; locals: App.Locals }) => {
|
||||
const body = Object.fromEntries(await request.formData());
|
||||
|
||||
|
||||
try {
|
||||
const email = body.email.toString();
|
||||
const password = body.password.toString();
|
||||
|
|
@ -21,7 +21,7 @@ export const actions = {
|
|||
}
|
||||
|
||||
throw redirect(303, '/');
|
||||
},
|
||||
}
|
||||
// TODO: Implement MS Auth
|
||||
// msauth: async ({ request, cookies }) => {
|
||||
// const form = await request.formData();
|
||||
|
|
|
|||
|
|
@ -31,12 +31,17 @@
|
|||
<div class="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
|
||||
<div class="flex flex-col space-y-2 text-center">
|
||||
<h1 class="text-2xl font-semibold tracking-tight">Log into your account</h1>
|
||||
<p class="text-muted-foreground text-sm">
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Enter your email and password below to log into your account
|
||||
</p>
|
||||
</div>
|
||||
<div class={cn('grid gap-6')} {...$$restProps}>
|
||||
<form method="POST" use:enhance={() => { isLoading = true; }}>
|
||||
<form
|
||||
method="POST"
|
||||
use:enhance={() => {
|
||||
isLoading = true;
|
||||
}}
|
||||
>
|
||||
<div class="grid gap-2">
|
||||
<div class="grid gap-1">
|
||||
<Label class="sr-only" for="email">Email</Label>
|
||||
|
|
@ -80,7 +85,7 @@
|
|||
<span class="w-full border-t" />
|
||||
</div>
|
||||
<div class="relative flex justify-center text-xs uppercase">
|
||||
<span class="bg-background text-muted-foreground px-2"> Or continue with </span>
|
||||
<span class="bg-background px-2 text-muted-foreground"> Or continue with </span>
|
||||
</div>
|
||||
</div>
|
||||
<form action="/?msauth" method="POST">
|
||||
|
|
@ -95,7 +100,7 @@
|
|||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<p class="text-muted-foreground px-8 text-center text-sm">
|
||||
<p class="px-8 text-center text-sm text-muted-foreground">
|
||||
Don't have an account? <a class="text-primary underline" href="/register">Sign up.</a> <br />
|
||||
Forgot password? <a class="text-primary underline" href="/reset-password">Reset password.</a>
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { redirect } from "@sveltejs/kit";
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export const GET = ({locals}: {locals: App.Locals}) => {
|
||||
locals.pocketBase.authStore.clear();
|
||||
throw redirect(303, '/login');
|
||||
}
|
||||
export const GET = ({ locals }: { locals: App.Locals }) => {
|
||||
locals.pocketBase.authStore.clear();
|
||||
throw redirect(303, '/login');
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,22 +13,19 @@
|
|||
<div class="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
|
||||
<div class="flex flex-col space-y-2 text-center">
|
||||
<h1 class="text-2xl font-semibold tracking-tight">Create your account</h1>
|
||||
<p class="text-muted-foreground text-sm">
|
||||
Enter your details below to create a new account
|
||||
</p>
|
||||
<p class="text-sm text-muted-foreground">Enter your details below to create a new account</p>
|
||||
</div>
|
||||
<div class={cn('grid gap-6')} {...$$restProps}>
|
||||
<form method="POST" use:enhance={() => { isLoading = true; }}>
|
||||
<form
|
||||
method="POST"
|
||||
use:enhance={() => {
|
||||
isLoading = true;
|
||||
}}
|
||||
>
|
||||
<div class="grid gap-2">
|
||||
<div class="grid gap-1">
|
||||
<Label class="sr-only" for="email">Name</Label>
|
||||
<Input
|
||||
id="name"
|
||||
name="name"
|
||||
placeholder="Name"
|
||||
type="name"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<Input id="name" name="name" placeholder="Name" type="name" disabled={isLoading} />
|
||||
</div>
|
||||
<div class="grid gap-1">
|
||||
<Label class="sr-only" for="email">Email</Label>
|
||||
|
|
@ -45,13 +42,25 @@
|
|||
</div>
|
||||
<div class="grid gap-1">
|
||||
<Label class="sr-only" for="password">Password</Label>
|
||||
<Input id="password" name="password" type="password" disabled={isLoading} placeholder="Password" />
|
||||
<Input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
disabled={isLoading}
|
||||
placeholder="Password"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid gap-1">
|
||||
<Label class="sr-only" for="password">Confirm Password</Label>
|
||||
<Input id="password" name="passwordConfirm" type="password" disabled={isLoading} placeholder="Confirm password" />
|
||||
<Input
|
||||
id="password"
|
||||
name="passwordConfirm"
|
||||
type="password"
|
||||
disabled={isLoading}
|
||||
placeholder="Confirm password"
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" disabled={isLoading} on:click={() => isLoading = true}>
|
||||
<Button type="submit" disabled={isLoading} on:click={() => (isLoading = true)}>
|
||||
{#if isLoading}
|
||||
<Icons.spinner class="mr-2 h-4 w-4 animate-spin" />
|
||||
{/if}
|
||||
|
|
@ -64,7 +73,7 @@
|
|||
<span class="w-full border-t" />
|
||||
</div>
|
||||
<div class="relative flex justify-center text-xs uppercase">
|
||||
<span class="bg-background text-muted-foreground px-2"> Or continue with </span>
|
||||
<span class="bg-background px-2 text-muted-foreground"> Or continue with </span>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" type="button" disabled={true}>
|
||||
|
|
@ -77,8 +86,9 @@
|
|||
Microsoft
|
||||
</Button>
|
||||
</div>
|
||||
<p class="text-muted-foreground px-8 text-center text-sm">
|
||||
Or <a class="text-primary underline" href="/login">sign in</a> if you already have an account.<br />
|
||||
<p class="px-8 text-center text-sm text-muted-foreground">
|
||||
Or <a class="text-primary underline" href="/login">sign in</a> if you already have an account.<br
|
||||
/>
|
||||
Forgot password? <a class="text-primary underline" href="/reset-password">Reset password.</a>
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,4 +15,4 @@ export const actions = {
|
|||
throw error(500, 'Something went wrong');
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { cn } from '$lib/utils';
|
||||
import { CheckCircled } from "radix-icons-svelte";
|
||||
import * as Alert from "$lib/components/ui/alert";
|
||||
import { CheckCircled } from 'radix-icons-svelte';
|
||||
import * as Alert from '$lib/components/ui/alert';
|
||||
|
||||
let isLoading = false;
|
||||
export let form;
|
||||
|
|
@ -16,16 +16,21 @@
|
|||
<div class="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
|
||||
<div class="flex flex-col space-y-2 text-center">
|
||||
<h1 class="text-2xl font-semibold tracking-tight">Reset password</h1>
|
||||
<p class="text-muted-foreground text-sm">
|
||||
<p class="text-sm text-muted-foreground">
|
||||
We'll send you an email with a link to reset your password.
|
||||
</p>
|
||||
</div>
|
||||
<div class={cn('grid gap-6')} {...$$restProps}>
|
||||
<form method="POST" use:enhance={() => { isLoading = true;
|
||||
return async ({ update }) => {
|
||||
isLoading = false;
|
||||
update();
|
||||
}; }}>
|
||||
<form
|
||||
method="POST"
|
||||
use:enhance={() => {
|
||||
isLoading = true;
|
||||
return async ({ update }) => {
|
||||
isLoading = false;
|
||||
update();
|
||||
};
|
||||
}}
|
||||
>
|
||||
<div class="grid gap-2">
|
||||
<div class="grid gap-1">
|
||||
<Label class="sr-only" for="email">Email</Label>
|
||||
|
|
@ -50,9 +55,7 @@
|
|||
{#if form?.success}
|
||||
<Alert.Root variant="default" class="mt-2">
|
||||
<CheckCircled class="h-4 w-4" />
|
||||
<Alert.Description
|
||||
>An email has been sent to reset your password.</Alert.Description
|
||||
>
|
||||
<Alert.Description>An email has been sent to reset your password.</Alert.Description>
|
||||
</Alert.Root>
|
||||
{/if}
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export const load = (async ({ locals }: { locals: App.Locals }) => {
|
||||
export const load = async ({ locals }: { locals: App.Locals }) => {
|
||||
if (!locals.pocketBase.authStore.isValid) {
|
||||
throw redirect(303, '/register');
|
||||
}
|
||||
|
||||
return {};
|
||||
})
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { Checkbox } from "$lib/components/ui/checkbox";
|
||||
import type { HTMLButtonAttributes } from "svelte/elements";
|
||||
import type { Writable } from "svelte/store";
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import type { HTMLButtonAttributes } from 'svelte/elements';
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
type $$Props = HTMLButtonAttributes & {
|
||||
checked: Writable<boolean>;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
<script lang="ts">
|
||||
import { ArrowDown, ArrowUp, CaretSort } from "radix-icons-svelte";
|
||||
import { cn } from "$lib/utils";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import { ArrowDown, ArrowUp, CaretSort } from 'radix-icons-svelte';
|
||||
import { cn } from '$lib/utils';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
|
||||
let className: string | undefined | null = undefined;
|
||||
export { className as class };
|
||||
export let props: {
|
||||
select: never;
|
||||
sort: {
|
||||
order: "desc" | "asc" | undefined;
|
||||
order: 'desc' | 'asc' | undefined;
|
||||
toggle: (event: Event) => void;
|
||||
clear: () => void;
|
||||
disabled: boolean;
|
||||
|
|
@ -18,14 +18,14 @@
|
|||
};
|
||||
|
||||
function handleAscSort(e: Event) {
|
||||
if (props.sort.order === "asc") {
|
||||
if (props.sort.order === 'asc') {
|
||||
return;
|
||||
}
|
||||
props.sort.toggle(e);
|
||||
}
|
||||
|
||||
function handleDescSort(e: Event) {
|
||||
if (props.sort.order === "desc") {
|
||||
if (props.sort.order === 'desc') {
|
||||
return;
|
||||
}
|
||||
props.sort.toggle(e);
|
||||
|
|
@ -33,18 +33,14 @@
|
|||
</script>
|
||||
|
||||
{#if !props.sort.disabled}
|
||||
<div class={cn("flex items-center", className)}>
|
||||
<div class={cn('flex items-center', className)}>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild let:builder>
|
||||
<Button
|
||||
variant="ghost"
|
||||
builders={[builder]}
|
||||
class="-ml-3 h-8 data-[state=open]:bg-accent"
|
||||
>
|
||||
<Button variant="ghost" builders={[builder]} class="-ml-3 h-8 data-[state=open]:bg-accent">
|
||||
<slot />
|
||||
{#if props.sort.order === "desc"}
|
||||
{#if props.sort.order === 'desc'}
|
||||
<ArrowDown class="ml-2 h-4 w-4" />
|
||||
{:else if props.sort.order === "asc"}
|
||||
{:else if props.sort.order === 'asc'}
|
||||
<ArrowUp class="ml-2 h-4 w-4" />
|
||||
{:else}
|
||||
<CaretSort class="ml-2 h-4 w-4" />
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<script lang="ts">
|
||||
import { PlusCircled, Check } from "radix-icons-svelte";
|
||||
import * as Command from "$lib/components/ui/command";
|
||||
import * as Popover from "$lib/components/ui/popover";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { cn } from "$lib/utils";
|
||||
import Separator from "$lib/components/ui/separator/separator.svelte";
|
||||
import Badge from "$lib/components/ui/badge/badge.svelte";
|
||||
import type { statuses } from "../(data)/data";
|
||||
import { PlusCircled, Check } from 'radix-icons-svelte';
|
||||
import * as Command from '$lib/components/ui/command';
|
||||
import * as Popover from '$lib/components/ui/popover';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { cn } from '$lib/utils';
|
||||
import Separator from '$lib/components/ui/separator/separator.svelte';
|
||||
import Badge from '$lib/components/ui/badge/badge.svelte';
|
||||
import type { statuses } from '../(data)/data';
|
||||
|
||||
export let filterValues: string[] = [];
|
||||
export let title: string;
|
||||
|
|
@ -65,13 +65,13 @@
|
|||
>
|
||||
<div
|
||||
class={cn(
|
||||
"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
|
||||
'mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary',
|
||||
filterValues.includes(option.value)
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "opacity-50 [&_svg]:invisible"
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'opacity-50 [&_svg]:invisible'
|
||||
)}
|
||||
>
|
||||
<Check className={cn("h-4 w-4")} />
|
||||
<Check className={cn('h-4 w-4')} />
|
||||
</div>
|
||||
<span>
|
||||
{option.label}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,11 @@
|
|||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import {
|
||||
ChevronRight,
|
||||
ChevronLeft,
|
||||
DoubleArrowRight,
|
||||
DoubleArrowLeft
|
||||
} from "radix-icons-svelte";
|
||||
import * as Select from "$lib/components/ui/select";
|
||||
import type { Ticket } from "../(data)/schemas";
|
||||
import type { AnyPlugins } from "svelte-headless-table/plugins";
|
||||
import type { TableViewModel } from "svelte-headless-table";
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { ChevronRight, ChevronLeft, DoubleArrowRight, DoubleArrowLeft } from 'radix-icons-svelte';
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import type { Ticket } from '../(data)/schemas';
|
||||
import type { AnyPlugins } from 'svelte-headless-table/plugins';
|
||||
import type { TableViewModel } from 'svelte-headless-table';
|
||||
import { defaultPageSize } from '.';
|
||||
|
||||
export let tableModel: TableViewModel<Ticket, AnyPlugins>;
|
||||
|
||||
|
|
@ -22,7 +18,7 @@
|
|||
|
||||
<div class="flex items-center justify-between px-2">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
{Object.keys($selectedDataIds).length} of{" "}
|
||||
{Object.keys($selectedDataIds).length} of{' '}
|
||||
{$rows.length} row(s) selected.
|
||||
</div>
|
||||
<div class="flex items-center space-x-6 lg:space-x-8">
|
||||
|
|
@ -30,7 +26,7 @@
|
|||
<p class="text-sm font-medium">Rows per page</p>
|
||||
<Select.Root
|
||||
onSelectedChange={(selected) => pageSize.set(Number(selected?.value))}
|
||||
selected={{ value: 10, label: "10" }}
|
||||
selected={{ value: defaultPageSize, label: defaultPageSize.toString() }}
|
||||
>
|
||||
<Select.Trigger class="w-[180px]">
|
||||
<Select.Value placeholder="Select page size" />
|
||||
|
|
@ -41,6 +37,7 @@
|
|||
<Select.Item value="30">30</Select.Item>
|
||||
<Select.Item value="40">40</Select.Item>
|
||||
<Select.Item value="50">50</Select.Item>
|
||||
<Select.Item value="100">100</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
|
@ -59,7 +56,7 @@
|
|||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
class="p-0 w-8 h-8"
|
||||
class="h-8 w-8 p-0"
|
||||
on:click={() => ($pageIndex = $pageIndex - 1)}
|
||||
disabled={!$hasPreviousPage}
|
||||
>
|
||||
|
|
@ -68,9 +65,9 @@
|
|||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
class="p-0 w-8 h-8"
|
||||
disabled={!$hasNextPage}
|
||||
class="h-8 w-8 p-0"
|
||||
on:click={() => ($pageIndex = $pageIndex + 1)}
|
||||
disabled={!$hasNextPage}
|
||||
>
|
||||
<span class="sr-only">Go to next page</span>
|
||||
<ChevronRight size={15} />
|
||||
|
|
@ -78,8 +75,8 @@
|
|||
<Button
|
||||
variant="outline"
|
||||
class="hidden h-8 w-8 p-0 lg:flex"
|
||||
disabled={!$hasNextPage}
|
||||
on:click={() => ($pageIndex = Math.ceil($rows.length / $pageRows.length) - 1)}
|
||||
disabled={!$hasNextPage}
|
||||
>
|
||||
<span class="sr-only">Go to last page</span>
|
||||
<DoubleArrowRight size={15} />
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { priorities } from "../(data)/data";
|
||||
import { priorities } from '../(data)/data';
|
||||
export let value: string;
|
||||
const priority = priorities.find((priority) => priority.value === value);
|
||||
const Icon = priority?.icon;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<script lang="ts">
|
||||
import { DotsHorizontal } from "radix-icons-svelte";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import { labels } from "../(data)/data";
|
||||
import { ticketSchema, type Ticket } from "../(data)/schemas";
|
||||
import { DotsHorizontal } from 'radix-icons-svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { labels } from '../(data)/data';
|
||||
import { ticketSchema, type Ticket } from '../(data)/schemas';
|
||||
|
||||
export let row: Ticket;
|
||||
const task = ticketSchema.parse(row);
|
||||
const ticket = ticketSchema.parse(row);
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
<DropdownMenu.Sub>
|
||||
<DropdownMenu.SubTrigger>Labels</DropdownMenu.SubTrigger>
|
||||
<DropdownMenu.SubContent>
|
||||
<DropdownMenu.RadioGroup value={task.label}>
|
||||
<DropdownMenu.RadioGroup value={ticket.label}>
|
||||
{#each labels as label}
|
||||
<DropdownMenu.RadioItem value={label.value}>
|
||||
{label.label}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { statuses } from "../(data)/data";
|
||||
import { statuses } from '../(data)/data';
|
||||
|
||||
export let value: string;
|
||||
const status = statuses.find((status) => status.value === value);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { Badge } from "$lib/components/ui/badge";
|
||||
import { labels } from "../(data)/data";
|
||||
import { Badge } from '$lib/components/ui/badge';
|
||||
import { labels } from '../(data)/data';
|
||||
|
||||
export let value: string;
|
||||
export let labelValue: string;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
<script lang="ts">
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { DataTableFacetedFilter, DataTableViewOptions } from ".";
|
||||
import type { Ticket } from "../(data)/schemas";
|
||||
import Button from "$lib/components/ui/button/button.svelte";
|
||||
import { Cross2 } from "radix-icons-svelte";
|
||||
import { statuses, priorities } from "../(data)/data";
|
||||
import type { Writable } from "svelte/store";
|
||||
import type { TableViewModel } from "svelte-headless-table";
|
||||
import type { AnyPlugins } from "svelte-headless-table/plugins";
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { DataTableFacetedFilter, DataTableViewOptions } from '.';
|
||||
import type { Ticket } from '../(data)/schemas';
|
||||
import Button from '$lib/components/ui/button/button.svelte';
|
||||
import { Cross2 } from 'radix-icons-svelte';
|
||||
import { statuses, priorities } from '../(data)/data';
|
||||
import type { Writable } from 'svelte/store';
|
||||
import type { TableViewModel } from 'svelte-headless-table';
|
||||
import type { AnyPlugins } from 'svelte-headless-table/plugins';
|
||||
|
||||
export let tableModel: TableViewModel<Ticket, AnyPlugins>;
|
||||
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-1 items-center space-x-2">
|
||||
<Input
|
||||
placeholder="Filter tasks..."
|
||||
placeholder="Filter tickets..."
|
||||
class="h-8 w-[150px] lg:w-[250px]"
|
||||
type="search"
|
||||
bind:value={$filterValue}
|
||||
|
|
@ -52,7 +52,7 @@
|
|||
{#if showReset}
|
||||
<Button
|
||||
on:click={() => {
|
||||
$filterValue = "";
|
||||
$filterValue = '';
|
||||
$filterValues.status = [];
|
||||
$filterValues.priority = [];
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<script lang="ts">
|
||||
import { MixerHorizontal } from "radix-icons-svelte";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import type { Ticket } from "../(data)/schemas";
|
||||
import type { TableViewModel } from "svelte-headless-table";
|
||||
import type { AnyPlugins } from "svelte-headless-table/plugins";
|
||||
import { MixerHorizontal } from 'radix-icons-svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import type { Ticket } from '../(data)/schemas';
|
||||
import type { TableViewModel } from 'svelte-headless-table';
|
||||
import type { AnyPlugins } from 'svelte-headless-table/plugins';
|
||||
|
||||
export let tableModel: TableViewModel<Ticket, AnyPlugins>;
|
||||
const { pluginStates, flatColumns } = tableModel;
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
.filter(([, hide]) => !hide)
|
||||
.map(([id]) => id);
|
||||
|
||||
const hidableCols = ["title", "status", "priority"];
|
||||
const hidableCols = ['title', 'status', 'priority'];
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { get, readable } from "svelte/store";
|
||||
import { Render, Subscribe, createRender, createTable } from "svelte-headless-table";
|
||||
import * as Table from "$lib/components/ui/table";
|
||||
import { get, readable } from 'svelte/store';
|
||||
import { Render, Subscribe, createRender, createTable } from 'svelte-headless-table';
|
||||
import * as Table from '$lib/components/ui/table';
|
||||
import {
|
||||
addColumnFilters,
|
||||
addHiddenColumns,
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
addSelectedRows,
|
||||
addSortBy,
|
||||
addTableFilter
|
||||
} from "svelte-headless-table/plugins";
|
||||
} from 'svelte-headless-table/plugins';
|
||||
import {
|
||||
DataTableCheckbox,
|
||||
DataTableTitleCell,
|
||||
|
|
@ -18,19 +18,20 @@
|
|||
DataTablePriorityCell,
|
||||
DataTableColumnHeader,
|
||||
DataTableToolbar,
|
||||
DataTablePagination
|
||||
} from ".";
|
||||
DataTablePagination,
|
||||
defaultPageSize
|
||||
} from '.';
|
||||
|
||||
import type { Ticket } from "../(data)/schemas";
|
||||
import type { Ticket } from '../(data)/schemas';
|
||||
|
||||
export let data: Ticket[];
|
||||
|
||||
const table = createTable(readable(data), {
|
||||
select: addSelectedRows(),
|
||||
sort: addSortBy({
|
||||
toggleOrder: ["asc", "desc"]
|
||||
toggleOrder: ['asc', 'desc']
|
||||
}),
|
||||
page: addPagination(),
|
||||
page: addPagination({ initialPageSize: defaultPageSize }),
|
||||
filter: addTableFilter({
|
||||
fn: ({ filterValue, value }) => {
|
||||
return value.toLowerCase().includes(filterValue.toLowerCase());
|
||||
|
|
@ -42,12 +43,12 @@
|
|||
|
||||
const columns = table.createColumns([
|
||||
table.display({
|
||||
id: "select",
|
||||
id: 'select',
|
||||
header: (_, { pluginStates }) => {
|
||||
const { allPageRowsSelected } = pluginStates.select;
|
||||
return createRender(DataTableCheckbox, {
|
||||
checked: allPageRowsSelected,
|
||||
"aria-label": "Select all"
|
||||
'aria-label': 'Select all'
|
||||
});
|
||||
},
|
||||
cell: ({ row }, { pluginStates }) => {
|
||||
|
|
@ -55,8 +56,8 @@
|
|||
const { isSelected } = getRowState(row);
|
||||
return createRender(DataTableCheckbox, {
|
||||
checked: isSelected,
|
||||
"aria-label": "Select row",
|
||||
class: "translate-y-[2px]"
|
||||
'aria-label': 'Select row',
|
||||
class: 'translate-y-[2px]'
|
||||
});
|
||||
},
|
||||
plugins: {
|
||||
|
|
@ -66,11 +67,11 @@
|
|||
}
|
||||
}),
|
||||
table.column({
|
||||
accessor: "id",
|
||||
accessor: 'id',
|
||||
header: () => {
|
||||
return "Task";
|
||||
return 'Ticket ID';
|
||||
},
|
||||
id: "task",
|
||||
id: 'ticket',
|
||||
plugins: {
|
||||
sort: {
|
||||
disable: true
|
||||
|
|
@ -78,9 +79,9 @@
|
|||
}
|
||||
}),
|
||||
table.column({
|
||||
accessor: "title",
|
||||
header: "Title",
|
||||
id: "title",
|
||||
accessor: 'title',
|
||||
header: 'Title',
|
||||
id: 'title',
|
||||
cell: ({ value, row }) => {
|
||||
if (row.isData()) {
|
||||
return createRender(DataTableTitleCell, {
|
||||
|
|
@ -92,9 +93,9 @@
|
|||
}
|
||||
}),
|
||||
table.column({
|
||||
accessor: "status",
|
||||
header: "Status",
|
||||
id: "status",
|
||||
accessor: 'status',
|
||||
header: 'Status',
|
||||
id: 'status',
|
||||
cell: ({ value }) => {
|
||||
return createRender(DataTableStatusCell, {
|
||||
value
|
||||
|
|
@ -104,7 +105,7 @@
|
|||
colFilter: {
|
||||
fn: ({ filterValue, value }) => {
|
||||
if (filterValue.length === 0) return true;
|
||||
if (!Array.isArray(filterValue) || typeof value !== "string") return true;
|
||||
if (!Array.isArray(filterValue) || typeof value !== 'string') return true;
|
||||
return filterValue.some((filter) => {
|
||||
return value.includes(filter);
|
||||
});
|
||||
|
|
@ -117,9 +118,9 @@
|
|||
}
|
||||
}),
|
||||
table.column({
|
||||
accessor: "priority",
|
||||
id: "priority",
|
||||
header: "Priority",
|
||||
accessor: 'priority',
|
||||
id: 'priority',
|
||||
header: 'Priority',
|
||||
cell: ({ value }) => {
|
||||
return createRender(DataTablePriorityCell, {
|
||||
value
|
||||
|
|
@ -129,7 +130,7 @@
|
|||
colFilter: {
|
||||
fn: ({ filterValue, value }) => {
|
||||
if (filterValue.length === 0) return true;
|
||||
if (!Array.isArray(filterValue) || typeof value !== "string") return true;
|
||||
if (!Array.isArray(filterValue) || typeof value !== 'string') return true;
|
||||
|
||||
return filterValue.some((filter) => {
|
||||
return value.includes(filter);
|
||||
|
|
@ -143,9 +144,9 @@
|
|||
}
|
||||
}),
|
||||
table.display({
|
||||
id: "actions",
|
||||
id: 'actions',
|
||||
header: () => {
|
||||
return "";
|
||||
return '';
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
if (row.isData() && row.original) {
|
||||
|
|
@ -153,7 +154,7 @@
|
|||
row: row.original
|
||||
});
|
||||
}
|
||||
return "";
|
||||
return '';
|
||||
}
|
||||
})
|
||||
]);
|
||||
|
|
@ -172,18 +173,11 @@
|
|||
<Subscribe rowAttrs={headerRow.attrs()}>
|
||||
<Table.Row>
|
||||
{#each headerRow.cells as cell (cell.id)}
|
||||
<Subscribe
|
||||
attrs={cell.attrs()}
|
||||
let:attrs
|
||||
props={cell.props()}
|
||||
let:props
|
||||
>
|
||||
<Subscribe attrs={cell.attrs()} let:attrs props={cell.props()} let:props>
|
||||
<Table.Head {...attrs}>
|
||||
{#if cell.id !== "select" && cell.id !== "actions"}
|
||||
{#if cell.id !== 'select' && cell.id !== 'actions'}
|
||||
<DataTableColumnHeader {props}
|
||||
><Render
|
||||
of={cell.render()}
|
||||
/></DataTableColumnHeader
|
||||
><Render of={cell.render()} /></DataTableColumnHeader
|
||||
>
|
||||
{:else}
|
||||
<Render of={cell.render()} />
|
||||
|
|
@ -202,7 +196,7 @@
|
|||
{#each row.cells as cell (cell.id)}
|
||||
<Subscribe attrs={cell.attrs()} let:attrs>
|
||||
<Table.Cell {...attrs}>
|
||||
{#if cell.id === "task"}
|
||||
{#if cell.id === 'ticket'}
|
||||
<div class="w-[80px]">
|
||||
<Render of={cell.render()} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
export { default as DataTableCheckbox } from "./data-table-checkbox.svelte";
|
||||
export { default as DataTableTitleCell } from "./data-table-title-cell.svelte";
|
||||
export { default as DataTableStatusCell } from "./data-table-status-cell.svelte";
|
||||
export { default as DataTableRowActions } from "./data-table-row-actions.svelte";
|
||||
export { default as DataTablePriorityCell } from "./data-table-priority-cell.svelte";
|
||||
export { default as DataTableColumnHeader } from "./data-table-column-header.svelte";
|
||||
export { default as DataTableToolbar } from "./data-table-toolbar.svelte";
|
||||
export { default as DataTablePagination } from "./data-table-pagination.svelte";
|
||||
export { default as DataTableViewOptions } from "./data-table-view-options.svelte";
|
||||
export { default as DataTableFacetedFilter } from "./data-table-faceted-filter.svelte";
|
||||
export { default as DataTableCheckbox } from './data-table-checkbox.svelte';
|
||||
export { default as DataTableTitleCell } from './data-table-title-cell.svelte';
|
||||
export { default as DataTableStatusCell } from './data-table-status-cell.svelte';
|
||||
export { default as DataTableRowActions } from './data-table-row-actions.svelte';
|
||||
export { default as DataTablePriorityCell } from './data-table-priority-cell.svelte';
|
||||
export { default as DataTableColumnHeader } from './data-table-column-header.svelte';
|
||||
export { default as DataTableToolbar } from './data-table-toolbar.svelte';
|
||||
export { default as DataTablePagination } from './data-table-pagination.svelte';
|
||||
export { default as DataTableViewOptions } from './data-table-view-options.svelte';
|
||||
export { default as DataTableFacetedFilter } from './data-table-faceted-filter.svelte';
|
||||
|
||||
export const defaultPageSize = 15;
|
||||
|
|
|
|||
|
|
@ -7,65 +7,65 @@ import {
|
|||
CrossCircled,
|
||||
QuestionMarkCircled,
|
||||
Stopwatch
|
||||
} from "radix-icons-svelte";
|
||||
} from 'radix-icons-svelte';
|
||||
|
||||
export const labels = [
|
||||
{
|
||||
value: "bug",
|
||||
label: "Incident"
|
||||
value: 'bug',
|
||||
label: 'Incident'
|
||||
},
|
||||
{
|
||||
value: "feature",
|
||||
label: "Change"
|
||||
value: 'feature',
|
||||
label: 'Change'
|
||||
},
|
||||
{
|
||||
value: "documentation",
|
||||
label: "Information"
|
||||
value: 'documentation',
|
||||
label: 'Information'
|
||||
}
|
||||
];
|
||||
|
||||
export const statuses = [
|
||||
{
|
||||
value: "backlog",
|
||||
label: "Backlog",
|
||||
value: 'backlog',
|
||||
label: 'Backlog',
|
||||
icon: QuestionMarkCircled
|
||||
},
|
||||
{
|
||||
value: "todo",
|
||||
label: "Todo",
|
||||
value: 'todo',
|
||||
label: 'Todo',
|
||||
icon: Circle
|
||||
},
|
||||
{
|
||||
value: "in progress",
|
||||
label: "In Progress",
|
||||
value: 'in progress',
|
||||
label: 'In Progress',
|
||||
icon: Stopwatch
|
||||
},
|
||||
{
|
||||
value: "done",
|
||||
label: "Done",
|
||||
value: 'done',
|
||||
label: 'Done',
|
||||
icon: CheckCircled
|
||||
},
|
||||
{
|
||||
value: "canceled",
|
||||
label: "Canceled",
|
||||
value: 'canceled',
|
||||
label: 'Canceled',
|
||||
icon: CrossCircled
|
||||
}
|
||||
];
|
||||
|
||||
export const priorities = [
|
||||
{
|
||||
label: "Low",
|
||||
value: "low",
|
||||
label: 'Low',
|
||||
value: 'low',
|
||||
icon: ArrowDown
|
||||
},
|
||||
{
|
||||
label: "Medium",
|
||||
value: "medium",
|
||||
label: 'Medium',
|
||||
value: 'medium',
|
||||
icon: ArrowRight
|
||||
},
|
||||
{
|
||||
label: "High",
|
||||
value: "high",
|
||||
label: 'High',
|
||||
value: 'high',
|
||||
icon: ArrowUp
|
||||
}
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { z } from "zod";
|
||||
import { z } from 'zod';
|
||||
|
||||
// We're keeping a simple non-relational schema here.
|
||||
// IRL, you will have a schema for your data models.
|
||||
|
|
@ -10,7 +10,7 @@ export const ticketSchema = z.object({
|
|||
label: z.string(),
|
||||
// createdAt: z.date(),
|
||||
// updatedAt: z.date(),
|
||||
priority: z.string(), // z.number().min(0).max(4),
|
||||
priority: z.string() // z.number().min(0).max(4),
|
||||
// source: z.number(),
|
||||
// assignee: z.string(),
|
||||
});
|
||||
|
|
|
|||
14
apps/web/src/routes/(dashboard)/dashboard/+layout.svelte
Normal file
14
apps/web/src/routes/(dashboard)/dashboard/+layout.svelte
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<script>
|
||||
import Separator from '$lib/components/ui/separator/separator.svelte';
|
||||
</script>
|
||||
|
||||
<div class="space-y-6 p-10 pb-16 md:block">
|
||||
<div class="space-y-0.5">
|
||||
<h2 class="text-2xl font-bold tracking-tight">Dashboard</h2>
|
||||
<p class="text-muted-foreground">
|
||||
Utilize the filtering options and actions to manage your tickets.
|
||||
</p>
|
||||
</div>
|
||||
<Separator class="my-6" />
|
||||
<slot />
|
||||
</div>
|
||||
|
|
@ -1,13 +1,8 @@
|
|||
<script lang="ts">
|
||||
import DataTable from "./(components)/data-table.svelte";
|
||||
import ticketData from "./(data)/tickets.json";
|
||||
import DataTable from './(components)/data-table.svelte';
|
||||
import ticketData from './(data)/tickets.json';
|
||||
</script>
|
||||
|
||||
<div class="hidden h-full flex-1 flex-col space-y-8 p-8 md:flex">
|
||||
<div class="flex items-center justify-between space-y-2">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold tracking-tight">Dashboard</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-6">
|
||||
<DataTable data={ticketData} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import { page } from "$app/stores";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { cubicInOut } from "svelte/easing";
|
||||
import { crossfade } from "svelte/transition";
|
||||
import { cn } from '$lib/utils';
|
||||
import { page } from '$app/stores';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { cubicInOut } from 'svelte/easing';
|
||||
import { crossfade } from 'svelte/transition';
|
||||
|
||||
let className: string | undefined | null = undefined;
|
||||
export let items: { href: string; title: string }[];
|
||||
|
|
@ -15,24 +15,21 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<nav class={cn("flex space-x-2 lg:flex-col lg:space-x-0 lg:space-y-1", className)}>
|
||||
<nav class={cn('flex space-x-2 lg:flex-col lg:space-x-0 lg:space-y-1', className)}>
|
||||
{#each items as item}
|
||||
{@const isActive = $page.url.pathname === item.href}
|
||||
|
||||
<Button
|
||||
href={item.href}
|
||||
variant="ghost"
|
||||
class={cn(
|
||||
!isActive && "hover:underline",
|
||||
"relative justify-start hover:bg-transparent"
|
||||
)}
|
||||
class={cn(!isActive && 'hover:underline', 'relative justify-start hover:bg-transparent')}
|
||||
data-sveltekit-noscroll
|
||||
>
|
||||
{#if isActive}
|
||||
<div
|
||||
class="absolute inset-0 rounded-md bg-muted"
|
||||
in:send={{ key: "active-sidebar-tab" }}
|
||||
out:receive={{ key: "active-sidebar-tab" }}
|
||||
in:send={{ key: 'active-sidebar-tab' }}
|
||||
out:receive={{ key: 'active-sidebar-tab' }}
|
||||
/>
|
||||
{/if}
|
||||
<div class="relative">
|
||||
|
|
|
|||
|
|
@ -1,33 +1,31 @@
|
|||
<script lang="ts">
|
||||
import { Separator } from "$lib/components/ui/separator";
|
||||
import SidebarNav from "./(components)/sidebar-nav.svelte";
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
import SidebarNav from './(components)/sidebar-nav.svelte';
|
||||
|
||||
const sidebarNavItems = [
|
||||
{
|
||||
title: "Profile",
|
||||
href: "/settings"
|
||||
title: 'Profile',
|
||||
href: '/settings'
|
||||
},
|
||||
{
|
||||
title: "Account",
|
||||
href: "/settings/account"
|
||||
title: 'Account',
|
||||
href: '/settings/account'
|
||||
},
|
||||
{
|
||||
title: "Appearance",
|
||||
href: "/settings/appearance"
|
||||
title: 'Appearance',
|
||||
href: '/settings/appearance'
|
||||
},
|
||||
{
|
||||
title: "Notifications",
|
||||
href: "/settings/notifications"
|
||||
title: 'Notifications',
|
||||
href: '/settings/notifications'
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<div class="hidden space-y-6 p-10 pb-16 md:block">
|
||||
<div class="space-y-6 p-10 pb-16 md:block">
|
||||
<div class="space-y-0.5">
|
||||
<h2 class="text-2xl font-bold tracking-tight">Settings</h2>
|
||||
<p class="text-muted-foreground">
|
||||
Manage your account settings and set e-mail preferences.
|
||||
</p>
|
||||
<p class="text-muted-foreground">Manage your account settings and set e-mail preferences.</p>
|
||||
</div>
|
||||
<Separator class="my-6" />
|
||||
<div class="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { PageServerLoad } from "./$types";
|
||||
import { superValidate } from "sveltekit-superforms/server";
|
||||
import { profileFormSchema } from "./profile-form.svelte";
|
||||
import { fail, type Actions } from "@sveltejs/kit";
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { superValidate } from 'sveltekit-superforms/server';
|
||||
import { profileFormSchema } from './profile-form.svelte';
|
||||
import { fail, type Actions } from '@sveltejs/kit';
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import type { PageData } from "./$types";
|
||||
import ProfileForm from "./profile-form.svelte";
|
||||
import { Separator } from "$lib/components/ui/separator";
|
||||
import type { PageData } from './$types';
|
||||
import ProfileForm from './profile-form.svelte';
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { superValidate } from "sveltekit-superforms/server";
|
||||
import type { PageServerLoad } from "./$types";
|
||||
import { accountFormSchema } from "./account-form.svelte";
|
||||
import { fail, type Actions } from "@sveltejs/kit";
|
||||
import { superValidate } from 'sveltekit-superforms/server';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { accountFormSchema } from './account-form.svelte';
|
||||
import { fail, type Actions } from '@sveltejs/kit';
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { Separator } from "$lib/components/ui/separator";
|
||||
import AccountForm from "./account-form.svelte";
|
||||
import type { PageData } from "./$types";
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
import AccountForm from './account-form.svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
<script lang="ts" context="module">
|
||||
import { z } from "zod";
|
||||
import { z } from 'zod';
|
||||
|
||||
const languages = {
|
||||
en: "English",
|
||||
fr: "French",
|
||||
de: "German",
|
||||
es: "Spanish",
|
||||
pt: "Portuguese",
|
||||
ru: "Russian",
|
||||
ja: "Japanese",
|
||||
ko: "Korean",
|
||||
zh: "Chinese"
|
||||
en: 'English',
|
||||
fr: 'French',
|
||||
de: 'German',
|
||||
es: 'Spanish',
|
||||
pt: 'Portuguese',
|
||||
ru: 'Russian',
|
||||
ja: 'Japanese',
|
||||
ko: 'Korean',
|
||||
zh: 'Chinese'
|
||||
} as const;
|
||||
|
||||
type Language = keyof typeof languages;
|
||||
|
|
@ -18,10 +18,10 @@
|
|||
export const accountFormSchema = z.object({
|
||||
name: z
|
||||
.string({
|
||||
required_error: "Required."
|
||||
required_error: 'Required.'
|
||||
})
|
||||
.min(2, "Name must be at least 2 characters.")
|
||||
.max(30, "Name must not be longer than 30 characters"),
|
||||
.min(2, 'Name must be at least 2 characters.')
|
||||
.max(30, 'Name must not be longer than 30 characters'),
|
||||
// Hack: https://github.com/colinhacks/zod/issues/2280
|
||||
language: z.enum(Object.keys(languages) as [Language, ...Language[]])
|
||||
});
|
||||
|
|
@ -30,9 +30,9 @@
|
|||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as Form from "$lib/components/ui/form";
|
||||
import type { SuperValidated } from "sveltekit-superforms";
|
||||
import { cn } from "$lib/utils";
|
||||
import * as Form from '$lib/components/ui/form';
|
||||
import type { SuperValidated } from 'sveltekit-superforms';
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
export let data: SuperValidated<AccountFormSchema>;
|
||||
</script>
|
||||
|
|
@ -62,10 +62,7 @@
|
|||
<Form.Select selected={{ value, label: languages[value] }}>
|
||||
<Form.SelectTrigger
|
||||
placeholder="Select language"
|
||||
class={cn(
|
||||
"w-[200px] justify-between",
|
||||
!attrs.input.value && "text-muted-foreground"
|
||||
)}
|
||||
class={cn('w-[200px] justify-between', !attrs.input.value && 'text-muted-foreground')}
|
||||
/>
|
||||
<Form.SelectContent class="h-52 overflow-y-auto">
|
||||
{#each Object.entries(languages) as [value, lang]}
|
||||
|
|
@ -75,9 +72,7 @@
|
|||
{/each}
|
||||
</Form.SelectContent>
|
||||
</Form.Select>
|
||||
<Form.Description>
|
||||
This is the language that will be used in the dashboard.
|
||||
</Form.Description>
|
||||
<Form.Description>This is the language that will be used in the dashboard.</Form.Description>
|
||||
<Form.Validation />
|
||||
</Form.Field>
|
||||
</Form.Item>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { superValidate } from "sveltekit-superforms/server";
|
||||
import type { PageServerLoad } from "../$types";
|
||||
import { appearanceFormSchema } from "./appearance-form.svelte";
|
||||
import { fail, type Actions } from "@sveltejs/kit";
|
||||
import { superValidate } from 'sveltekit-superforms/server';
|
||||
import type { PageServerLoad } from '../$types';
|
||||
import { appearanceFormSchema } from './appearance-form.svelte';
|
||||
import { fail, type Actions } from '@sveltejs/kit';
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { Separator } from "$lib/components/ui/separator";
|
||||
import type { PageData } from "./$types";
|
||||
import AppearanceForm from "./appearance-form.svelte";
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
import type { PageData } from './$types';
|
||||
import AppearanceForm from './appearance-form.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
<script lang="ts" context="module">
|
||||
import type { SuperValidated } from "sveltekit-superforms";
|
||||
import { z } from "zod";
|
||||
import type { SuperValidated } from 'sveltekit-superforms';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const appearanceFormSchema = z.object({
|
||||
theme: z.enum(["light", "dark"], {
|
||||
required_error: "Please select a theme."
|
||||
theme: z.enum(['light', 'dark'], {
|
||||
required_error: 'Please select a theme.'
|
||||
}),
|
||||
font: z.enum(["inter", "manrope", "system"], {
|
||||
invalid_type_error: "Select a font",
|
||||
required_error: "Please select a font."
|
||||
font: z.enum(['inter', 'manrope', 'system'], {
|
||||
invalid_type_error: 'Select a font',
|
||||
required_error: 'Please select a font.'
|
||||
})
|
||||
});
|
||||
|
||||
|
|
@ -16,8 +16,8 @@
|
|||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as Form from "$lib/components/ui/form";
|
||||
import Label from "$lib/components/ui/label/label.svelte";
|
||||
import * as Form from '$lib/components/ui/form';
|
||||
import Label from '$lib/components/ui/label/label.svelte';
|
||||
export let data: SuperValidated<AppearanceFormSchema>;
|
||||
</script>
|
||||
|
||||
|
|
@ -51,23 +51,17 @@
|
|||
<Form.RadioGroup class="grid max-w-md grid-cols-2 gap-8 pt-2" orientation="horizontal">
|
||||
<Label for="light" class="[&:has([data-state=checked])>div]:border-primary">
|
||||
<Form.RadioItem id="light" value="light" class="sr-only" />
|
||||
<div
|
||||
class="items-center rounded-md border-2 border-muted p-1 hover:border-accent"
|
||||
>
|
||||
<div class="items-center rounded-md border-2 border-muted p-1 hover:border-accent">
|
||||
<div class="space-y-2 rounded-sm bg-[#ecedef] p-2">
|
||||
<div class="space-y-2 rounded-md bg-white p-2 shadow-sm">
|
||||
<div class="h-2 w-[80px] rounded-lg bg-[#ecedef]" />
|
||||
<div class="h-2 w-[100px] rounded-lg bg-[#ecedef]" />
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center space-x-2 rounded-md bg-white p-2 shadow-sm"
|
||||
>
|
||||
<div class="flex items-center space-x-2 rounded-md bg-white p-2 shadow-sm">
|
||||
<div class="h-4 w-4 rounded-full bg-[#ecedef]" />
|
||||
<div class="h-2 w-[100px] rounded-lg bg-[#ecedef]" />
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center space-x-2 rounded-md bg-white p-2 shadow-sm"
|
||||
>
|
||||
<div class="flex items-center space-x-2 rounded-md bg-white p-2 shadow-sm">
|
||||
<div class="h-4 w-4 rounded-full bg-[#ecedef]" />
|
||||
<div class="h-2 w-[100px] rounded-lg bg-[#ecedef]" />
|
||||
</div>
|
||||
|
|
@ -85,15 +79,11 @@
|
|||
<div class="h-2 w-[80px] rounded-lg bg-slate-400" />
|
||||
<div class="h-2 w-[100px] rounded-lg bg-slate-400" />
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center space-x-2 rounded-md bg-slate-800 p-2 shadow-sm"
|
||||
>
|
||||
<div class="flex items-center space-x-2 rounded-md bg-slate-800 p-2 shadow-sm">
|
||||
<div class="h-4 w-4 rounded-full bg-slate-400" />
|
||||
<div class="h-2 w-[100px] rounded-lg bg-slate-400" />
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center space-x-2 rounded-md bg-slate-800 p-2 shadow-sm"
|
||||
>
|
||||
<div class="flex items-center space-x-2 rounded-md bg-slate-800 p-2 shadow-sm">
|
||||
<div class="h-4 w-4 rounded-full bg-slate-400" />
|
||||
<div class="h-2 w-[100px] rounded-lg bg-slate-400" />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { superValidate } from "sveltekit-superforms/server";
|
||||
import type { PageServerLoad } from "../$types";
|
||||
import { notificationsFormSchema } from "./notifications-form.svelte";
|
||||
import { fail, type Actions } from "@sveltejs/kit";
|
||||
import { superValidate } from 'sveltekit-superforms/server';
|
||||
import type { PageServerLoad } from '../$types';
|
||||
import { notificationsFormSchema } from './notifications-form.svelte';
|
||||
import { fail, type Actions } from '@sveltejs/kit';
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { Separator } from "$lib/components/ui/separator";
|
||||
import NotificationsForm from "./notifications-form.svelte";
|
||||
import type { PageData } from "./$types";
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
import NotificationsForm from './notifications-form.svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts" context="module">
|
||||
import { z } from "zod";
|
||||
import { z } from 'zod';
|
||||
export const notificationsFormSchema = z.object({
|
||||
type: z.enum(["all", "mentions", "none"], {
|
||||
required_error: "You need to select a notification type."
|
||||
type: z.enum(['all', 'mentions', 'none'], {
|
||||
required_error: 'You need to select a notification type.'
|
||||
}),
|
||||
mobile: z.boolean().default(false).optional(),
|
||||
communication_emails: z.boolean().default(false).optional(),
|
||||
|
|
@ -14,9 +14,9 @@
|
|||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import type { SuperValidated } from "sveltekit-superforms";
|
||||
import * as Form from "$lib/components/ui/form";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import type { SuperValidated } from 'sveltekit-superforms';
|
||||
import * as Form from '$lib/components/ui/form';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
export let data: SuperValidated<NotificationFormSchema>;
|
||||
</script>
|
||||
|
||||
|
|
@ -54,9 +54,7 @@
|
|||
<Form.Item class="flex flex-row items-center justify-between rounded-lg border p-4">
|
||||
<div class="space-y-0.5">
|
||||
<Form.Label class="text-base">Communication emails</Form.Label>
|
||||
<Form.Description>
|
||||
Receive emails about your account activity.
|
||||
</Form.Description>
|
||||
<Form.Description>Receive emails about your account activity.</Form.Description>
|
||||
</div>
|
||||
<Form.Switch />
|
||||
</Form.Item>
|
||||
|
|
@ -102,8 +100,7 @@
|
|||
<div class="space-y-1 leading-none">
|
||||
<Form.Label>Use different settings for my mobile devices</Form.Label>
|
||||
<Form.Description>
|
||||
You can manage your mobile notifications in the{" "}<a href="/settings"
|
||||
>mobile settings</a
|
||||
You can manage your mobile notifications in the{' '}<a href="/settings">mobile settings</a
|
||||
> page.
|
||||
</Form.Description>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
<script lang="ts" context="module">
|
||||
import { z } from "zod";
|
||||
import { z } from 'zod';
|
||||
export const profileFormSchema = z.object({
|
||||
username: z
|
||||
.string()
|
||||
.min(2, "Username must be at least 2 characters.")
|
||||
.max(30, "Username must not be longer than 30 characters"),
|
||||
email: z.string({ required_error: "Please select an email to display" }).email(),
|
||||
bio: z.string().min(4).max(160).default("I own a computer."),
|
||||
.min(2, 'Username must be at least 2 characters.')
|
||||
.max(30, 'Username must not be longer than 30 characters'),
|
||||
email: z.string({ required_error: 'Please select an email to display' }).email(),
|
||||
bio: z.string().min(4).max(160).default('I own a computer.'),
|
||||
website: z
|
||||
.string()
|
||||
.url({ message: "Please enter a valid URL." })
|
||||
.default("https://shadcn-svelte.com")
|
||||
.url({ message: 'Please enter a valid URL.' })
|
||||
.default('https://shadcn-svelte.com')
|
||||
});
|
||||
export type ProfileFormSchema = typeof profileFormSchema;
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as Form from "$lib/components/ui/form";
|
||||
import type { SuperValidated } from "sveltekit-superforms";
|
||||
import * as Form from '$lib/components/ui/form';
|
||||
import type { SuperValidated } from 'sveltekit-superforms';
|
||||
|
||||
export let data: SuperValidated<ProfileFormSchema>;
|
||||
</script>
|
||||
|
|
@ -35,8 +35,8 @@
|
|||
<Form.Label>Username</Form.Label>
|
||||
<Form.Input placeholder="@shadcn" />
|
||||
<Form.Description>
|
||||
This is your public display name. It can be your real name or a pseudonym. You can
|
||||
only change this once every 30 days.
|
||||
This is your public display name. It can be your real name or a pseudonym. You can only
|
||||
change this once every 30 days.
|
||||
</Form.Description>
|
||||
<Form.Validation />
|
||||
</Form.Field>
|
||||
|
|
@ -50,17 +50,14 @@
|
|||
<Form.SelectItem value="m@example.com" label="m@example.com"
|
||||
>m@example.com
|
||||
</Form.SelectItem>
|
||||
<Form.SelectItem value="m@google.com" label="m@google.com"
|
||||
>m@google.com
|
||||
</Form.SelectItem>
|
||||
<Form.SelectItem value="m@google.com" label="m@google.com">m@google.com</Form.SelectItem>
|
||||
<Form.SelectItem value="m@support.com" label="m@support.com"
|
||||
>m@support.com
|
||||
</Form.SelectItem>
|
||||
</Form.SelectContent>
|
||||
</Form.Select>
|
||||
<Form.Description>
|
||||
You can manage verified email addresses in your <a href="/examples/forms"
|
||||
>email settings</a
|
||||
You can manage verified email addresses in your <a href="/examples/forms">email settings</a
|
||||
>.
|
||||
</Form.Description>
|
||||
<Form.Validation />
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import type { LayoutServerLoad } from './$types';
|
||||
|
||||
export const load: LayoutServerLoad = async ({ locals }: { locals: App.Locals }) => {
|
||||
const user = locals.pocketBase.authStore.model;
|
||||
if (user) user.avatarUrl = locals.pocketBase.getFileUrl(user, user.avatar);
|
||||
|
||||
return {
|
||||
authenticated: locals.pocketBase.authStore.isValid,
|
||||
user: locals.pocketBase.authStore.model,
|
||||
user
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
import '../app.pcss';
|
||||
import { fade } from 'svelte/transition';
|
||||
import type { LayoutData } from './$types';
|
||||
import DataIndicator from '$lib/components/site/data-indicator.svelte';
|
||||
|
||||
export let data: LayoutData;
|
||||
</script>
|
||||
|
|
@ -15,14 +16,16 @@
|
|||
|
||||
<div class="relative flex min-h-screen flex-col" id="page">
|
||||
<SiteNavBar authenticated={data.authenticated} user={data.user} />
|
||||
<main class="container relative mb-4 mt-12 flex-1">
|
||||
<main class="container relative flex-1">
|
||||
<div in:fade={{ duration: 200, delay: 100 }} out:fade={{ duration: 100 }}>
|
||||
<slot />
|
||||
</div>
|
||||
</main>
|
||||
<SiteFooter />
|
||||
{#if dev}
|
||||
<!-- <pre>{JSON.stringify(data, null, 2)}</pre> -->
|
||||
<TailwindIndicator />
|
||||
<div class="fixed bottom-1 left-1 z-50 flex font-mono uppercase">
|
||||
<DataIndicator {data} />
|
||||
<TailwindIndicator />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,31 +1,37 @@
|
|||
<script lang="ts">
|
||||
import { Icons } from '$lib/components/site';
|
||||
import Particles from '$lib/components/site/particles.svelte';
|
||||
import Button from '$lib/components/ui/button/button.svelte';
|
||||
import Separator from '$lib/components/ui/separator/separator.svelte';
|
||||
import { siteConfig } from '$lib/config/site';
|
||||
</script>
|
||||
|
||||
<div class="absolute inset-0 -z-10">
|
||||
<Particles />
|
||||
</div>
|
||||
<div class="pb-16 pt-32 md:pb-32 md:pt-52">
|
||||
<div class="container mx-auto text-center">
|
||||
<h1
|
||||
class="bg-gradient-to-r from-zinc-200/60 via-zinc-200 to-zinc-200/60 bg-clip-text pb-4 text-6xl font-extrabold tracking-tight text-transparent lg:text-7xl"
|
||||
class="bg-gradient-to-r from-zinc-800 via-zinc-800/60 to-zinc-800 bg-clip-text pb-4 text-6xl font-extrabold tracking-tight text-transparent dark:from-zinc-200/60 dark:via-zinc-200 dark:to-zinc-200/60 lg:text-7xl"
|
||||
>
|
||||
<span class="inline-block align-top decoration-inherit text-balance">One Dashboard, Countless Solutions</span
|
||||
<span class="inline-block text-balance align-top decoration-inherit"
|
||||
>One Dashboard, Countless Solutions</span
|
||||
>
|
||||
</h1>
|
||||
<p class="mb-8 text-lg text-zinc-300">
|
||||
<p class="mb-8 text-lg text-zinc-800 dark:text-zinc-300">
|
||||
Tame ticket overload and keep your operations teams sane
|
||||
</p>
|
||||
<div
|
||||
class="mx-auto flex max-w-xs flex-col items-center gap-4 sm:inline-flex sm:max-w-none sm:flex-row sm:justify-center"
|
||||
>
|
||||
|
||||
<Button href="/dashboard" class="group flex w-full items-center transition duration-150 ease-in-out">
|
||||
Get Started <Icons.arrowRight class="text-primary-500 ml-1 h-3 w-3 tracking-normal transition-transform duration-150 ease-in-out group-hover:translate-x-0.5"/>
|
||||
</Button>
|
||||
<Button href={siteConfig.links.gitHubProject} variant="outline">
|
||||
Star on GitHub
|
||||
<Button
|
||||
href="/dashboard"
|
||||
class="group flex w-full items-center transition duration-150 ease-in-out"
|
||||
>
|
||||
Get Started <Icons.arrowRight
|
||||
class="text-primary-500 ml-1 h-3 w-3 tracking-normal transition-transform duration-150 ease-in-out group-hover:translate-x-0.5"
|
||||
/>
|
||||
</Button>
|
||||
<Button href={siteConfig.links.gitHubProject} variant="outline">Star on GitHub</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue