Frank 4 months ago
parent
commit
5b27130d60

+ 25 - 0
packages/console/app/src/routes/workspace/[id].css

@@ -177,10 +177,35 @@
       line-height: 1.5;
       font-size: var(--font-size-md);
       color: var(--color-text-muted);
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      gap: var(--space-4);
+
+      @media (max-width: 48rem) {
+        flex-direction: column;
+        align-items: flex-start;
+        gap: var(--space-3);
+      }
 
       a {
         color: var(--color-text-muted);
       }
+
+      [data-slot="billing-info"] {
+        flex-shrink: 0;
+        margin-left: auto;
+      }
+
+      [data-slot="balance"] {
+        font-size: var(--font-size-sm);
+        color: var(--color-text-muted);
+
+        b {
+          font-weight: 600;
+          color: var(--color-text);
+        }
+      }
     }
   }
 }

+ 42 - 8
packages/console/app/src/routes/workspace/[id]/index.tsx

@@ -3,24 +3,58 @@ import { UsageSection } from "./usage-section"
 import { ModelSection } from "./model-section"
 import { ProviderSection } from "./provider-section"
 import { IconLogo } from "~/component/icon"
-import { createAsync, useParams } from "@solidjs/router"
-import { querySessionInfo } from "../common"
-import { Show } from "solid-js"
+import { createAsync, useParams, useAction, useSubmission } from "@solidjs/router"
+import { querySessionInfo, queryBillingInfo, createCheckoutUrl } from "../common"
+import { Show, createMemo } from "solid-js"
 
 export default function () {
   const params = useParams()
   const userInfo = createAsync(() => querySessionInfo(params.id))
+  const billingInfo = createAsync(() => queryBillingInfo(params.id))
+  const createCheckoutUrlAction = useAction(createCheckoutUrl)
+  const createCheckoutUrlSubmission = useSubmission(createCheckoutUrl)
+
+  const balanceAmount = createMemo(() => {
+    return ((billingInfo()?.balance ?? 0) / 100000000).toFixed(2)
+  })
 
   return (
     <div data-page="workspace-[id]">
       <section data-component="header-section">
         <IconLogo />
         <p>
-          Curated list of models provided by opencode.{" "}
-          <a target="_blank" href="/docs/zen">
-            Learn more
-          </a>
-          .
+          <span>
+            Reliable optimized models for coding agents.{" "}
+            <a target="_blank" href="/docs/zen">
+              Learn more
+            </a>
+            .
+          </span>
+          <span data-slot="billing-info">
+            <Show
+              when={billingInfo()?.reload}
+              fallback={
+                <button
+                  data-color="primary"
+                  data-size="sm"
+                  disabled={createCheckoutUrlSubmission.pending}
+                  onClick={async () => {
+                    const baseUrl = window.location.href
+                    const checkoutUrl = await createCheckoutUrlAction(params.id, baseUrl, baseUrl)
+                    if (checkoutUrl) {
+                      window.location.href = checkoutUrl
+                    }
+                  }}
+                >
+                  {createCheckoutUrlSubmission.pending ? "Loading..." : "Enable billing"}
+                </button>
+              }
+            >
+              <span data-slot="balance">
+                Current balance: <b>${balanceAmount() === "-0.00" ? "0.00" : balanceAmount()}</b>
+              </span>
+            </Show>
+          </span>
         </p>
       </section>
 

+ 5 - 0
packages/console/app/src/routes/workspace/common.tsx

@@ -44,3 +44,8 @@ export const createCheckoutUrl = action(async (workspaceID: string, successUrl:
   "use server"
   return withActor(() => Billing.generateCheckoutUrl({ successUrl, cancelUrl }), workspaceID)
 }, "checkoutUrl")
+
+export const queryBillingInfo = query(async (workspaceID: string) => {
+  "use server"
+  return withActor(() => Billing.get(), workspaceID)
+}, "billing.get")