diff --git a/packages/console/app/src/component/go-referral.css b/packages/console/app/src/component/go-referral.css index 96d0e47d7f..c9050e6cf9 100644 --- a/packages/console/app/src/component/go-referral.css +++ b/packages/console/app/src/component/go-referral.css @@ -1,46 +1,3 @@ -[data-component="go-credit-confirm"], -[data-component="go-referral-section"] { - a[data-color], - button { - display: inline-flex; - align-items: center; - justify-content: center; - padding: var(--space-3) var(--space-4); - 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-sans); - font-weight: 500; - line-height: 1; - cursor: pointer; - text-decoration: none; - transition: all 0.15s ease; - - &:disabled { - opacity: 0.5; - cursor: not-allowed; - } - - &:hover:not(:disabled) { - background-color: var(--color-surface-hover); - border-color: var(--color-accent); - } - - &[data-color="primary"] { - background-color: var(--color-primary); - border-color: var(--color-primary); - color: var(--color-primary-text); - - &:hover:not(:disabled) { - background-color: var(--color-primary-hover); - border-color: var(--color-primary-hover); - } - } - } -} - [data-component="go-credit-confirm"] { display: flex; flex-direction: column; @@ -177,6 +134,9 @@ } button { + display: inline-flex; + align-items: center; + justify-content: center; gap: var(--space-2); min-width: 130px; white-space: nowrap; diff --git a/packages/console/app/src/component/go-referral.tsx b/packages/console/app/src/component/go-referral.tsx index 1897caf33f..6f0b305bb8 100644 --- a/packages/console/app/src/component/go-referral.tsx +++ b/packages/console/app/src/component/go-referral.tsx @@ -2,20 +2,16 @@ import { action, createAsync, json, query, useAction, useSubmission } from "@sol import { createEffect, createMemo, createSignal, For, onCleanup, Show } from "solid-js" import { getRequestEvent } from "solid-js/web" import { Referral } from "@opencode-ai/console-core/referral.js" -import { Database, and, eq, isNull } from "@opencode-ai/console-core/drizzle/index.js" -import { LiteData } from "@opencode-ai/console-core/lite.js" -import { LiteTable } from "@opencode-ai/console-core/schema/billing.sql.js" -import { ReferralRewardTable } from "@opencode-ai/console-core/schema/referral.sql.js" -import { Subscription } from "@opencode-ai/console-core/subscription.js" import { withActor } from "~/context/auth.withActor" import { Modal } from "~/component/modal" import { IconCheck, IconCopy } from "~/component/icon" import { useI18n } from "~/context/i18n" import { useLanguage } from "~/context/language" -import { formatResetTime, queryLiteSubscription } from "~/routes/workspace/[id]/go/lite-section" +import { formatResetTime, liteResetTimeKeys } from "~/lib/format-reset-time" +import { queryLiteSubscription } from "~/routes/workspace/[id]/go/lite-section" import "./go-referral.css" -export type GoReferralReward = { +type GoReferralReward = { id: string amount: number email: string @@ -25,7 +21,7 @@ export type GoReferralReward = { timeApplied: string | Date | null } -export type GoReferralSummary = { +type GoReferralSummary = { inviteCode: string inviteUrl: string validInviteCount: number @@ -36,12 +32,6 @@ export type GoReferralSummary = { rewards: GoReferralReward[] } -type AnalyzedUsage = { - status: "ok" | "rate-limited" - resetInSec: number - usagePercent: number -} - type GoReferralUsagePreview = { rollingUsage: GoReferralUsagePreviewItem weeklyUsage: GoReferralUsagePreviewItem @@ -74,76 +64,7 @@ export const queryGoReferral = query(async (workspaceID: string) => { export const queryGoReferralUsagePreview = query(async (workspaceID: string, referralID?: string) => { "use server" if (!referralID) return null - return withActor(async () => { - const row = await Database.use((tx) => - tx - .select({ - rewardAmount: ReferralRewardTable.amount, - rollingUsage: LiteTable.rollingUsage, - weeklyUsage: LiteTable.weeklyUsage, - monthlyUsage: LiteTable.monthlyUsage, - timeRollingUpdated: LiteTable.timeRollingUpdated, - timeWeeklyUpdated: LiteTable.timeWeeklyUpdated, - timeMonthlyUpdated: LiteTable.timeMonthlyUpdated, - timeCreated: LiteTable.timeCreated, - }) - .from(ReferralRewardTable) - .innerJoin(LiteTable, eq(LiteTable.workspaceID, ReferralRewardTable.workspaceID)) - .where( - and( - eq(ReferralRewardTable.workspaceID, workspaceID), - eq(ReferralRewardTable.referralID, referralID), - isNull(ReferralRewardTable.timeApplied), - isNull(ReferralRewardTable.timeDeleted), - isNull(LiteTable.timeDeleted), - ), - ) - .then((rows) => rows[0]), - ) - if (!row) return null - - const limits = LiteData.getLimits() - const rollingBefore = Subscription.analyzeRollingUsage({ - limit: limits.rollingLimit, - window: limits.rollingWindow, - usage: row.rollingUsage ?? 0, - timeUpdated: row.timeRollingUpdated ?? new Date(), - }) - const rollingAfter = Subscription.analyzeRollingUsage({ - limit: limits.rollingLimit, - window: limits.rollingWindow, - usage: Math.max(0, (row.rollingUsage ?? 0) - row.rewardAmount), - timeUpdated: row.timeRollingUpdated ?? new Date(), - }) - const weeklyBefore = Subscription.analyzeWeeklyUsage({ - limit: limits.weeklyLimit, - usage: row.weeklyUsage ?? 0, - timeUpdated: row.timeWeeklyUpdated ?? new Date(), - }) - const weeklyAfter = Subscription.analyzeWeeklyUsage({ - limit: limits.weeklyLimit, - usage: Math.max(0, (row.weeklyUsage ?? 0) - row.rewardAmount), - timeUpdated: row.timeWeeklyUpdated ?? new Date(), - }) - const monthlyBefore = Subscription.analyzeMonthlyUsage({ - limit: limits.monthlyLimit, - usage: row.monthlyUsage ?? 0, - timeUpdated: row.timeMonthlyUpdated ?? new Date(), - timeSubscribed: row.timeCreated, - }) - const monthlyAfter = Subscription.analyzeMonthlyUsage({ - limit: limits.monthlyLimit, - usage: Math.max(0, (row.monthlyUsage ?? 0) - row.rewardAmount), - timeUpdated: row.timeMonthlyUpdated ?? new Date(), - timeSubscribed: row.timeCreated, - }) - - return { - rollingUsage: usagePreview(rollingBefore, rollingAfter), - weeklyUsage: usagePreview(weeklyBefore, weeklyAfter), - monthlyUsage: usagePreview(monthlyBefore, monthlyAfter), - } satisfies GoReferralUsagePreview - }, workspaceID) + return withActor(() => Referral.usagePreview({ referralID }), workspaceID) }, "go.referral.usagePreview") export const applyGoReferralReward = action(async (workspaceID: string, referralID: string) => { @@ -160,18 +81,14 @@ export const applyGoReferralReward = action(async (workspaceID: string, referral ) }, "go.referral.reward.apply") -function usagePreview(before: AnalyzedUsage, after: AnalyzedUsage) { +function currentUsagePreview(usage: { resetInSec: number; usagePercent: number }) { return { - beforePercent: before.usagePercent, - afterPercent: after.usagePercent, - resetInSec: after.resetInSec, + beforePercent: usage.usagePercent, + afterPercent: usage.usagePercent, + resetInSec: usage.resetInSec, } } -function currentUsagePreview(usage: AnalyzedUsage) { - return usagePreview(usage, usage) -} - function formatCurrency(amount: number) { if (amount % 100 === 0) return `$${amount / 100}` return `$${(amount / 100).toFixed(2)}` @@ -299,7 +216,14 @@ export function GoReferralSection(props: { workspaceID: string; summary: GoRefer - +
+
    +
  1. {i18n.t("workspace.referral.instructions.share")}
  2. +
  3. {i18n.t("workspace.referral.instructions.subscribe")}
  4. +
  5. {i18n.t("workspace.referral.instructions.claim")}
  6. +
  7. {i18n.t("workspace.referral.instructions.apply", { amount: formatCurrency(props.summary.rewardAmount) })}
  8. +
+

{i18n.t("workspace.referral.rewards.title")}

@@ -327,9 +251,9 @@ export function GoReferralSection(props: { workspaceID: string; summary: GoRefer {(reward) => { - const applied = createMemo(() => reward.status === "applied") - const pending = createMemo(() => reward.status === "pending") - const earnedAt = createMemo(() => formatDate(reward.timeCreated, language.tag(language.locale()))) + const applied = reward.status === "applied" + const pending = reward.status === "pending" + const earnedAt = () => formatDate(reward.timeCreated, language.tag(language.locale())) return ( {formatCurrency(reward.amount)} @@ -346,8 +270,8 @@ export function GoReferralSection(props: { workspaceID: string; summary: GoRefer disabled={reward.status !== "available" || !props.summary.hasActiveGo || submission.pending} onClick={() => setSelected(reward)} > - - {pending() + + {pending ? i18n.t(rewardPendingStatusKey(reward.source)) : props.summary.hasActiveGo ? i18n.t("workspace.referral.apply.action") @@ -424,23 +348,8 @@ function GoReferralUsagePreviewRow(props: { label: string; usage: GoReferralUsag
- {i18n.t("workspace.lite.subscription.resetsIn")} {formatResetTime(props.usage.resetInSec, i18n)} + {i18n.t("workspace.lite.subscription.resetsIn")} {formatResetTime(props.usage.resetInSec, i18n, liteResetTimeKeys)}
) } - -function InvitationInstructions(props: { rewardAmount: number }) { - const i18n = useI18n() - - return ( -
-
    -
  1. {i18n.t("workspace.referral.instructions.share")}
  2. -
  3. {i18n.t("workspace.referral.instructions.subscribe")}
  4. -
  5. {i18n.t("workspace.referral.instructions.claim")}
  6. -
  7. {i18n.t("workspace.referral.instructions.apply", { amount: formatCurrency(props.rewardAmount) })}
  8. -
-
- ) -} diff --git a/packages/console/app/src/component/modal.css b/packages/console/app/src/component/modal.css index cc7708bba8..ab7f222b02 100644 --- a/packages/console/app/src/component/modal.css +++ b/packages/console/app/src/component/modal.css @@ -55,6 +55,61 @@ @media (prefers-color-scheme: dark) { box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5); } + + button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: var(--space-3) var(--space-4); + 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-sans); + font-weight: 500; + line-height: 1; + cursor: pointer; + transition: all 0.15s ease; + + &:hover:not(:disabled) { + background-color: var(--color-surface-hover); + border-color: var(--color-accent); + } + + &:active:not(:disabled) { + transform: translateY(1px); + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; + } + + &[data-color="primary"] { + background-color: var(--color-primary); + border-color: var(--color-primary); + color: var(--color-primary-text); + + &:hover:not(:disabled) { + background-color: var(--color-primary-hover); + border-color: var(--color-primary-hover); + } + } + + &[data-color="ghost"] { + background-color: transparent; + border-color: transparent; + color: var(--color-text-muted); + + &:hover:not(:disabled) { + background-color: var(--color-surface-hover); + border-color: var(--color-border); + color: var(--color-text); + } + } + } } [data-slot="title"] { diff --git a/packages/console/app/src/i18n/en.ts b/packages/console/app/src/i18n/en.ts index e2fe92efb5..ad5bc7d5fe 100644 --- a/packages/console/app/src/i18n/en.ts +++ b/packages/console/app/src/i18n/en.ts @@ -662,7 +662,6 @@ export const dict = { "workspace.lite.promo.otherMethods": "Other payment methods", "workspace.lite.promo.selectMethod": "Select payment method", - "workspace.referral.inviteLink": "Invite link", "workspace.referral.copyLink": "Copy Link", "workspace.referral.copied": "Copied", "workspace.referral.overview.title": "Invite friends to Go", @@ -680,8 +679,6 @@ export const dict = { "workspace.referral.table.reward": "Reward", "workspace.referral.table.referral": "Description", "workspace.referral.table.date": "Date", - "workspace.referral.reward.source.inviter": "Earned by inviting a friend", - "workspace.referral.reward.source.invitee": "Received from an invite", "workspace.referral.reward.source.pendingInviter": "Waiting for them to subscribe", "workspace.referral.reward.source.pendingInvitee": "Subscribe to unlock reward", "workspace.referral.reward.source.available": "Reward ready to apply", @@ -689,9 +686,6 @@ export const dict = { "workspace.referral.reward.status.applied": "Applied", "workspace.referral.reward.status.pendingInviter": "Waiting for subscription", "workspace.referral.reward.status.pendingInvitee": "Subscribe to unlock", - "workspace.referral.reward.pending.inviter": "Reward unlocks when they subscribe to Go.", - "workspace.referral.reward.pending.invitee": "Subscribe to Go to unlock this reward.", - "workspace.referral.reward.earnedOn": "{{date}}", "workspace.referral.apply.noGo": "Subscribe to Go", "workspace.referral.apply.action": "Apply", "workspace.referral.apply.confirmTitle": "Apply Go reward", diff --git a/packages/console/app/src/i18n/zh.ts b/packages/console/app/src/i18n/zh.ts index 62a78a6f25..4fbb7d600e 100644 --- a/packages/console/app/src/i18n/zh.ts +++ b/packages/console/app/src/i18n/zh.ts @@ -643,7 +643,6 @@ export const dict = { "workspace.lite.promo.otherMethods": "其他付款方式", "workspace.lite.promo.selectMethod": "选择付款方式", - "workspace.referral.inviteLink": "邀请链接", "workspace.referral.copyLink": "复制链接", "workspace.referral.copied": "已复制", "workspace.referral.overview.title": "邀请好友使用 Go", @@ -661,8 +660,6 @@ export const dict = { "workspace.referral.table.reward": "奖励", "workspace.referral.table.referral": "描述", "workspace.referral.table.date": "日期", - "workspace.referral.reward.source.inviter": "邀请好友获得", - "workspace.referral.reward.source.invitee": "通过邀请获得", "workspace.referral.reward.source.pendingInviter": "等待对方订阅", "workspace.referral.reward.source.pendingInvitee": "订阅即可解锁奖励", "workspace.referral.reward.source.available": "奖励可使用", @@ -670,9 +667,6 @@ export const dict = { "workspace.referral.reward.status.applied": "已使用", "workspace.referral.reward.status.pendingInviter": "等待订阅", "workspace.referral.reward.status.pendingInvitee": "订阅后解锁", - "workspace.referral.reward.pending.inviter": "好友订阅 Go 后即可解锁奖励。", - "workspace.referral.reward.pending.invitee": "订阅 Go 后即可解锁此奖励。", - "workspace.referral.reward.earnedOn": "{{date}}", "workspace.referral.apply.noGo": "订阅 Go", "workspace.referral.apply.action": "使用", "workspace.referral.apply.confirmTitle": "使用 Go 奖励", diff --git a/packages/console/app/src/lib/format-reset-time.ts b/packages/console/app/src/lib/format-reset-time.ts new file mode 100644 index 0000000000..2462a87a3f --- /dev/null +++ b/packages/console/app/src/lib/format-reset-time.ts @@ -0,0 +1,47 @@ +import type { Key } from "~/i18n" +import type { useI18n } from "~/context/i18n" + +type ResetTimeKeys = { + day: Key + days: Key + hour: Key + hours: Key + minute: Key + minutes: Key + fewSeconds: Key +} + +export const liteResetTimeKeys = { + day: "workspace.lite.time.day", + days: "workspace.lite.time.days", + hour: "workspace.lite.time.hour", + hours: "workspace.lite.time.hours", + minute: "workspace.lite.time.minute", + minutes: "workspace.lite.time.minutes", + fewSeconds: "workspace.lite.time.fewSeconds", +} satisfies ResetTimeKeys + +export const blackResetTimeKeys = { + day: "workspace.black.time.day", + days: "workspace.black.time.days", + hour: "workspace.black.time.hour", + hours: "workspace.black.time.hours", + minute: "workspace.black.time.minute", + minutes: "workspace.black.time.minutes", + fewSeconds: "workspace.black.time.fewSeconds", +} satisfies ResetTimeKeys + +export function formatResetTime(seconds: number, i18n: ReturnType, keys: ResetTimeKeys) { + const days = Math.floor(seconds / 86400) + if (days >= 1) { + const hours = Math.floor((seconds % 86400) / 3600) + return `${days} ${days === 1 ? i18n.t(keys.day) : i18n.t(keys.days)} ${hours} ${hours === 1 ? i18n.t(keys.hour) : i18n.t(keys.hours)}` + } + + const hours = Math.floor(seconds / 3600) + const minutes = Math.floor((seconds % 3600) / 60) + if (hours >= 1) + return `${hours} ${hours === 1 ? i18n.t(keys.hour) : i18n.t(keys.hours)} ${minutes} ${minutes === 1 ? i18n.t(keys.minute) : i18n.t(keys.minutes)}` + if (minutes === 0) return i18n.t(keys.fewSeconds) + return `${minutes} ${minutes === 1 ? i18n.t(keys.minute) : i18n.t(keys.minutes)}` +} diff --git a/packages/console/app/src/lib/referral-invite.ts b/packages/console/app/src/lib/referral-invite.ts index 835364d40c..f5bb96f065 100644 --- a/packages/console/app/src/lib/referral-invite.ts +++ b/packages/console/app/src/lib/referral-invite.ts @@ -1,8 +1,10 @@ +import { Referral } from "@opencode-ai/console-core/referral.js" + const INVITE_COOKIE = "opencode.go.invite" const INVITE_MAX_AGE = 60 * 60 * 24 * 30 export function normalizeInviteCode(code?: string | null) { - return code?.toUpperCase().replace(/[^A-Z0-9]/g, "").slice(0, 10) + return Referral.normalizeCode(code) } export function inviteCookie(code: string) { diff --git a/packages/console/app/src/routes/workspace-picker.css b/packages/console/app/src/routes/workspace-picker.css index e566e9665e..df7552fb75 100644 --- a/packages/console/app/src/routes/workspace-picker.css +++ b/packages/console/app/src/routes/workspace-picker.css @@ -39,56 +39,6 @@ [data-component="workspace-create-modal"] { width: 100%; - button { - padding: var(--space-3) var(--space-4); - 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-sans); - font-weight: 500; - cursor: pointer; - transition: all 0.15s ease; - - &:hover:not(:disabled) { - background-color: var(--color-surface-hover); - border-color: var(--color-accent); - } - - &:active { - transform: translateY(1px); - } - - &:disabled { - opacity: 0.5; - transform: none; - } - - &[data-color="primary"] { - background-color: var(--color-primary); - border-color: var(--color-primary); - color: var(--color-primary-text); - - &:hover:not(:disabled) { - background-color: var(--color-primary-hover); - border-color: var(--color-primary-hover); - } - } - - &[data-color="ghost"] { - background-color: transparent; - border-color: transparent; - color: var(--color-text-muted); - - &:hover:not(:disabled) { - background-color: var(--color-surface-hover); - border-color: var(--color-border); - color: var(--color-text); - } - } - } - [data-slot="create-form"] { width: 100%; } diff --git a/packages/console/app/src/routes/workspace-picker.tsx b/packages/console/app/src/routes/workspace-picker.tsx index 16d0b8197b..83f5582ebe 100644 --- a/packages/console/app/src/routes/workspace-picker.tsx +++ b/packages/console/app/src/routes/workspace-picker.tsx @@ -1,6 +1,5 @@ import { query, useParams, action, createAsync, redirect, useSubmission } from "@solidjs/router" -import { For, createEffect } from "solid-js" -import { createStore } from "solid-js/store" +import { For, createEffect, createSignal } from "solid-js" import { withActor } from "~/context/auth.withActor" import { Actor } from "@opencode-ai/console-core/actor.js" import { and, Database, eq, isNull } from "@opencode-ai/console-core/drizzle/index.js" @@ -51,9 +50,7 @@ export function WorkspacePicker() { const i18n = useI18n() const workspaces = createAsync(() => getWorkspaces()) const submission = useSubmission(createWorkspace) - const [store, setStore] = createStore({ - showForm: false, - }) + const [showForm, setShowForm] = createSignal(false) let inputRef: HTMLInputElement | undefined const currentWorkspace = () => { @@ -61,12 +58,8 @@ export function WorkspacePicker() { return ws ? ws.name : i18n.t("workspace.select") } - const handleWorkspaceNew = () => { - setStore("showForm", true) - } - createEffect(() => { - if (store.showForm && inputRef) { + if (showForm() && inputRef) { setTimeout(() => inputRef?.focus(), 0) } }) @@ -79,7 +72,7 @@ export function WorkspacePicker() { // Reset signals when workspace ID changes createEffect(() => { params.id - setStore("showForm", false) + setShowForm(false) }) return ( @@ -92,12 +85,12 @@ export function WorkspacePicker() { )} - - setStore("showForm", false)} title={i18n.t("workspace.modal.title")}> + setShowForm(false)} title={i18n.t("workspace.modal.title")}>
@@ -110,7 +103,7 @@ export function WorkspacePicker() { required />
-
{i18n.t("workspace.black.subscription.resetsIn")}{" "} - {formatResetTime(sub().rollingUsage.resetInSec, i18n)} + {formatResetTime(sub().rollingUsage.resetInSec, i18n, blackResetTimeKeys)}
@@ -222,7 +209,7 @@ export function BlackSection() {
{i18n.t("workspace.black.subscription.resetsIn")}{" "} - {formatResetTime(sub().weeklyUsage.resetInSec, i18n)} + {formatResetTime(sub().weeklyUsage.resetInSec, i18n, blackResetTimeKeys)}
diff --git a/packages/console/app/src/routes/workspace/[id]/go/lite-section.module.css b/packages/console/app/src/routes/workspace/[id]/go/lite-section.module.css index c239e0461a..0904f8b4aa 100644 --- a/packages/console/app/src/routes/workspace/[id]/go/lite-section.module.css +++ b/packages/console/app/src/routes/workspace/[id]/go/lite-section.module.css @@ -215,45 +215,6 @@ } .paymentMethodModal { - button { - padding: var(--space-3) var(--space-4); - 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-sans); - font-weight: 500; - cursor: pointer; - transition: all 0.15s ease; - - &:hover:not(:disabled) { - background-color: var(--color-surface-hover); - border-color: var(--color-accent); - } - - &:active { - transform: translateY(1px); - } - - &:disabled { - opacity: 0.5; - transform: none; - } - - &[data-color="ghost"] { - background-color: transparent; - border-color: transparent; - color: var(--color-text-muted); - - &:hover:not(:disabled) { - background-color: var(--color-surface-hover); - border-color: var(--color-border); - color: var(--color-text); - } - } - } - [data-slot="modal-actions"] { display: flex; gap: var(--space-3); diff --git a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx index 054752a7a9..04d45ce258 100644 --- a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx @@ -14,6 +14,7 @@ import styles from "./lite-section.module.css" import { useI18n } from "~/context/i18n" import { useLanguage } from "~/context/language" import { formError } from "~/lib/form-error" +import { formatResetTime, liteResetTimeKeys } from "~/lib/format-reset-time" import { IconAlipay, IconUpi } from "~/component/icon" @@ -67,20 +68,6 @@ export const queryLiteSubscription = query(async (workspaceID: string) => { }, workspaceID) }, "lite.subscription.get") -export function formatResetTime(seconds: number, i18n: ReturnType) { - const days = Math.floor(seconds / 86400) - if (days >= 1) { - const hours = Math.floor((seconds % 86400) / 3600) - return `${days} ${days === 1 ? i18n.t("workspace.lite.time.day") : i18n.t("workspace.lite.time.days")} ${hours} ${hours === 1 ? i18n.t("workspace.lite.time.hour") : i18n.t("workspace.lite.time.hours")}` - } - const hours = Math.floor(seconds / 3600) - const minutes = Math.floor((seconds % 3600) / 60) - if (hours >= 1) - return `${hours} ${hours === 1 ? i18n.t("workspace.lite.time.hour") : i18n.t("workspace.lite.time.hours")} ${minutes} ${minutes === 1 ? i18n.t("workspace.lite.time.minute") : i18n.t("workspace.lite.time.minutes")}` - if (minutes === 0) return i18n.t("workspace.lite.time.fewSeconds") - return `${minutes} ${minutes === 1 ? i18n.t("workspace.lite.time.minute") : i18n.t("workspace.lite.time.minutes")}` -} - const createLiteCheckoutUrl = action( async (workspaceID: string, successUrl: string, cancelUrl: string, method?: "alipay" | "upi") => { "use server" @@ -140,6 +127,25 @@ const setLiteUseBalance = action(async (form: FormData) => { ) }, "setLiteUseBalance") +function LiteUsageItem(props: { label: string; usage: { usagePercent: number; resetInSec: number } }) { + const i18n = useI18n() + + return ( +
+
+ {props.label} + {props.usage.usagePercent}% +
+
+
+
+ + {i18n.t("workspace.lite.subscription.resetsIn")} {formatResetTime(props.usage.resetInSec, i18n, liteResetTimeKeys)} + +
+ ) +} + export function LiteSection() { const params = useParams() const i18n = useI18n() @@ -207,44 +213,9 @@ export function LiteSection() { .
-
-
- {i18n.t("workspace.lite.subscription.rollingUsage")} - {sub().rollingUsage.usagePercent}% -
-
-
-
- - {i18n.t("workspace.lite.subscription.resetsIn")}{" "} - {formatResetTime(sub().rollingUsage.resetInSec, i18n)} - -
-
-
- {i18n.t("workspace.lite.subscription.weeklyUsage")} - {sub().weeklyUsage.usagePercent}% -
-
-
-
- - {i18n.t("workspace.lite.subscription.resetsIn")} {formatResetTime(sub().weeklyUsage.resetInSec, i18n)} - -
-
-
- {i18n.t("workspace.lite.subscription.monthlyUsage")} - {sub().monthlyUsage.usagePercent}% -
-
-
-
- - {i18n.t("workspace.lite.subscription.resetsIn")}{" "} - {formatResetTime(sub().monthlyUsage.resetInSec, i18n)} - -
+ + +

{i18n.t("workspace.lite.subscription.useBalance")}

@@ -330,7 +301,7 @@ export function LiteSection() { onClose={() => setStore("showModal", false)} title={i18n.t("workspace.lite.promo.selectMethod")} > -
+