Browse Source

ignore: zen

Jay V 5 months ago
parent
commit
74f9fcea88
2 changed files with 306 additions and 166 deletions
  1. 169 12
      cloud/app/src/routes/workspace/[id].css
  2. 137 154
      cloud/app/src/routes/workspace/[id].tsx

+ 169 - 12
cloud/app/src/routes/workspace/[id].css

@@ -269,9 +269,39 @@
     }
   }
 
-  /* Balance Section */
-  [data-component="balance-section"] {
-    [data-slot="balance"] {
+  /* Billing Section */
+  [data-component="billing-section"] {
+    [data-slot="section-content"] {
+      display: flex;
+      flex-direction: column;
+      gap: var(--space-3);
+    }
+
+    [data-slot="reload-error"] {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      gap: var(--space-4);
+      padding: var(--space-4);
+      border: 1px solid var(--color-border);
+      border-radius: var(--border-radius-sm);
+
+      p {
+        color: var(--color-danger);
+        font-size: var(--font-size-sm);
+        line-height: 1.4;
+        margin: 0;
+        flex: 1;
+      }
+
+      [data-slot="create-form"] {
+        display: flex;
+        gap: var(--space-2);
+        margin: 0;
+        flex-shrink: 0;
+      }
+    }
+    [data-slot="payment"] {
       display: flex;
       flex-direction: column;
       gap: var(--space-3);
@@ -281,23 +311,93 @@
       min-width: 14.5rem;
       width: fit-content;
 
-      [data-slot="amount"] {
+      [data-slot="credit-card"] {
         padding: var(--space-3-5) var(--space-4);
         background-color: var(--color-bg-surface);
         border-radius: var(--border-radius-sm);
         display: flex;
-        align-items: baseline;
-        gap: var(--space-1);
-        justify-content: flex-end;
+        align-items: center;
+        justify-content: space-between;
 
-        &[data-state="danger"] {
-          [data-slot="value"] {
-            color: var(--color-danger);
+        [data-slot="card-icon"] {
+          display: flex;
+          align-items: center;
+        }
+
+        [data-slot="card-details"] {
+          display: flex;
+          align-items: baseline;
+          gap: var(--space-1);
+
+          [data-slot="secret"] {
+            position: relative;
+            bottom: 2px;
+            font-size: var(--font-size-lg);
+            color: var(--color-text-muted);
+            font-weight: 400;
           }
-          [data-slot="currency"] {
-            color: var(--color-danger);
+
+          [data-slot="number"] {
+            font-size: var(--font-size-3xl);
+            font-weight: 500;
+            color: var(--color-text);
           }
         }
+      }
+
+      [data-slot="button-row"] {
+        display: flex;
+        gap: var(--space-2);
+        align-items: center;
+
+        [data-slot="create-form"] {
+          margin: 0;
+        }
+
+        /* Make Enable Billing button full width when it's the only button */
+        > button {
+          flex: 1;
+        }
+      }
+    }
+    [data-slot="usage"] {
+      p {
+        font-size: var(--font-size-sm);
+        line-height: 1.5;
+        color: var(--color-text-secondary);
+        b {
+          font-weight: 600;
+        }
+      }
+    }
+  }
+
+  /* Monthly Limit Section */
+  [data-component="monthly-limit-section"] {
+    [data-slot="section-content"] {
+      display: flex;
+      flex-direction: column;
+      gap: var(--space-3);
+    }
+
+    [data-slot="balance"] {
+      display: flex;
+      flex-direction: column;
+      gap: var(--space-3);
+      padding: var(--space-4);
+      border: 1px solid var(--color-border);
+      border-radius: var(--border-radius-sm);
+      min-width: 15rem;
+      width: fit-content;
+
+      [data-slot="amount"] {
+        padding: var(--space-3-5) var(--space-4);
+        background-color: var(--color-bg-surface);
+        border-radius: var(--border-radius-sm);
+        display: flex;
+        align-items: baseline;
+        gap: var(--space-1);
+        justify-content: flex-end;
 
         [data-slot="currency"] {
           position: relative;
@@ -313,6 +413,63 @@
           color: var(--color-text);
         }
       }
+
+      [data-slot="create-form"] {
+        display: flex;
+        flex-direction: column;
+        gap: var(--space-3);
+        margin-top: var(--space-1);
+
+        [data-slot="input-container"] {
+          display: flex;
+          flex-direction: column;
+          gap: var(--space-1);
+        }
+
+        @media (max-width: 30rem) {
+          gap: var(--space-2);
+        }
+
+        input {
+          flex: 1;
+          padding: var(--space-2) var(--space-3);
+          border: 1px solid var(--color-border);
+          border-radius: var(--border-radius-sm);
+          background-color: var(--color-bg);
+          color: var(--color-text);
+          font-size: var(--font-size-sm);
+          font-family: var(--font-mono);
+
+          &:focus {
+            outline: none;
+            border-color: var(--color-accent);
+          }
+
+          &::placeholder {
+            color: var(--color-text-disabled);
+          }
+        }
+
+        [data-slot="form-actions"] {
+          display: flex;
+          gap: var(--space-2);
+          justify-content: flex-end;
+        }
+
+        [data-slot="form-error"] {
+          color: var(--color-danger);
+          font-size: var(--font-size-sm);
+          margin-top: var(--space-1);
+          line-height: 1.4;
+        }
+      }
+    }
+
+    [data-slot="usage-status"] {
+      font-size: var(--font-size-sm);
+      color: var(--color-text-secondary);
+      margin: 0;
+      line-height: 1.4;
     }
   }
 

+ 137 - 154
cloud/app/src/routes/workspace/[id].tsx

@@ -278,140 +278,122 @@ function KeyCreateForm() {
   )
 }
 
-function BalanceSection() {
+function BillingSection() {
   const params = useParams()
   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)
 
+  const balanceAmount = createMemo(() => {
+    return ((balanceInfo()?.balance ?? 0) / 100000000).toFixed(2)
+  })
+
   return (
-    <section data-component="balance-section">
+    <section data-component="billing-section">
       <div data-slot="section-title">
-        <h2>Balance</h2>
-        <p>Add credits to your account.</p>
+        <h2>Billing</h2>
+        <p>Manage the payment method for your account.</p>
       </div>
-      <div data-slot="balance">
-        <div
-          data-slot="amount"
-          data-state={(() => {
-            const balanceStr = ((balanceInfo()?.balance ?? 0) / 100000000).toFixed(2)
-            return balanceStr === "0.00" || balanceStr === "-0.00" ? "danger" : undefined
-          })()}
-        >
-          <span data-slot="currency">$</span>
-          <span data-slot="value">
-            {(() => {
-              const balanceStr = ((balanceInfo()?.balance ?? 0) / 100000000).toFixed(2)
-              return balanceStr === "-0.00" ? "0.00" : balanceStr
-            })()}
-          </span>
-        </div>
-        <Show
-          when={balanceInfo()?.reload}
-          fallback={
-            <>
+      <div data-slot="section-content">
+        <Show when={balanceInfo()?.reloadError}>
+          <div data-slot="reload-error">
+            <p>
+              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.
+            </p>
+            <form action={reload} method="post" data-slot="create-form">
+              <input type="hidden" name="workspaceID" value={params.id} />
+              <button data-color="primary" type="submit" disabled={reloadSubmission.pending}>
+                {reloadSubmission.pending ? "Reloading..." : "Reload"}
+              </button>
+            </form>
+          </div>
+        </Show>
+        <div data-slot="payment">
+          <div data-slot="credit-card">
+            <div data-slot="card-icon">
+              <IconCreditCard style={{ width: "32px", height: "32px" }} />
+            </div>
+            <div data-slot="card-details">
+              <Show when={balanceInfo()?.paymentMethodLast4} fallback={<span data-slot="number">----</span>}>
+                <span data-slot="secret">••••</span>
+                <span data-slot="number">{balanceInfo()?.paymentMethodLast4}</span>
+              </Show>
+            </div>
+          </div>
+          <div data-slot="button-row">
+            <Show
+              when={balanceInfo()?.reload}
+              fallback={
+                <button
+                  data-color="primary"
+                  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>
+              }
+            >
               <button
                 data-color="primary"
-                disabled={createCheckoutUrlSubmission.pending}
+                disabled={createSessionUrlSubmission.pending}
                 onClick={async () => {
                   const baseUrl = window.location.href
-                  const checkoutUrl = await createCheckoutUrlAction(params.id, baseUrl, baseUrl)
-                  if (checkoutUrl) {
-                    window.location.href = checkoutUrl
+                  const sessionUrl = await createSessionUrlAction(params.id, baseUrl)
+                  if (sessionUrl) {
+                    window.location.href = sessionUrl
                   }
                 }}
               >
-                {createCheckoutUrlSubmission.pending ? "Loading..." : "Enable Billing"}
+                {createSessionUrlSubmission.pending ? "Loading..." : "Manage Payment Methods"}
               </button>
-              <p>You can continue using the API with the remaining credits.</p>
-            </>
-          }
-        >
-          <>
-            <div>
-              <p>
-                You will be automatically reloading <b>$20</b> (+$1.23 processing fee) when your balance reaches{" "}
-                <b>$5</b>.
-              </p>
-              <p>You will be able to continue using the API with the remaining credits after disabling billing.</p>
               <form action={disableReload} method="post" data-slot="create-form">
                 <input type="hidden" name="workspaceID" value={params.id} />
-                <button data-color="primary" type="submit" disabled={disableReloadSubmission.pending}>
-                  {disableReloadSubmission.pending ? "Disabling..." : "Disable Billing"}
+                <button data-color="ghost" type="submit" disabled={disableReloadSubmission.pending}>
+                  {disableReloadSubmission.pending ? "Disabling..." : "Disable"}
                 </button>
               </form>
-              <Show when={balanceInfo()?.reloadError}>
-                <>
-                  <p>
-                    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.
-                  </p>
-                  <form action={reload} method="post" data-slot="create-form">
-                    <input type="hidden" name="workspaceID" value={params.id} />
-                    <button data-color="primary" type="submit" disabled={reloadSubmission.pending}>
-                      {reloadSubmission.pending ? "Reloading..." : "Retry Reload"}
-                    </button>
-                  </form>
-                </>
-              </Show>
-            </div>
-          </>
-        </Show>
-      </div>
-      <Show when={balanceInfo()?.reload}>
-        <BalancePaymentForm />
-        <BalanceLimitForm />
-      </Show>
-    </section>
-  )
-}
-
-function BalancePaymentForm() {
-  const params = useParams()
-  const createSessionUrlAction = useAction(createSessionUrl)
-  const createSessionUrlSubmission = useSubmission(createSessionUrl)
-  const balanceInfo = createAsync(() => getBillingInfo(params.id))
-
-  return (
-    <>
-      <div data-slot="section-title">
-        <h2>Payment Method</h2>
-      </div>
-      <div data-slot="balance">
-        <div data-slot="amount">
-          <IconCreditCard style={{ width: "32px", height: "32px" }} />
-          <span data-slot="currency">••••</span>
-          <span data-slot="value">{balanceInfo()?.paymentMethodLast4}</span>
+            </Show>
+          </div>
+        </div>
+        <div data-slot="usage">
+          <Show when={!balanceInfo()?.reload && !(balanceAmount() === "0.00" || balanceAmount() === "-0.00")}>
+            <p>
+              You have <b data-slot="value">${balanceAmount() === "-0.00" ? "0.00" : balanceAmount()}</b> remaining in
+              your account. You can continue using the API with your remaining balance.
+            </p>
+          </Show>
+          <Show when={balanceInfo()?.reload && !balanceInfo()?.reloadError}>
+            <p>
+              Your current balance is <b data-slot="value">${balanceAmount() === "-0.00" ? "0.00" : balanceAmount()}</b>
+              . It'll be reloaded to <b>$20</b> (+$1.23 processing fee) when it reaches <b>$5</b>.
+            </p>
+          </Show>
         </div>
-        <button
-          data-color="primary"
-          disabled={createSessionUrlSubmission.pending}
-          onClick={async () => {
-            const baseUrl = window.location.href
-            const sessionUrl = await createSessionUrlAction(params.id, baseUrl)
-            if (sessionUrl) {
-              window.location.href = sessionUrl
-            }
-          }}
-        >
-          {createSessionUrlSubmission.pending ? "Loading..." : "Manage Payment Methods"}
-        </button>
       </div>
-    </>
+    </section>
   )
 }
 
-function BalanceLimitForm() {
+function MonthlyLimitSection() {
   const params = useParams()
   const submission = useSubmission(setMonthlyLimit)
   const [store, setStore] = createStore({ show: false })
@@ -444,19 +426,47 @@ function BalanceLimitForm() {
   }
 
   return (
-    <>
+    <section data-component="monthly-limit-section">
       <div data-slot="section-title">
         <h2>Monthly Limit</h2>
+        <p>Set a monthly spending limit for your account.</p>
       </div>
-      <div data-slot="balance">
-        <div data-slot="amount">
-          <span data-slot="currency">$</span>
-          <span data-slot="value">{balanceInfo()?.monthlyLimit ?? "-"}</span>
+      <div data-slot="section-content">
+        <div data-slot="balance">
+          <div data-slot="amount">
+            {balanceInfo()?.monthlyLimit ? <span data-slot="currency">$</span> : null}
+            <span data-slot="value">{balanceInfo()?.monthlyLimit ?? "-"}</span>
+          </div>
+          <Show
+            when={!store.show}
+            fallback={
+              <form action={setMonthlyLimit} method="post" data-slot="create-form">
+                <div data-slot="input-container">
+                  <input ref={(r) => (input = r)} data-component="input" name="limit" type="number" placeholder="50" />
+                  <Show when={submission.result && submission.result.error}>
+                    {(err) => <div data-slot="form-error">{err()}</div>}
+                  </Show>
+                </div>
+                <input type="hidden" name="workspaceID" value={params.id} />
+                <div data-slot="form-actions">
+                  <button type="reset" data-color="ghost" onClick={() => hide()}>
+                    Cancel
+                  </button>
+                  <button type="submit" data-color="primary" disabled={submission.pending}>
+                    {submission.pending ? "Setting..." : "Set"}
+                  </button>
+                </div>
+              </form>
+            }
+          >
+            <button data-color="primary" onClick={() => show()}>
+              {balanceInfo()?.monthlyLimit ? "Edit Limit" : "Set Limit"}
+            </button>
+          </Show>
         </div>
-        <Show when={balanceInfo()?.monthlyLimit} fallback={<p>No spending limit set.</p>}>
-          <p>
-            Current usage for the month of {new Date().toLocaleDateString("en-US", { month: "long", timeZone: "UTC" })}{" "}
-            is $
+        <Show when={balanceInfo()?.monthlyLimit} fallback={<p data-slot="usage-status">No spending limit set.</p>}>
+          <p data-slot="usage-status">
+            Current usage for {new Date().toLocaleDateString("en-US", { month: "long", timeZone: "UTC" })} is $
             {(() => {
               const dateLastUsed = balanceInfo()?.timeMonthlyUsageUpdated
               if (!dateLastUsed) return "0"
@@ -474,42 +484,11 @@ function BalanceLimitForm() {
               if (current !== lastUsed) return "0"
               return ((balanceInfo()?.monthlyUsage ?? 0) / 100000000).toFixed(2)
             })()}
+            .
           </p>
         </Show>
       </div>
-      <Show
-        when={store.show}
-        fallback={
-          <button data-color="primary" onClick={() => show()}>
-            {balanceInfo()?.monthlyLimit ? "Edit Spending Limit" : "Set Spending Limit"}
-          </button>
-        }
-      >
-        <form action={setMonthlyLimit} method="post" data-slot="create-form">
-          <div data-slot="input-container">
-            <input
-              ref={(r) => (input = r)}
-              data-component="input"
-              name="limit"
-              type="number"
-              placeholder="Enter limit"
-            />
-            <Show when={submission.result && submission.result.error}>
-              {(err) => <div data-slot="form-error">{err()}</div>}
-            </Show>
-          </div>
-          <input type="hidden" name="workspaceID" value={params.id} />
-          <div data-slot="form-actions">
-            <button type="reset" data-color="ghost" onClick={() => hide()}>
-              Cancel
-            </button>
-            <button type="submit" data-color="primary" disabled={submission.pending}>
-              {submission.pending ? "Setting..." : "Set"}
-            </button>
-          </div>
-        </form>
-      </Show>
-    </>
+    </section>
   )
 }
 
@@ -570,7 +549,6 @@ function UsageSection() {
 function PaymentSection() {
   const params = useParams()
   const payments = createAsync(() => getPaymentsInfo(params.id))
-  console.log("!#!@", payments())
 
   return (
     payments() &&
@@ -675,13 +653,13 @@ function NewUserSection() {
 
         <div data-component="next-steps">
           <ol>
+            <li>Enable billing</li>
             <li>
               Run <code>opencode auth login</code> and select opencode
             </li>
             <li>Paste your API key</li>
-            <li>Start opencode</li>
             <li>
-              Run <code>/models</code> to see available models
+              Start opencode and run <code>/models</code> to select a model
             </li>
           </ol>
         </div>
@@ -690,7 +668,9 @@ function NewUserSection() {
   )
 }
 
-export default function () {
+export default function() {
+  const params = useParams()
+
   return (
     <div data-page="workspace-[id]">
       <section data-component="title-section">
@@ -707,7 +687,10 @@ export default function () {
       <div data-slot="sections">
         <NewUserSection />
         <KeySection />
-        <BalanceSection />
+        <BillingSection />
+        <Show when={createAsync(() => getBillingInfo(params.id))()?.reload}>
+          <MonthlyLimitSection />
+        </Show>
         <UsageSection />
         <PaymentSection />
       </div>