summaryrefslogtreecommitdiffhomepage
path: root/packages/console/app/src/routes/workspace
diff options
context:
space:
mode:
Diffstat (limited to 'packages/console/app/src/routes/workspace')
-rw-r--r--packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx103
-rw-r--r--packages/console/app/src/routes/workspace/[id]/billing/black-waitlist-section.module.css15
-rw-r--r--packages/console/app/src/routes/workspace/[id]/billing/black-waitlist-section.tsx57
-rw-r--r--packages/console/app/src/routes/workspace/[id]/billing/index.tsx6
-rw-r--r--packages/console/app/src/routes/workspace/[id]/index.tsx20
-rw-r--r--packages/console/app/src/routes/workspace/common.tsx1
6 files changed, 123 insertions, 79 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 98d4debbb..03c75387a 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
@@ -9,6 +9,7 @@ import { Black } from "@opencode-ai/console-core/black.js"
import { withActor } from "~/context/auth.withActor"
import { queryBillingInfo } from "../../common"
import styles from "./black-section.module.css"
+import waitlistStyles from "./black-waitlist-section.module.css"
const querySubscription = query(async (workspaceID: string) => {
"use server"
@@ -27,7 +28,7 @@ const querySubscription = query(async (workspaceID: string) => {
.where(and(eq(SubscriptionTable.workspaceID, Actor.workspace()), isNull(SubscriptionTable.timeDeleted)))
.then((r) => r[0]),
)
- if (!row.subscription) return null
+ if (!row?.subscription) return null
return {
plan: row.subscription.plan,
@@ -58,6 +59,37 @@ function formatResetTime(seconds: number) {
return `${minutes} ${minutes === 1 ? "minute" : "minutes"}`
}
+const cancelWaitlist = action(async (workspaceID: string) => {
+ "use server"
+ return json(
+ await withActor(async () => {
+ await Database.use((tx) =>
+ tx
+ .update(BillingTable)
+ .set({
+ subscriptionPlan: null,
+ timeSubscriptionBooked: null,
+ timeSubscriptionSelected: null,
+ })
+ .where(eq(BillingTable.workspaceID, workspaceID)),
+ )
+ return { error: undefined }
+ }, workspaceID).catch((e) => ({ error: e.message as string })),
+ { revalidate: [queryBillingInfo.key, querySubscription.key] },
+ )
+}, "cancelWaitlist")
+
+const enroll = action(async (workspaceID: string) => {
+ "use server"
+ return json(
+ await withActor(async () => {
+ await Billing.subscribe({ seats: 1 })
+ return { error: undefined }
+ }, workspaceID).catch((e) => ({ error: e.message as string })),
+ { revalidate: [queryBillingInfo.key, querySubscription.key] },
+ )
+}, "enroll")
+
const createSessionUrl = action(async (workspaceID: string, returnUrl: string) => {
"use server"
return json(
@@ -71,17 +103,24 @@ const createSessionUrl = action(async (workspaceID: string, returnUrl: string) =
})),
workspaceID,
),
- { revalidate: queryBillingInfo.key },
+ { revalidate: [queryBillingInfo.key, querySubscription.key] },
)
}, "sessionUrl")
export function BlackSection() {
const params = useParams()
+ const billing = createAsync(() => queryBillingInfo(params.id!))
+ const subscription = createAsync(() => querySubscription(params.id!))
const sessionAction = useAction(createSessionUrl)
const sessionSubmission = useSubmission(createSessionUrl)
- const subscription = createAsync(() => querySubscription(params.id!))
+ const cancelAction = useAction(cancelWaitlist)
+ const cancelSubmission = useSubmission(cancelWaitlist)
+ const enrollAction = useAction(enroll)
+ const enrollSubmission = useSubmission(enroll)
const [store, setStore] = createStore({
sessionRedirecting: false,
+ cancelled: false,
+ enrolled: false,
})
async function onClickSession() {
@@ -92,11 +131,25 @@ export function BlackSection() {
}
}
+ async function onClickCancel() {
+ const result = await cancelAction(params.id!)
+ if (!result.error) {
+ setStore("cancelled", true)
+ }
+ }
+
+ async function onClickEnroll() {
+ const result = await enrollAction(params.id!)
+ if (!result.error) {
+ setStore("enrolled", true)
+ }
+ }
+
return (
- <section class={styles.root}>
+ <>
<Show when={subscription()}>
{(sub) => (
- <>
+ <section class={styles.root}>
<div data-slot="section-title">
<h2>Subscription</h2>
<div data-slot="title-row">
@@ -132,9 +185,45 @@ export function BlackSection() {
<span data-slot="reset-time">Resets in {formatResetTime(sub().weeklyUsage.resetInSec)}</span>
</div>
</div>
- </>
+ </section>
)}
</Show>
- </section>
+ <Show when={billing()?.timeSubscriptionBooked}>
+ <section class={waitlistStyles.root}>
+ <div data-slot="section-title">
+ <h2>Waitlist</h2>
+ <div data-slot="title-row">
+ <p>
+ {billing()?.timeSubscriptionSelected
+ ? `We're ready to enroll you into the $${billing()?.subscriptionPlan} per month OpenCode Black plan.`
+ : `You are on the waitlist for the $${billing()?.subscriptionPlan} per month OpenCode Black plan.`}
+ </p>
+ <button
+ data-color="danger"
+ disabled={cancelSubmission.pending || store.cancelled}
+ onClick={onClickCancel}
+ >
+ {cancelSubmission.pending ? "Leaving..." : store.cancelled ? "Left" : "Leave Waitlist"}
+ </button>
+ </div>
+ </div>
+ <Show when={billing()?.timeSubscriptionSelected}>
+ <div data-slot="enroll-section">
+ <button
+ data-slot="enroll-button"
+ data-color="primary"
+ disabled={enrollSubmission.pending || store.enrolled}
+ onClick={onClickEnroll}
+ >
+ {enrollSubmission.pending ? "Enrolling..." : store.enrolled ? "Enrolled" : "Enroll"}
+ </button>
+ <p data-slot="enroll-note">
+ When you click Enroll, your subscription starts immediately and your card will be charged.
+ </p>
+ </div>
+ </Show>
+ </section>
+ </Show>
+ </>
)
}
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/black-waitlist-section.module.css b/packages/console/app/src/routes/workspace/[id]/billing/black-waitlist-section.module.css
index c189f0d64..685d62c93 100644
--- a/packages/console/app/src/routes/workspace/[id]/billing/black-waitlist-section.module.css
+++ b/packages/console/app/src/routes/workspace/[id]/billing/black-waitlist-section.module.css
@@ -5,4 +5,19 @@
align-items: center;
gap: var(--space-4);
}
+
+ [data-slot="enroll-section"] {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-3);
+ }
+
+ [data-slot="enroll-button"] {
+ align-self: flex-start;
+ }
+
+ [data-slot="enroll-note"] {
+ font-size: var(--font-size-sm);
+ color: var(--color-text-muted);
+ }
}
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/black-waitlist-section.tsx b/packages/console/app/src/routes/workspace/[id]/billing/black-waitlist-section.tsx
deleted file mode 100644
index 3ec9be395..000000000
--- a/packages/console/app/src/routes/workspace/[id]/billing/black-waitlist-section.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import { action, useParams, useAction, useSubmission, json, createAsync } from "@solidjs/router"
-import { createStore } from "solid-js/store"
-import { Database, eq } from "@opencode-ai/console-core/drizzle/index.js"
-import { BillingTable } from "@opencode-ai/console-core/schema/billing.sql.js"
-import { withActor } from "~/context/auth.withActor"
-import { queryBillingInfo } from "../../common"
-import styles from "./black-waitlist-section.module.css"
-
-const cancelWaitlist = action(async (workspaceID: string) => {
- "use server"
- return json(
- await withActor(async () => {
- await Database.use((tx) =>
- tx
- .update(BillingTable)
- .set({
- subscriptionPlan: null,
- timeSubscriptionBooked: null,
- })
- .where(eq(BillingTable.workspaceID, workspaceID)),
- )
- return { error: undefined }
- }, workspaceID).catch((e) => ({ error: e.message as string })),
- { revalidate: queryBillingInfo.key },
- )
-}, "cancelWaitlist")
-
-export function BlackWaitlistSection() {
- const params = useParams()
- const billingInfo = createAsync(() => queryBillingInfo(params.id!))
- const cancelAction = useAction(cancelWaitlist)
- const cancelSubmission = useSubmission(cancelWaitlist)
- const [store, setStore] = createStore({
- cancelled: false,
- })
-
- async function onClickCancel() {
- const result = await cancelAction(params.id!)
- if (!result.error) {
- setStore("cancelled", true)
- }
- }
-
- return (
- <section class={styles.root}>
- <div data-slot="section-title">
- <h2>Waitlist</h2>
- <div data-slot="title-row">
- <p>You are on the waitlist for the ${billingInfo()?.subscriptionPlan} per month OpenCode Black plan.</p>
- <button data-color="danger" disabled={cancelSubmission.pending || store.cancelled} onClick={onClickCancel}>
- {cancelSubmission.pending ? "Leaving..." : store.cancelled ? "Left" : "Leave Waitlist"}
- </button>
- </div>
- </div>
- </section>
- )
-}
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 7fe4092be..a252a0234 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 { BlackWaitlistSection } from "./black-waitlist-section"
import { Show } from "solid-js"
import { createAsync, useParams } from "@solidjs/router"
import { queryBillingInfo, querySessionInfo } from "../../common"
@@ -17,12 +16,9 @@ export default function () {
<div data-page="workspace-[id]">
<div data-slot="sections">
<Show when={sessionInfo()?.isAdmin}>
- <Show when={billingInfo()?.subscriptionID}>
+ <Show when={billingInfo()?.subscriptionID || billingInfo()?.timeSubscriptionBooked}>
<BlackSection />
</Show>
- <Show when={billingInfo()?.timeSubscriptionBooked}>
- <BlackWaitlistSection />
- </Show>
<BillingSection />
<Show when={billingInfo()?.customerID}>
<ReloadSection />
diff --git a/packages/console/app/src/routes/workspace/[id]/index.tsx b/packages/console/app/src/routes/workspace/[id]/index.tsx
index e25e09645..fe308dbb0 100644
--- a/packages/console/app/src/routes/workspace/[id]/index.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/index.tsx
@@ -1,4 +1,4 @@
-import { Show, createMemo } from "solid-js"
+import { Match, Show, Switch, createMemo } from "solid-js"
import { createStore } from "solid-js/store"
import { createAsync, useParams, useAction, useSubmission } from "@solidjs/router"
import { NewUserSection } from "./new-user-section"
@@ -43,9 +43,8 @@ export default function () {
</span>
<Show when={userInfo()?.isAdmin}>
<span data-slot="billing-info">
- <Show
- when={billingInfo()?.reload}
- fallback={
+ <Switch>
+ <Match when={!billingInfo()?.customerID}>
<button
data-color="primary"
data-size="sm"
@@ -54,12 +53,13 @@ export default function () {
>
{checkoutSubmission.pending || store.checkoutRedirecting ? "Loading..." : "Enable billing"}
</button>
- }
- >
- <span data-slot="balance">
- Current balance <b>${balance()}</b>
- </span>
- </Show>
+ </Match>
+ <Match when={!billingInfo()?.subscriptionID}>
+ <span data-slot="balance">
+ Current balance <b>${balance()}</b>
+ </span>
+ </Match>
+ </Switch>
</span>
</Show>
</p>
diff --git a/packages/console/app/src/routes/workspace/common.tsx b/packages/console/app/src/routes/workspace/common.tsx
index ddd3fd3e6..b5a90f749 100644
--- a/packages/console/app/src/routes/workspace/common.tsx
+++ b/packages/console/app/src/routes/workspace/common.tsx
@@ -113,6 +113,7 @@ export const queryBillingInfo = query(async (workspaceID: string) => {
subscriptionID: billing.subscriptionID,
subscriptionPlan: billing.subscriptionPlan,
timeSubscriptionBooked: billing.timeSubscriptionBooked,
+ timeSubscriptionSelected: billing.timeSubscriptionSelected,
}
}, workspaceID)
}, "billing.get")