saved cards store
This commit is contained in:
@@ -83,7 +83,9 @@
|
||||
|
||||
let isGenerateValid = $derived(generateAmount && !generateError);
|
||||
let isTopUpValid = $derived(topUpAmount && !topUpError);
|
||||
let isTransferValid = $derived(transferAmount && !transferAmountError && transferToCode && !transferCodeError);
|
||||
let isTransferValid = $derived(
|
||||
transferAmount && !transferAmountError && transferToCode && !transferCodeError
|
||||
);
|
||||
|
||||
// Choice flow state
|
||||
let generateStep = $state<'choice' | 'amount'>('choice');
|
||||
@@ -234,63 +236,28 @@
|
||||
topUpAmount = '';
|
||||
}
|
||||
|
||||
async function handleTillPaymentComplete(result: { id: string; total_amount: number; payment_method: string; status: string }) {
|
||||
async function handleTillPaymentComplete(result: {
|
||||
id: string;
|
||||
item_id?: string;
|
||||
total_amount: number;
|
||||
payment_method: string;
|
||||
status: string;
|
||||
}) {
|
||||
// The till sale endpoint already created/topped-up the gift card atomically.
|
||||
// All we need to do here is show confirmation and refresh the list.
|
||||
if (tillAction === 'create') {
|
||||
if (!isGenerateValid) return;
|
||||
creating = true;
|
||||
try {
|
||||
const res = await fetch('/api/admin/gift-cards', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
},
|
||||
body: JSON.stringify({ amount: Number(generateAmount) })
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
toast.success(`Gift card ${formatCardCode(data.id)} generated successfully!`);
|
||||
showTillPayment = false;
|
||||
showGenerateModal = false;
|
||||
resetGenerateModal();
|
||||
await fetchGiftCards();
|
||||
} else {
|
||||
const errText = await res.text();
|
||||
toast.error(errText || 'Failed to generate gift card');
|
||||
}
|
||||
} catch (err) {
|
||||
toast.error('Network error generating gift card');
|
||||
} finally {
|
||||
creating = false;
|
||||
}
|
||||
const code = result.item_id ? formatCardCode(result.item_id) : 'unknown';
|
||||
toast.success(`Gift card ${code} generated successfully!`);
|
||||
showTillPayment = false;
|
||||
showGenerateModal = false;
|
||||
resetGenerateModal();
|
||||
} else {
|
||||
if (!isTopUpValid || !selectedCardId) return;
|
||||
toppingUp = true;
|
||||
try {
|
||||
const res = await fetch(`/api/admin/gift-cards/${selectedCardId}/topup`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
},
|
||||
body: JSON.stringify({ amount: Number(topUpAmount) })
|
||||
});
|
||||
if (res.ok) {
|
||||
toast.success('Gift card topped up successfully');
|
||||
showTillPayment = false;
|
||||
showTopUpModal = false;
|
||||
resetTopUpModal();
|
||||
await fetchGiftCards();
|
||||
} else {
|
||||
const errText = await res.text();
|
||||
toast.error(errText || 'Failed to top up gift card');
|
||||
}
|
||||
} catch (err) {
|
||||
toast.error('Network error topping up gift card');
|
||||
} finally {
|
||||
toppingUp = false;
|
||||
}
|
||||
toast.success('Gift card topped up successfully');
|
||||
showTillPayment = false;
|
||||
showTopUpModal = false;
|
||||
resetTopUpModal();
|
||||
}
|
||||
await fetchGiftCards();
|
||||
}
|
||||
|
||||
function handleCodeInput(e: Event) {
|
||||
@@ -322,7 +289,16 @@
|
||||
}
|
||||
|
||||
// =============== Sorting ===============
|
||||
type SortKey = 'code' | 'added' | 'remaining' | 'created' | 'status' | 'name' | 'email' | 'balance' | 'updated';
|
||||
type SortKey =
|
||||
| 'code'
|
||||
| 'added'
|
||||
| 'remaining'
|
||||
| 'created'
|
||||
| 'status'
|
||||
| 'name'
|
||||
| 'email'
|
||||
| 'balance'
|
||||
| 'updated';
|
||||
|
||||
let cardSort = $state<{ key: SortKey; dir: 'asc' | 'desc' }>({ key: 'created', dir: 'desc' });
|
||||
let balanceSort = $state<{ key: SortKey; dir: 'asc' | 'desc' }>({ key: 'updated', dir: 'desc' });
|
||||
@@ -351,16 +327,21 @@
|
||||
const mul = dir === 'asc' ? 1 : -1;
|
||||
cards.sort((a, b) => {
|
||||
switch (key) {
|
||||
case 'code': return a.id.localeCompare(b.id) * mul;
|
||||
case 'added': return (a.total_funds_added - b.total_funds_added) * mul;
|
||||
case 'remaining': return (a.amount_remaining - b.amount_remaining) * mul;
|
||||
case 'created': return (new Date(a.created_at).getTime() - new Date(b.created_at).getTime()) * mul;
|
||||
case 'code':
|
||||
return a.id.localeCompare(b.id) * mul;
|
||||
case 'added':
|
||||
return (a.total_funds_added - b.total_funds_added) * mul;
|
||||
case 'remaining':
|
||||
return (a.amount_remaining - b.amount_remaining) * mul;
|
||||
case 'created':
|
||||
return (new Date(a.created_at).getTime() - new Date(b.created_at).getTime()) * mul;
|
||||
case 'status': {
|
||||
const aVal = a.redeemed_by ? 2 : a.amount_remaining === 0 ? 1 : 0;
|
||||
const bVal = b.redeemed_by ? 2 : b.amount_remaining === 0 ? 1 : 0;
|
||||
return (aVal - bVal) * mul;
|
||||
}
|
||||
default: return 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
return cards;
|
||||
@@ -372,11 +353,16 @@
|
||||
const mul = dir === 'asc' ? 1 : -1;
|
||||
bals.sort((a, b) => {
|
||||
switch (key) {
|
||||
case 'name': return a.name.localeCompare(b.name) * mul;
|
||||
case 'email': return a.email.localeCompare(b.email) * mul;
|
||||
case 'balance': return (a.balance - b.balance) * mul;
|
||||
case 'updated': return (new Date(a.updated_at).getTime() - new Date(b.updated_at).getTime()) * mul;
|
||||
default: return 0;
|
||||
case 'name':
|
||||
return a.name.localeCompare(b.name) * mul;
|
||||
case 'email':
|
||||
return a.email.localeCompare(b.email) * mul;
|
||||
case 'balance':
|
||||
return (a.balance - b.balance) * mul;
|
||||
case 'updated':
|
||||
return (new Date(a.updated_at).getTime() - new Date(b.updated_at).getTime()) * mul;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
return bals;
|
||||
@@ -403,9 +389,7 @@
|
||||
Create, top-up, and track gift cards. Unclaimed cards can be topped up or transferred.
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button onclick={() => showGenerateModal = true}>
|
||||
Generate Gift Card
|
||||
</Button>
|
||||
<Button onclick={() => (showGenerateModal = true)}>Generate Gift Card</Button>
|
||||
</div>
|
||||
</Card.Header>
|
||||
|
||||
@@ -435,19 +419,20 @@
|
||||
<div class="flex border-b border-gray-200">
|
||||
<button
|
||||
type="button"
|
||||
class="px-4 py-2 text-sm font-medium transition-colors border-b-2 {activeSection === 'cards'
|
||||
? 'border-primary text-primary font-semibold'
|
||||
class="border-b-2 px-4 py-2 text-sm font-medium transition-colors {activeSection === 'cards'
|
||||
? 'border-primary font-semibold text-primary'
|
||||
: 'border-transparent text-muted-foreground hover:text-foreground'}"
|
||||
onclick={() => activeSection = 'cards'}
|
||||
onclick={() => (activeSection = 'cards')}
|
||||
>
|
||||
Physical Gift Cards ({summary.gift_cards.length})
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="px-4 py-2 text-sm font-medium transition-colors border-b-2 {activeSection === 'balances'
|
||||
? 'border-primary text-primary font-semibold'
|
||||
class="border-b-2 px-4 py-2 text-sm font-medium transition-colors {activeSection ===
|
||||
'balances'
|
||||
? 'border-primary font-semibold text-primary'
|
||||
: 'border-transparent text-muted-foreground hover:text-foreground'}"
|
||||
onclick={() => activeSection = 'balances'}
|
||||
onclick={() => (activeSection = 'balances')}
|
||||
>
|
||||
Customer Account Balances ({summary.user_balances.length})
|
||||
</button>
|
||||
@@ -457,23 +442,38 @@
|
||||
<div class="hidden w-full overflow-x-auto md:block">
|
||||
<table class="w-full table-auto border-collapse text-sm">
|
||||
<thead>
|
||||
<tr class="border-b text-left text-xs text-gray-500 uppercase tracking-wider">
|
||||
<th class="py-3 font-medium cursor-pointer hover:text-foreground select-none" onclick={() => toggleCardSort('code')}>
|
||||
<tr class="border-b text-left text-xs tracking-wider text-gray-500 uppercase">
|
||||
<th
|
||||
class="cursor-pointer py-3 font-medium select-none hover:text-foreground"
|
||||
onclick={() => toggleCardSort('code')}
|
||||
>
|
||||
Card Code{sortArrow('code', cardSort)}
|
||||
</th>
|
||||
<th class="py-3 font-medium cursor-pointer hover:text-foreground select-none" onclick={() => toggleCardSort('added')}>
|
||||
<th
|
||||
class="cursor-pointer py-3 font-medium select-none hover:text-foreground"
|
||||
onclick={() => toggleCardSort('added')}
|
||||
>
|
||||
Total Added{sortArrow('added', cardSort)}
|
||||
</th>
|
||||
<th class="py-3 font-medium cursor-pointer hover:text-foreground select-none" onclick={() => toggleCardSort('remaining')}>
|
||||
<th
|
||||
class="cursor-pointer py-3 font-medium select-none hover:text-foreground"
|
||||
onclick={() => toggleCardSort('remaining')}
|
||||
>
|
||||
Remaining{sortArrow('remaining', cardSort)}
|
||||
</th>
|
||||
<th class="py-3 font-medium cursor-pointer hover:text-foreground select-none" onclick={() => toggleCardSort('created')}>
|
||||
<th
|
||||
class="cursor-pointer py-3 font-medium select-none hover:text-foreground"
|
||||
onclick={() => toggleCardSort('created')}
|
||||
>
|
||||
Created On{sortArrow('created', cardSort)}
|
||||
</th>
|
||||
<th class="py-3 font-medium cursor-pointer hover:text-foreground select-none" onclick={() => toggleCardSort('status')}>
|
||||
<th
|
||||
class="cursor-pointer py-3 font-medium select-none hover:text-foreground"
|
||||
onclick={() => toggleCardSort('status')}
|
||||
>
|
||||
Status{sortArrow('status', cardSort)}
|
||||
</th>
|
||||
<th class="py-3 font-medium text-center">Actions</th>
|
||||
<th class="py-3 text-center font-medium">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -498,20 +498,30 @@
|
||||
{#each sortedCards as gc (gc.id)}
|
||||
<tr class="border-b hover:bg-gray-50">
|
||||
<td class="py-3 font-mono font-bold text-gray-900">{formatCardCode(gc.id)}</td>
|
||||
<td class="py-3 font-medium text-gray-600">{formatCurrency(gc.total_funds_added)}</td>
|
||||
<td class="py-3 font-semibold text-card-foreground">{formatCurrency(gc.amount_remaining)}</td>
|
||||
<td class="py-3 font-medium text-gray-600"
|
||||
>{formatCurrency(gc.total_funds_added)}</td
|
||||
>
|
||||
<td class="py-3 font-semibold text-card-foreground"
|
||||
>{formatCurrency(gc.amount_remaining)}</td
|
||||
>
|
||||
<td class="py-3 text-gray-600">{formatDate(gc.created_at)}</td>
|
||||
<td class="py-3">
|
||||
{#if gc.redeemed_by}
|
||||
<span class="inline-flex items-center rounded-full bg-green-50 px-2.5 py-0.5 text-xs font-medium text-green-700 border border-green-200">
|
||||
<span
|
||||
class="inline-flex items-center rounded-full border border-green-200 bg-green-50 px-2.5 py-0.5 text-xs font-medium text-green-700"
|
||||
>
|
||||
Claimed
|
||||
</span>
|
||||
{:else if gc.amount_remaining === 0}
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 border border-gray-200">
|
||||
<span
|
||||
class="inline-flex items-center rounded-full border border-gray-200 bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600"
|
||||
>
|
||||
Spent
|
||||
</span>
|
||||
{:else}
|
||||
<span class="inline-flex items-center rounded-full bg-gray-50 px-2.5 py-0.5 text-xs font-medium text-gray-600 border border-gray-200">
|
||||
<span
|
||||
class="inline-flex items-center rounded-full border border-gray-200 bg-gray-50 px-2.5 py-0.5 text-xs font-medium text-gray-600"
|
||||
>
|
||||
Active
|
||||
</span>
|
||||
{/if}
|
||||
@@ -555,7 +565,7 @@
|
||||
<div class="grid gap-4 md:hidden">
|
||||
{#if loading}
|
||||
{#each Array(2) as _, i (i)}
|
||||
<div class="rounded-lg border p-4 space-y-3">
|
||||
<div class="space-y-3 rounded-lg border p-4">
|
||||
<Skeleton class="h-4 w-32" />
|
||||
<Skeleton class="h-4 w-full" />
|
||||
<Skeleton class="h-4 w-24" />
|
||||
@@ -567,35 +577,45 @@
|
||||
</div>
|
||||
{:else}
|
||||
{#each sortedCards as gc (gc.id)}
|
||||
<div class="rounded-lg border p-4 space-y-3 hover:bg-gray-50">
|
||||
<div class="space-y-3 rounded-lg border p-4 hover:bg-gray-50">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-mono font-bold text-gray-900">{formatCardCode(gc.id)}</span>
|
||||
{#if gc.redeemed_by}
|
||||
<span class="inline-flex items-center rounded-full bg-green-50 px-2 py-0.5 text-xs font-medium text-green-700 border border-green-200">
|
||||
<span
|
||||
class="inline-flex items-center rounded-full border border-green-200 bg-green-50 px-2 py-0.5 text-xs font-medium text-green-700"
|
||||
>
|
||||
Claimed
|
||||
</span>
|
||||
{:else if gc.amount_remaining === 0}
|
||||
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600 border border-gray-200">
|
||||
<span
|
||||
class="inline-flex items-center rounded-full border border-gray-200 bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600"
|
||||
>
|
||||
Spent
|
||||
</span>
|
||||
{:else}
|
||||
<span class="inline-flex items-center rounded-full bg-blue-50 px-2.5 py-0.5 text-xs font-medium text-blue-700 border border-blue-200">
|
||||
<span
|
||||
class="inline-flex items-center rounded-full border border-blue-200 bg-blue-50 px-2.5 py-0.5 text-xs font-medium text-blue-700"
|
||||
>
|
||||
Active
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2 text-xs text-gray-600 border-t border-b py-2">
|
||||
<div class="grid grid-cols-2 gap-2 border-t border-b py-2 text-xs text-gray-600">
|
||||
<div>
|
||||
<span class="text-gray-400">Total Added:</span>
|
||||
<span class="font-semibold text-gray-700 ml-1">{formatCurrency(gc.total_funds_added)}</span>
|
||||
<span class="ml-1 font-semibold text-gray-700"
|
||||
>{formatCurrency(gc.total_funds_added)}</span
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-400">Remaining:</span>
|
||||
<span class="font-semibold text-gray-700 ml-1">{formatCurrency(gc.amount_remaining)}</span>
|
||||
<span class="ml-1 font-semibold text-gray-700"
|
||||
>{formatCurrency(gc.amount_remaining)}</span
|
||||
>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<span class="text-gray-400">Created:</span>
|
||||
<span class="font-medium text-gray-700 ml-1">{formatDate(gc.created_at)}</span>
|
||||
<span class="ml-1 font-medium text-gray-700">{formatDate(gc.created_at)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 pt-1">
|
||||
@@ -633,17 +653,29 @@
|
||||
<div class="hidden w-full overflow-x-auto md:block">
|
||||
<table class="w-full table-auto border-collapse text-sm">
|
||||
<thead>
|
||||
<tr class="border-b text-left text-xs text-gray-500 uppercase tracking-wider">
|
||||
<th class="py-3 font-medium cursor-pointer hover:text-foreground select-none" onclick={() => toggleBalanceSort('name')}>
|
||||
<tr class="border-b text-left text-xs tracking-wider text-gray-500 uppercase">
|
||||
<th
|
||||
class="cursor-pointer py-3 font-medium select-none hover:text-foreground"
|
||||
onclick={() => toggleBalanceSort('name')}
|
||||
>
|
||||
Customer{sortArrow('name', balanceSort)}
|
||||
</th>
|
||||
<th class="py-3 font-medium cursor-pointer hover:text-foreground select-none" onclick={() => toggleBalanceSort('email')}>
|
||||
<th
|
||||
class="cursor-pointer py-3 font-medium select-none hover:text-foreground"
|
||||
onclick={() => toggleBalanceSort('email')}
|
||||
>
|
||||
Email{sortArrow('email', balanceSort)}
|
||||
</th>
|
||||
<th class="py-3 font-medium cursor-pointer hover:text-foreground select-none" onclick={() => toggleBalanceSort('balance')}>
|
||||
<th
|
||||
class="cursor-pointer py-3 font-medium select-none hover:text-foreground"
|
||||
onclick={() => toggleBalanceSort('balance')}
|
||||
>
|
||||
Account Balance{sortArrow('balance', balanceSort)}
|
||||
</th>
|
||||
<th class="py-3 font-medium cursor-pointer hover:text-foreground select-none" onclick={() => toggleBalanceSort('updated')}>
|
||||
<th
|
||||
class="cursor-pointer py-3 font-medium select-none hover:text-foreground"
|
||||
onclick={() => toggleBalanceSort('updated')}
|
||||
>
|
||||
Last Updated{sortArrow('updated', balanceSort)}
|
||||
</th>
|
||||
</tr>
|
||||
@@ -682,7 +714,7 @@
|
||||
<div class="grid gap-4 md:hidden">
|
||||
{#if loading}
|
||||
{#each Array(2) as _, i (i)}
|
||||
<div class="rounded-lg border p-4 space-y-3">
|
||||
<div class="space-y-3 rounded-lg border p-4">
|
||||
<Skeleton class="h-4 w-36" />
|
||||
<Skeleton class="h-4 w-full" />
|
||||
<Skeleton class="h-4 w-24" />
|
||||
@@ -694,22 +726,22 @@
|
||||
</div>
|
||||
{:else}
|
||||
{#each sortedBalances as ub (ub.user_id)}
|
||||
<div class="rounded-lg border p-4 space-y-3 hover:bg-gray-50">
|
||||
<div class="space-y-3 rounded-lg border p-4 hover:bg-gray-50">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-medium text-gray-900">{ub.name}</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2 text-xs text-gray-600 border-t border-b py-2">
|
||||
<div class="grid grid-cols-2 gap-2 border-t border-b py-2 text-xs text-gray-600">
|
||||
<div class="col-span-2">
|
||||
<span class="text-gray-400">Email:</span>
|
||||
<span class="font-medium text-gray-700 ml-1">{ub.email}</span>
|
||||
<span class="ml-1 font-medium text-gray-700">{ub.email}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-400">Balance:</span>
|
||||
<span class="font-bold text-primary ml-1">{formatCurrency(ub.balance)}</span>
|
||||
<span class="ml-1 font-bold text-primary">{formatCurrency(ub.balance)}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-400">Updated:</span>
|
||||
<span class="font-medium text-gray-700 ml-1">{formatDate(ub.updated_at)}</span>
|
||||
<span class="ml-1 font-medium text-gray-700">{formatDate(ub.updated_at)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -734,22 +766,32 @@
|
||||
<button
|
||||
type="button"
|
||||
class="w-full rounded-lg border border-gray-200 bg-white p-4 text-left transition-colors hover:bg-gray-50"
|
||||
onclick={() => { generateMode = 'giveaway'; generateStep = 'amount'; }}
|
||||
onclick={() => {
|
||||
generateMode = 'giveaway';
|
||||
generateStep = 'amount';
|
||||
}}
|
||||
>
|
||||
<div class="font-semibold text-card-foreground">Giveaway (On the House)</div>
|
||||
<div class="mt-1 text-sm text-muted-foreground">Free promotional card, no payment needed</div>
|
||||
<div class="mt-1 text-sm text-muted-foreground">
|
||||
Free promotional card, no payment needed
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="w-full rounded-lg border border-gray-200 bg-white p-4 text-left transition-colors hover:bg-gray-50"
|
||||
onclick={() => { generateMode = 'purchase'; generateStep = 'amount'; }}
|
||||
onclick={() => {
|
||||
generateMode = 'purchase';
|
||||
generateStep = 'amount';
|
||||
}}
|
||||
>
|
||||
<div class="font-semibold text-card-foreground">Customer Purchase</div>
|
||||
<div class="mt-1 text-sm text-muted-foreground">Collect payment via cash or card machine</div>
|
||||
<div class="mt-1 text-sm text-muted-foreground">
|
||||
Collect payment via cash or card machine
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<Modal.Footer>
|
||||
<Button variant="ghost" onclick={() => showGenerateModal = false}>Cancel</Button>
|
||||
<Button variant="ghost" onclick={() => (showGenerateModal = false)}>Cancel</Button>
|
||||
</Modal.Footer>
|
||||
{:else}
|
||||
<div class="space-y-4 py-4">
|
||||
@@ -763,25 +805,19 @@
|
||||
bind:value={generateAmount}
|
||||
/>
|
||||
{#if generateError}
|
||||
<span class="text-xs text-red-500 font-medium">{generateError}</span>
|
||||
<span class="text-xs font-medium text-red-500">{generateError}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal.Footer>
|
||||
<Button variant="ghost" onclick={() => generateStep = 'choice'}>Back</Button>
|
||||
<Button variant="ghost" onclick={() => (generateStep = 'choice')}>Back</Button>
|
||||
{#if generateMode === 'giveaway'}
|
||||
<Button
|
||||
onclick={generateGiftCard}
|
||||
disabled={creating || !isGenerateValid}
|
||||
>
|
||||
<Button onclick={generateGiftCard} disabled={creating || !isGenerateValid}>
|
||||
{creating ? 'Generating...' : 'Generate Card'}
|
||||
</Button>
|
||||
{:else}
|
||||
<Button
|
||||
onclick={goToGeneratePayment}
|
||||
disabled={!isGenerateValid}
|
||||
>
|
||||
<Button onclick={goToGeneratePayment} disabled={!isGenerateValid}>
|
||||
Continue to Payment
|
||||
</Button>
|
||||
{/if}
|
||||
@@ -795,7 +831,11 @@
|
||||
<Modal.Content class="max-w-md">
|
||||
<Modal.Header>
|
||||
<Modal.Title>Top Up Gift Card</Modal.Title>
|
||||
<Modal.Description>Add additional funds to active, unclaimed gift card {formatCardCode(selectedCardId ?? '')}.</Modal.Description>
|
||||
<Modal.Description
|
||||
>Add additional funds to active, unclaimed gift card {formatCardCode(
|
||||
selectedCardId ?? ''
|
||||
)}.</Modal.Description
|
||||
>
|
||||
</Modal.Header>
|
||||
|
||||
{#if topUpStep === 'choice'}
|
||||
@@ -804,7 +844,10 @@
|
||||
<button
|
||||
type="button"
|
||||
class="w-full rounded-lg border border-gray-200 bg-white p-4 text-left transition-colors hover:bg-gray-50"
|
||||
onclick={() => { topUpMode = 'giveaway'; topUpStep = 'amount'; }}
|
||||
onclick={() => {
|
||||
topUpMode = 'giveaway';
|
||||
topUpStep = 'amount';
|
||||
}}
|
||||
>
|
||||
<div class="font-semibold text-card-foreground">Giveaway (On the House)</div>
|
||||
<div class="mt-1 text-sm text-muted-foreground">Free top-up, no payment needed</div>
|
||||
@@ -812,14 +855,19 @@
|
||||
<button
|
||||
type="button"
|
||||
class="w-full rounded-lg border border-gray-200 bg-white p-4 text-left transition-colors hover:bg-gray-50"
|
||||
onclick={() => { topUpMode = 'purchase'; topUpStep = 'amount'; }}
|
||||
onclick={() => {
|
||||
topUpMode = 'purchase';
|
||||
topUpStep = 'amount';
|
||||
}}
|
||||
>
|
||||
<div class="font-semibold text-card-foreground">Customer Purchase</div>
|
||||
<div class="mt-1 text-sm text-muted-foreground">Collect payment via cash or card machine</div>
|
||||
<div class="mt-1 text-sm text-muted-foreground">
|
||||
Collect payment via cash or card machine
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<Modal.Footer>
|
||||
<Button variant="ghost" onclick={() => showTopUpModal = false}>Cancel</Button>
|
||||
<Button variant="ghost" onclick={() => (showTopUpModal = false)}>Cancel</Button>
|
||||
</Modal.Footer>
|
||||
{:else}
|
||||
<div class="space-y-4 py-4">
|
||||
@@ -833,27 +881,19 @@
|
||||
bind:value={topUpAmount}
|
||||
/>
|
||||
{#if topUpError}
|
||||
<span class="text-xs text-red-500 font-medium">{topUpError}</span>
|
||||
<span class="text-xs font-medium text-red-500">{topUpError}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal.Footer>
|
||||
<Button variant="ghost" onclick={() => topUpStep = 'choice'}>Back</Button>
|
||||
<Button variant="ghost" onclick={() => (topUpStep = 'choice')}>Back</Button>
|
||||
{#if topUpMode === 'giveaway'}
|
||||
<Button
|
||||
onclick={topUpCard}
|
||||
disabled={toppingUp || !isTopUpValid}
|
||||
>
|
||||
<Button onclick={topUpCard} disabled={toppingUp || !isTopUpValid}>
|
||||
{toppingUp ? 'Topping Up...' : 'Add Funds'}
|
||||
</Button>
|
||||
{:else}
|
||||
<Button
|
||||
onclick={goToTopUpPayment}
|
||||
disabled={!isTopUpValid}
|
||||
>
|
||||
Continue to Payment
|
||||
</Button>
|
||||
<Button onclick={goToTopUpPayment} disabled={!isTopUpValid}>Continue to Payment</Button>
|
||||
{/if}
|
||||
</Modal.Footer>
|
||||
{/if}
|
||||
@@ -865,7 +905,9 @@
|
||||
<Modal.Content class="max-w-md">
|
||||
<Modal.Header>
|
||||
<Modal.Title>Transfer Balance</Modal.Title>
|
||||
<Modal.Description>Transfer funds from {formatCardCode(selectedCardId ?? '')} directly to another card.</Modal.Description>
|
||||
<Modal.Description
|
||||
>Transfer funds from {formatCardCode(selectedCardId ?? '')} directly to another card.</Modal.Description
|
||||
>
|
||||
</Modal.Header>
|
||||
|
||||
<div class="space-y-4 py-4">
|
||||
@@ -880,7 +922,7 @@
|
||||
oninput={handleCodeInput}
|
||||
/>
|
||||
{#if transferCodeError}
|
||||
<span class="text-xs text-red-500 font-medium">{transferCodeError}</span>
|
||||
<span class="text-xs font-medium text-red-500">{transferCodeError}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
@@ -893,17 +935,14 @@
|
||||
bind:value={transferAmount}
|
||||
/>
|
||||
{#if transferAmountError}
|
||||
<span class="text-xs text-red-500 font-medium">{transferAmountError}</span>
|
||||
<span class="text-xs font-medium text-red-500">{transferAmountError}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal.Footer>
|
||||
<Button variant="outline" onclick={() => showTransferModal = false}>Cancel</Button>
|
||||
<Button
|
||||
onclick={transferCard}
|
||||
disabled={transferring || !isTransferValid}
|
||||
>
|
||||
<Button variant="outline" onclick={() => (showTransferModal = false)}>Cancel</Button>
|
||||
<Button onclick={transferCard} disabled={transferring || !isTransferValid}>
|
||||
{transferring ? 'Transferring...' : 'Transfer Balance'}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
@@ -916,7 +955,9 @@
|
||||
itemType="gift_card"
|
||||
action={tillAction}
|
||||
giftCardId={tillGiftCardId}
|
||||
onClose={() => { showTillPayment = false; }}
|
||||
onClose={() => {
|
||||
showTillPayment = false;
|
||||
}}
|
||||
onComplete={handleTillPaymentComplete}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user