diff options
| author | Dax Raad <[email protected]> | 2026-03-11 23:24:38 -0400 |
|---|---|---|
| committer | Dax Raad <[email protected]> | 2026-03-11 23:24:38 -0400 |
| commit | b1ef501207aa9d2c7f7ee70cbd1d9fde360be219 (patch) | |
| tree | 67a74164219fd8adff77fdd549934424333e5bd2 /packages/console/app/src/routes | |
| parent | 9fb12a906eb8b1ef5f346623d11bd86689f926b1 (diff) | |
| parent | fafbc29316e7240b348db9e91e7a8e97cd803aef (diff) | |
| download | opencode-b1ef501207aa9d2c7f7ee70cbd1d9fde360be219.tar.gz opencode-b1ef501207aa9d2c7f7ee70cbd1d9fde360be219.zip | |
Merge remote-tracking branch 'origin/dev' into dev
Diffstat (limited to 'packages/console/app/src/routes')
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() { |
