feat: add device settings page

- Add device CRUD operations (create, delete, regenerate api keys)
- Add API key visibility toggle and copy button
This commit is contained in:
2026-03-09 14:16:53 -04:00
parent 51c1900d8c
commit 3072f72958
5 changed files with 350 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
import { command, form, getRequestEvent, query } from '$app/server';
import { createDeviceSchema, type Device } from '$lib/schema/device';
import { error } from '@sveltejs/kit';
import z from 'zod';
export const listDevices = query(async (): Promise<Device[]> => {
const { locals } = getRequestEvent();
const response = await locals.api.get(`/devices`);
if (!response.ok) error(500, 'An unkown error occurred');
const deviceResult = await response.json();
return deviceResult.items
});
export const createDevice = form(createDeviceSchema, async (data): Promise<Device> => {
const { locals } = getRequestEvent();
const response = await locals.api.post(`/devices`, data);
if (!response.ok) error(500, 'An unknown error occurred');
return await response.json();
});
export const regenerateDeviceApiKey = command(z.string(), async (deviceId): Promise<Device> => {
const { locals } = getRequestEvent();
const response = await locals.api.get(`/devices/${deviceId}/regenerate`);
if (!response.ok) error(500, 'An unknown error occurred');
return await response.json();
})
export const deleteDevice = command(z.string(), async (deviceId): Promise<void> => {
const { locals } = getRequestEvent();
const response = await locals.api.delete(`/devices/${deviceId}`);
if (!response.ok) error(500, 'An unknown error occurred');
});
@@ -0,0 +1,50 @@
<script lang="ts">
import { createDevice } from '$lib/api/device.remote';
import * as Field from '$lib/components/ui/field/index.js';
import { Input } from '$lib/components/ui/input/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import { toast } from 'svelte-sonner';
interface Props {
onSuccess?: () => void;
}
let { onSuccess }: Props = $props();
</script>
<form
{...createDevice.enhance(async ({ form, submit }) => {
try {
await submit();
const issues = createDevice.fields.allIssues();
if (issues && issues.length > 0) {
return;
}
const deviceName = createDevice.fields.name.value();
form.reset();
toast.success(`Device '${deviceName}' created`);
onSuccess?.();
} catch (error) {
console.error('Failed to create device: ', error);
toast.error('Failed to create device');
}
})}
class="flex flex-col gap-3"
>
<Field.Set>
<Field.Group>
<Field.Field>
<Field.Label for="name">Device name</Field.Label>
<Input {...createDevice.fields.name.as('text')} placeholder="e.g. Kindle Paperwhite" />
{#each createDevice.fields.name.issues() ?? [] as issue}
<Field.Error>{issue.message}</Field.Error>
{/each}
</Field.Field>
</Field.Group>
</Field.Set>
<Button type="submit" class="ml-auto w-24">Create</Button>
</form>
+8
View File
@@ -0,0 +1,8 @@
import { z } from 'zod';
import type { components } from './openapi/schema';
export type Device = components['schemas']['KosyncDeviceRead']
export const createDeviceSchema = z.object({
name: z.string().min(1, 'Name cannot be empty')
})