|
|
|
@@ -27,10 +27,10 @@ const loading = ref(true);
|
|
|
|
|
const loaded = ref(false);
|
|
|
|
|
const error = ref<string | null>(null);
|
|
|
|
|
const pending = ref<string | null>(null);
|
|
|
|
|
const selectedCodeId = ref<string | null>(typeof route.query.codeId === "string" ? route.query.codeId : null);
|
|
|
|
|
const selectedCodeId = ref<string | null>(typeof route.query.selectedCodeId === "string" ? route.query.selectedCodeId : null);
|
|
|
|
|
const createOpen = ref(false);
|
|
|
|
|
const createdCode = ref<string | null>(null);
|
|
|
|
|
const filters = reactive({ status: typeof route.query.filter === "string" ? route.query.filter : "", userId: typeof route.query.userId === "string" ? route.query.userId : "", codeId: typeof route.query.redemptionCodeId === "string" ? route.query.redemptionCodeId : "" });
|
|
|
|
|
const filters = reactive({ status: typeof route.query.filter === "string" ? route.query.filter : "", userId: typeof route.query.userId === "string" ? route.query.userId : "", codeId: typeof route.query.codeId === "string" ? route.query.codeId : "" });
|
|
|
|
|
const form = reactive({ displayName: "", creditAmount: 10, reason: "redeem", maxRedemptions: 1, perUserLimit: 1, expiresAt: "" });
|
|
|
|
|
let refreshSequence = 0;
|
|
|
|
|
const activeTab = computed({ get: () => viewState.tab.value === "redemptions" ? "redemptions" : "codes", set: (value: string) => void viewState.update({ tab: value === "codes" ? undefined : "redemptions" }, false) });
|
|
|
|
@@ -42,12 +42,12 @@ const codeColumns: DataGridColumn[] = [{ key: "code", label: "Code" }, { key: "s
|
|
|
|
|
const redemptionColumns: DataGridColumn[] = [{ key: "time", label: "Time" }, { key: "user", label: "User" }, { key: "code", label: "Code" }, { key: "credits", label: "Credits", align: "right" }, { key: "status", label: "Status" }, { key: "ledger", label: "Ledger" }];
|
|
|
|
|
|
|
|
|
|
onMounted(refreshAll);
|
|
|
|
|
watch(() => [route.query.filter, route.query.userId, route.query.redemptionCodeId].join("\0"), () => { filters.status = typeof route.query.filter === "string" ? route.query.filter : ""; filters.userId = typeof route.query.userId === "string" ? route.query.userId : ""; filters.codeId = typeof route.query.redemptionCodeId === "string" ? route.query.redemptionCodeId : ""; void refreshAll(); });
|
|
|
|
|
watch(() => route.query.codeId, (value) => { selectedCodeId.value = typeof value === "string" ? value : null; });
|
|
|
|
|
watch(() => [route.query.filter, route.query.userId, route.query.codeId].join("\0"), () => { filters.status = typeof route.query.filter === "string" ? route.query.filter : ""; filters.userId = typeof route.query.userId === "string" ? route.query.userId : ""; filters.codeId = typeof route.query.codeId === "string" ? route.query.codeId : ""; void refreshAll(); });
|
|
|
|
|
watch(() => route.query.selectedCodeId, (value) => { selectedCodeId.value = typeof value === "string" ? value : null; });
|
|
|
|
|
async function refreshAll(): Promise<void> { const requestSequence = ++refreshSequence; loading.value = true; error.value = null; const [codeResult, redemptionResult] = await Promise.all([billingAPI.adminRedeemCodes({ status: filters.status || undefined, limit: 100 }), billingAPI.adminRedeemRedemptions({ userId: filters.userId || undefined, codeId: filters.codeId || undefined, limit: 100 })]); if (requestSequence !== refreshSequence) return; const errors: string[] = []; if (codeResult.ok) codes.value = codeResult.data?.codes ?? []; else errors.push(`Codes: ${codeResult.error ?? `HTTP ${codeResult.status}`}`); if (redemptionResult.ok) redemptions.value = redemptionResult.data?.redemptions ?? []; else errors.push(`Redemptions: ${redemptionResult.error ?? `HTTP ${redemptionResult.status}`}`); error.value = errors.join(" · ") || null; loading.value = false; loaded.value = true; }
|
|
|
|
|
async function persistFilters(): Promise<void> { await router.push({ query: { ...route.query, filter: filters.status || undefined, userId: filters.userId.trim() || undefined, redemptionCodeId: filters.codeId.trim() || undefined } }); }
|
|
|
|
|
async function selectCode(code: RedeemCodeSummary): Promise<void> { const codeId = codeKey(code); selectedCodeId.value = codeId; await router.push({ query: { ...route.query, codeId } }); }
|
|
|
|
|
async function closeInspector(): Promise<void> { selectedCodeId.value = null; await router.push({ query: { ...route.query, codeId: undefined } }); }
|
|
|
|
|
async function persistFilters(): Promise<void> { await router.push({ query: { ...route.query, filter: filters.status || undefined, userId: filters.userId.trim() || undefined, codeId: filters.codeId.trim() || undefined } }); }
|
|
|
|
|
async function selectCode(code: RedeemCodeSummary): Promise<void> { const codeId = codeKey(code); selectedCodeId.value = codeId; await router.push({ query: { ...route.query, selectedCodeId: codeId } }); }
|
|
|
|
|
async function closeInspector(): Promise<void> { selectedCodeId.value = null; await router.push({ query: { ...route.query, selectedCodeId: undefined } }); }
|
|
|
|
|
async function setStatus(code: RedeemCodeSummary): Promise<void> { if (!code.id) return; pending.value = `status:${code.id}`; const response = await billingAPI.updateAdminRedeemCode(code.id, { status: code.status === "disabled" ? "active" : "disabled" }); pending.value = null; if (!response.ok) error.value = response.error ?? `HTTP ${response.status}`; else await refreshAll(); }
|
|
|
|
|
async function createCode(): Promise<void> { pending.value = "create"; const body: Record<string, unknown> = { ...form, creditAmount: Number(form.creditAmount), maxRedemptions: Number(form.maxRedemptions), perUserLimit: Number(form.perUserLimit) }; if (form.expiresAt) body.expiresAt = new Date(form.expiresAt).toISOString(); else delete body.expiresAt; const response = await billingAPI.createAdminRedeemCode(body); pending.value = null; if (!response.ok) { error.value = response.error ?? `HTTP ${response.status}`; return; } createdCode.value = response.data?.redeemCode ?? null; await refreshAll(); }
|
|
|
|
|
function codeKey(item: RedeemCodeSummary): string { return item.id || item.codePrefix || "redeem-code"; }
|
|
|
|
|