From 4ceabdffa07b1af8d99eb73622a4d549d99ec6d2 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 18 Sep 2025 10:59:01 -0400 Subject: wip: zen --- .../src/component/workspace/billing-section.tsx | 193 +++++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 packages/console/app/src/component/workspace/billing-section.tsx (limited to 'packages/console/app/src/component/workspace/billing-section.tsx') diff --git a/packages/console/app/src/component/workspace/billing-section.tsx b/packages/console/app/src/component/workspace/billing-section.tsx new file mode 100644 index 000000000..57316e208 --- /dev/null +++ b/packages/console/app/src/component/workspace/billing-section.tsx @@ -0,0 +1,193 @@ +import { json, query, action, useParams, useAction, createAsync, useSubmission } from "@solidjs/router" +import { createMemo, Show } from "solid-js" +import { Billing } from "@opencode/console-core/billing.js" +import { withActor } from "~/context/auth.withActor" +import { IconCreditCard } from "~/component/icon" +import styles from "./billing-section.module.css" + +const createCheckoutUrl = action(async (workspaceID: string, successUrl: string, cancelUrl: string) => { + "use server" + return withActor(() => Billing.generateCheckoutUrl({ successUrl, cancelUrl }), workspaceID) +}, "checkoutUrl") + +const reload = action(async (form: FormData) => { + "use server" + const workspaceID = form.get("workspaceID")?.toString() + if (!workspaceID) return { error: "Workspace ID is required" } + return json(await withActor(() => Billing.reload(), workspaceID), { revalidate: getBillingInfo.key }) +}, "billing.reload") + +const disableReload = action(async (form: FormData) => { + "use server" + const workspaceID = form.get("workspaceID")?.toString() + if (!workspaceID) return { error: "Workspace ID is required" } + return json(await withActor(() => Billing.disableReload(), workspaceID), { revalidate: getBillingInfo.key }) +}, "billing.disableReload") + +const createSessionUrl = action(async (workspaceID: string, returnUrl: string) => { + "use server" + return withActor(() => Billing.generateSessionUrl({ returnUrl }), workspaceID) +}, "sessionUrl") + +const getBillingInfo = query(async (workspaceID: string) => { + "use server" + return withActor(async () => { + return await Billing.get() + }, workspaceID) +}, "billing.get") + +export function BillingSection() { + const params = useParams() + // ORIGINAL CODE - COMMENTED OUT FOR TESTING + const balanceInfo = createAsync(() => getBillingInfo(params.id)) + const createCheckoutUrlAction = useAction(createCheckoutUrl) + const createCheckoutUrlSubmission = useSubmission(createCheckoutUrl) + const createSessionUrlAction = useAction(createSessionUrl) + const createSessionUrlSubmission = useSubmission(createSessionUrl) + const disableReloadSubmission = useSubmission(disableReload) + const reloadSubmission = useSubmission(reload) + + // DUMMY DATA FOR TESTING - UNCOMMENT ONE OF THE SCENARIOS BELOW + + // Scenario 1: User has not added billing details and has no balance + // const balanceInfo = () => ({ + // balance: 0, + // paymentMethodLast4: null as string | null, + // reload: false, + // reloadError: null as string | null, + // timeReloadError: null as Date | null, + // }) + + // Scenario 2: User has not added billing details but has a balance + // const balanceInfo = () => ({ + // balance: 1500000000, // $15.00 + // paymentMethodLast4: null as string | null, + // reload: false, + // reloadError: null as string | null, + // timeReloadError: null as Date | null + // }) + + // Scenario 3: User has added billing details (reload enabled) + // const balanceInfo = () => ({ + // balance: 750000000, // $7.50 + // paymentMethodLast4: "4242", + // reload: true, + // reloadError: null as string | null, + // timeReloadError: null as Date | null + // }) + + // Scenario 4: User has billing details but reload failed + // const balanceInfo = () => ({ + // balance: 250000000, // $2.50 + // paymentMethodLast4: "4242", + // reload: true, + // reloadError: "Your card was declined." as string, + // timeReloadError: new Date(Date.now() - 3600000) as Date // 1 hour ago + // }) + + const balanceAmount = createMemo(() => { + return ((balanceInfo()?.balance ?? 0) / 100000000).toFixed(2) + }) + + return ( +
+
+

Billing

+

+ Manage payments methods. Contact us if you have any questions. +

+
+
+ +
+

+ Reload failed at{" "} + {balanceInfo()?.timeReloadError!.toLocaleString("en-US", { + month: "short", + day: "numeric", + hour: "numeric", + minute: "2-digit", + second: "2-digit", + })} + . Reason: {balanceInfo()?.reloadError?.replace(/\.$/, "")}. Please update your payment method and try + again. +

+
+ + +
+
+
+
+
+
+ +
+
+ ----}> + •••• + {balanceInfo()?.paymentMethodLast4} + +
+
+
+ { + const baseUrl = window.location.href + const checkoutUrl = await createCheckoutUrlAction(params.id, baseUrl, baseUrl) + if (checkoutUrl) { + window.location.href = checkoutUrl + } + }} + > + {createCheckoutUrlSubmission.pending ? "Loading..." : "Enable Billing"} + + } + > + +
+ + +
+
+
+
+
+ +

+ You have ${balanceAmount() === "-0.00" ? "0.00" : balanceAmount()} remaining in + your account. You can continue using the API with your remaining balance. +

+
+ +

+ Your current balance is ${balanceAmount() === "-0.00" ? "0.00" : balanceAmount()} + . We'll automatically reload $20 (+$1.23 processing fee) when it reaches $5. +

+
+
+
+
+ ) +} -- cgit v1.2.3