feat: added particle and reformatting

This commit is contained in:
Bart van der Braak 2024-02-02 01:08:13 +01:00
parent 5158767019
commit 32b6bf7582
147 changed files with 1186 additions and 922 deletions

View file

@ -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();

View file

@ -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>

View file

@ -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');
};

View file

@ -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>

View file

@ -15,4 +15,4 @@ export const actions = {
throw error(500, 'Something went wrong');
}
}
};
};

View file

@ -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>

View file

@ -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 {};
})
};

View file

@ -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>;

View file

@ -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" />

View file

@ -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}

View file

@ -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} />

View file

@ -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;

View file

@ -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}

View file

@ -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);

View file

@ -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;

View file

@ -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 = [];
}}

View file

@ -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>

View file

@ -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>

View file

@ -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;

View file

@ -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
}
];

View file

@ -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(),
});

View 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>

View file

@ -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>

View file

@ -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">

View file

@ -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">

View file

@ -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 {

View file

@ -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>

View file

@ -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 {

View file

@ -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>

View file

@ -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>

View file

@ -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 {

View file

@ -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>

View file

@ -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>

View file

@ -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 {

View file

@ -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>

View file

@ -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>

View file

@ -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 />

View file

@ -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
};
};

View file

@ -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>

View file

@ -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>