summaryrefslogtreecommitdiffhomepage
path: root/packages/console/app/src/routes
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2026-03-11 23:24:38 -0400
committerDax Raad <[email protected]>2026-03-11 23:24:38 -0400
commitb1ef501207aa9d2c7f7ee70cbd1d9fde360be219 (patch)
tree67a74164219fd8adff77fdd549934424333e5bd2 /packages/console/app/src/routes
parent9fb12a906eb8b1ef5f346623d11bd86689f926b1 (diff)
parentfafbc29316e7240b348db9e91e7a8e97cd803aef (diff)
downloadopencode-b1ef501207aa9d2c7f7ee70cbd1d9fde360be219.tar.gz
opencode-b1ef501207aa9d2c7f7ee70cbd1d9fde360be219.zip
Merge remote-tracking branch 'origin/dev' into dev
Diffstat (limited to 'packages/console/app/src/routes')
-rw-r--r--packages/console/app/src/routes/go/index.css13
-rw-r--r--packages/console/app/src/routes/go/index.tsx13
-rw-r--r--packages/console/app/src/routes/workspace/[id].tsx12
-rw-r--r--packages/console/app/src/routes/workspace/[id]/billing/billing-section.tsx8
-rw-r--r--packages/console/app/src/routes/workspace/[id]/billing/index.tsx4
-rw-r--r--packages/console/app/src/routes/workspace/[id]/go/index.tsx30
-rw-r--r--packages/console/app/src/routes/workspace/[id]/go/lite-section.module.css (renamed from packages/console/app/src/routes/workspace/[id]/billing/lite-section.module.css)5
-rw-r--r--packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx (renamed from packages/console/app/src/routes/workspace/[id]/billing/lite-section.tsx)36
-rw-r--r--packages/console/app/src/routes/workspace/[id]/index.tsx12
-rw-r--r--packages/console/app/src/routes/workspace/[id]/usage/graph-section.module.css (renamed from packages/console/app/src/routes/workspace/[id]/graph-section.module.css)0
-rw-r--r--packages/console/app/src/routes/workspace/[id]/usage/graph-section.tsx (renamed from packages/console/app/src/routes/workspace/[id]/graph-section.tsx)0
-rw-r--r--packages/console/app/src/routes/workspace/[id]/usage/index.tsx21
-rw-r--r--packages/console/app/src/routes/workspace/[id]/usage/usage-section.module.css (renamed from packages/console/app/src/routes/workspace/[id]/usage-section.module.css)0
-rw-r--r--packages/console/app/src/routes/workspace/[id]/usage/usage-section.tsx (renamed from packages/console/app/src/routes/workspace/[id]/usage-section.tsx)2
-rw-r--r--packages/console/app/src/routes/zen/index.tsx3
15 files changed, 126 insertions, 33 deletions
diff --git a/packages/console/app/src/routes/go/index.css b/packages/console/app/src/routes/go/index.css
index d3239d482..c73e6f0d0 100644
--- a/packages/console/app/src/routes/go/index.css
+++ b/packages/console/app/src/routes/go/index.css
@@ -368,7 +368,18 @@ body {
text-decoration: none;
[data-slot="cta-price"] {
- opacity: 0.6;
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ }
+
+ [data-slot="cta-price-old"] {
+ opacity: 0.45;
+ text-decoration: line-through;
+ }
+
+ [data-slot="cta-price-new"] {
+ opacity: 1;
}
svg {
diff --git a/packages/console/app/src/routes/go/index.tsx b/packages/console/app/src/routes/go/index.tsx
index 842256ca3..c9a1424bb 100644
--- a/packages/console/app/src/routes/go/index.tsx
+++ b/packages/console/app/src/routes/go/index.tsx
@@ -62,7 +62,7 @@ function LimitsGraph(props: { href: string }) {
const rmax = Math.max(1, ...models.map((m) => ratio(m.req)))
const log = (n: number) => Math.log10(Math.max(n, 1))
const base = 24
- const p = 2.2
+ const p = 1.8
const x = (r: number) => left + base + Math.pow(log(r) / log(rmax), p) * (plot - base)
const start = (x(1) / w) * 100
@@ -205,7 +205,7 @@ function LimitsGraph(props: { href: string }) {
export default function Home() {
const workspaceID = createAsync(() => checkLoggedIn())
- const subscribeUrl = createMemo(() => (workspaceID() ? `/workspace/${workspaceID()}/billing` : "/auth"))
+ const subscribeUrl = createMemo(() => (workspaceID() ? `/workspace/${workspaceID()}/go` : "/auth"))
const i18n = useI18n()
const language = useLanguage()
return (
@@ -320,7 +320,14 @@ export default function Home() {
>
{(part) => {
if (part === "{{text}}") return <span>{i18n.t("go.cta.text")}</span>
- if (part === "{{price}}") return <span data-slot="cta-price">{i18n.t("go.cta.price")}</span>
+ if (part === "{{price}}") {
+ return (
+ <span data-slot="cta-price">
+ <span data-slot="cta-price-old">{i18n.t("go.cta.price")}</span>
+ <span data-slot="cta-price-new">{i18n.t("go.cta.promo")}</span>
+ </span>
+ )
+ }
return part
}}
</For>
diff --git a/packages/console/app/src/routes/workspace/[id].tsx b/packages/console/app/src/routes/workspace/[id].tsx
index fde074e5e..7a8e1616f 100644
--- a/packages/console/app/src/routes/workspace/[id].tsx
+++ b/packages/console/app/src/routes/workspace/[id].tsx
@@ -19,6 +19,12 @@ export default function WorkspaceLayout(props: RouteSectionProps) {
<A href={`/workspace/${params.id}`} end activeClass="active" data-nav-button>
{i18n.t("workspace.nav.zen")}
</A>
+ <A href={`/workspace/${params.id}/go`} activeClass="active" data-nav-button>
+ {i18n.t("workspace.nav.go")}
+ </A>
+ <A href={`/workspace/${params.id}/usage`} activeClass="active" data-nav-button>
+ {i18n.t("workspace.nav.usage")}
+ </A>
<A href={`/workspace/${params.id}/keys`} activeClass="active" data-nav-button>
{i18n.t("workspace.nav.apiKeys")}
</A>
@@ -41,6 +47,12 @@ export default function WorkspaceLayout(props: RouteSectionProps) {
<A href={`/workspace/${params.id}`} end activeClass="active" data-nav-button>
{i18n.t("workspace.nav.zen")}
</A>
+ <A href={`/workspace/${params.id}/go`} activeClass="active" data-nav-button>
+ {i18n.t("workspace.nav.go")}
+ </A>
+ <A href={`/workspace/${params.id}/usage`} activeClass="active" data-nav-button>
+ {i18n.t("workspace.nav.usage")}
+ </A>
<A href={`/workspace/${params.id}/keys`} activeClass="active" data-nav-button>
{i18n.t("workspace.nav.apiKeys")}
</A>
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/billing-section.tsx b/packages/console/app/src/routes/workspace/[id]/billing/billing-section.tsx
index db89a1c9e..d966f38d3 100644
--- a/packages/console/app/src/routes/workspace/[id]/billing/billing-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/billing/billing-section.tsx
@@ -3,7 +3,7 @@ import { createMemo, Match, Show, Switch, createEffect } from "solid-js"
import { createStore } from "solid-js/store"
import { Billing } from "@opencode-ai/console-core/billing.js"
import { withActor } from "~/context/auth.withActor"
-import { IconCreditCard, IconStripe } from "~/component/icon"
+import { IconAlipay, IconCreditCard, IconStripe } from "~/component/icon"
import styles from "./billing-section.module.css"
import { createCheckoutUrl, formatBalance, queryBillingInfo } from "../../common"
import { useI18n } from "~/context/i18n"
@@ -205,6 +205,9 @@ export function BillingSection() {
<Match when={billingInfo()?.paymentMethodType === "link"}>
<IconStripe style={{ width: "24px", height: "24px" }} />
</Match>
+ <Match when={billingInfo()?.paymentMethodType === "alipay"}>
+ <IconAlipay style={{ width: "24px", height: "24px" }} />
+ </Match>
</Switch>
</div>
<div data-slot="card-details">
@@ -218,6 +221,9 @@ export function BillingSection() {
<Match when={billingInfo()?.paymentMethodType === "link"}>
<span data-slot="type">{i18n.t("workspace.billing.linkedToStripe")}</span>
</Match>
+ <Match when={billingInfo()?.paymentMethodType === "alipay"}>
+ <span data-slot="type">{i18n.t("workspace.billing.alipay")}</span>
+ </Match>
</Switch>
</div>
<button
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 e039a09ef..4a7dc2488 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,6 @@ import { BillingSection } from "./billing-section"
import { ReloadSection } from "./reload-section"
import { PaymentSection } from "./payment-section"
import { BlackSection } from "./black-section"
-import { LiteSection } from "./lite-section"
import { createMemo, Show } from "solid-js"
import { createAsync, useParams } from "@solidjs/router"
import { queryBillingInfo, querySessionInfo } from "../../common"
@@ -21,9 +20,6 @@ export default function () {
<Show when={isBlack()}>
<BlackSection />
</Show>
- <Show when={!isBlack()}>
- <LiteSection />
- </Show>
<BillingSection />
<Show when={billingInfo()?.customerID}>
<ReloadSection />
diff --git a/packages/console/app/src/routes/workspace/[id]/go/index.tsx b/packages/console/app/src/routes/workspace/[id]/go/index.tsx
new file mode 100644
index 000000000..fb89e3c70
--- /dev/null
+++ b/packages/console/app/src/routes/workspace/[id]/go/index.tsx
@@ -0,0 +1,30 @@
+import { IconGo } from "~/component/icon"
+import { useI18n } from "~/context/i18n"
+import { useLanguage } from "~/context/language"
+import { LiteSection } from "./lite-section"
+
+export default function () {
+ const i18n = useI18n()
+ const language = useLanguage()
+
+ return (
+ <div data-page="workspace-[id]">
+ <section data-component="header-section">
+ <IconGo />
+ <p>
+ <span>
+ {i18n.t("workspace.lite.banner.beforeLink")}{" "}
+ <a target="_blank" href={language.route("/docs/go")}>
+ {i18n.t("common.learnMore")}
+ </a>
+ .
+ </span>
+ </p>
+ </section>
+
+ <div data-slot="sections">
+ <LiteSection />
+ </div>
+ </div>
+ )
+}
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/lite-section.module.css b/packages/console/app/src/routes/workspace/[id]/go/lite-section.module.css
index 76d9bcfb0..a760753d0 100644
--- a/packages/console/app/src/routes/workspace/[id]/billing/lite-section.module.css
+++ b/packages/console/app/src/routes/workspace/[id]/go/lite-section.module.css
@@ -167,6 +167,11 @@
color: var(--color-text-secondary);
line-height: 1.5;
margin-top: var(--space-2);
+
+ strong {
+ color: var(--color-text);
+ font-weight: 600;
+ }
}
[data-slot="promo-models-title"] {
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/lite-section.tsx b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx
index f67775d79..ede870921 100644
--- a/packages/console/app/src/routes/workspace/[id]/billing/lite-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx
@@ -1,6 +1,6 @@
import { action, useParams, useAction, useSubmission, json, query, createAsync } from "@solidjs/router"
import { createStore } from "solid-js/store"
-import { Show } from "solid-js"
+import { createMemo, For, 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"
@@ -138,6 +138,8 @@ export function LiteSection() {
const params = useParams()
const i18n = useI18n()
const language = useLanguage()
+ const billingInfo = createAsync(() => queryBillingInfo(params.id!))
+ const isBlack = createMemo(() => billingInfo()?.subscriptionID || billingInfo()?.timeSubscriptionBooked)
const lite = createAsync(() => queryLiteSubscription(params.id!))
const sessionAction = useAction(createSessionUrl)
const sessionSubmission = useSubmission(createSessionUrl)
@@ -166,11 +168,15 @@ export function LiteSection() {
return (
<>
- <Show when={lite() && lite()!.mine && lite()!}>
+ <Show when={isBlack()}>
+ <section class={styles.root}>
+ <p data-slot="other-message">{i18n.t("workspace.lite.black.message")}</p>
+ </section>
+ </Show>
+ <Show when={!isBlack() && 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
@@ -248,20 +254,26 @@ export function LiteSection() {
</section>
)}
</Show>
- <Show when={lite() && !lite()!.mine}>
+ <Show when={!isBlack() && 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}>
+ <Show when={!isBlack() && 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>
+ <p data-slot="promo-description">
+ <For
+ each={i18n
+ .t("workspace.lite.promo.description")
+ .split(/(\{\{price\}\})/g)
+ .filter(Boolean)}
+ >
+ {(part) => {
+ if (part === "{{price}}") return <strong>{i18n.t("workspace.lite.promo.price")}</strong>
+ return part
+ }}
+ </For>
+ </p>
<h3 data-slot="promo-models-title">{i18n.t("workspace.lite.promo.modelsTitle")}</h3>
<ul data-slot="promo-models">
<li>Kimi K2.5</li>
diff --git a/packages/console/app/src/routes/workspace/[id]/index.tsx b/packages/console/app/src/routes/workspace/[id]/index.tsx
index c91cfd2bc..b5c597b12 100644
--- a/packages/console/app/src/routes/workspace/[id]/index.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/index.tsx
@@ -1,12 +1,10 @@
-import { Match, Show, Switch, createMemo } from "solid-js"
+import { Show, createMemo } from "solid-js"
import { createStore } from "solid-js/store"
import { createAsync, useParams, useAction, useSubmission } from "@solidjs/router"
import { NewUserSection } from "./new-user-section"
-import { UsageSection } from "./usage-section"
import { ModelSection } from "./model-section"
import { ProviderSection } from "./provider-section"
-import { GraphSection } from "./graph-section"
-import { IconLogo } from "~/component/icon"
+import { IconZen } from "~/component/icon"
import { querySessionInfo, queryBillingInfo, createCheckoutUrl, formatBalance } from "../common"
import { useI18n } from "~/context/i18n"
import { useLanguage } from "~/context/language"
@@ -36,7 +34,7 @@ export default function () {
return (
<div data-page="workspace-[id]">
<section data-component="header-section">
- <IconLogo />
+ <IconZen />
<p>
<span>
{i18n.t("workspace.home.banner.beforeLink")}{" "}
@@ -73,14 +71,10 @@ export default function () {
<div data-slot="sections">
<NewUserSection />
- <Show when={userInfo()?.isAdmin}>
- <GraphSection />
- </Show>
<ModelSection />
<Show when={userInfo()?.isAdmin}>
<ProviderSection />
</Show>
- <UsageSection />
</div>
</div>
)
diff --git a/packages/console/app/src/routes/workspace/[id]/graph-section.module.css b/packages/console/app/src/routes/workspace/[id]/usage/graph-section.module.css
index 24b85be74..24b85be74 100644
--- a/packages/console/app/src/routes/workspace/[id]/graph-section.module.css
+++ b/packages/console/app/src/routes/workspace/[id]/usage/graph-section.module.css
diff --git a/packages/console/app/src/routes/workspace/[id]/graph-section.tsx b/packages/console/app/src/routes/workspace/[id]/usage/graph-section.tsx
index bb4b4f4cf..bb4b4f4cf 100644
--- a/packages/console/app/src/routes/workspace/[id]/graph-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/usage/graph-section.tsx
diff --git a/packages/console/app/src/routes/workspace/[id]/usage/index.tsx b/packages/console/app/src/routes/workspace/[id]/usage/index.tsx
new file mode 100644
index 000000000..3a9c8db29
--- /dev/null
+++ b/packages/console/app/src/routes/workspace/[id]/usage/index.tsx
@@ -0,0 +1,21 @@
+import { Show } from "solid-js"
+import { createAsync, useParams } from "@solidjs/router"
+import { GraphSection } from "./graph-section"
+import { UsageSection } from "./usage-section"
+import { querySessionInfo } from "../../common"
+
+export default function () {
+ const params = useParams()
+ const user = createAsync(() => querySessionInfo(params.id!))
+
+ return (
+ <div data-page="workspace-[id]">
+ <div data-slot="sections">
+ <Show when={user()?.isAdmin}>
+ <GraphSection />
+ </Show>
+ <UsageSection />
+ </div>
+ </div>
+ )
+}
diff --git a/packages/console/app/src/routes/workspace/[id]/usage-section.module.css b/packages/console/app/src/routes/workspace/[id]/usage/usage-section.module.css
index 00232de88..00232de88 100644
--- a/packages/console/app/src/routes/workspace/[id]/usage-section.module.css
+++ b/packages/console/app/src/routes/workspace/[id]/usage/usage-section.module.css
diff --git a/packages/console/app/src/routes/workspace/[id]/usage-section.tsx b/packages/console/app/src/routes/workspace/[id]/usage/usage-section.tsx
index a20a5bf0d..2cf8ef850 100644
--- a/packages/console/app/src/routes/workspace/[id]/usage-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/usage/usage-section.tsx
@@ -1,7 +1,7 @@
import { Billing } from "@opencode-ai/console-core/billing.js"
import { createAsync, query, useParams } from "@solidjs/router"
import { createMemo, For, Show, Switch, Match, createEffect, createSignal } from "solid-js"
-import { formatDateUTC, formatDateForTable } from "../common"
+import { formatDateUTC, formatDateForTable } from "../../common"
import { withActor } from "~/context/auth.withActor"
import { IconChevronLeft, IconChevronRight, IconBreakdown } from "~/component/icon"
import styles from "./usage-section.module.css"
diff --git a/packages/console/app/src/routes/zen/index.tsx b/packages/console/app/src/routes/zen/index.tsx
index 5b5581b53..62e8f5d37 100644
--- a/packages/console/app/src/routes/zen/index.tsx
+++ b/packages/console/app/src/routes/zen/index.tsx
@@ -24,8 +24,7 @@ import { LocaleLinks } from "~/component/locale-links"
const checkLoggedIn = query(async () => {
"use server"
- const workspaceID = await getLastSeenWorkspaceID().catch(() => {})
- if (workspaceID) throw redirect(`/workspace/${workspaceID}`)
+ return await getLastSeenWorkspaceID().catch(() => {})
}, "checkLoggedIn.get")
export default function Home() {