diff options
| author | Frank <[email protected]> | 2026-04-18 16:26:58 -0400 |
|---|---|---|
| committer | Frank <[email protected]> | 2026-04-18 17:33:28 -0400 |
| commit | 9d012b062186ef9900cc2673b77c446d38ebd789 (patch) | |
| tree | 0185f564ac585be5e50e1ba090da8a461e6fb4fc /packages/console/app/src | |
| parent | fbb0a93e12740c7fb3f5f7ff62eee027c157e351 (diff) | |
| download | opencode-9d012b062186ef9900cc2673b77c446d38ebd789.tar.gz opencode-9d012b062186ef9900cc2673b77c446d38ebd789.zip | |
zen: redeem credit
Diffstat (limited to 'packages/console/app/src')
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> + ) +} |
