Files
portal/src/routes/(protected)/(user)/dashboard/+page.svelte
T
NeilClack 960673bbc6
Build and Push to Digital Ocean Container Registry / build-and-push (push) Failing after 4s
Build and Push to Digital Ocean Container Registry / migrate (push) Skipped
Add a site-wide dark mode
2026-09-11 12:49:46 -05:00

128 lines
4.2 KiB
Svelte

<script lang="ts">
import DashboardSection from '$lib/components/DashboardSection.svelte';
import type { PageProps } from './$types';
let { data }: PageProps = $props();
function formatDate(value: Date | string): string {
const date = value instanceof Date ? value : new Date(value);
return date.toLocaleDateString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric'
});
}
</script>
<div>
<div>
<h1 class="text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl dark:text-slate-100">
Dashboard
</h1>
<p class="mt-1 text-sm text-slate-500 sm:text-base dark:text-slate-400">
Manage your account and license keys.
</p>
</div>
<!--
Dashboard sections. This page is the account management surface; personal
information (name, email, phone, addresses) lives on the Profile page.
Planned sections to add below as <DashboardSection> blocks:
- Schedules: create and manage listening schedules.
- Devices / playback.
- Billing / orders.
-->
<DashboardSection title="License Keys" description="Your active and inactive license keys.">
{#if data.licenseKeys.length > 0}
<div
class="overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900"
>
<!-- Desktop / tablet: table -->
<table class="hidden w-full text-left text-sm sm:table">
<thead
class="border-b border-slate-200 bg-slate-50 text-xs tracking-wide text-slate-500 uppercase dark:border-slate-800 dark:bg-slate-950 dark:text-slate-400"
>
<tr>
<th class="px-6 py-3 font-semibold">Key</th>
<th class="px-6 py-3 font-semibold">Status</th>
<th class="px-6 py-3 font-semibold">Created</th>
<th class="px-6 py-3 font-semibold">Last verified</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
{#each data.licenseKeys as key (key.key)}
<tr class="hover:bg-slate-50 dark:hover:bg-slate-800">
<td class="px-6 py-4 font-mono text-slate-800 dark:text-slate-200">{key.key}</td>
<td class="px-6 py-4">
{#if key.active}
<span
class="text-lg text-green-600 dark:text-green-400"
title="Active"
aria-label="Active">☑</span
>
{:else}
<span
class="text-lg text-slate-300 dark:text-slate-600"
title="Inactive"
aria-label="Inactive">☐</span
>
{/if}
</td>
<td class="px-6 py-4 text-slate-600 dark:text-slate-400"
>{formatDate(key.createdAt)}</td
>
<td class="px-6 py-4 text-slate-600 dark:text-slate-400">
{key.lastValidatedAt ? formatDate(key.lastValidatedAt) : 'Never'}
</td>
</tr>
{/each}
</tbody>
</table>
<!-- Mobile: card list -->
<ul class="divide-y divide-slate-100 sm:hidden">
{#each data.licenseKeys as key (key.key)}
<li class="flex flex-col gap-2 px-4 py-4">
<div class="flex items-center justify-between gap-4">
<span class="min-w-0 font-mono text-sm break-all text-slate-800 dark:text-slate-200"
>{key.key}</span
>
<span class="shrink-0">
{#if key.active}
<span
class="text-lg text-green-600 dark:text-green-400"
title="Active"
aria-label="Active">☑</span
>
{:else}
<span
class="text-lg text-slate-300 dark:text-slate-600"
title="Inactive"
aria-label="Inactive">☐</span
>
{/if}
</span>
</div>
<span class="text-xs text-slate-500 dark:text-slate-400"
>Created {formatDate(key.createdAt)}</span
>
<span class="text-xs text-slate-500 dark:text-slate-400">
Last verified {key.lastValidatedAt ? formatDate(key.lastValidatedAt) : 'Never'}
</span>
</li>
{/each}
</ul>
</div>
{:else}
<div
class="rounded-2xl border border-dashed border-slate-300 bg-white p-8 text-center dark:border-slate-700 dark:bg-slate-900"
>
<p class="text-sm text-slate-600 dark:text-slate-400">
You don't have any license keys yet.
</p>
</div>
{/if}
</DashboardSection>
</div>