- 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
132 lines
3.8 KiB
Svelte
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}
|