diff options
Diffstat (limited to 'packages/console/app/src/routes/workspace')
7 files changed, 506 insertions, 34 deletions
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx b/packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx index 5326306e2..b8f089864 100644 --- a/packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx @@ -90,7 +90,7 @@ const enroll = action(async (workspaceID: string) => { "use server" return json( await withActor(async () => { - await Billing.subscribe({ seats: 1 }) + await Billing.subscribeBlack({ seats: 1 }) return { error: undefined } }, workspaceID).catch((e) => ({ error: e.message as string })), { revalidate: [queryBillingInfo.key, querySubscription.key] }, 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 a252a0234..9fbdad2ef 100644 --- a/packages/console/app/src/routes/workspace/[id]/billing/index.tsx +++ b/packages/console/app/src/routes/workspace/[id]/billing/index.tsx @@ -3,7 +3,8 @@ import { BillingSection } from "./billing-section" import { ReloadSection } from "./reload-section" import { PaymentSection } from "./payment-section" import { BlackSection } from "./black-section" -import { Show } from "solid-js" +import { LiteSection } from "./lite-section" +import { createMemo, Show } from "solid-js" import { createAsync, useParams } from "@solidjs/router" import { queryBillingInfo, querySessionInfo } from "../../common" @@ -11,14 +12,18 @@ export default function () { const params = useParams() const sessionInfo = createAsync(() => querySessionInfo(params.id!)) const billingInfo = createAsync(() => queryBillingInfo(params.id!)) + const isBlack = createMemo(() => billingInfo()?.subscriptionID || billingInfo()?.timeSubscriptionBooked) return ( <div data-page="workspace-[id]"> <div data-slot="sections"> <Show when={sessionInfo()?.isAdmin}> - <Show when={billingInfo()?.subscriptionID || billingInfo()?.timeSubscriptionBooked}> + <Show when={isBlack()}> <BlackSection /> </Show> + <Show when={!isBlack() && sessionInfo()?.isBeta}> + <LiteSection /> + </Show> <BillingSection /> <Show when={billingInfo()?.customerID}> <ReloadSection /> diff --git a/packages/console/app/src/routes/workspace/[id]/billing/lite-section.module.css b/packages/console/app/src/routes/workspace/[id]/billing/lite-section.module.css new file mode 100644 index 000000000..20662ab61 --- /dev/null +++ b/packages/console/app/src/routes/workspace/[id]/billing/lite-section.module.css @@ -0,0 +1,160 @@ +.root { + [data-slot="title-row"] { + display: flex; + justify-content: space-between; + align-items: center; + gap: var(--space-4); + } + + [data-slot="usage"] { + display: flex; + gap: var(--space-6); + margin-top: var(--space-4); + + @media (max-width: 40rem) { + flex-direction: column; + gap: var(--space-4); + } + } + + [data-slot="usage-item"] { + flex: 1; + display: flex; + flex-direction: column; + gap: var(--space-2); + } + + [data-slot="usage-header"] { + display: flex; + justify-content: space-between; + align-items: baseline; + } + + [data-slot="usage-label"] { + font-size: var(--font-size-md); + font-weight: 500; + color: var(--color-text); + } + + [data-slot="usage-value"] { + font-size: var(--font-size-sm); + color: var(--color-text-muted); + } + + [data-slot="progress"] { + height: 8px; + background-color: var(--color-bg-surface); + border-radius: var(--border-radius-sm); + overflow: hidden; + } + + [data-slot="progress-bar"] { + height: 100%; + background-color: var(--color-accent); + border-radius: var(--border-radius-sm); + transition: width 0.3s ease; + } + + [data-slot="reset-time"] { + font-size: var(--font-size-sm); + color: var(--color-text-muted); + } + + [data-slot="setting-row"] { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-3); + margin-top: var(--space-4); + + p { + font-size: var(--font-size-sm); + line-height: 1.5; + color: var(--color-text-secondary); + margin: 0; + } + } + + [data-slot="toggle-label"] { + position: relative; + display: inline-block; + width: 2.5rem; + height: 1.5rem; + cursor: pointer; + flex-shrink: 0; + + input { + opacity: 0; + width: 0; + height: 0; + } + + span { + position: absolute; + inset: 0; + background-color: #ccc; + border: 1px solid #bbb; + border-radius: 1.5rem; + transition: all 0.3s ease; + cursor: pointer; + + &::before { + content: ""; + position: absolute; + top: 50%; + left: 0.125rem; + width: 1.25rem; + height: 1.25rem; + background-color: white; + border: 1px solid #ddd; + border-radius: 50%; + transform: translateY(-50%); + transition: all 0.3s ease; + } + } + + input:checked + span { + background-color: #21ad0e; + border-color: #148605; + + &::before { + transform: translateX(1rem) translateY(-50%); + } + } + + &:hover span { + box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2); + } + + input:checked:hover + span { + box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.3); + } + + &:has(input:disabled) { + cursor: not-allowed; + } + + input:disabled + span { + opacity: 0.5; + cursor: not-allowed; + } + } + + [data-slot="other-message"] { + font-size: var(--font-size-sm); + color: var(--color-text-muted); + line-height: 1.5; + } + + [data-slot="promo-description"] { + font-size: var(--font-size-sm); + color: var(--color-text-secondary); + line-height: 1.5; + margin-top: var(--space-2); + } + + [data-slot="subscribe-button"] { + align-self: flex-start; + margin-top: var(--space-4); + } +} diff --git a/packages/console/app/src/routes/workspace/[id]/billing/lite-section.tsx b/packages/console/app/src/routes/workspace/[id]/billing/lite-section.tsx new file mode 100644 index 000000000..c9192fdcf --- /dev/null +++ b/packages/console/app/src/routes/workspace/[id]/billing/lite-section.tsx @@ -0,0 +1,269 @@ +import { action, useParams, useAction, useSubmission, json, query, createAsync } from "@solidjs/router" +import { createStore } from "solid-js/store" +import { Show } from "solid-js" +import { Billing } from "@opencode-ai/console-core/billing.js" +import { Database, eq, and, isNull } from "@opencode-ai/console-core/drizzle/index.js" +import { BillingTable, LiteTable } from "@opencode-ai/console-core/schema/billing.sql.js" +import { Actor } from "@opencode-ai/console-core/actor.js" +import { Subscription } from "@opencode-ai/console-core/subscription.js" +import { LiteData } from "@opencode-ai/console-core/lite.js" +import { withActor } from "~/context/auth.withActor" +import { queryBillingInfo } from "../../common" +import styles from "./lite-section.module.css" +import { useI18n } from "~/context/i18n" + +const queryLiteSubscription = query(async (workspaceID: string) => { + "use server" + return withActor(async () => { + const row = await Database.use((tx) => + tx + .select({ + userID: LiteTable.userID, + rollingUsage: LiteTable.rollingUsage, + weeklyUsage: LiteTable.weeklyUsage, + monthlyUsage: LiteTable.monthlyUsage, + timeRollingUpdated: LiteTable.timeRollingUpdated, + timeWeeklyUpdated: LiteTable.timeWeeklyUpdated, + timeMonthlyUpdated: LiteTable.timeMonthlyUpdated, + timeCreated: LiteTable.timeCreated, + lite: BillingTable.lite, + }) + .from(BillingTable) + .innerJoin(LiteTable, eq(LiteTable.workspaceID, BillingTable.workspaceID)) + .where(and(eq(LiteTable.workspaceID, Actor.workspace()), isNull(LiteTable.timeDeleted))) + .then((r) => r[0]), + ) + if (!row) return null + + const limits = LiteData.getLimits() + const mine = row.userID === Actor.userID() + + return { + mine, + useBalance: row.lite?.useBalance ?? false, + rollingUsage: Subscription.analyzeRollingUsage({ + limit: limits.rollingLimit, + window: limits.rollingWindow, + usage: row.rollingUsage ?? 0, + timeUpdated: row.timeRollingUpdated ?? new Date(), + }), + weeklyUsage: Subscription.analyzeWeeklyUsage({ + limit: limits.weeklyLimit, + usage: row.weeklyUsage ?? 0, + timeUpdated: row.timeWeeklyUpdated ?? new Date(), + }), + monthlyUsage: Subscription.analyzeMonthlyUsage({ + limit: limits.monthlyLimit, + usage: row.monthlyUsage ?? 0, + timeUpdated: row.timeMonthlyUpdated ?? new Date(), + timeSubscribed: row.timeCreated, + }), + } + }, workspaceID) +}, "lite.subscription.get") + +function formatResetTime(seconds: number, i18n: ReturnType<typeof useI18n>) { + 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) => { + "use server" + return json( + await withActor( + () => + Billing.generateLiteCheckoutUrl({ successUrl, cancelUrl }) + .then((data) => ({ error: undefined, data })) + .catch((e) => ({ + error: e.message as string, + data: undefined, + })), + workspaceID, + ), + { revalidate: [queryBillingInfo.key, queryLiteSubscription.key] }, + ) +}, "liteCheckoutUrl") + +const createSessionUrl = action(async (workspaceID: string, returnUrl: string) => { + "use server" + return json( + await withActor( + () => + Billing.generateSessionUrl({ returnUrl }) + .then((data) => ({ error: undefined, data })) + .catch((e) => ({ + error: e.message as string, + data: undefined, + })), + workspaceID, + ), + { revalidate: [queryBillingInfo.key, queryLiteSubscription.key] }, + ) +}, "liteSessionUrl") + +const setLiteUseBalance = action(async (form: FormData) => { + "use server" + const workspaceID = form.get("workspaceID")?.toString() + if (!workspaceID) return { error: "Workspace ID is required" } + const useBalance = form.get("useBalance")?.toString() === "true" + + return json( + await withActor(async () => { + await Database.use((tx) => + tx + .update(BillingTable) + .set({ + lite: useBalance ? { useBalance: true } : {}, + }) + .where(eq(BillingTable.workspaceID, workspaceID)), + ) + return { error: undefined } + }, workspaceID).catch((e) => ({ error: e.message as string })), + { revalidate: [queryBillingInfo.key, queryLiteSubscription.key] }, + ) +}, "setLiteUseBalance") + +export function LiteSection() { + const params = useParams() + const i18n = useI18n() + const lite = createAsync(() => queryLiteSubscription(params.id!)) + const sessionAction = useAction(createSessionUrl) + const sessionSubmission = useSubmission(createSessionUrl) + const checkoutAction = useAction(createLiteCheckoutUrl) + const checkoutSubmission = useSubmission(createLiteCheckoutUrl) + const useBalanceSubmission = useSubmission(setLiteUseBalance) + const [store, setStore] = createStore({ + redirecting: false, + }) + + async function onClickSession() { + const result = await sessionAction(params.id!, window.location.href) + if (result.data) { + setStore("redirecting", true) + window.location.href = result.data + } + } + + async function onClickSubscribe() { + const result = await checkoutAction(params.id!, window.location.href, window.location.href) + if (result.data) { + setStore("redirecting", true) + window.location.href = result.data + } + } + + return ( + <> + <Show when={lite() && lite()!.mine && lite()!}> + {(sub) => ( + <section class={styles.root}> + <div data-slot="section-title"> + <h2>{i18n.t("workspace.lite.subscription.title")}</h2> + <div data-slot="title-row"> + <p>{i18n.t("workspace.lite.subscription.message")}</p> + <button + data-color="primary" + disabled={sessionSubmission.pending || store.redirecting} + onClick={onClickSession} + > + {sessionSubmission.pending || store.redirecting + ? i18n.t("workspace.lite.loading") + : i18n.t("workspace.lite.subscription.manage")} + </button> + </div> + </div> + <div data-slot="usage"> + <div data-slot="usage-item"> + <div data-slot="usage-header"> + <span data-slot="usage-label">{i18n.t("workspace.lite.subscription.rollingUsage")}</span> + <span data-slot="usage-value">{sub().rollingUsage.usagePercent}%</span> + </div> + <div data-slot="progress"> + <div data-slot="progress-bar" style={{ width: `${sub().rollingUsage.usagePercent}%` }} /> + </div> + <span data-slot="reset-time"> + {i18n.t("workspace.lite.subscription.resetsIn")}{" "} + {formatResetTime(sub().rollingUsage.resetInSec, i18n)} + </span> + </div> + <div data-slot="usage-item"> + <div data-slot="usage-header"> + <span data-slot="usage-label">{i18n.t("workspace.lite.subscription.weeklyUsage")}</span> + <span data-slot="usage-value">{sub().weeklyUsage.usagePercent}%</span> + </div> + <div data-slot="progress"> + <div data-slot="progress-bar" style={{ width: `${sub().weeklyUsage.usagePercent}%` }} /> + </div> + <span data-slot="reset-time"> + {i18n.t("workspace.lite.subscription.resetsIn")} {formatResetTime(sub().weeklyUsage.resetInSec, i18n)} + </span> + </div> + <div data-slot="usage-item"> + <div data-slot="usage-header"> + <span data-slot="usage-label">{i18n.t("workspace.lite.subscription.monthlyUsage")}</span> + <span data-slot="usage-value">{sub().monthlyUsage.usagePercent}%</span> + </div> + <div data-slot="progress"> + <div data-slot="progress-bar" style={{ width: `${sub().monthlyUsage.usagePercent}%` }} /> + </div> + <span data-slot="reset-time"> + {i18n.t("workspace.lite.subscription.resetsIn")}{" "} + {formatResetTime(sub().monthlyUsage.resetInSec, i18n)} + </span> + </div> + </div> + <form action={setLiteUseBalance} method="post" data-slot="setting-row"> + <p>{i18n.t("workspace.lite.subscription.useBalance")}</p> + <input type="hidden" name="workspaceID" value={params.id} /> + <input type="hidden" name="useBalance" value={sub().useBalance ? "false" : "true"} /> + <label data-slot="toggle-label"> + <input + type="checkbox" + checked={sub().useBalance} + disabled={useBalanceSubmission.pending} + onChange={(e) => e.currentTarget.form?.requestSubmit()} + /> + <span></span> + </label> + </form> + </section> + )} + </Show> + <Show when={lite() && !lite()!.mine}> + <section class={styles.root}> + <div data-slot="section-title"> + <h2>{i18n.t("workspace.lite.other.title")}</h2> + </div> + <p data-slot="other-message">{i18n.t("workspace.lite.other.message")}</p> + </section> + </Show> + <Show when={lite() === null}> + <section class={styles.root}> + <div data-slot="section-title"> + <h2>{i18n.t("workspace.lite.promo.title")}</h2> + </div> + <p data-slot="promo-description">{i18n.t("workspace.lite.promo.description")}</p> + <button + data-slot="subscribe-button" + data-color="primary" + disabled={checkoutSubmission.pending || store.redirecting} + onClick={onClickSubscribe} + > + {checkoutSubmission.pending || store.redirecting + ? i18n.t("workspace.lite.promo.subscribing") + : i18n.t("workspace.lite.promo.subscribe")} + </button> + </section> + </Show> + </> + ) +} diff --git a/packages/console/app/src/routes/workspace/[id]/graph-section.tsx b/packages/console/app/src/routes/workspace/[id]/graph-section.tsx index f26c7291d..56a31cdd0 100644 --- a/packages/console/app/src/routes/workspace/[id]/graph-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/graph-section.tsx @@ -36,7 +36,7 @@ async function getCosts(workspaceID: string, year: number, month: number) { model: UsageTable.model, totalCost: sum(UsageTable.cost), keyId: UsageTable.keyID, - subscription: sql<boolean>`COALESCE(JSON_EXTRACT(${UsageTable.enrichment}, '$.plan') = 'sub', false)`, + plan: sql<string | null>`JSON_EXTRACT(${UsageTable.enrichment}, '$.plan')`, }) .from(UsageTable) .where( @@ -50,13 +50,13 @@ async function getCosts(workspaceID: string, year: number, month: number) { sql`DATE(${UsageTable.timeCreated})`, UsageTable.model, UsageTable.keyID, - sql`COALESCE(JSON_EXTRACT(${UsageTable.enrichment}, '$.plan') = 'sub', false)`, + sql`JSON_EXTRACT(${UsageTable.enrichment}, '$.plan')`, ) .then((x) => x.map((r) => ({ ...r, totalCost: r.totalCost ? parseInt(r.totalCost) : 0, - subscription: Boolean(r.subscription), + plan: r.plan as "sub" | "lite" | "byok" | null, })), ), ) @@ -218,18 +218,21 @@ export function GraphSection() { const colorTextSecondary = styles.getPropertyValue("--color-text-secondary").trim() const colorBorder = styles.getPropertyValue("--color-border").trim() const subSuffix = ` (${i18n.t("workspace.cost.subscriptionShort")})` + const liteSuffix = ` (${i18n.t("workspace.cost.liteShort")})` + const dailyDataRegular = new Map<string, Map<string, number>>() const dailyDataSub = new Map<string, Map<string, number>>() - const dailyDataNonSub = new Map<string, Map<string, number>>() + const dailyDataLite = new Map<string, Map<string, number>>() for (const dateKey of dates) { + dailyDataRegular.set(dateKey, new Map()) dailyDataSub.set(dateKey, new Map()) - dailyDataNonSub.set(dateKey, new Map()) + dailyDataLite.set(dateKey, new Map()) } data.usage .filter((row) => (store.key ? row.keyId === store.key : true)) .forEach((row) => { - const targetMap = row.subscription ? dailyDataSub : dailyDataNonSub + const targetMap = row.plan === "sub" ? dailyDataSub : row.plan === "lite" ? dailyDataLite : dailyDataRegular const dayMap = targetMap.get(row.date) if (!dayMap) return dayMap.set(row.model, (dayMap.get(row.model) ?? 0) + row.totalCost) @@ -237,15 +240,15 @@ export function GraphSection() { const filteredModels = store.model === null ? getModels() : [store.model] - // Create datasets: non-subscription first, then subscription (with hatched pattern effect via opacity) + // Create datasets: regular first, then subscription, then lite (with visual distinction via opacity) const datasets = [ ...filteredModels - .filter((model) => dates.some((date) => (dailyDataNonSub.get(date)?.get(model) || 0) > 0)) + .filter((model) => dates.some((date) => (dailyDataRegular.get(date)?.get(model) || 0) > 0)) .map((model) => { const color = getModelColor(model) return { label: model, - data: dates.map((date) => (dailyDataNonSub.get(date)?.get(model) || 0) / 100_000_000), + data: dates.map((date) => (dailyDataRegular.get(date)?.get(model) || 0) / 100_000_000), backgroundColor: color, hoverBackgroundColor: color, borderWidth: 0, @@ -266,6 +269,21 @@ export function GraphSection() { stack: "subscription", } }), + ...filteredModels + .filter((model) => dates.some((date) => (dailyDataLite.get(date)?.get(model) || 0) > 0)) + .map((model) => { + const color = getModelColor(model) + return { + label: `${model}${liteSuffix}`, + data: dates.map((date) => (dailyDataLite.get(date)?.get(model) || 0) / 100_000_000), + backgroundColor: addOpacityToColor(color, 0.35), + hoverBackgroundColor: addOpacityToColor(color, 0.55), + borderWidth: 1, + borderColor: addOpacityToColor(color, 0.7), + borderDash: [4, 2], + stack: "lite", + } + }), ] return { @@ -347,9 +365,18 @@ export function GraphSection() { const meta = chart.getDatasetMeta(i) const label = dataset.label || "" const isSub = label.endsWith(subSuffix) - const model = isSub ? label.slice(0, -subSuffix.length) : label + const isLite = label.endsWith(liteSuffix) + const model = isSub + ? label.slice(0, -subSuffix.length) + : isLite + ? label.slice(0, -liteSuffix.length) + : label const baseColor = getModelColor(model) - const originalColor = isSub ? addOpacityToColor(baseColor, 0.5) : baseColor + const originalColor = isSub + ? addOpacityToColor(baseColor, 0.5) + : isLite + ? addOpacityToColor(baseColor, 0.35) + : baseColor const color = i === legendItem.datasetIndex ? originalColor : addOpacityToColor(baseColor, 0.15) meta.data.forEach((bar: any) => { bar.options.backgroundColor = color @@ -363,9 +390,18 @@ export function GraphSection() { const meta = chart.getDatasetMeta(i) const label = dataset.label || "" const isSub = label.endsWith(subSuffix) - const model = isSub ? label.slice(0, -subSuffix.length) : label + const isLite = label.endsWith(liteSuffix) + const model = isSub + ? label.slice(0, -subSuffix.length) + : isLite + ? label.slice(0, -liteSuffix.length) + : label const baseColor = getModelColor(model) - const color = isSub ? addOpacityToColor(baseColor, 0.5) : baseColor + const color = isSub + ? addOpacityToColor(baseColor, 0.5) + : isLite + ? addOpacityToColor(baseColor, 0.35) + : baseColor meta.data.forEach((bar: any) => { bar.options.backgroundColor = color }) diff --git a/packages/console/app/src/routes/workspace/[id]/usage-section.tsx b/packages/console/app/src/routes/workspace/[id]/usage-section.tsx index 3d6637610..a20a5bf0d 100644 --- a/packages/console/app/src/routes/workspace/[id]/usage-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/usage-section.tsx @@ -1,6 +1,6 @@ import { Billing } from "@opencode-ai/console-core/billing.js" import { createAsync, query, useParams } from "@solidjs/router" -import { createMemo, For, Show, createEffect, createSignal } from "solid-js" +import { createMemo, For, Show, Switch, Match, createEffect, createSignal } from "solid-js" import { formatDateUTC, formatDateForTable } from "../common" import { withActor } from "~/context/auth.withActor" import { IconChevronLeft, IconChevronRight, IconBreakdown } from "~/component/icon" @@ -175,23 +175,23 @@ export function UsageSection() { </div> </td> <td data-slot="usage-cost"> - <Show - when={usage.enrichment?.plan === "sub"} - fallback={ - <Show - when={usage.enrichment?.plan === "byok"} - fallback={<>${((usage.cost ?? 0) / 100000000).toFixed(4)}</>} - > - {i18n.t("workspace.usage.byok", { - amount: ((usage.cost ?? 0) / 100000000).toFixed(4), - })} - </Show> - } - > - {i18n.t("workspace.usage.subscription", { - amount: ((usage.cost ?? 0) / 100000000).toFixed(4), - })} - </Show> + <Switch fallback={<>${((usage.cost ?? 0) / 100000000).toFixed(4)}</>}> + <Match when={usage.enrichment?.plan === "sub"}> + {i18n.t("workspace.usage.subscription", { + amount: ((usage.cost ?? 0) / 100000000).toFixed(4), + })} + </Match> + <Match when={usage.enrichment?.plan === "lite"}> + {i18n.t("workspace.usage.lite", { + amount: ((usage.cost ?? 0) / 100000000).toFixed(4), + })} + </Match> + <Match when={usage.enrichment?.plan === "byok"}> + {i18n.t("workspace.usage.byok", { + amount: ((usage.cost ?? 0) / 100000000).toFixed(4), + })} + </Match> + </Switch> </td> <td data-slot="usage-session">{usage.sessionID?.slice(-8) ?? "-"}</td> </tr> diff --git a/packages/console/app/src/routes/workspace/common.tsx b/packages/console/app/src/routes/workspace/common.tsx index 5cbd67183..d41793dd9 100644 --- a/packages/console/app/src/routes/workspace/common.tsx +++ b/packages/console/app/src/routes/workspace/common.tsx @@ -115,6 +115,8 @@ export const queryBillingInfo = query(async (workspaceID: string) => { subscriptionPlan: billing.subscriptionPlan, timeSubscriptionBooked: billing.timeSubscriptionBooked, timeSubscriptionSelected: billing.timeSubscriptionSelected, + lite: billing.lite, + liteSubscriptionID: billing.liteSubscriptionID, } }, workspaceID) }, "billing.get") |
