diff options
Diffstat (limited to 'cloud/app/src/component')
12 files changed, 676 insertions, 6 deletions
diff --git a/cloud/app/src/component/workspace/billing-section.module.css b/cloud/app/src/component/workspace/billing-section.module.css new file mode 100644 index 000000000..d63a3d429 --- /dev/null +++ b/cloud/app/src/component/workspace/billing-section.module.css @@ -0,0 +1,102 @@ +.root { + [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); + padding: var(--space-4); + border: 1px solid var(--color-border); + border-radius: var(--border-radius-sm); + min-width: 14.5rem; + width: fit-content; + + [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: center; + justify-content: space-between; + + [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="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; + } + } + } +} + diff --git a/cloud/app/src/component/workspace/billing-section.tsx b/cloud/app/src/component/workspace/billing-section.tsx index 4bc4d4225..457ba1392 100644 --- a/cloud/app/src/component/workspace/billing-section.tsx +++ b/cloud/app/src/component/workspace/billing-section.tsx @@ -3,6 +3,7 @@ import { createEffect, createMemo, createSignal, For, Show } from "solid-js" import { Billing } from "@opencode/cloud-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" @@ -50,7 +51,7 @@ export function BillingSection() { }) return ( - <section data-component="billing-section"> + <section class={styles.root}> <div data-slot="section-title"> <h2>Billing</h2> <p> diff --git a/cloud/app/src/component/workspace/key-section.module.css b/cloud/app/src/component/workspace/key-section.module.css new file mode 100644 index 000000000..3a9f3ef85 --- /dev/null +++ b/cloud/app/src/component/workspace/key-section.module.css @@ -0,0 +1,156 @@ +.root { + [data-slot="create-form"] { + 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); + + [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); + } + + [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="api-keys-table"] { + overflow-x: auto; + } + + [data-slot="api-keys-table-element"] { + width: 100%; + border-collapse: collapse; + font-size: var(--font-size-sm); + + thead { + border-bottom: 1px solid var(--color-border); + } + + th { + padding: var(--space-3) var(--space-4); + text-align: left; + font-weight: normal; + color: var(--color-text-muted); + text-transform: uppercase; + } + + td { + padding: var(--space-3) var(--space-4); + border-bottom: 1px solid var(--color-border-muted); + color: var(--color-text-muted); + font-family: var(--font-mono); + + &[data-slot="key-name"] { + color: var(--color-text); + font-family: var(--font-sans); + font-weight: 500; + } + + &[data-slot="key-value"] { + font-family: var(--font-mono); + + button { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-3); + font-size: var(--font-size-sm); + font-weight: 400; + border: none; + background-color: transparent; + color: var(--color-text-muted); + font-family: var(--font-mono); + border-radius: var(--border-radius-sm); + cursor: pointer; + transition: all 0.15s ease; + text-transform: none; + + &:hover:not(:disabled) { + background-color: var(--color-bg-surface); + color: var(--color-text); + } + + &:disabled { + cursor: default; + color: var(--color-text); + } + + span { + font-family: inherit; + } + } + } + + &[data-slot="key-date"] { + color: var(--color-text); + } + + &[data-slot="key-actions"] { + font-family: var(--font-sans); + } + } + + tbody tr { + &:last-child td { + border-bottom: none; + } + } + + @media (max-width: 40rem) { + th, + td { + padding: var(--space-2) var(--space-3); + font-size: var(--font-size-xs); + } + + th { + &:nth-child(3) /* Date */ { + display: none; + } + } + + td { + &:nth-child(3) /* Date */ { + display: none; + } + } + } + } +} diff --git a/cloud/app/src/component/workspace/key-section.tsx b/cloud/app/src/component/workspace/key-section.tsx index ef1601567..4158ce793 100644 --- a/cloud/app/src/component/workspace/key-section.tsx +++ b/cloud/app/src/component/workspace/key-section.tsx @@ -5,6 +5,7 @@ import { Key } from "@opencode/cloud-core/key.js" import { withActor } from "~/context/auth.withActor" import { createStore } from "solid-js/store" import { formatDateUTC, formatDateForTable } from "./common" +import styles from "./key-section.module.css" const removeKey = action(async (form: FormData) => { "use server" @@ -109,7 +110,7 @@ export function KeySection() { } return ( - <section data-component="api-keys-section"> + <section class={styles.root}> <div data-slot="section-title"> <h2>API Keys</h2> <p>Manage your API keys for accessing opencode services.</p> diff --git a/cloud/app/src/component/workspace/monthly-limit-section.module.css b/cloud/app/src/component/workspace/monthly-limit-section.module.css new file mode 100644 index 000000000..061c2e72d --- /dev/null +++ b/cloud/app/src/component/workspace/monthly-limit-section.module.css @@ -0,0 +1,99 @@ +.root { + [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; + bottom: 2px; + font-size: var(--font-size-lg); + color: var(--color-text-muted); + font-weight: 400; + } + + [data-slot="value"] { + font-size: var(--font-size-3xl); + font-weight: 500; + 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; + } +} diff --git a/cloud/app/src/component/workspace/monthly-limit-section.tsx b/cloud/app/src/component/workspace/monthly-limit-section.tsx index 0ed454789..d00d09da6 100644 --- a/cloud/app/src/component/workspace/monthly-limit-section.tsx +++ b/cloud/app/src/component/workspace/monthly-limit-section.tsx @@ -3,6 +3,7 @@ import { createEffect, Show } from "solid-js" import { createStore } from "solid-js/store" import { withActor } from "~/context/auth.withActor" import { Billing } from "@opencode/cloud-core/billing.js" +import styles from "./monthly-limit-section.module.css" const getBillingInfo = query(async (workspaceID: string) => { "use server" @@ -62,7 +63,7 @@ export function MonthlyLimitSection() { } return ( - <section data-component="monthly-limit-section"> + <section class={styles.root}> <div data-slot="section-title"> <h2>Monthly Limit</h2> <p>Set a monthly spending limit for your account.</p> diff --git a/cloud/app/src/component/workspace/new-user-section.module.css b/cloud/app/src/component/workspace/new-user-section.module.css new file mode 100644 index 000000000..2edc7cc14 --- /dev/null +++ b/cloud/app/src/component/workspace/new-user-section.module.css @@ -0,0 +1,163 @@ +.root { + display: flex; + flex-direction: column; + gap: var(--space-8); + padding: var(--space-6); + background-color: var(--color-bg-surface); + border: 1px dashed var(--color-border); + border-radius: var(--border-radius-sm); + + @media (max-width: 30rem) { + gap: var(--space-8); + padding: var(--space-4); + } + + [data-component="feature-grid"] { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: var(--space-6); + + @media (max-width: 30rem) { + grid-template-columns: 1fr; + gap: var(--space-4); + } + + [data-slot="feature"] { + display: flex; + flex-direction: column; + gap: var(--space-2); + padding: var(--space-4); + border: 1px solid var(--color-border); + border-radius: var(--border-radius-sm); + + h3 { + font-size: var(--font-size-sm); + font-weight: 600; + margin: 0; + color: var(--color-text); + text-transform: uppercase; + letter-spacing: -0.025rem; + } + + p { + font-size: var(--font-size-sm); + line-height: 1.5; + margin: 0; + color: var(--color-text-muted); + } + } + } + + [data-component="api-key-highlight"] { + display: flex; + flex-direction: column; + gap: var(--space-6); + + [data-slot="section-title"] { + display: flex; + flex-direction: column; + gap: var(--space-1); + + h2 { + font-size: var(--font-size-md); + font-weight: 600; + line-height: 1.2; + letter-spacing: -0.03125rem; + margin: 0; + color: var(--color-text-secondary); + text-transform: uppercase; + + @media (max-width: 30rem) { + font-size: var(--font-size-md); + } + } + } + + [data-slot="key-display"] { + display: flex; + flex-direction: column; + gap: var(--space-3); + + [data-slot="key-container"] { + display: flex; + gap: var(--space-3); + padding: var(--space-4); + border: 2px solid var(--color-accent); + border-radius: var(--border-radius-sm); + align-items: center; + + @media (max-width: 40rem) { + flex-direction: column; + gap: var(--space-3); + align-items: stretch; + } + + [data-slot="key-value"] { + flex: 1; + font-family: var(--font-mono); + font-size: var(--font-size-sm); + color: var(--color-text); + background-color: var(--color-bg); + padding: var(--space-3); + border-radius: var(--border-radius-sm); + border: 1px solid var(--color-border); + word-break: break-all; + line-height: 1.4; + + @media (max-width: 40rem) { + font-size: var(--font-size-xs); + padding: var(--space-2-5); + } + } + + button { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-3) var(--space-4); + font-size: var(--font-size-sm); + font-weight: 500; + white-space: nowrap; + min-width: 130px; + + @media (max-width: 40rem) { + justify-content: center; + padding: var(--space-2-5) var(--space-3); + font-size: var(--font-size-xs); + min-width: 96px; + } + } + } + } + } + + [data-component="next-steps"] { + display: flex; + flex-direction: column; + gap: var(--space-6); + + ol { + margin: 0; + padding-left: 0; + display: flex; + flex-direction: column; + gap: var(--space-2); + list-style-position: inside; + + li { + font-size: var(--font-size-md); + line-height: 1.5; + color: var(--color-text-secondary); + + code { + font-family: var(--font-mono); + font-size: var(--font-size-sm); + padding: var(--space-1) var(--space-2); + border: 1px solid var(--color-border); + border-radius: var(--border-radius-sm); + color: var(--color-text); + } + } + } + } +} diff --git a/cloud/app/src/component/workspace/new-user-section.tsx b/cloud/app/src/component/workspace/new-user-section.tsx index 15f8bcb5c..6e031e371 100644 --- a/cloud/app/src/component/workspace/new-user-section.tsx +++ b/cloud/app/src/component/workspace/new-user-section.tsx @@ -4,6 +4,7 @@ import { IconCopy, IconCheck } from "~/component/icon" import { Key } from "@opencode/cloud-core/key.js" import { Billing } from "@opencode/cloud-core/billing.js" import { withActor } from "~/context/auth.withActor" +import styles from "./new-user-section.module.css" const getUsageInfo = query(async (workspaceID: string) => { "use server" @@ -31,7 +32,7 @@ export function NewUserSection() { return ( <Show when={isNew()}> - <div data-slot="new-user-sections"> + <div class={styles.root}> <div data-component="feature-grid"> <div data-slot="feature"> <h3>Tested & Verified Models</h3> diff --git a/cloud/app/src/component/workspace/payment-section.module.css b/cloud/app/src/component/workspace/payment-section.module.css new file mode 100644 index 000000000..ea8e2ed42 --- /dev/null +++ b/cloud/app/src/component/workspace/payment-section.module.css @@ -0,0 +1,72 @@ +.root { + [data-slot="payments-table"] { + overflow-x: auto; + } + + [data-slot="payments-table-element"] { + width: 100%; + border-collapse: collapse; + font-size: var(--font-size-sm); + + thead { + border-bottom: 1px solid var(--color-border); + } + + th { + padding: var(--space-3) var(--space-4); + text-align: left; + font-weight: normal; + color: var(--color-text-muted); + text-transform: uppercase; + } + + td { + padding: var(--space-3) var(--space-4); + border-bottom: 1px solid var(--color-border-muted); + color: var(--color-text-muted); + font-family: var(--font-mono); + + &[data-slot="payment-date"] { + color: var(--color-text); + } + + &[data-slot="payment-id"] { + font-family: var(--font-mono); + font-weight: 400; + color: var(--color-text-muted); + max-width: 200px; + word-break: break-word; + } + + &[data-slot="payment-amount"] { + color: var(--color-text); + } + } + + tbody tr { + &:last-child td { + border-bottom: none; + } + } + + @media (max-width: 40rem) { + th, + td { + padding: var(--space-2) var(--space-3); + font-size: var(--font-size-xs); + } + + th { + &:nth-child(2) /* Payment ID */ { + display: none; + } + } + + td { + &:nth-child(2) /* Payment ID */ { + display: none; + } + } + } + } +} diff --git a/cloud/app/src/component/workspace/payment-section.tsx b/cloud/app/src/component/workspace/payment-section.tsx index f802fa96a..a346cb55f 100644 --- a/cloud/app/src/component/workspace/payment-section.tsx +++ b/cloud/app/src/component/workspace/payment-section.tsx @@ -3,6 +3,7 @@ import { query, useParams, createAsync } from "@solidjs/router" import { For } from "solid-js" import { withActor } from "~/context/auth.withActor" import { formatDateUTC, formatDateForTable } from "./common" +import styles from "./payment-section.module.css" const getPaymentsInfo = query(async (workspaceID: string) => { "use server" @@ -18,7 +19,7 @@ export function PaymentSection() { return ( payments() && payments()!.length > 0 && ( - <section data-component="payments-section"> + <section class={styles.root}> <div data-slot="section-title"> <h2>Payments History</h2> <p>Recent payment transactions.</p> diff --git a/cloud/app/src/component/workspace/usage-section.module.css b/cloud/app/src/component/workspace/usage-section.module.css new file mode 100644 index 000000000..520dc8f21 --- /dev/null +++ b/cloud/app/src/component/workspace/usage-section.module.css @@ -0,0 +1,72 @@ +.root { + [data-slot="usage-table"] { + overflow-x: auto; + } + + [data-slot="usage-table-element"] { + width: 100%; + border-collapse: collapse; + font-size: var(--font-size-sm); + + thead { + border-bottom: 1px solid var(--color-border); + } + + th { + padding: var(--space-3) var(--space-4); + text-align: left; + font-weight: normal; + color: var(--color-text-muted); + text-transform: uppercase; + } + + td { + padding: var(--space-3) var(--space-4); + border-bottom: 1px solid var(--color-border-muted); + color: var(--color-text-muted); + font-family: var(--font-mono); + + &[data-slot="usage-date"] { + color: var(--color-text); + } + + &[data-slot="usage-model"] { + font-family: var(--font-sans); + font-weight: 400; + color: var(--color-text-secondary); + max-width: 200px; + word-break: break-word; + } + + &[data-slot="usage-cost"] { + color: var(--color-text); + } + } + + tbody tr { + &:last-child td { + border-bottom: none; + } + } + + @media (max-width: 40rem) { + th, + td { + padding: var(--space-2) var(--space-3); + font-size: var(--font-size-xs); + } + + th { + &:nth-child(2) /* Model */ { + display: none; + } + } + + td { + &:nth-child(2) /* Model */ { + display: none; + } + } + } + } +} diff --git a/cloud/app/src/component/workspace/usage-section.tsx b/cloud/app/src/component/workspace/usage-section.tsx index a2ad507af..37265d8e5 100644 --- a/cloud/app/src/component/workspace/usage-section.tsx +++ b/cloud/app/src/component/workspace/usage-section.tsx @@ -3,6 +3,7 @@ import { query, useParams, createAsync } from "@solidjs/router" import { createMemo, For, Show } from "solid-js" import { formatDateUTC, formatDateForTable } from "./common" import { withActor } from "~/context/auth.withActor" +import styles from "./usage-section.module.css" const getUsageInfo = query(async (workspaceID: string) => { "use server" @@ -16,7 +17,7 @@ export function UsageSection() { const usage = createAsync(() => getUsageInfo(params.id)) return ( - <section data-component="usage-section"> + <section class={styles.root}> <div data-slot="section-title"> <h2>Usage History</h2> <p>Recent API usage and costs.</p> |
