@withpotter/components 0.13.0-next.82 → 0.13.0-next.85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dashboard/activity-feed.d.ts +2 -0
- package/dist/dashboard/activity-feed.js +3 -1
- package/dist/dashboard/activity-feed.js.map +1 -1
- package/dist/dashboard/activity-paid.d.ts +10 -0
- package/dist/dashboard/activity-paid.js +7 -0
- package/dist/dashboard/activity-paid.js.map +1 -0
- package/dist/dashboard/home/ask-controls.js +1 -0
- package/dist/dashboard/home/ask-controls.js.map +1 -1
- package/dist/dashboard/home/ask-mode-chip.js +4 -4
- package/dist/dashboard/home/ask-mode-chip.js.map +1 -1
- package/dist/dashboard/home/home-ask.js +3 -3
- package/dist/dashboard/home/home-ask.js.map +1 -1
- package/dist/dashboard/home/home-quick-actions.js +9 -1
- package/dist/dashboard/home/home-quick-actions.js.map +1 -1
- package/dist/dashboard/home/host-quick-actions.d.ts +19 -0
- package/dist/dashboard/home/host-quick-actions.js +12 -0
- package/dist/dashboard/home/host-quick-actions.js.map +1 -0
- package/dist/dashboard/home/widgets/activity-widget.js +56 -43
- package/dist/dashboard/home/widgets/activity-widget.js.map +1 -1
- package/dist/dashboard/home/widgets/home-widgets.js +3 -2
- package/dist/dashboard/home/widgets/home-widgets.js.map +1 -1
- package/dist/dashboard/home/widgets/host-widgets.d.ts +6 -0
- package/dist/dashboard/home/widgets/host-widgets.js +16 -0
- package/dist/dashboard/home/widgets/host-widgets.js.map +1 -0
- package/dist/dashboard/home/widgets/member-label.d.ts +11 -0
- package/dist/dashboard/home/widgets/member-label.js +10 -0
- package/dist/dashboard/home/widgets/member-label.js.map +1 -0
- package/dist/dashboard/home/widgets/middle-ellipsis.d.ts +5 -0
- package/dist/dashboard/home/widgets/middle-ellipsis.js +11 -0
- package/dist/dashboard/home/widgets/middle-ellipsis.js.map +1 -0
- package/dist/dashboard/home/widgets/money-widgets.js +9 -4
- package/dist/dashboard/home/widgets/money-widgets.js.map +1 -1
- package/dist/dashboard/home/widgets/registry.d.ts +6 -3
- package/dist/dashboard/home/widgets/registry.js +12 -2
- package/dist/dashboard/home/widgets/registry.js.map +1 -1
- package/dist/dashboard/home/widgets/revenue-series.d.ts +12 -0
- package/dist/dashboard/home/widgets/revenue-series.js +26 -0
- package/dist/dashboard/home/widgets/revenue-series.js.map +1 -0
- package/dist/dashboard/home/widgets/team-widget.js +2 -1
- package/dist/dashboard/home/widgets/team-widget.js.map +1 -1
- package/dist/dashboard/home/widgets/types.d.ts +13 -1
- package/dist/dashboard/home/widgets/types.js.map +1 -1
- package/dist/dashboard/home/widgets/use-home-layout.js +9 -4
- package/dist/dashboard/home/widgets/use-home-layout.js.map +1 -1
- package/dist/dashboard/home/widgets/website-widget.js +9 -1
- package/dist/dashboard/home/widgets/website-widget.js.map +1 -1
- package/dist/features/gift-card-artwork.js +1 -1
- package/dist/features/gift-card-artwork.js.map +1 -1
- package/dist/features/gift-cards/gift-card-logic.d.ts +41 -0
- package/dist/features/gift-cards/gift-card-logic.js +65 -0
- package/dist/features/gift-cards/gift-card-logic.js.map +1 -0
- package/dist/features/gift-cards/gift-cards-feature.js +656 -518
- package/dist/features/gift-cards/gift-cards-feature.js.map +1 -1
- package/dist/features/gift-cards/hint.d.ts +9 -0
- package/dist/features/gift-cards/hint.js +25 -0
- package/dist/features/gift-cards/hint.js.map +1 -0
- package/dist/features/product-form.js +1 -1
- package/dist/features/product-form.js.map +1 -1
- package/dist/sidebar/nav-renderers.js +2 -0
- package/dist/sidebar/nav-renderers.js.map +1 -1
- package/dist/sidebar/nav-section.js +5 -3
- package/dist/sidebar/nav-section.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
|
-
import {
|
|
5
|
-
Copy,
|
|
6
|
-
Download,
|
|
7
|
-
Gift,
|
|
8
|
-
History,
|
|
9
|
-
Mail,
|
|
10
|
-
Plus,
|
|
11
|
-
Search,
|
|
12
|
-
X
|
|
13
|
-
} from "lucide-react";
|
|
3
|
+
import { useCallback, useEffect, useId, useRef, useState } from "react";
|
|
4
|
+
import { Copy, Download, Gift, History, Mail, Plus, Search } from "lucide-react";
|
|
14
5
|
import { useAuth } from "@withpotter/lib/auth-context";
|
|
15
6
|
import { normalizeCurrencyCode } from "@withpotter/lib/utils";
|
|
7
|
+
import { Button } from "@withpotter/ui-kit/button";
|
|
8
|
+
import { Field } from "@withpotter/ui-kit/field";
|
|
9
|
+
import { FormDialog } from "@withpotter/ui-kit/form-dialog";
|
|
16
10
|
import { InlineDeleteButton } from "@withpotter/ui-kit/inline-delete-button";
|
|
11
|
+
import { Input } from "@withpotter/ui-kit/input";
|
|
12
|
+
import { ListPage } from "@withpotter/ui-kit/list-page";
|
|
13
|
+
import { Textarea } from "@withpotter/ui-kit/textarea";
|
|
14
|
+
import { Tooltip } from "@withpotter/ui-kit/tooltip";
|
|
17
15
|
import {
|
|
18
16
|
giftCardsApi,
|
|
17
|
+
giftcardsApi,
|
|
19
18
|
communicationsApi
|
|
20
19
|
} from "@withpotter/lib/api";
|
|
21
20
|
import { useFormatCurrency } from "@withpotter/hooks/use-format-currency";
|
|
@@ -34,6 +33,15 @@ import {
|
|
|
34
33
|
GiftCardArtwork,
|
|
35
34
|
GIFT_CARD_PRESETS
|
|
36
35
|
} from "../gift-card-artwork";
|
|
36
|
+
import {
|
|
37
|
+
createInFlightDeduper,
|
|
38
|
+
currencySymbol,
|
|
39
|
+
isFirstRun,
|
|
40
|
+
PAGE_HINT_LINES,
|
|
41
|
+
STATUS_FILTERS,
|
|
42
|
+
STATUS_LABEL
|
|
43
|
+
} from "./gift-card-logic";
|
|
44
|
+
import { Hint } from "./hint";
|
|
37
45
|
const PAGE_SIZE = 20;
|
|
38
46
|
const LIVE = process.env.NEXT_PUBLIC_BOX_GIFT_CARDS_LIVE === "true";
|
|
39
47
|
const LIVE_REFRESH_MS = 2e3;
|
|
@@ -65,24 +73,6 @@ function statusClass(status) {
|
|
|
65
73
|
return "bg-[var(--bg-muted)] text-[var(--text-tertiary)]";
|
|
66
74
|
}
|
|
67
75
|
}
|
|
68
|
-
function Modal({ title, onClose, children }) {
|
|
69
|
-
return /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-end justify-center bg-black/40 sm:items-center sm:p-4", children: /* @__PURE__ */ jsxs("div", { className: "max-h-[92vh] w-full overflow-y-auto bg-[var(--bg-base)] sm:max-w-xl sm:rounded-md sm:border sm:border-[var(--border-default)]", children: [
|
|
70
|
-
/* @__PURE__ */ jsxs("div", { className: "sticky top-0 z-10 flex items-center justify-between border-b border-[var(--border-default)] bg-[var(--bg-base)] px-4 py-3", children: [
|
|
71
|
-
/* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-[var(--text-primary)]", children: title }),
|
|
72
|
-
/* @__PURE__ */ jsx(
|
|
73
|
-
"button",
|
|
74
|
-
{
|
|
75
|
-
type: "button",
|
|
76
|
-
onClick: onClose,
|
|
77
|
-
className: "rounded-md p-2 text-[var(--text-tertiary)] hover:bg-[var(--interactive-hover)]",
|
|
78
|
-
"aria-label": "Close",
|
|
79
|
-
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
80
|
-
}
|
|
81
|
-
)
|
|
82
|
-
] }),
|
|
83
|
-
children
|
|
84
|
-
] }) });
|
|
85
|
-
}
|
|
86
76
|
function IssueGiftCardModal({
|
|
87
77
|
currency,
|
|
88
78
|
merchantName,
|
|
@@ -90,6 +80,16 @@ function IssueGiftCardModal({
|
|
|
90
80
|
onCreated
|
|
91
81
|
}) {
|
|
92
82
|
const { tenant } = useAuth();
|
|
83
|
+
const formId = useId();
|
|
84
|
+
const ids = {
|
|
85
|
+
amount: `${formId}-amount`,
|
|
86
|
+
recipientName: `${formId}-recipient-name`,
|
|
87
|
+
recipientEmail: `${formId}-recipient-email`,
|
|
88
|
+
purchaserName: `${formId}-purchaser-name`,
|
|
89
|
+
purchaserEmail: `${formId}-purchaser-email`,
|
|
90
|
+
message: `${formId}-message`,
|
|
91
|
+
expiresAt: `${formId}-expires-at`
|
|
92
|
+
};
|
|
93
93
|
const [form, setForm] = useState({
|
|
94
94
|
originalAmount: 0,
|
|
95
95
|
currency,
|
|
@@ -115,178 +115,233 @@ function IssueGiftCardModal({
|
|
|
115
115
|
setSaving(false);
|
|
116
116
|
}
|
|
117
117
|
};
|
|
118
|
-
const
|
|
119
|
-
return /* @__PURE__ */ jsx(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}).format(form.originalAmount) : void 0,
|
|
130
|
-
recipientName: form.recipientName,
|
|
131
|
-
senderName: form.purchaserName
|
|
132
|
-
}
|
|
133
|
-
),
|
|
134
|
-
/* @__PURE__ */ jsxs("fieldset", { children: [
|
|
135
|
-
/* @__PURE__ */ jsx("legend", { className: "text-sm font-medium text-[var(--text-secondary)]", children: "Design" }),
|
|
136
|
-
/* @__PURE__ */ jsx("div", { className: "mt-2 grid grid-cols-4 gap-2 sm:grid-cols-1", children: GIFT_CARD_PRESETS.map((design) => {
|
|
137
|
-
const selected = form.design?.designId === design.designId;
|
|
138
|
-
return /* @__PURE__ */ jsxs(
|
|
139
|
-
"button",
|
|
140
|
-
{
|
|
141
|
-
type: "button",
|
|
142
|
-
onClick: () => setField("design", design),
|
|
143
|
-
className: `flex min-w-0 items-center gap-2 rounded-md border p-2 text-left text-xs font-medium ${selected ? "border-brand-500 bg-brand-500/10 text-[var(--text-primary)]" : "border-[var(--border-default)] text-[var(--text-secondary)] hover:bg-[var(--interactive-hover)]"}`,
|
|
144
|
-
"aria-label": design.name,
|
|
145
|
-
"aria-pressed": selected,
|
|
146
|
-
children: [
|
|
147
|
-
/* @__PURE__ */ jsx(
|
|
148
|
-
"span",
|
|
149
|
-
{
|
|
150
|
-
className: "h-5 w-5 shrink-0 rounded-sm border border-black/10",
|
|
151
|
-
style: { backgroundColor: design.palette.background }
|
|
152
|
-
}
|
|
153
|
-
),
|
|
154
|
-
/* @__PURE__ */ jsx("span", { className: "hidden truncate sm:block", children: design.name })
|
|
155
|
-
]
|
|
156
|
-
},
|
|
157
|
-
design.designId
|
|
158
|
-
);
|
|
159
|
-
}) })
|
|
160
|
-
] })
|
|
161
|
-
] }),
|
|
162
|
-
/* @__PURE__ */ jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
|
|
163
|
-
/* @__PURE__ */ jsxs("label", { className: "space-y-1.5", children: [
|
|
164
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-[var(--text-secondary)]", children: "Amount" }),
|
|
165
|
-
/* @__PURE__ */ jsx(
|
|
166
|
-
"input",
|
|
167
|
-
{
|
|
168
|
-
className: inputClass,
|
|
169
|
-
type: "number",
|
|
170
|
-
min: "0.01",
|
|
171
|
-
step: "0.01",
|
|
172
|
-
required: true,
|
|
173
|
-
value: form.originalAmount || "",
|
|
174
|
-
onChange: (event) => setField("originalAmount", Number(event.target.value))
|
|
175
|
-
}
|
|
176
|
-
)
|
|
177
|
-
] }),
|
|
178
|
-
/* @__PURE__ */ jsxs("label", { className: "space-y-1.5", children: [
|
|
179
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-[var(--text-secondary)]", children: "Currency" }),
|
|
180
|
-
/* @__PURE__ */ jsx(
|
|
181
|
-
"input",
|
|
182
|
-
{
|
|
183
|
-
className: inputClass,
|
|
184
|
-
readOnly: true,
|
|
185
|
-
required: true,
|
|
186
|
-
value: form.currency
|
|
187
|
-
}
|
|
188
|
-
)
|
|
189
|
-
] }),
|
|
190
|
-
/* @__PURE__ */ jsxs("label", { className: "space-y-1.5", children: [
|
|
191
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-[var(--text-secondary)]", children: "Recipient name" }),
|
|
192
|
-
/* @__PURE__ */ jsx(
|
|
193
|
-
"input",
|
|
194
|
-
{
|
|
195
|
-
className: inputClass,
|
|
196
|
-
value: form.recipientName ?? "",
|
|
197
|
-
onChange: (event) => setField("recipientName", event.target.value)
|
|
198
|
-
}
|
|
199
|
-
),
|
|
200
|
-
/* @__PURE__ */ jsx("span", { className: "block text-xs text-[var(--text-muted)]", children: "The person who receives the gift card and spends it." })
|
|
201
|
-
] }),
|
|
202
|
-
/* @__PURE__ */ jsxs("label", { className: "space-y-1.5", children: [
|
|
203
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-[var(--text-secondary)]", children: "Recipient email" }),
|
|
118
|
+
const symbol = currencySymbol(form.currency || currency);
|
|
119
|
+
return /* @__PURE__ */ jsx(
|
|
120
|
+
FormDialog,
|
|
121
|
+
{
|
|
122
|
+
title: "Issue gift card",
|
|
123
|
+
description: "We create the code for you. Fields marked * are required.",
|
|
124
|
+
size: "lg",
|
|
125
|
+
className: "max-w-4xl",
|
|
126
|
+
onClose,
|
|
127
|
+
footer: /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
128
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", onClick: onClose, children: "Cancel" }),
|
|
204
129
|
/* @__PURE__ */ jsx(
|
|
205
|
-
|
|
130
|
+
Button,
|
|
206
131
|
{
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
132
|
+
type: "submit",
|
|
133
|
+
form: formId,
|
|
134
|
+
disabled: saving || form.originalAmount <= 0,
|
|
135
|
+
children: saving ? "Issuing..." : "Issue gift card"
|
|
211
136
|
}
|
|
212
137
|
)
|
|
213
138
|
] }),
|
|
214
|
-
/* @__PURE__ */ jsxs(
|
|
215
|
-
|
|
216
|
-
/* @__PURE__ */ jsx(
|
|
217
|
-
"input",
|
|
218
|
-
{
|
|
219
|
-
className: inputClass,
|
|
220
|
-
value: form.purchaserName ?? "",
|
|
221
|
-
onChange: (event) => setField("purchaserName", event.target.value)
|
|
222
|
-
}
|
|
223
|
-
),
|
|
224
|
-
/* @__PURE__ */ jsx("span", { className: "block text-xs text-[var(--text-muted)]", children: "The person paying for the gift card." })
|
|
225
|
-
] }),
|
|
226
|
-
/* @__PURE__ */ jsxs("label", { className: "space-y-1.5", children: [
|
|
227
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-[var(--text-secondary)]", children: "Purchaser email" }),
|
|
228
|
-
/* @__PURE__ */ jsx(
|
|
229
|
-
"input",
|
|
230
|
-
{
|
|
231
|
-
className: inputClass,
|
|
232
|
-
type: "email",
|
|
233
|
-
value: form.purchaserEmail ?? "",
|
|
234
|
-
onChange: (event) => setField("purchaserEmail", event.target.value)
|
|
235
|
-
}
|
|
236
|
-
)
|
|
237
|
-
] })
|
|
238
|
-
] }),
|
|
239
|
-
/* @__PURE__ */ jsxs("label", { className: "block space-y-1.5", children: [
|
|
240
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-[var(--text-secondary)]", children: "Personal message" }),
|
|
241
|
-
/* @__PURE__ */ jsx(
|
|
242
|
-
"textarea",
|
|
243
|
-
{
|
|
244
|
-
className: `${inputClass} min-h-24 resize-y`,
|
|
245
|
-
maxLength: 500,
|
|
246
|
-
value: form.personalMessage ?? "",
|
|
247
|
-
onChange: (event) => setField("personalMessage", event.target.value)
|
|
248
|
-
}
|
|
249
|
-
)
|
|
250
|
-
] }),
|
|
251
|
-
/* @__PURE__ */ jsxs("label", { className: "block space-y-1.5", children: [
|
|
252
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-[var(--text-secondary)]", children: "Expiration date" }),
|
|
253
|
-
/* @__PURE__ */ jsx(
|
|
254
|
-
"input",
|
|
139
|
+
children: /* @__PURE__ */ jsxs(
|
|
140
|
+
"form",
|
|
255
141
|
{
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
142
|
+
id: formId,
|
|
143
|
+
onSubmit: submit,
|
|
144
|
+
className: "grid gap-6 md:grid-cols-[17rem_minmax(0,1fr)]",
|
|
145
|
+
children: [
|
|
146
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4 md:self-start", children: [
|
|
147
|
+
/* @__PURE__ */ jsx(
|
|
148
|
+
GiftCardArtwork,
|
|
149
|
+
{
|
|
150
|
+
design: form.design ?? GIFT_CARD_PRESETS[0],
|
|
151
|
+
merchantName,
|
|
152
|
+
amount: form.originalAmount > 0 ? new Intl.NumberFormat(void 0, {
|
|
153
|
+
style: "currency",
|
|
154
|
+
currency: form.currency || currency
|
|
155
|
+
}).format(form.originalAmount) : void 0,
|
|
156
|
+
recipientName: form.recipientName,
|
|
157
|
+
senderName: form.purchaserName
|
|
158
|
+
}
|
|
159
|
+
),
|
|
160
|
+
/* @__PURE__ */ jsxs("fieldset", { children: [
|
|
161
|
+
/* @__PURE__ */ jsx("legend", { className: "text-sm font-medium text-[var(--text-secondary)]", children: "Design" }),
|
|
162
|
+
/* @__PURE__ */ jsx("div", { className: "mt-2 grid grid-cols-4 gap-2 md:grid-cols-2", children: GIFT_CARD_PRESETS.map((design) => {
|
|
163
|
+
const selected = form.design?.designId === design.designId;
|
|
164
|
+
return /* @__PURE__ */ jsxs(
|
|
165
|
+
"button",
|
|
166
|
+
{
|
|
167
|
+
type: "button",
|
|
168
|
+
onClick: () => setField("design", design),
|
|
169
|
+
className: `flex min-w-0 items-center gap-2 rounded-md border p-2 text-left text-xs font-medium ${selected ? "border-brand-500 bg-brand-500/10 text-[var(--text-primary)]" : "border-[var(--border-default)] text-[var(--text-secondary)] hover:bg-[var(--interactive-hover)]"}`,
|
|
170
|
+
"aria-label": design.name,
|
|
171
|
+
"aria-pressed": selected,
|
|
172
|
+
children: [
|
|
173
|
+
/* @__PURE__ */ jsx(
|
|
174
|
+
"span",
|
|
175
|
+
{
|
|
176
|
+
className: "h-5 w-5 shrink-0 rounded-sm border border-black/10",
|
|
177
|
+
style: { backgroundColor: design.palette.background }
|
|
178
|
+
}
|
|
179
|
+
),
|
|
180
|
+
/* @__PURE__ */ jsx("span", { className: "hidden truncate md:block", children: design.name })
|
|
181
|
+
]
|
|
182
|
+
},
|
|
183
|
+
design.designId
|
|
184
|
+
);
|
|
185
|
+
}) })
|
|
186
|
+
] })
|
|
187
|
+
] }),
|
|
188
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
189
|
+
/* @__PURE__ */ jsx(
|
|
190
|
+
Field,
|
|
191
|
+
{
|
|
192
|
+
label: "Amount",
|
|
193
|
+
htmlFor: ids.amount,
|
|
194
|
+
required: true,
|
|
195
|
+
hint: `In ${form.currency || currency}, your business currency. It cannot be changed here.`,
|
|
196
|
+
children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
197
|
+
/* @__PURE__ */ jsx(
|
|
198
|
+
"span",
|
|
199
|
+
{
|
|
200
|
+
"aria-hidden": true,
|
|
201
|
+
className: "pointer-events-none absolute inset-y-0 left-3 flex items-center text-sm text-[var(--text-tertiary)]",
|
|
202
|
+
children: symbol
|
|
203
|
+
}
|
|
204
|
+
),
|
|
205
|
+
/* @__PURE__ */ jsx(
|
|
206
|
+
Input,
|
|
207
|
+
{
|
|
208
|
+
id: ids.amount,
|
|
209
|
+
type: "number",
|
|
210
|
+
min: "0.01",
|
|
211
|
+
step: "0.01",
|
|
212
|
+
required: true,
|
|
213
|
+
"aria-required": true,
|
|
214
|
+
className: symbol.length > 2 ? "pl-14" : "pl-8",
|
|
215
|
+
value: form.originalAmount || "",
|
|
216
|
+
onChange: (event) => setField("originalAmount", Number(event.target.value))
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
] })
|
|
220
|
+
}
|
|
221
|
+
),
|
|
222
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
|
|
223
|
+
/* @__PURE__ */ jsx(
|
|
224
|
+
Field,
|
|
225
|
+
{
|
|
226
|
+
label: "Recipient name",
|
|
227
|
+
htmlFor: ids.recipientName,
|
|
228
|
+
hint: "Optional. Who receives and spends the card.",
|
|
229
|
+
children: /* @__PURE__ */ jsx(
|
|
230
|
+
Input,
|
|
231
|
+
{
|
|
232
|
+
id: ids.recipientName,
|
|
233
|
+
value: form.recipientName ?? "",
|
|
234
|
+
onChange: (event) => setField("recipientName", event.target.value)
|
|
235
|
+
}
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
),
|
|
239
|
+
/* @__PURE__ */ jsx(
|
|
240
|
+
Field,
|
|
241
|
+
{
|
|
242
|
+
label: "Recipient email",
|
|
243
|
+
htmlFor: ids.recipientEmail,
|
|
244
|
+
hint: "Optional. Nothing is emailed when you issue. Send it from the card's details.",
|
|
245
|
+
children: /* @__PURE__ */ jsx(
|
|
246
|
+
Input,
|
|
247
|
+
{
|
|
248
|
+
id: ids.recipientEmail,
|
|
249
|
+
type: "email",
|
|
250
|
+
value: form.recipientEmail ?? "",
|
|
251
|
+
onChange: (event) => setField("recipientEmail", event.target.value)
|
|
252
|
+
}
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
),
|
|
256
|
+
/* @__PURE__ */ jsx(
|
|
257
|
+
Field,
|
|
258
|
+
{
|
|
259
|
+
label: "Purchaser name",
|
|
260
|
+
htmlFor: ids.purchaserName,
|
|
261
|
+
hint: "Optional. Who is paying for the card.",
|
|
262
|
+
children: /* @__PURE__ */ jsx(
|
|
263
|
+
Input,
|
|
264
|
+
{
|
|
265
|
+
id: ids.purchaserName,
|
|
266
|
+
value: form.purchaserName ?? "",
|
|
267
|
+
onChange: (event) => setField("purchaserName", event.target.value)
|
|
268
|
+
}
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
),
|
|
272
|
+
/* @__PURE__ */ jsx(
|
|
273
|
+
Field,
|
|
274
|
+
{
|
|
275
|
+
label: "Purchaser email",
|
|
276
|
+
htmlFor: ids.purchaserEmail,
|
|
277
|
+
hint: "Optional.",
|
|
278
|
+
children: /* @__PURE__ */ jsx(
|
|
279
|
+
Input,
|
|
280
|
+
{
|
|
281
|
+
id: ids.purchaserEmail,
|
|
282
|
+
type: "email",
|
|
283
|
+
value: form.purchaserEmail ?? "",
|
|
284
|
+
onChange: (event) => setField("purchaserEmail", event.target.value)
|
|
285
|
+
}
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
)
|
|
289
|
+
] }),
|
|
290
|
+
/* @__PURE__ */ jsx(
|
|
291
|
+
Field,
|
|
292
|
+
{
|
|
293
|
+
label: "Personal message",
|
|
294
|
+
htmlFor: ids.message,
|
|
295
|
+
hint: "Optional. Up to 500 characters.",
|
|
296
|
+
children: /* @__PURE__ */ jsx(
|
|
297
|
+
Textarea,
|
|
298
|
+
{
|
|
299
|
+
id: ids.message,
|
|
300
|
+
rows: 3,
|
|
301
|
+
maxLength: 500,
|
|
302
|
+
value: form.personalMessage ?? "",
|
|
303
|
+
onChange: (event) => setField("personalMessage", event.target.value)
|
|
304
|
+
}
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
),
|
|
308
|
+
/* @__PURE__ */ jsx(
|
|
309
|
+
Field,
|
|
310
|
+
{
|
|
311
|
+
label: "Expiration date",
|
|
312
|
+
htmlFor: ids.expiresAt,
|
|
313
|
+
hint: "Optional. Leave it empty and the card has no expiry date.",
|
|
314
|
+
children: /* @__PURE__ */ jsx(
|
|
315
|
+
Input,
|
|
316
|
+
{
|
|
317
|
+
id: ids.expiresAt,
|
|
318
|
+
type: "date",
|
|
319
|
+
min: minimumExpirationDate(),
|
|
320
|
+
value: form.expiresAt ?? "",
|
|
321
|
+
onChange: (event) => setField("expiresAt", event.target.value)
|
|
322
|
+
}
|
|
323
|
+
)
|
|
324
|
+
}
|
|
325
|
+
),
|
|
326
|
+
error && /* @__PURE__ */ jsx(
|
|
327
|
+
"p",
|
|
328
|
+
{
|
|
329
|
+
role: "alert",
|
|
330
|
+
className: "rounded-md bg-[var(--error-bg)] px-3 py-2 text-sm text-[var(--error)]",
|
|
331
|
+
children: error
|
|
332
|
+
}
|
|
333
|
+
)
|
|
334
|
+
] })
|
|
335
|
+
]
|
|
282
336
|
}
|
|
283
337
|
)
|
|
284
|
-
|
|
285
|
-
|
|
338
|
+
}
|
|
339
|
+
);
|
|
286
340
|
}
|
|
287
341
|
function BalanceModal({ onClose, initialCode = "" }) {
|
|
288
342
|
const { tenant } = useAuth();
|
|
289
343
|
const formatCurrency = useFormatCurrency();
|
|
344
|
+
const codeId = useId();
|
|
290
345
|
const [code, setCode] = useState(initialCode);
|
|
291
346
|
const [checking, setChecking] = useState(false);
|
|
292
347
|
const [result, setResult] = useState(null);
|
|
@@ -304,35 +359,36 @@ function BalanceModal({ onClose, initialCode = "" }) {
|
|
|
304
359
|
setChecking(false);
|
|
305
360
|
}
|
|
306
361
|
};
|
|
307
|
-
return /* @__PURE__ */ jsx(
|
|
308
|
-
/* @__PURE__ */
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
362
|
+
return /* @__PURE__ */ jsx(FormDialog, { title: "Check gift card balance", size: "sm", onClose, children: /* @__PURE__ */ jsxs("form", { onSubmit: submit, className: "space-y-4", children: [
|
|
363
|
+
/* @__PURE__ */ jsx(Field, { label: "Gift card code", htmlFor: codeId, required: true, children: /* @__PURE__ */ jsx(
|
|
364
|
+
Input,
|
|
365
|
+
{
|
|
366
|
+
id: codeId,
|
|
367
|
+
required: true,
|
|
368
|
+
"aria-required": true,
|
|
369
|
+
autoComplete: "off",
|
|
370
|
+
spellCheck: false,
|
|
371
|
+
className: "font-mono uppercase",
|
|
372
|
+
value: code,
|
|
373
|
+
onChange: (event) => {
|
|
374
|
+
setCode(event.target.value.toUpperCase());
|
|
375
|
+
setResult(null);
|
|
320
376
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
result && /* @__PURE__ */ jsx(
|
|
377
|
+
}
|
|
378
|
+
) }),
|
|
379
|
+
/* @__PURE__ */ jsx("div", { role: "status", "aria-live": "polite", children: result && /* @__PURE__ */ jsx(
|
|
324
380
|
"div",
|
|
325
381
|
{
|
|
326
382
|
className: `rounded-md p-4 ${result.valid ? "bg-[var(--success-bg)] text-[var(--success)]" : "bg-[var(--error-bg)] text-[var(--error)]"}`,
|
|
327
383
|
children: result.valid ? `Available balance: ${formatCurrency(result.balance ?? 0)}` : result.error || "This gift card is not valid."
|
|
328
384
|
}
|
|
329
|
-
),
|
|
385
|
+
) }),
|
|
330
386
|
/* @__PURE__ */ jsx(
|
|
331
|
-
|
|
387
|
+
Button,
|
|
332
388
|
{
|
|
333
389
|
type: "submit",
|
|
390
|
+
className: "w-full",
|
|
334
391
|
disabled: checking || !code.trim(),
|
|
335
|
-
className: "w-full rounded-md bg-brand-500 px-4 py-2 text-sm font-medium text-[var(--text-inverse)] hover:bg-brand-600 disabled:opacity-50",
|
|
336
392
|
children: checking ? "Checking..." : "Check balance"
|
|
337
393
|
}
|
|
338
394
|
)
|
|
@@ -409,7 +465,7 @@ function GiftCardDetailModal({
|
|
|
409
465
|
if (!tenant?.slug) return;
|
|
410
466
|
setSaving(true);
|
|
411
467
|
try {
|
|
412
|
-
await
|
|
468
|
+
await giftcardsApi.cancel(tenant.slug, giftCard.id);
|
|
413
469
|
toast({ title: "Gift card cancelled" });
|
|
414
470
|
onChanged();
|
|
415
471
|
onClose();
|
|
@@ -418,60 +474,63 @@ function GiftCardDetailModal({
|
|
|
418
474
|
setSaving(false);
|
|
419
475
|
}
|
|
420
476
|
};
|
|
421
|
-
return /* @__PURE__ */ jsx(
|
|
477
|
+
return /* @__PURE__ */ jsx(FormDialog, { title: "Gift card details", onClose, children: /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
|
|
422
478
|
/* @__PURE__ */ jsx(GiftCardArtwork, { ...artworkData }),
|
|
423
479
|
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
424
|
-
/* @__PURE__ */
|
|
425
|
-
|
|
480
|
+
/* @__PURE__ */ jsx(
|
|
481
|
+
Button,
|
|
426
482
|
{
|
|
427
483
|
type: "button",
|
|
484
|
+
variant: "secondary",
|
|
485
|
+
icon: /* @__PURE__ */ jsx(Download, {}),
|
|
428
486
|
onClick: () => downloadGiftCardArtwork(artworkData),
|
|
429
|
-
|
|
430
|
-
children: [
|
|
431
|
-
/* @__PURE__ */ jsx(Download, { className: "h-4 w-4" }),
|
|
432
|
-
"Download"
|
|
433
|
-
]
|
|
487
|
+
children: "Download"
|
|
434
488
|
}
|
|
435
489
|
),
|
|
436
|
-
/* @__PURE__ */
|
|
437
|
-
|
|
490
|
+
/* @__PURE__ */ jsx(
|
|
491
|
+
Tooltip,
|
|
438
492
|
{
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
493
|
+
label: giftCard.recipientEmail ? "" : "Add a recipient email to send this card",
|
|
494
|
+
children: /* @__PURE__ */ jsx("span", { className: "block", children: /* @__PURE__ */ jsx(
|
|
495
|
+
Button,
|
|
496
|
+
{
|
|
497
|
+
type: "button",
|
|
498
|
+
variant: "secondary",
|
|
499
|
+
icon: /* @__PURE__ */ jsx(Mail, {}),
|
|
500
|
+
className: "w-full",
|
|
501
|
+
onClick: sendByEmail,
|
|
502
|
+
disabled: sharing || !giftCard.recipientEmail,
|
|
503
|
+
children: sharing ? "Sending..." : "Send by email"
|
|
504
|
+
}
|
|
505
|
+
) })
|
|
448
506
|
}
|
|
449
507
|
)
|
|
450
508
|
] }),
|
|
451
509
|
/* @__PURE__ */ jsxs("div", { className: "rounded-md border border-[var(--border-default)] bg-[var(--bg-subtle)] p-4", children: [
|
|
452
510
|
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
|
|
453
511
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
454
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium
|
|
512
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-[var(--text-muted)]", children: "Code" }),
|
|
455
513
|
/* @__PURE__ */ jsx("p", { className: "mt-1 font-mono text-xl font-semibold text-[var(--text-primary)]", children: giftCard.code })
|
|
456
514
|
] }),
|
|
457
515
|
/* @__PURE__ */ jsx(
|
|
458
|
-
|
|
516
|
+
Button,
|
|
459
517
|
{
|
|
460
518
|
type: "button",
|
|
519
|
+
variant: "secondary",
|
|
520
|
+
size: "icon-sm",
|
|
521
|
+
"aria-label": "Copy code",
|
|
461
522
|
onClick: copyCode,
|
|
462
|
-
|
|
463
|
-
title: "Copy code",
|
|
464
|
-
children: /* @__PURE__ */ jsx(Copy, { className: "h-4 w-4" })
|
|
523
|
+
children: /* @__PURE__ */ jsx(Copy, {})
|
|
465
524
|
}
|
|
466
525
|
)
|
|
467
526
|
] }),
|
|
468
527
|
/* @__PURE__ */ jsxs("div", { className: "mt-4 grid grid-cols-2 gap-4", children: [
|
|
469
528
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
470
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--text-muted)]", children: "Balance" }),
|
|
529
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--text-muted)]", children: "Balance left" }),
|
|
471
530
|
/* @__PURE__ */ jsx("p", { className: "font-semibold text-[var(--text-primary)]", children: formatCurrency(Number(giftCard.balance), giftCard.currency) })
|
|
472
531
|
] }),
|
|
473
532
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
474
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--text-muted)]", children: "
|
|
533
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--text-muted)]", children: "Original value" }),
|
|
475
534
|
/* @__PURE__ */ jsx("p", { className: "font-semibold text-[var(--text-primary)]", children: formatCurrency(
|
|
476
535
|
Number(giftCard.originalAmount),
|
|
477
536
|
giftCard.currency
|
|
@@ -491,44 +550,41 @@ function GiftCardDetailModal({
|
|
|
491
550
|
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-[var(--text-primary)]", children: "Redeem value" }),
|
|
492
551
|
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
|
|
493
552
|
/* @__PURE__ */ jsx(
|
|
494
|
-
|
|
553
|
+
Input,
|
|
495
554
|
{
|
|
555
|
+
label: "Amount",
|
|
496
556
|
type: "number",
|
|
497
557
|
min: "0.01",
|
|
498
558
|
max: Number(giftCard.balance),
|
|
499
559
|
step: "0.01",
|
|
500
560
|
required: true,
|
|
501
|
-
|
|
561
|
+
"aria-required": true,
|
|
502
562
|
value: amount,
|
|
503
|
-
onChange: (event) => setAmount(event.target.value)
|
|
504
|
-
className: "rounded-md border border-[var(--border-default)] bg-[var(--bg-base)] px-3 py-2 text-sm focus:border-brand-500 focus:outline-none"
|
|
563
|
+
onChange: (event) => setAmount(event.target.value)
|
|
505
564
|
}
|
|
506
565
|
),
|
|
507
566
|
/* @__PURE__ */ jsx(
|
|
508
|
-
|
|
567
|
+
Input,
|
|
509
568
|
{
|
|
510
|
-
|
|
569
|
+
label: "Order or reference ID (optional)",
|
|
511
570
|
value: referenceId,
|
|
512
|
-
onChange: (event) => setReferenceId(event.target.value)
|
|
513
|
-
className: "rounded-md border border-[var(--border-default)] bg-[var(--bg-base)] px-3 py-2 text-sm focus:border-brand-500 focus:outline-none"
|
|
571
|
+
onChange: (event) => setReferenceId(event.target.value)
|
|
514
572
|
}
|
|
515
573
|
)
|
|
516
574
|
] }),
|
|
517
575
|
/* @__PURE__ */ jsx(
|
|
518
|
-
|
|
576
|
+
Input,
|
|
519
577
|
{
|
|
520
|
-
|
|
578
|
+
label: "Note (optional)",
|
|
521
579
|
value: note,
|
|
522
|
-
onChange: (event) => setNote(event.target.value)
|
|
523
|
-
className: "w-full rounded-md border border-[var(--border-default)] bg-[var(--bg-base)] px-3 py-2 text-sm focus:border-brand-500 focus:outline-none"
|
|
580
|
+
onChange: (event) => setNote(event.target.value)
|
|
524
581
|
}
|
|
525
582
|
),
|
|
526
583
|
/* @__PURE__ */ jsx(
|
|
527
|
-
|
|
584
|
+
Button,
|
|
528
585
|
{
|
|
529
586
|
type: "submit",
|
|
530
587
|
disabled: saving || !amount || Number(amount) <= 0,
|
|
531
|
-
className: "rounded-md bg-brand-500 px-4 py-2 text-sm font-medium text-[var(--text-inverse)] hover:bg-brand-600 disabled:opacity-50",
|
|
532
588
|
children: saving ? "Redeeming..." : "Redeem gift card"
|
|
533
589
|
}
|
|
534
590
|
)
|
|
@@ -556,8 +612,15 @@ function GiftCardDetailModal({
|
|
|
556
612
|
redemption.id
|
|
557
613
|
)) })
|
|
558
614
|
] }),
|
|
559
|
-
error && /* @__PURE__ */ jsx(
|
|
560
|
-
|
|
615
|
+
error && /* @__PURE__ */ jsx(
|
|
616
|
+
"p",
|
|
617
|
+
{
|
|
618
|
+
role: "alert",
|
|
619
|
+
className: "rounded-md bg-[var(--error-bg)] px-3 py-2 text-sm text-[var(--error)]",
|
|
620
|
+
children: error
|
|
621
|
+
}
|
|
622
|
+
),
|
|
623
|
+
giftCard.status !== "cancelled" && /* @__PURE__ */ jsx("div", { className: "flex justify-end border-t border-[var(--border-default)] pt-4", children: /* @__PURE__ */ jsx(
|
|
561
624
|
InlineDeleteButton,
|
|
562
625
|
{
|
|
563
626
|
onDelete: remove,
|
|
@@ -573,12 +636,14 @@ function GiftCardsFeature() {
|
|
|
573
636
|
const { tenant } = useAuth();
|
|
574
637
|
const formatCurrency = useFormatCurrency();
|
|
575
638
|
const { toast } = useToast();
|
|
639
|
+
const slug = tenant?.slug;
|
|
576
640
|
const currency = normalizeCurrencyCode(
|
|
577
641
|
tenant?.settings?.currency
|
|
578
642
|
);
|
|
579
643
|
const [giftCards, setGiftCards] = useState([]);
|
|
580
644
|
const [stats, setStats] = useState(null);
|
|
581
645
|
const [loading, setLoading] = useState(true);
|
|
646
|
+
const [loaded, setLoaded] = useState(false);
|
|
582
647
|
const [search, setSearch] = useState("");
|
|
583
648
|
const [status, setStatus] = useState("");
|
|
584
649
|
const [page, setPage] = useState(1);
|
|
@@ -586,313 +651,386 @@ function GiftCardsFeature() {
|
|
|
586
651
|
const [showIssue, setShowIssue] = useState(false);
|
|
587
652
|
const [showBalance, setShowBalance] = useState(false);
|
|
588
653
|
const [selected, setSelected] = useState(null);
|
|
654
|
+
const [dedupe] = useState(createInFlightDeduper);
|
|
655
|
+
const toastRef = useRef(toast);
|
|
656
|
+
useEffect(() => {
|
|
657
|
+
toastRef.current = toast;
|
|
658
|
+
});
|
|
589
659
|
const latestRead = useRef(0);
|
|
590
660
|
const readGiftCards = useCallback(
|
|
591
661
|
async (quiet) => {
|
|
592
|
-
if (!
|
|
662
|
+
if (!slug) return;
|
|
593
663
|
const read = quiet ? latestRead.current : ++latestRead.current;
|
|
594
664
|
if (!quiet) setLoading(true);
|
|
665
|
+
const query = {
|
|
666
|
+
...status ? { status } : {},
|
|
667
|
+
...search.trim() ? { search: search.trim() } : {},
|
|
668
|
+
page,
|
|
669
|
+
limit: PAGE_SIZE
|
|
670
|
+
};
|
|
595
671
|
try {
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
page,
|
|
601
|
-
limit: PAGE_SIZE
|
|
602
|
-
}),
|
|
603
|
-
giftCardsApi.stats(tenant.slug)
|
|
604
|
-
]);
|
|
672
|
+
const list = await dedupe.run(
|
|
673
|
+
`list:${slug}:${JSON.stringify(query)}`,
|
|
674
|
+
() => giftCardsApi.list(slug, query)
|
|
675
|
+
);
|
|
605
676
|
if (read !== latestRead.current) return;
|
|
606
677
|
setGiftCards(list.rows);
|
|
607
678
|
setCount(list.count);
|
|
608
|
-
setStats(giftCardStats);
|
|
609
679
|
} catch (loadError) {
|
|
610
680
|
if (quiet) throw loadError;
|
|
611
|
-
|
|
681
|
+
toastRef.current({
|
|
612
682
|
title: "Unable to load gift cards",
|
|
613
683
|
description: errorMessage(loadError)
|
|
614
684
|
});
|
|
615
685
|
} finally {
|
|
616
|
-
if (!quiet)
|
|
686
|
+
if (!quiet && read === latestRead.current) {
|
|
687
|
+
setLoading(false);
|
|
688
|
+
setLoaded(true);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
},
|
|
692
|
+
[dedupe, page, search, slug, status]
|
|
693
|
+
);
|
|
694
|
+
const readStats = useCallback(
|
|
695
|
+
async (quiet) => {
|
|
696
|
+
if (!slug) return;
|
|
697
|
+
try {
|
|
698
|
+
setStats(await dedupe.run(`stats:${slug}`, () => giftCardsApi.stats(slug)));
|
|
699
|
+
} catch (statsError) {
|
|
700
|
+
if (quiet) throw statsError;
|
|
617
701
|
}
|
|
618
702
|
},
|
|
619
|
-
[
|
|
703
|
+
[dedupe, slug]
|
|
704
|
+
);
|
|
705
|
+
const reloadAll = useCallback(
|
|
706
|
+
() => Promise.all([readGiftCards(false), readStats(false)]),
|
|
707
|
+
[readGiftCards, readStats]
|
|
620
708
|
);
|
|
621
|
-
const loadGiftCards = useCallback(() => readGiftCards(false), [readGiftCards]);
|
|
622
709
|
useEffect(() => {
|
|
623
|
-
|
|
624
|
-
}, [
|
|
710
|
+
readGiftCards(false);
|
|
711
|
+
}, [readGiftCards]);
|
|
712
|
+
useEffect(() => {
|
|
713
|
+
readStats(false);
|
|
714
|
+
}, [readStats]);
|
|
625
715
|
useQuietPoll(
|
|
626
|
-
useCallback(
|
|
716
|
+
useCallback(
|
|
717
|
+
() => Promise.all([readGiftCards(true), readStats(true)]),
|
|
718
|
+
[readGiftCards, readStats]
|
|
719
|
+
),
|
|
627
720
|
LIVE_REFRESH_MS,
|
|
628
721
|
LIVE
|
|
629
722
|
);
|
|
630
|
-
useRegisterRefresh(
|
|
723
|
+
useRegisterRefresh(reloadAll);
|
|
631
724
|
const copyCode = async (event, code) => {
|
|
632
725
|
event.stopPropagation();
|
|
633
726
|
await navigator.clipboard.writeText(code);
|
|
634
727
|
toast({ title: "Gift card code copied", description: code });
|
|
635
728
|
};
|
|
636
729
|
const totalPages = Math.max(1, Math.ceil(count / PAGE_SIZE));
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
] }),
|
|
657
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
658
|
-
!LIVE && /* @__PURE__ */ jsx(RefreshButton, { onClick: loadGiftCards, refreshing: loading }),
|
|
659
|
-
/* @__PURE__ */ jsxs(
|
|
660
|
-
"button",
|
|
661
|
-
{
|
|
662
|
-
type: "button",
|
|
663
|
-
onClick: () => setShowBalance(true),
|
|
664
|
-
className: "flex items-center gap-2 rounded-md border border-[var(--border-default)] px-3 py-2 text-sm font-medium text-[var(--text-secondary)] hover:bg-[var(--interactive-hover)]",
|
|
665
|
-
children: [
|
|
666
|
-
/* @__PURE__ */ jsx(Search, { className: "h-4 w-4" }),
|
|
667
|
-
"Check balance"
|
|
668
|
-
]
|
|
669
|
-
}
|
|
670
|
-
),
|
|
671
|
-
/* @__PURE__ */ jsxs(
|
|
672
|
-
"button",
|
|
673
|
-
{
|
|
674
|
-
type: "button",
|
|
675
|
-
onClick: () => setShowIssue(true),
|
|
676
|
-
className: "flex items-center gap-2 rounded-md bg-brand-500 px-4 py-2 text-sm font-medium text-[var(--text-inverse)] hover:bg-brand-600",
|
|
677
|
-
children: [
|
|
678
|
-
/* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
|
|
679
|
-
"Issue gift card"
|
|
680
|
-
]
|
|
681
|
-
}
|
|
682
|
-
)
|
|
683
|
-
] })
|
|
684
|
-
] }),
|
|
685
|
-
/* @__PURE__ */ jsx("div", { className: "hidden lg:block", children: /* @__PURE__ */ jsxs(MetricRow, { children: [
|
|
686
|
-
/* @__PURE__ */ jsx(
|
|
687
|
-
MetricTile,
|
|
688
|
-
{
|
|
689
|
-
label: "Issued value",
|
|
690
|
-
value: formatCurrency(stats?.totalIssued ?? 0, currency)
|
|
691
|
-
}
|
|
692
|
-
),
|
|
693
|
-
/* @__PURE__ */ jsx(
|
|
694
|
-
MetricTile,
|
|
695
|
-
{
|
|
696
|
-
label: "Outstanding",
|
|
697
|
-
value: formatCurrency(stats?.outstanding ?? 0, currency)
|
|
698
|
-
}
|
|
699
|
-
),
|
|
700
|
-
/* @__PURE__ */ jsx(
|
|
701
|
-
MetricTile,
|
|
702
|
-
{
|
|
703
|
-
label: "Redeemed",
|
|
704
|
-
value: formatCurrency(stats?.redeemed ?? 0, currency)
|
|
705
|
-
}
|
|
706
|
-
),
|
|
707
|
-
/* @__PURE__ */ jsx(MetricTile, { label: "Active cards", value: stats?.activeCards ?? 0 })
|
|
708
|
-
] }) }),
|
|
709
|
-
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-px overflow-hidden rounded-md bg-[var(--border-default)] lg:hidden", children: [
|
|
710
|
-
/* @__PURE__ */ jsxs("div", { className: "bg-[var(--bg-subtle)] p-4", children: [
|
|
711
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--text-muted)]", children: "Outstanding" }),
|
|
712
|
-
/* @__PURE__ */ jsx("p", { className: "mt-1 text-lg font-semibold text-[var(--text-primary)]", children: formatCurrency(stats?.outstanding ?? 0, currency) })
|
|
713
|
-
] }),
|
|
714
|
-
/* @__PURE__ */ jsxs("div", { className: "bg-[var(--bg-subtle)] p-4", children: [
|
|
715
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--text-muted)]", children: "Active cards" }),
|
|
716
|
-
/* @__PURE__ */ jsx("p", { className: "mt-1 text-lg font-semibold text-[var(--text-primary)]", children: stats?.activeCards ?? 0 })
|
|
717
|
-
] })
|
|
718
|
-
] }),
|
|
719
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-2 overflow-x-auto lg:hidden", children: [
|
|
720
|
-
/* @__PURE__ */ jsxs(
|
|
721
|
-
"button",
|
|
730
|
+
const hasFilters = Boolean(search.trim() || status);
|
|
731
|
+
const firstRun = loaded && isFirstRun({
|
|
732
|
+
loading,
|
|
733
|
+
search,
|
|
734
|
+
status,
|
|
735
|
+
count,
|
|
736
|
+
totalIssued: stats?.totalIssued
|
|
737
|
+
});
|
|
738
|
+
const hasCards = loaded && !firstRun;
|
|
739
|
+
const showStats = hasCards && stats !== null;
|
|
740
|
+
const openIssue = () => setShowIssue(true);
|
|
741
|
+
const openBalance = () => setShowBalance(true);
|
|
742
|
+
return /* @__PURE__ */ jsxs(
|
|
743
|
+
ListPage,
|
|
744
|
+
{
|
|
745
|
+
largeTitle: true,
|
|
746
|
+
title: "Gift cards",
|
|
747
|
+
mobileHeader: /* @__PURE__ */ jsx(
|
|
748
|
+
MobilePageHeader,
|
|
722
749
|
{
|
|
723
|
-
|
|
724
|
-
onClick:
|
|
725
|
-
className: "flex shrink-0 items-center gap-1.5 rounded-full bg-brand-500 px-3.5 py-2 text-sm font-medium text-[var(--text-inverse)]",
|
|
726
|
-
children: [
|
|
727
|
-
/* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
|
|
728
|
-
"Issue gift card"
|
|
729
|
-
]
|
|
750
|
+
title: "Gift cards",
|
|
751
|
+
action: hasCards ? { label: "Issue gift card", onClick: openIssue } : void 0
|
|
730
752
|
}
|
|
731
753
|
),
|
|
732
|
-
/* @__PURE__ */ jsxs(
|
|
733
|
-
"
|
|
734
|
-
{
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
/* @__PURE__ */ jsx(
|
|
749
|
-
"input",
|
|
750
|
-
{
|
|
751
|
-
type: "search",
|
|
752
|
-
placeholder: "Search code, recipient, or email",
|
|
753
|
-
value: search,
|
|
754
|
-
onChange: (event) => {
|
|
755
|
-
setSearch(event.target.value);
|
|
756
|
-
setPage(1);
|
|
757
|
-
},
|
|
758
|
-
className: "w-full rounded-md border border-[var(--border-default)] bg-[var(--bg-base)] py-2 pl-10 pr-3 text-sm focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-500/20"
|
|
759
|
-
}
|
|
760
|
-
)
|
|
754
|
+
actions: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
755
|
+
/* @__PURE__ */ jsx(Hint, { lines: PAGE_HINT_LINES, topic: "gift cards" }),
|
|
756
|
+
!LIVE && /* @__PURE__ */ jsx(RefreshButton, { onClick: reloadAll, refreshing: loading }),
|
|
757
|
+
hasCards && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
758
|
+
/* @__PURE__ */ jsx(
|
|
759
|
+
Button,
|
|
760
|
+
{
|
|
761
|
+
type: "button",
|
|
762
|
+
variant: "secondary",
|
|
763
|
+
icon: /* @__PURE__ */ jsx(Search, {}),
|
|
764
|
+
onClick: openBalance,
|
|
765
|
+
children: "Check balance"
|
|
766
|
+
}
|
|
767
|
+
),
|
|
768
|
+
/* @__PURE__ */ jsx(Button, { type: "button", icon: /* @__PURE__ */ jsx(Plus, {}), onClick: openIssue, children: "Issue gift card" })
|
|
769
|
+
] })
|
|
761
770
|
] }),
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
children: [
|
|
793
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
794
|
-
/* @__PURE__ */ jsx("p", { className: "truncate font-mono text-sm font-semibold text-[var(--text-primary)]", children: giftCard.code }),
|
|
795
|
-
/* @__PURE__ */ jsx("p", { className: "truncate text-sm text-[var(--text-tertiary)]", children: giftCard.recipientName || giftCard.recipientEmail || "Walk-in" })
|
|
771
|
+
children: [
|
|
772
|
+
showStats && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
773
|
+
/* @__PURE__ */ jsx("div", { className: "hidden lg:block", children: /* @__PURE__ */ jsxs(MetricRow, { children: [
|
|
774
|
+
/* @__PURE__ */ jsx(
|
|
775
|
+
MetricTile,
|
|
776
|
+
{
|
|
777
|
+
label: "Total sold",
|
|
778
|
+
value: formatCurrency(stats?.totalIssued ?? 0, currency)
|
|
779
|
+
}
|
|
780
|
+
),
|
|
781
|
+
/* @__PURE__ */ jsx(
|
|
782
|
+
MetricTile,
|
|
783
|
+
{
|
|
784
|
+
label: "Still to spend",
|
|
785
|
+
value: formatCurrency(stats?.outstanding ?? 0, currency)
|
|
786
|
+
}
|
|
787
|
+
),
|
|
788
|
+
/* @__PURE__ */ jsx(
|
|
789
|
+
MetricTile,
|
|
790
|
+
{
|
|
791
|
+
label: "Spent",
|
|
792
|
+
value: formatCurrency(stats?.redeemed ?? 0, currency)
|
|
793
|
+
}
|
|
794
|
+
),
|
|
795
|
+
/* @__PURE__ */ jsx(MetricTile, { label: "Cards in use", value: stats?.activeCards ?? 0 })
|
|
796
|
+
] }) }),
|
|
797
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-px overflow-hidden rounded-md bg-[var(--border-default)] lg:hidden", children: [
|
|
798
|
+
/* @__PURE__ */ jsxs("div", { className: "bg-[var(--bg-subtle)] p-4", children: [
|
|
799
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--text-muted)]", children: "Still to spend" }),
|
|
800
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-lg font-semibold text-[var(--text-primary)]", children: formatCurrency(stats?.outstanding ?? 0, currency) })
|
|
796
801
|
] }),
|
|
797
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
798
|
-
/* @__PURE__ */ jsx("p", { className: "text-
|
|
799
|
-
/* @__PURE__ */ jsx("
|
|
802
|
+
/* @__PURE__ */ jsxs("div", { className: "bg-[var(--bg-subtle)] p-4", children: [
|
|
803
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--text-muted)]", children: "Cards in use" }),
|
|
804
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-lg font-semibold text-[var(--text-primary)]", children: stats?.activeCards ?? 0 })
|
|
800
805
|
] })
|
|
801
|
-
]
|
|
802
|
-
},
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
/* @__PURE__ */ jsx("div", { className: "hidden overflow-x-auto rounded-md border border-[var(--border-default)] bg-[var(--bg-base)] lg:block", children: /* @__PURE__ */ jsxs("table", { className: "w-full min-w-[760px]", children: [
|
|
806
|
-
/* @__PURE__ */ jsx("thead", { className: "border-b border-[var(--border-default)] bg-[var(--bg-subtle)]", children: /* @__PURE__ */ jsx("tr", { children: ["Code", "Recipient", "Issued", "Balance", "Expires", "Status"].map(
|
|
807
|
-
(heading) => /* @__PURE__ */ jsx(
|
|
808
|
-
"th",
|
|
809
|
-
{
|
|
810
|
-
className: "px-4 py-3 text-left text-xs font-medium uppercase text-[var(--text-tertiary)]",
|
|
811
|
-
children: heading
|
|
812
|
-
},
|
|
813
|
-
heading
|
|
814
|
-
)
|
|
815
|
-
) }) }),
|
|
816
|
-
/* @__PURE__ */ jsx("tbody", { className: "divide-y divide-[var(--border-default)]", children: giftCards.map((giftCard) => /* @__PURE__ */ jsxs(
|
|
817
|
-
"tr",
|
|
806
|
+
] })
|
|
807
|
+
] }),
|
|
808
|
+
hasCards && /* @__PURE__ */ jsx("div", { className: "flex gap-2 overflow-x-auto lg:hidden", children: /* @__PURE__ */ jsx(
|
|
809
|
+
Button,
|
|
818
810
|
{
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
811
|
+
type: "button",
|
|
812
|
+
variant: "secondary",
|
|
813
|
+
shape: "pill",
|
|
814
|
+
icon: /* @__PURE__ */ jsx(Search, {}),
|
|
815
|
+
onClick: openBalance,
|
|
816
|
+
children: "Check balance"
|
|
817
|
+
}
|
|
818
|
+
) }),
|
|
819
|
+
!firstRun && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 lg:flex-row lg:items-center", children: [
|
|
820
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex-1", children: [
|
|
821
|
+
/* @__PURE__ */ jsx(
|
|
822
|
+
Search,
|
|
823
|
+
{
|
|
824
|
+
"aria-hidden": true,
|
|
825
|
+
className: "pointer-events-none absolute left-3 top-1/2 z-10 h-4 w-4 -translate-y-1/2 text-[var(--text-muted)]"
|
|
826
|
+
}
|
|
827
|
+
),
|
|
828
|
+
/* @__PURE__ */ jsx(
|
|
829
|
+
Input,
|
|
830
|
+
{
|
|
831
|
+
type: "search",
|
|
832
|
+
"aria-label": "Search gift cards",
|
|
833
|
+
placeholder: "Search code, recipient, or email",
|
|
834
|
+
value: search,
|
|
835
|
+
onChange: (event) => {
|
|
836
|
+
setSearch(event.target.value);
|
|
837
|
+
setPage(1);
|
|
838
|
+
},
|
|
839
|
+
className: "pl-10"
|
|
840
|
+
}
|
|
841
|
+
)
|
|
842
|
+
] }),
|
|
843
|
+
/* @__PURE__ */ jsx(
|
|
844
|
+
"div",
|
|
845
|
+
{
|
|
846
|
+
role: "group",
|
|
847
|
+
"aria-label": "Filter by status",
|
|
848
|
+
className: "flex gap-1 overflow-x-auto",
|
|
849
|
+
children: STATUS_FILTERS.map((filter) => {
|
|
850
|
+
const pressed = status === filter.value;
|
|
851
|
+
return /* @__PURE__ */ jsx(Tooltip, { label: filter.hint, children: /* @__PURE__ */ jsx(
|
|
852
|
+
Button,
|
|
826
853
|
{
|
|
827
854
|
type: "button",
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
855
|
+
size: "sm",
|
|
856
|
+
variant: pressed ? "secondary" : "ghost",
|
|
857
|
+
"aria-pressed": pressed,
|
|
858
|
+
className: "shrink-0",
|
|
859
|
+
onClick: () => {
|
|
860
|
+
setStatus(filter.value);
|
|
861
|
+
setPage(1);
|
|
862
|
+
},
|
|
863
|
+
children: filter.label
|
|
832
864
|
}
|
|
833
|
-
)
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
865
|
+
) }, filter.value);
|
|
866
|
+
})
|
|
867
|
+
}
|
|
868
|
+
)
|
|
869
|
+
] }),
|
|
870
|
+
/* @__PURE__ */ jsx("h2", { className: "sr-only", children: "Issued gift cards" }),
|
|
871
|
+
!loaded && /* @__PURE__ */ jsx(InlineListSkeleton, {}),
|
|
872
|
+
loading && loaded && giftCards.length === 0 && /* @__PURE__ */ jsx(InlineListSkeleton, {}),
|
|
873
|
+
firstRun && /* @__PURE__ */ jsxs("div", { children: [
|
|
874
|
+
/* @__PURE__ */ jsx(
|
|
875
|
+
TableEmptyState,
|
|
876
|
+
{
|
|
877
|
+
icon: Gift,
|
|
878
|
+
title: "Issue your first gift card",
|
|
879
|
+
description: "Create a stored-value card for a customer or walk-in purchase",
|
|
880
|
+
action: { label: "Issue gift card", onClick: openIssue }
|
|
881
|
+
}
|
|
882
|
+
),
|
|
883
|
+
/* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx(
|
|
884
|
+
Button,
|
|
885
|
+
{
|
|
886
|
+
type: "button",
|
|
887
|
+
variant: "ghost",
|
|
888
|
+
icon: /* @__PURE__ */ jsx(Search, {}),
|
|
889
|
+
onClick: openBalance,
|
|
890
|
+
children: "Check balance"
|
|
891
|
+
}
|
|
892
|
+
) })
|
|
893
|
+
] }),
|
|
894
|
+
loaded && !loading && !firstRun && giftCards.length === 0 && /* @__PURE__ */ jsx(
|
|
895
|
+
TableEmptyState,
|
|
896
|
+
{
|
|
897
|
+
icon: Gift,
|
|
898
|
+
title: "No gift cards found",
|
|
899
|
+
description: "Try changing the search or status filter",
|
|
900
|
+
action: hasFilters ? {
|
|
901
|
+
label: "Clear search and filters",
|
|
902
|
+
onClick: () => {
|
|
903
|
+
setSearch("");
|
|
904
|
+
setStatus("");
|
|
905
|
+
setPage(1);
|
|
906
|
+
}
|
|
907
|
+
} : void 0
|
|
908
|
+
}
|
|
909
|
+
),
|
|
910
|
+
giftCards.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
911
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-2 lg:hidden", children: giftCards.map((giftCard) => /* @__PURE__ */ jsxs(
|
|
912
|
+
"button",
|
|
913
|
+
{
|
|
914
|
+
type: "button",
|
|
915
|
+
onClick: () => setSelected(giftCard),
|
|
916
|
+
className: "flex w-full items-center justify-between gap-3 rounded-md border border-[var(--border-default)] bg-[var(--bg-base)] p-4 text-left",
|
|
917
|
+
children: [
|
|
918
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
919
|
+
/* @__PURE__ */ jsx("p", { className: "truncate font-mono text-sm font-semibold text-[var(--text-primary)]", children: giftCard.code }),
|
|
920
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm text-[var(--text-tertiary)]", children: giftCard.recipientName || giftCard.recipientEmail || "Walk-in" })
|
|
921
|
+
] }),
|
|
922
|
+
/* @__PURE__ */ jsxs("div", { className: "shrink-0 text-right", children: [
|
|
923
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-semibold text-[var(--text-primary)]", children: formatCurrency(Number(giftCard.balance), giftCard.currency) }),
|
|
924
|
+
/* @__PURE__ */ jsx("span", { className: `text-xs ${statusClass(giftCard.status)}`, children: STATUS_LABEL[giftCard.status] })
|
|
925
|
+
] })
|
|
926
|
+
]
|
|
927
|
+
},
|
|
928
|
+
giftCard.id
|
|
929
|
+
)) }),
|
|
930
|
+
/* @__PURE__ */ jsx("div", { className: "hidden overflow-x-auto rounded-md border border-[var(--border-default)] bg-[var(--bg-base)] lg:block", children: /* @__PURE__ */ jsxs("table", { className: "w-full min-w-[760px]", children: [
|
|
931
|
+
/* @__PURE__ */ jsx("thead", { className: "border-b border-[var(--border-default)] bg-[var(--bg-subtle)]", children: /* @__PURE__ */ jsx("tr", { children: ["Code", "Recipient", "Issued", "Balance", "Expires", "Status"].map(
|
|
932
|
+
(heading) => /* @__PURE__ */ jsx(
|
|
933
|
+
"th",
|
|
847
934
|
{
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
935
|
+
scope: "col",
|
|
936
|
+
className: "px-4 py-3 text-left text-xs font-medium uppercase text-[var(--text-tertiary)]",
|
|
937
|
+
children: heading
|
|
938
|
+
},
|
|
939
|
+
heading
|
|
940
|
+
)
|
|
941
|
+
) }) }),
|
|
942
|
+
/* @__PURE__ */ jsx("tbody", { className: "divide-y divide-[var(--border-default)]", children: giftCards.map((giftCard) => /* @__PURE__ */ jsxs(
|
|
943
|
+
"tr",
|
|
944
|
+
{
|
|
945
|
+
tabIndex: 0,
|
|
946
|
+
"aria-label": `Open gift card ${giftCard.code}`,
|
|
947
|
+
onClick: () => setSelected(giftCard),
|
|
948
|
+
onKeyDown: (event) => {
|
|
949
|
+
if (event.target !== event.currentTarget) return;
|
|
950
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
951
|
+
event.preventDefault();
|
|
952
|
+
setSelected(giftCard);
|
|
953
|
+
}
|
|
954
|
+
},
|
|
955
|
+
className: "cursor-pointer hover:bg-[var(--interactive-hover)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--focus-ring)]",
|
|
956
|
+
children: [
|
|
957
|
+
/* @__PURE__ */ jsx("td", { className: "px-4 py-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
958
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-sm font-medium text-[var(--text-primary)]", children: giftCard.code }),
|
|
959
|
+
/* @__PURE__ */ jsx(
|
|
960
|
+
Button,
|
|
961
|
+
{
|
|
962
|
+
type: "button",
|
|
963
|
+
variant: "ghost",
|
|
964
|
+
size: "icon-sm",
|
|
965
|
+
"aria-label": `Copy code ${giftCard.code}`,
|
|
966
|
+
onClick: (event) => copyCode(event, giftCard.code),
|
|
967
|
+
children: /* @__PURE__ */ jsx(Copy, {})
|
|
968
|
+
}
|
|
969
|
+
)
|
|
970
|
+
] }) }),
|
|
971
|
+
/* @__PURE__ */ jsxs("td", { className: "px-4 py-3", children: [
|
|
972
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-[var(--text-primary)]", children: giftCard.recipientName || "Walk-in" }),
|
|
973
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--text-muted)]", children: giftCard.recipientEmail })
|
|
974
|
+
] }),
|
|
975
|
+
/* @__PURE__ */ jsx("td", { className: "px-4 py-3 text-sm text-[var(--text-secondary)]", children: formatCurrency(
|
|
976
|
+
Number(giftCard.originalAmount),
|
|
977
|
+
giftCard.currency
|
|
978
|
+
) }),
|
|
979
|
+
/* @__PURE__ */ jsx("td", { className: "px-4 py-3 text-sm font-medium text-[var(--text-primary)]", children: formatCurrency(Number(giftCard.balance), giftCard.currency) }),
|
|
980
|
+
/* @__PURE__ */ jsx("td", { className: "px-4 py-3 text-sm text-[var(--text-secondary)]", children: dateLabel(giftCard.expiresAt) }),
|
|
981
|
+
/* @__PURE__ */ jsx("td", { className: "px-4 py-3", children: /* @__PURE__ */ jsx(
|
|
982
|
+
"span",
|
|
983
|
+
{
|
|
984
|
+
className: `inline-flex rounded-full px-2.5 py-1 text-xs font-medium ${statusClass(giftCard.status)}`,
|
|
985
|
+
children: STATUS_LABEL[giftCard.status]
|
|
986
|
+
}
|
|
987
|
+
) })
|
|
988
|
+
]
|
|
989
|
+
},
|
|
990
|
+
giftCard.id
|
|
991
|
+
)) })
|
|
992
|
+
] }) }),
|
|
993
|
+
/* @__PURE__ */ jsx(
|
|
994
|
+
TablePagination,
|
|
995
|
+
{
|
|
996
|
+
page,
|
|
997
|
+
totalPages,
|
|
998
|
+
pageSize: PAGE_SIZE,
|
|
999
|
+
totalItems: count,
|
|
1000
|
+
entityName: "gift cards",
|
|
1001
|
+
onPageChange: setPage
|
|
1002
|
+
}
|
|
1003
|
+
)
|
|
1004
|
+
] }),
|
|
1005
|
+
showIssue && /* @__PURE__ */ jsx(
|
|
1006
|
+
IssueGiftCardModal,
|
|
1007
|
+
{
|
|
1008
|
+
currency,
|
|
1009
|
+
merchantName: tenant?.businessName || "Gift card",
|
|
1010
|
+
onClose: () => setShowIssue(false),
|
|
1011
|
+
onCreated: (giftCard) => {
|
|
1012
|
+
setShowIssue(false);
|
|
1013
|
+
setSelected(giftCard);
|
|
1014
|
+
toast({
|
|
1015
|
+
title: "Gift card issued",
|
|
1016
|
+
description: giftCard.code
|
|
1017
|
+
});
|
|
1018
|
+
reloadAll();
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
),
|
|
1022
|
+
showBalance && /* @__PURE__ */ jsx(BalanceModal, { onClose: () => setShowBalance(false) }),
|
|
1023
|
+
selected && /* @__PURE__ */ jsx(
|
|
1024
|
+
GiftCardDetailModal,
|
|
1025
|
+
{
|
|
1026
|
+
giftCard: selected,
|
|
1027
|
+
onClose: () => setSelected(null),
|
|
1028
|
+
onChanged: reloadAll
|
|
1029
|
+
}
|
|
1030
|
+
)
|
|
1031
|
+
]
|
|
1032
|
+
}
|
|
1033
|
+
);
|
|
896
1034
|
}
|
|
897
1035
|
export {
|
|
898
1036
|
GiftCardsFeature
|