Files
portal/src/routes/(protected)/admin/license-keys/+page.svelte
T
NeilClack 4d21288511 api: switch license endpoint to POST/JSON, fix active check, add key copy button
- Change /api/v1/license from GET with query params to POST with JSON body
- Request body uses license_key and email_address
- Response returns key, email_address, subscription_level
- Add active check (reject deactivated license keys)
- Exclude /api/v1/automations/ from API key auth (GHL webhook)
- Add copyableFields prop to AdminTable with clipboard icon
- Enable copy button on license key field in license-keys admin page
2026-07-08 14:11:01 -05:00

132 lines
3.8 KiB
Svelte

<script lang="ts">
import type { PageProps } from './$types';
import { enhance } from '$app/forms';
import AdminTable from '$lib/components/admin/AdminTable.svelte';
import AdminModal from '$lib/components/admin/AdminModal.svelte';
import Toast from '$lib/components/admin/Toast.svelte';
import { getModelConfig } from '$lib/svelte-admin/registry';
import LicenseKeyForm from './LicenseKeyForm.svelte';
let { data }: PageProps = $props();
const config = getModelConfig('license-keys')!;
let createOpen = $state(false);
let replaceOpen = $state(false);
let replaceRecord = $state<Record<string, unknown> | null>(null);
let toastMessage = $state('');
let toastType = $state<'success' | 'error'>('success');
function openCreate() {
createOpen = true;
}
function openReplace(record: Record<string, unknown>) {
replaceRecord = record;
replaceOpen = true;
}
function closeCreate() {
createOpen = false;
}
function closeReplace() {
replaceOpen = false;
replaceRecord = null;
}
function replaceEnhance() {
return async ({ result, update }: any) => {
if (result.type === 'success' && result.status === 200) {
closeReplace();
toastMessage = 'License key replaced';
toastType = 'success';
} else {
toastMessage = 'Failed to replace license key';
toastType = 'error';
}
await update();
};
}
</script>
<div class="p-6">
<div class="mb-4 flex items-center justify-between">
<h1 class="text-xl font-bold text-gray-900 dark:text-gray-100">{config.displayName}</h1>
<button
type="button"
onclick={openCreate}
class="border border-gray-300 px-3 py-1.5 text-sm font-medium dark:border-gray-600 dark:text-gray-300"
>
New License Key +
</button>
</div>
<AdminTable
records={data.records}
listFields={config.listFields}
searchFields={config.searchFields}
fieldTypes={config.fieldTypes}
singular={config.displayNameSingular}
copyableFields={['key']}
onRowClick={openReplace}
/>
</div>
<AdminModal title="Create License Key" open={createOpen} onClose={closeCreate}>
<LicenseKeyForm subscriptionLevels={data.subscriptionLevels} onSuccess={closeCreate} />
</AdminModal>
<AdminModal title="Replace License Key" open={replaceOpen} onClose={closeReplace}>
{#if replaceRecord}
<div class="flex flex-col gap-4">
<p class="text-sm text-gray-700 dark:text-gray-300">
Generate a new key to replace <strong>{String(replaceRecord.key)}</strong>?
</p>
<div
class="rounded border border-gray-200 bg-gray-50 p-3 text-sm dark:border-gray-700 dark:bg-gray-800"
>
<div class="mb-1 text-gray-500 dark:text-gray-400">
User: <span class="font-medium text-gray-700 dark:text-gray-300"
>{String(replaceRecord['user.name'] ?? '')}</span
>
</div>
<div class="text-gray-500 dark:text-gray-400">
Level: <span class="font-medium text-gray-700 dark:text-gray-300"
>{String(replaceRecord.level ?? '')}</span
>
</div>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400">
The existing key will be deleted and a new one with the same subscription level will be
created and emailed.
</p>
<form
method="POST"
action="?/replace"
use:enhance={replaceEnhance}
class="flex justify-end gap-2 pt-2"
>
<input type="hidden" name="id" value={String(replaceRecord.id)} />
<button
type="button"
onclick={closeReplace}
class="border border-gray-300 px-3 py-1.5 text-sm dark:border-gray-600 dark:text-gray-300"
>
Cancel
</button>
<button
type="submit"
class="border border-amber-300 bg-amber-50 px-4 py-1.5 text-sm font-medium hover:bg-amber-100 dark:border-amber-700 dark:bg-amber-900/30 dark:text-amber-300 dark:hover:bg-amber-900/50"
>
Generate Replacement
</button>
</form>
</div>
{/if}
</AdminModal>
{#if toastMessage}
<Toast message={toastMessage} type={toastType} />
{/if}