summaryrefslogtreecommitdiffhomepage
path: root/packages/console/app/src
diff options
context:
space:
mode:
authorFrank <[email protected]>2026-04-18 16:26:58 -0400
committerFrank <[email protected]>2026-04-18 17:33:28 -0400
commit9d012b062186ef9900cc2673b77c446d38ebd789 (patch)
tree0185f564ac585be5e50e1ba090da8a461e6fb4fc /packages/console/app/src
parentfbb0a93e12740c7fb3f5f7ff62eee027c157e351 (diff)
downloadopencode-9d012b062186ef9900cc2673b77c446d38ebd789.tar.gz
opencode-9d012b062186ef9900cc2673b77c446d38ebd789.zip
zen: redeem credit
Diffstat (limited to 'packages/console/app/src')
-rw-r--r--packages/console/app/src/i18n/ar.ts7
-rw-r--r--packages/console/app/src/i18n/br.ts7
-rw-r--r--packages/console/app/src/i18n/da.ts7
-rw-r--r--packages/console/app/src/i18n/de.ts7
-rw-r--r--packages/console/app/src/i18n/en.ts7
-rw-r--r--packages/console/app/src/i18n/es.ts7
-rw-r--r--packages/console/app/src/i18n/fr.ts7
-rw-r--r--packages/console/app/src/i18n/it.ts7
-rw-r--r--packages/console/app/src/i18n/ja.ts7
-rw-r--r--packages/console/app/src/i18n/ko.ts7
-rw-r--r--packages/console/app/src/i18n/no.ts7
-rw-r--r--packages/console/app/src/i18n/pl.ts7
-rw-r--r--packages/console/app/src/i18n/ru.ts7
-rw-r--r--packages/console/app/src/i18n/th.ts7
-rw-r--r--packages/console/app/src/i18n/tr.ts7
-rw-r--r--packages/console/app/src/i18n/zh.ts7
-rw-r--r--packages/console/app/src/i18n/zht.ts7
-rw-r--r--packages/console/app/src/routes/workspace/[id]/billing/index.tsx2
-rw-r--r--packages/console/app/src/routes/workspace/[id]/billing/redeem-section.module.css61
-rw-r--r--packages/console/app/src/routes/workspace/[id]/billing/redeem-section.tsx71
20 files changed, 253 insertions, 0 deletions
diff --git a/packages/console/app/src/i18n/ar.ts b/packages/console/app/src/i18n/ar.ts
index 11f76ca59..713703b25 100644
--- a/packages/console/app/src/i18n/ar.ts
+++ b/packages/console/app/src/i18n/ar.ts
@@ -558,6 +558,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "الاستخدام الحالي لـ",
"workspace.monthlyLimit.currentUsage.beforeAmount": "هو $",
+ "workspace.redeem.title": "استرداد قسيمة",
+ "workspace.redeem.subtitle": "استرد رمز القسيمة للحصول على رصيد أو مزايا.",
+ "workspace.redeem.placeholder": "أدخل رمز القسيمة",
+ "workspace.redeem.redeem": "استرداد",
+ "workspace.redeem.redeeming": "جارٍ الاسترداد...",
+ "workspace.redeem.success": "تم استرداد القسيمة بنجاح.",
+
"workspace.reload.title": "إعادة الشحن التلقائي",
"workspace.reload.disabled.before": "إعادة الشحن التلقائي",
"workspace.reload.disabled.state": "معطّل",
diff --git a/packages/console/app/src/i18n/br.ts b/packages/console/app/src/i18n/br.ts
index 79f64485a..bace96569 100644
--- a/packages/console/app/src/i18n/br.ts
+++ b/packages/console/app/src/i18n/br.ts
@@ -567,6 +567,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "Uso atual para",
"workspace.monthlyLimit.currentUsage.beforeAmount": "é $",
+ "workspace.redeem.title": "Resgatar Cupom",
+ "workspace.redeem.subtitle": "Resgate um código de cupom para receber créditos ou vantagens.",
+ "workspace.redeem.placeholder": "Digite o código do cupom",
+ "workspace.redeem.redeem": "Resgatar",
+ "workspace.redeem.redeeming": "Resgatando...",
+ "workspace.redeem.success": "Cupom resgatado com sucesso.",
+
"workspace.reload.title": "Recarga Automática",
"workspace.reload.disabled.before": "A recarga automática está",
"workspace.reload.disabled.state": "desativada",
diff --git a/packages/console/app/src/i18n/da.ts b/packages/console/app/src/i18n/da.ts
index 97b148c06..c2d3bf3ba 100644
--- a/packages/console/app/src/i18n/da.ts
+++ b/packages/console/app/src/i18n/da.ts
@@ -563,6 +563,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "Nuværende brug for",
"workspace.monthlyLimit.currentUsage.beforeAmount": "er $",
+ "workspace.redeem.title": "Indløs kupon",
+ "workspace.redeem.subtitle": "Indløs en kuponkode for at få kreditter eller fordele.",
+ "workspace.redeem.placeholder": "Indtast kuponkode",
+ "workspace.redeem.redeem": "Indløs",
+ "workspace.redeem.redeeming": "Indløser...",
+ "workspace.redeem.success": "Kuponen blev indløst.",
+
"workspace.reload.title": "Automatisk genopfyldning",
"workspace.reload.disabled.before": "Automatisk genopfyldning er",
"workspace.reload.disabled.state": "deaktiveret",
diff --git a/packages/console/app/src/i18n/de.ts b/packages/console/app/src/i18n/de.ts
index 1c5f631ae..e44335bab 100644
--- a/packages/console/app/src/i18n/de.ts
+++ b/packages/console/app/src/i18n/de.ts
@@ -566,6 +566,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "Aktuelle Nutzung für",
"workspace.monthlyLimit.currentUsage.beforeAmount": "ist $",
+ "workspace.redeem.title": "Gutschein einlösen",
+ "workspace.redeem.subtitle": "Löse einen Gutscheincode ein, um Guthaben oder Vorteile zu erhalten.",
+ "workspace.redeem.placeholder": "Gutscheincode eingeben",
+ "workspace.redeem.redeem": "Einlösen",
+ "workspace.redeem.redeeming": "Wird eingelöst...",
+ "workspace.redeem.success": "Gutschein erfolgreich eingelöst.",
+
"workspace.reload.title": "Auto-Reload",
"workspace.reload.disabled.before": "Auto-Reload ist",
"workspace.reload.disabled.state": "deaktiviert",
diff --git a/packages/console/app/src/i18n/en.ts b/packages/console/app/src/i18n/en.ts
index d21d0dc9e..a9c3ca3cc 100644
--- a/packages/console/app/src/i18n/en.ts
+++ b/packages/console/app/src/i18n/en.ts
@@ -559,6 +559,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "Current usage for",
"workspace.monthlyLimit.currentUsage.beforeAmount": "is $",
+ "workspace.redeem.title": "Redeem Coupon",
+ "workspace.redeem.subtitle": "Redeem a coupon code to claim credits or perks.",
+ "workspace.redeem.placeholder": "Enter coupon code",
+ "workspace.redeem.redeem": "Redeem",
+ "workspace.redeem.redeeming": "Redeeming...",
+ "workspace.redeem.success": "Coupon redeemed successfully.",
+
"workspace.reload.title": "Auto Reload",
"workspace.reload.disabled.before": "Auto reload is",
"workspace.reload.disabled.state": "disabled",
diff --git a/packages/console/app/src/i18n/es.ts b/packages/console/app/src/i18n/es.ts
index d6449fd95..e2a0c271a 100644
--- a/packages/console/app/src/i18n/es.ts
+++ b/packages/console/app/src/i18n/es.ts
@@ -567,6 +567,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "Uso actual para",
"workspace.monthlyLimit.currentUsage.beforeAmount": "es $",
+ "workspace.redeem.title": "Canjear cupón",
+ "workspace.redeem.subtitle": "Canjea un código de cupón para obtener crédito o beneficios.",
+ "workspace.redeem.placeholder": "Introduce el código del cupón",
+ "workspace.redeem.redeem": "Canjear",
+ "workspace.redeem.redeeming": "Canjeando...",
+ "workspace.redeem.success": "Cupón canjeado correctamente.",
+
"workspace.reload.title": "Auto Recarga",
"workspace.reload.disabled.before": "La auto recarga está",
"workspace.reload.disabled.state": "deshabilitada",
diff --git a/packages/console/app/src/i18n/fr.ts b/packages/console/app/src/i18n/fr.ts
index c54c43104..5a353e873 100644
--- a/packages/console/app/src/i18n/fr.ts
+++ b/packages/console/app/src/i18n/fr.ts
@@ -569,6 +569,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "L'utilisation actuelle pour",
"workspace.monthlyLimit.currentUsage.beforeAmount": "est de",
+ "workspace.redeem.title": "Utiliser un coupon",
+ "workspace.redeem.subtitle": "Utilisez un code promo pour obtenir du crédit ou des avantages.",
+ "workspace.redeem.placeholder": "Saisissez le code promo",
+ "workspace.redeem.redeem": "Utiliser",
+ "workspace.redeem.redeeming": "Utilisation...",
+ "workspace.redeem.success": "Coupon utilisé avec succès.",
+
"workspace.reload.title": "Rechargement automatique",
"workspace.reload.disabled.before": "Le rechargement automatique est",
"workspace.reload.disabled.state": "désactivé",
diff --git a/packages/console/app/src/i18n/it.ts b/packages/console/app/src/i18n/it.ts
index aadea6fec..b19bff978 100644
--- a/packages/console/app/src/i18n/it.ts
+++ b/packages/console/app/src/i18n/it.ts
@@ -565,6 +565,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "Utilizzo attuale per",
"workspace.monthlyLimit.currentUsage.beforeAmount": "è $",
+ "workspace.redeem.title": "Riscatta Coupon",
+ "workspace.redeem.subtitle": "Riscatta un codice coupon per ottenere credito o vantaggi.",
+ "workspace.redeem.placeholder": "Inserisci il codice coupon",
+ "workspace.redeem.redeem": "Riscatta",
+ "workspace.redeem.redeeming": "Riscatto in corso...",
+ "workspace.redeem.success": "Coupon riscattato con successo.",
+
"workspace.reload.title": "Ricarica Auto",
"workspace.reload.disabled.before": "La ricarica auto è",
"workspace.reload.disabled.state": "disabilitata",
diff --git a/packages/console/app/src/i18n/ja.ts b/packages/console/app/src/i18n/ja.ts
index f3b4c083e..1571345e5 100644
--- a/packages/console/app/src/i18n/ja.ts
+++ b/packages/console/app/src/i18n/ja.ts
@@ -564,6 +564,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "現在の使用状況(",
"workspace.monthlyLimit.currentUsage.beforeAmount": ")は $",
+ "workspace.redeem.title": "クーポンを利用",
+ "workspace.redeem.subtitle": "クーポンコードを利用して、クレジットや特典を受け取ります。",
+ "workspace.redeem.placeholder": "クーポンコードを入力",
+ "workspace.redeem.redeem": "利用する",
+ "workspace.redeem.redeeming": "利用中...",
+ "workspace.redeem.success": "クーポンを利用しました。",
+
"workspace.reload.title": "自動チャージ",
"workspace.reload.disabled.before": "自動チャージは",
"workspace.reload.disabled.state": "無効",
diff --git a/packages/console/app/src/i18n/ko.ts b/packages/console/app/src/i18n/ko.ts
index e2320c359..9ec931031 100644
--- a/packages/console/app/src/i18n/ko.ts
+++ b/packages/console/app/src/i18n/ko.ts
@@ -558,6 +558,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "현재",
"workspace.monthlyLimit.currentUsage.beforeAmount": "사용량: $",
+ "workspace.redeem.title": "쿠폰 사용",
+ "workspace.redeem.subtitle": "쿠폰 코드를 사용해 크레딧이나 혜택을 받으세요.",
+ "workspace.redeem.placeholder": "쿠폰 코드를 입력하세요",
+ "workspace.redeem.redeem": "사용",
+ "workspace.redeem.redeeming": "사용 중...",
+ "workspace.redeem.success": "쿠폰을 성공적으로 사용했습니다.",
+
"workspace.reload.title": "자동 충전",
"workspace.reload.disabled.before": "자동 충전이",
"workspace.reload.disabled.state": "비활성화",
diff --git a/packages/console/app/src/i18n/no.ts b/packages/console/app/src/i18n/no.ts
index 717c2ad1e..132b85a6e 100644
--- a/packages/console/app/src/i18n/no.ts
+++ b/packages/console/app/src/i18n/no.ts
@@ -564,6 +564,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "Gjeldende forbruk for",
"workspace.monthlyLimit.currentUsage.beforeAmount": "er $",
+ "workspace.redeem.title": "Løs inn kupong",
+ "workspace.redeem.subtitle": "Løs inn en kupongkode for å få kreditt eller fordeler.",
+ "workspace.redeem.placeholder": "Skriv inn kupongkode",
+ "workspace.redeem.redeem": "Løs inn",
+ "workspace.redeem.redeeming": "Løser inn...",
+ "workspace.redeem.success": "Kupongen ble løst inn.",
+
"workspace.reload.title": "Auto-påfyll",
"workspace.reload.disabled.before": "Auto-påfyll er",
"workspace.reload.disabled.state": "deaktivert",
diff --git a/packages/console/app/src/i18n/pl.ts b/packages/console/app/src/i18n/pl.ts
index c9daeedee..441dfc7be 100644
--- a/packages/console/app/src/i18n/pl.ts
+++ b/packages/console/app/src/i18n/pl.ts
@@ -565,6 +565,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "Aktualne użycie za",
"workspace.monthlyLimit.currentUsage.beforeAmount": "wynosi $",
+ "workspace.redeem.title": "Zrealizuj kupon",
+ "workspace.redeem.subtitle": "Zrealizuj kod kuponu, aby otrzymać środki lub korzyści.",
+ "workspace.redeem.placeholder": "Wpisz kod kuponu",
+ "workspace.redeem.redeem": "Zrealizuj",
+ "workspace.redeem.redeeming": "Realizowanie...",
+ "workspace.redeem.success": "Kupon został zrealizowany.",
+
"workspace.reload.title": "Automatyczne doładowanie",
"workspace.reload.disabled.before": "Automatyczne doładowanie jest",
"workspace.reload.disabled.state": "wyłączone",
diff --git a/packages/console/app/src/i18n/ru.ts b/packages/console/app/src/i18n/ru.ts
index 01baa8985..ef7bcacd8 100644
--- a/packages/console/app/src/i18n/ru.ts
+++ b/packages/console/app/src/i18n/ru.ts
@@ -571,6 +571,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "Текущее использование за",
"workspace.monthlyLimit.currentUsage.beforeAmount": "составляет $",
+ "workspace.redeem.title": "Активировать купон",
+ "workspace.redeem.subtitle": "Активируйте код купона, чтобы получить кредит или бонусы.",
+ "workspace.redeem.placeholder": "Введите код купона",
+ "workspace.redeem.redeem": "Активировать",
+ "workspace.redeem.redeeming": "Активация...",
+ "workspace.redeem.success": "Купон успешно активирован.",
+
"workspace.reload.title": "Автопополнение",
"workspace.reload.disabled.before": "Автопополнение",
"workspace.reload.disabled.state": "отключено",
diff --git a/packages/console/app/src/i18n/th.ts b/packages/console/app/src/i18n/th.ts
index 59c90ef65..d7d862d94 100644
--- a/packages/console/app/src/i18n/th.ts
+++ b/packages/console/app/src/i18n/th.ts
@@ -560,6 +560,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "การใช้งานปัจจุบันสำหรับ",
"workspace.monthlyLimit.currentUsage.beforeAmount": "คือ $",
+ "workspace.redeem.title": "แลกคูปอง",
+ "workspace.redeem.subtitle": "แลกรหัสคูปองเพื่อรับเครดิตหรือสิทธิพิเศษ",
+ "workspace.redeem.placeholder": "กรอกรหัสคูปอง",
+ "workspace.redeem.redeem": "แลก",
+ "workspace.redeem.redeeming": "กำลังแลก...",
+ "workspace.redeem.success": "แลกคูปองสำเร็จ",
+
"workspace.reload.title": "โหลดซ้ำอัตโนมัติ",
"workspace.reload.disabled.before": "การโหลดซ้ำอัตโนมัติ",
"workspace.reload.disabled.state": "ปิดใช้งานอยู่",
diff --git a/packages/console/app/src/i18n/tr.ts b/packages/console/app/src/i18n/tr.ts
index 196bf9d37..13a074642 100644
--- a/packages/console/app/src/i18n/tr.ts
+++ b/packages/console/app/src/i18n/tr.ts
@@ -567,6 +567,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "Şu anki kullanım",
"workspace.monthlyLimit.currentUsage.beforeAmount": "$",
+ "workspace.redeem.title": "Kupon Kullan",
+ "workspace.redeem.subtitle": "Kredi veya avantajlardan yararlanmak için bir kupon kodu kullanın.",
+ "workspace.redeem.placeholder": "Kupon kodunu girin",
+ "workspace.redeem.redeem": "Kullan",
+ "workspace.redeem.redeeming": "Kullanılıyor...",
+ "workspace.redeem.success": "Kupon başarıyla kullanıldı.",
+
"workspace.reload.title": "Otomatik Yeniden Yükleme",
"workspace.reload.disabled.before": "Otomatik yeniden yükleme:",
"workspace.reload.disabled.state": "devre dışı",
diff --git a/packages/console/app/src/i18n/zh.ts b/packages/console/app/src/i18n/zh.ts
index aaec74fba..c84ea5cc6 100644
--- a/packages/console/app/src/i18n/zh.ts
+++ b/packages/console/app/src/i18n/zh.ts
@@ -542,6 +542,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "当前",
"workspace.monthlyLimit.currentUsage.beforeAmount": "的使用量为 $",
+ "workspace.redeem.title": "兑换优惠券",
+ "workspace.redeem.subtitle": "兑换优惠码以领取充值额度或权益。",
+ "workspace.redeem.placeholder": "输入优惠码",
+ "workspace.redeem.redeem": "兑换",
+ "workspace.redeem.redeeming": "兑换中...",
+ "workspace.redeem.success": "优惠券兑换成功。",
+
"workspace.reload.title": "自动充值",
"workspace.reload.disabled.before": "自动充值已",
"workspace.reload.disabled.state": "禁用",
diff --git a/packages/console/app/src/i18n/zht.ts b/packages/console/app/src/i18n/zht.ts
index b3e0fb077..6a70a81c7 100644
--- a/packages/console/app/src/i18n/zht.ts
+++ b/packages/console/app/src/i18n/zht.ts
@@ -542,6 +542,13 @@ export const dict = {
"workspace.monthlyLimit.currentUsage.beforeMonth": "目前",
"workspace.monthlyLimit.currentUsage.beforeAmount": "的使用量為 $",
+ "workspace.redeem.title": "兌換優惠券",
+ "workspace.redeem.subtitle": "兌換優惠碼以領取儲值額度或權益。",
+ "workspace.redeem.placeholder": "輸入優惠碼",
+ "workspace.redeem.redeem": "兌換",
+ "workspace.redeem.redeeming": "兌換中...",
+ "workspace.redeem.success": "優惠券兌換成功。",
+
"workspace.reload.title": "自動儲值",
"workspace.reload.disabled.before": "自動儲值已",
"workspace.reload.disabled.state": "停用",
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/index.tsx b/packages/console/app/src/routes/workspace/[id]/billing/index.tsx
index 4a7dc2488..fb4848535 100644
--- a/packages/console/app/src/routes/workspace/[id]/billing/index.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/billing/index.tsx
@@ -3,6 +3,7 @@ import { BillingSection } from "./billing-section"
import { ReloadSection } from "./reload-section"
import { PaymentSection } from "./payment-section"
import { BlackSection } from "./black-section"
+import { RedeemSection } from "./redeem-section"
import { createMemo, Show } from "solid-js"
import { createAsync, useParams } from "@solidjs/router"
import { queryBillingInfo, querySessionInfo } from "../../common"
@@ -26,6 +27,7 @@ export default function () {
<MonthlyLimitSection />
<PaymentSection />
</Show>
+ <RedeemSection />
</Show>
</div>
</div>
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/redeem-section.module.css b/packages/console/app/src/routes/workspace/[id]/billing/redeem-section.module.css
new file mode 100644
index 000000000..42140e4e8
--- /dev/null
+++ b/packages/console/app/src/routes/workspace/[id]/billing/redeem-section.module.css
@@ -0,0 +1,61 @@
+.root {
+ [data-slot="redeem-container"] {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-3);
+ min-width: 20rem;
+ width: fit-content;
+
+ @media (max-width: 30rem) {
+ width: 100%;
+ }
+ }
+
+ [data-slot="redeem-form"] {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-2);
+
+ [data-slot="input-row"] {
+ display: flex;
+ gap: var(--space-2);
+ align-items: stretch;
+
+ @media (max-width: 30rem) {
+ flex-direction: column;
+ }
+ }
+
+ input {
+ flex: 1;
+ padding: var(--space-2) var(--space-3);
+ border: 1px solid var(--color-border);
+ border-radius: var(--border-radius-sm);
+ background-color: var(--color-bg);
+ color: var(--color-text);
+ font-size: var(--font-size-sm);
+ font-family: var(--font-mono);
+
+ &:focus {
+ outline: none;
+ border-color: var(--color-accent);
+ }
+
+ &::placeholder {
+ color: var(--color-text-disabled);
+ }
+ }
+
+ [data-slot="form-error"] {
+ color: var(--color-danger);
+ font-size: var(--font-size-sm);
+ line-height: 1.4;
+ }
+
+ [data-slot="form-success"] {
+ color: var(--color-success, var(--color-accent));
+ font-size: var(--font-size-sm);
+ line-height: 1.4;
+ }
+ }
+}
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/redeem-section.tsx b/packages/console/app/src/routes/workspace/[id]/billing/redeem-section.tsx
new file mode 100644
index 000000000..872e8954b
--- /dev/null
+++ b/packages/console/app/src/routes/workspace/[id]/billing/redeem-section.tsx
@@ -0,0 +1,71 @@
+import { json, action, useParams, useSubmission } from "@solidjs/router"
+import { Show } from "solid-js"
+import { withActor } from "~/context/auth.withActor"
+import { Billing } from "@opencode-ai/console-core/billing.js"
+import { User } from "@opencode-ai/console-core/user.js"
+import { Actor } from "@opencode-ai/console-core/actor.js"
+import { CouponType } from "@opencode-ai/console-core/schema/billing.sql.js"
+import styles from "./redeem-section.module.css"
+import { queryBillingInfo } from "../../common"
+import { useI18n } from "~/context/i18n"
+import { formError, localizeError } from "~/lib/form-error"
+
+const redeem = action(async (form: FormData) => {
+ "use server"
+ const workspaceID = form.get("workspaceID") as string | null
+ if (!workspaceID) return { error: formError.workspaceRequired }
+ const code = (form.get("code") as string | null)?.trim().toUpperCase()
+ if (!code) return { error: "Coupon code is required." }
+ if (!(CouponType as readonly string[]).includes(code)) return { error: "Invalid coupon code." }
+
+ return json(
+ await withActor(async () => {
+ const actor = Actor.assert("user")
+ const email = await User.getAuthEmail(actor.properties.userID)
+ if (!email) return { error: "No email on account." }
+ return Billing.redeemCoupon(email, code as (typeof CouponType)[number])
+ .then(() => ({ error: undefined, data: true }))
+ .catch((e) => ({ error: e.message as string }))
+ }, workspaceID),
+ { revalidate: queryBillingInfo.key },
+ )
+}, "billing.redeemCoupon")
+
+export function RedeemSection() {
+ const params = useParams()
+ const i18n = useI18n()
+ const submission = useSubmission(redeem)
+
+ return (
+ <section class={styles.root}>
+ <div data-slot="section-title">
+ <h2>{i18n.t("workspace.redeem.title")}</h2>
+ <p>{i18n.t("workspace.redeem.subtitle")}</p>
+ </div>
+ <div data-slot="redeem-container">
+ <form action={redeem} method="post" data-slot="redeem-form">
+ <div data-slot="input-row">
+ <input
+ required
+ data-component="input"
+ name="code"
+ type="text"
+ autocomplete="off"
+ placeholder={i18n.t("workspace.redeem.placeholder")}
+ />
+ <button type="submit" data-color="primary" disabled={submission.pending}>
+ {submission.pending ? i18n.t("workspace.redeem.redeeming") : i18n.t("workspace.redeem.redeem")}
+ </button>
+ </div>
+ <Show when={submission.result && (submission.result as any).error}>
+ {(err: any) => <div data-slot="form-error">{localizeError(i18n.t, err())}</div>}
+ </Show>
+ <Show when={submission.result && !(submission.result as any).error && (submission.result as any).data}>
+ <div data-slot="form-success">{i18n.t("workspace.redeem.success")}</div>
+ </Show>
+ <input type="hidden" name="workspaceID" value={params.id} />
+ </form>
+ </div>
+ </section>
+ )
+}