From 9c02c4cfe8880059fd83763bf758b4c91d074893 Mon Sep 17 00:00:00 2001 From: Jay V Date: Fri, 29 Aug 2025 12:48:01 -0400 Subject: ignore: cloud --- cloud/app/src/routes/workspace.tsx | 16 +- cloud/app/src/routes/workspace/[id].tsx | 279 +++++++++++++++++++++ cloud/app/src/routes/workspace/[workspaceID].tsx | 278 --------------------- cloud/app/src/routes/workspace/index.css | 251 +++++++++++++++++++ cloud/app/src/routes/workspace/workspace.css | 296 +---------------------- cloud/app/src/style/token/color.css | 5 +- 6 files changed, 543 insertions(+), 582 deletions(-) create mode 100644 cloud/app/src/routes/workspace/[id].tsx delete mode 100644 cloud/app/src/routes/workspace/[workspaceID].tsx create mode 100644 cloud/app/src/routes/workspace/index.css (limited to 'cloud/app/src') diff --git a/cloud/app/src/routes/workspace.tsx b/cloud/app/src/routes/workspace.tsx index d0678ef83..0724dd7f4 100644 --- a/cloud/app/src/routes/workspace.tsx +++ b/cloud/app/src/routes/workspace.tsx @@ -11,21 +11,9 @@ export default function WorkspaceLayout(props: RouteSectionProps) { -
- - - - - - - - - - + name@example.com + Logout
{props.children} diff --git a/cloud/app/src/routes/workspace/[id].tsx b/cloud/app/src/routes/workspace/[id].tsx new file mode 100644 index 000000000..abada1c89 --- /dev/null +++ b/cloud/app/src/routes/workspace/[id].tsx @@ -0,0 +1,279 @@ +import { Billing } from "@opencode/cloud-core/billing.js" +import { Key } from "@opencode/cloud-core/key.js" +import { action, createAsync, revalidate, query, useAction, useSubmission } from "@solidjs/router" +import { createEffect, createSignal, For, onMount, Show } from "solid-js" +import { getActor } from "~/context/auth" +import { withActor } from "~/context/auth.withActor" +import "./index.css" + +///////////////////////////////////// +// Keys related queries and actions +///////////////////////////////////// + +const listKeys = query(async () => { + "use server" + return withActor(() => Key.list()) +}, "keys") + +const createKey = action(async (name: string) => { + "use server" + return withActor(() => Key.create({ name })) +}, "createKey") + +const removeKey = action(async (id: string) => { + "use server" + return withActor(() => Key.remove({ id })) +}, "removeKey") + +///////////////////////////////////// +// Billing related queries and actions +///////////////////////////////////// + +const getBillingInfo = query(async () => { + "use server" + return withActor(async () => { + const billing = await Billing.get() + const payments = await Billing.payments() + const usage = await Billing.usages() + return { billing, payments, usage } + }) +}, "billingInfo") + +const createCheckoutUrl = action(async (successUrl: string, cancelUrl: string) => { + "use server" + return withActor(() => Billing.generateCheckoutUrl({ successUrl, cancelUrl })) +}, "checkoutUrl") + +const createPortalUrl = action(async (returnUrl: string) => { + "use server" + return withActor(() => Billing.generatePortalUrl({ returnUrl })) +}, "portalUrl") + +export default function() { + const actor = createAsync(() => getActor()) + onMount(() => { + console.log("MOUNTED", actor()) + }) + + ///////////////// + // Keys section + ///////////////// + const keys = createAsync(() => listKeys()) + const createKeyAction = useAction(createKey) + const removeKeyAction = useAction(removeKey) + const createKeySubmission = useSubmission(createKey) + const [showCreateForm, setShowCreateForm] = createSignal(false) + const [keyName, setKeyName] = createSignal("") + + const formatDate = (date: Date) => { + return date.toLocaleDateString() + } + + const formatKey = (key: string) => { + if (key.length <= 11) return key + return `${key.slice(0, 7)}...${key.slice(-4)}` + } + + const copyToClipboard = async (text: string) => { + try { + await navigator.clipboard.writeText(text) + } catch (error) { + console.error("Failed to copy to clipboard:", error) + } + } + + const handleCreateKey = async () => { + if (!keyName().trim()) return + + try { + await createKeyAction(keyName().trim()) + revalidate("keys") + setKeyName("") + setShowCreateForm(false) + } catch (error) { + console.error("Failed to create API key:", error) + } + } + + const handleDeleteKey = async (keyId: string) => { + if (!confirm("Are you sure you want to delete this API key? This action cannot be undone.")) { + return + } + + try { + await removeKeyAction(keyId) + revalidate("keys") + } catch (error) { + console.error("Failed to delete API key:", error) + } + } + + ///////////////// + // Billing section + ///////////////// + const billingInfo = createAsync(() => getBillingInfo()) + const [isLoading, setIsLoading] = createSignal(false) + const createCheckoutUrlAction = useAction(createCheckoutUrl) + + // Run once on component mount to check URL parameters + onMount(() => { + const url = new URL(window.location.href) + const result = url.hash + + console.log("STRIPE RESULT", result) + + if (url.hash === "#success") { + setIsLoading(true) + // Remove the hash from the URL + window.history.replaceState(null, "", window.location.pathname + window.location.search) + } + }) + + createEffect((old?: number) => { + if (old && old !== billingInfo()?.billing?.balance) { + setIsLoading(false) + } + return billingInfo()?.billing?.balance + }) + + const handleBuyCredits = async () => { + try { + setIsLoading(true) + const baseUrl = window.location.href + const successUrl = new URL(baseUrl) + successUrl.hash = "success" + + const checkoutUrl = await createCheckoutUrlAction(successUrl.toString(), baseUrl) + if (checkoutUrl) { + window.location.href = checkoutUrl + } + } catch (error) { + console.error("Failed to get checkout URL:", error) + setIsLoading(false) + } + } + + return ( +
+

Actor

+
{JSON.stringify(actor())}
+

API Keys

+ + setKeyName(e.currentTarget.value)} + onKeyPress={(e) => e.key === "Enter" && handleCreateKey()} + /> +
+ + +
+
+ } + > + + +
+ +

Create an API key to access opencode gateway

+
+ } + > + {(key) => ( +
+
+
{key.name}
+
{formatKey(key.key)}
+
+ Created: {formatDate(key.timeCreated)} + {key.timeUsed && ` • Last used: ${formatDate(key.timeUsed)}`} +
+
+
+ + +
+
+ )} + + + +

Balance

+

Manage your billing and add credits to your account.

+

+ {(() => { + const balanceStr = ((billingInfo()?.billing?.balance ?? 0) / 100000000).toFixed(2) + return `$${balanceStr === "-0.00" ? "0.00" : balanceStr}` + })()} +

+ + +

Payments History

+

Your recent payment transactions.

+ No payments found.

}> + {(payment) => ( +
+ {payment.id} + {" | "} + ${((payment.amount ?? 0) / 100000000).toFixed(2)} + {" | "} + {new Date(payment.timeCreated).toLocaleDateString()} +
+ )} +
+ +

Usage History

+

Your recent API usage and costs.

+ No usage found.

}> + {(usage) => ( +
+ {usage.model} + {" | "} + {usage.inputTokens + usage.outputTokens} tokens + {" | "} + ${((usage.cost ?? 0) / 100000000).toFixed(4)} + {" | "} + {new Date(usage.timeCreated).toLocaleDateString()} +
+ )} +
+ + ) +} diff --git a/cloud/app/src/routes/workspace/[workspaceID].tsx b/cloud/app/src/routes/workspace/[workspaceID].tsx deleted file mode 100644 index f507b5152..000000000 --- a/cloud/app/src/routes/workspace/[workspaceID].tsx +++ /dev/null @@ -1,278 +0,0 @@ -import { Billing } from "@opencode/cloud-core/billing.js" -import { Key } from "@opencode/cloud-core/key.js" -import { action, createAsync, revalidate, query, useAction, useSubmission } from "@solidjs/router" -import { createEffect, createSignal, For, onMount, Show } from "solid-js" -import { getActor } from "~/context/auth" -import { withActor } from "~/context/auth.withActor" - -///////////////////////////////////// -// Keys related queries and actions -///////////////////////////////////// - -const listKeys = query(async () => { - "use server" - return withActor(() => Key.list()) -}, "keys") - -const createKey = action(async (name: string) => { - "use server" - return withActor(() => Key.create({ name })) -}, "createKey") - -const removeKey = action(async (id: string) => { - "use server" - return withActor(() => Key.remove({ id })) -}, "removeKey") - -///////////////////////////////////// -// Billing related queries and actions -///////////////////////////////////// - -const getBillingInfo = query(async () => { - "use server" - return withActor(async () => { - const billing = await Billing.get() - const payments = await Billing.payments() - const usage = await Billing.usages() - return { billing, payments, usage } - }) -}, "billingInfo") - -const createCheckoutUrl = action(async (successUrl: string, cancelUrl: string) => { - "use server" - return withActor(() => Billing.generateCheckoutUrl({ successUrl, cancelUrl })) -}, "checkoutUrl") - -const createPortalUrl = action(async (returnUrl: string) => { - "use server" - return withActor(() => Billing.generatePortalUrl({ returnUrl })) -}, "portalUrl") - -export default function () { - const actor = createAsync(() => getActor()) - onMount(() => { - console.log("MOUNTED", actor()) - }) - - ///////////////// - // Keys section - ///////////////// - const keys = createAsync(() => listKeys()) - const createKeyAction = useAction(createKey) - const removeKeyAction = useAction(removeKey) - const createKeySubmission = useSubmission(createKey) - const [showCreateForm, setShowCreateForm] = createSignal(false) - const [keyName, setKeyName] = createSignal("") - - const formatDate = (date: Date) => { - return date.toLocaleDateString() - } - - const formatKey = (key: string) => { - if (key.length <= 11) return key - return `${key.slice(0, 7)}...${key.slice(-4)}` - } - - const copyToClipboard = async (text: string) => { - try { - await navigator.clipboard.writeText(text) - } catch (error) { - console.error("Failed to copy to clipboard:", error) - } - } - - const handleCreateKey = async () => { - if (!keyName().trim()) return - - try { - await createKeyAction(keyName().trim()) - revalidate("keys") - setKeyName("") - setShowCreateForm(false) - } catch (error) { - console.error("Failed to create API key:", error) - } - } - - const handleDeleteKey = async (keyId: string) => { - if (!confirm("Are you sure you want to delete this API key? This action cannot be undone.")) { - return - } - - try { - await removeKeyAction(keyId) - revalidate("keys") - } catch (error) { - console.error("Failed to delete API key:", error) - } - } - - ///////////////// - // Billing section - ///////////////// - const billingInfo = createAsync(() => getBillingInfo()) - const [isLoading, setIsLoading] = createSignal(false) - const createCheckoutUrlAction = useAction(createCheckoutUrl) - - // Run once on component mount to check URL parameters - onMount(() => { - const url = new URL(window.location.href) - const result = url.hash - - console.log("STRIPE RESULT", result) - - if (url.hash === "#success") { - setIsLoading(true) - // Remove the hash from the URL - window.history.replaceState(null, "", window.location.pathname + window.location.search) - } - }) - - createEffect((old?: number) => { - if (old && old !== billingInfo()?.billing?.balance) { - setIsLoading(false) - } - return billingInfo()?.billing?.balance - }) - - const handleBuyCredits = async () => { - try { - setIsLoading(true) - const baseUrl = window.location.href - const successUrl = new URL(baseUrl) - successUrl.hash = "success" - - const checkoutUrl = await createCheckoutUrlAction(successUrl.toString(), baseUrl) - if (checkoutUrl) { - window.location.href = checkoutUrl - } - } catch (error) { - console.error("Failed to get checkout URL:", error) - setIsLoading(false) - } - } - - return ( -
-

Actor

-
{JSON.stringify(actor())}
-

API Keys

- - setKeyName(e.currentTarget.value)} - onKeyPress={(e) => e.key === "Enter" && handleCreateKey()} - /> -
- - -
-
- } - > - - -
- -

Create an API key to access opencode gateway

-
- } - > - {(key) => ( -
-
-
{key.name}
-
{formatKey(key.key)}
-
- Created: {formatDate(key.timeCreated)} - {key.timeUsed && ` • Last used: ${formatDate(key.timeUsed)}`} -
-
-
- - -
-
- )} - - - -

Balance

-

Manage your billing and add credits to your account.

-

- {(() => { - const balanceStr = ((billingInfo()?.billing?.balance ?? 0) / 100000000).toFixed(2) - return `$${balanceStr === "-0.00" ? "0.00" : balanceStr}` - })()} -

- - -

Payments History

-

Your recent payment transactions.

- No payments found.

}> - {(payment) => ( -
- {payment.id} - {" | "} - ${((payment.amount ?? 0) / 100000000).toFixed(2)} - {" | "} - {new Date(payment.timeCreated).toLocaleDateString()} -
- )} -
- -

Usage History

-

Your recent API usage and costs.

- No usage found.

}> - {(usage) => ( -
- {usage.model} - {" | "} - {usage.inputTokens + usage.outputTokens} tokens - {" | "} - ${((usage.cost ?? 0) / 100000000).toFixed(4)} - {" | "} - {new Date(usage.timeCreated).toLocaleDateString()} -
- )} -
- - ) -} diff --git a/cloud/app/src/routes/workspace/index.css b/cloud/app/src/routes/workspace/index.css new file mode 100644 index 000000000..8d1006fca --- /dev/null +++ b/cloud/app/src/routes/workspace/index.css @@ -0,0 +1,251 @@ +[data-page="workspace"] { + /* Main content container */ + & > div { + display: flex; + flex-direction: column; + gap: var(--space-6); + } + + /* Adjust header spacing */ + [data-component="workspace-header"] + div { + margin-top: var(--space-2); + } + + /* Section headers */ + h1 { + font-size: var(--font-size-3xl); + font-weight: 500; + line-height: 1.2; + letter-spacing: -0.05em; + margin: 0; + color: var(--color-text); + + @media (max-width: 30rem) { + font-size: var(--font-size-2xl); + line-height: 1.25; + } + } + + /* Section descriptions */ + p { + margin: 0; + color: var(--color-text-secondary); + font-size: var(--font-size-md); + line-height: 1.5; + } + + /* API Keys Section */ + [data-slot="create-form"] { + display: flex; + flex-direction: column; + gap: var(--space-3); + padding: var(--space-4); + background-color: var(--color-bg-surface); + border: 1px solid var(--color-border); + border-radius: var(--space-2); + max-width: 32rem; + + input { + padding: var(--space-2) var(--space-3); + border: 1px solid var(--color-border); + border-radius: var(--space-2); + 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="key-list"] { + display: flex; + flex-direction: column; + gap: var(--space-2); + } + + [data-slot="key-item"] { + display: flex; + justify-content: space-between; + align-items: flex-start; + padding: var(--space-4); + background-color: var(--color-bg-surface); + border: 1px solid var(--color-border); + border-radius: var(--space-2); + gap: var(--space-4); + + @media (max-width: 30rem) { + flex-direction: column; + gap: var(--space-3); + } + } + + [data-slot="key-info"] { + display: flex; + flex-direction: column; + gap: var(--space-1); + flex: 1; + } + + [data-slot="key-name"] { + font-size: var(--font-size-md); + font-weight: 500; + color: var(--color-text); + } + + [data-slot="key-value"] { + font-size: var(--font-size-xs); + font-family: var(--font-mono); + color: var(--color-text-secondary); + background-color: var(--color-bg); + padding: var(--space-1) var(--space-2); + border-radius: var(--space-1); + border: 1px solid var(--color-border-muted); + } + + [data-slot="key-meta"] { + font-size: var(--font-size-xs); + color: var(--color-text-disabled); + } + + [data-slot="key-actions"] { + display: flex; + gap: var(--space-2); + } + + [data-slot="empty-state"] { + padding: var(--space-8); + text-align: center; + color: var(--color-text-muted); + background-color: var(--color-bg-surface); + border: 1px solid var(--color-border); + border-radius: var(--space-2); + + p { + margin: 0; + font-size: var(--font-size-sm); + } + } + + /* Balance Section */ + [data-slot="balance"] { + display: flex; + flex-direction: column; + gap: var(--space-3); + padding: var(--space-4); + background-color: var(--color-bg-surface); + border: 1px solid var(--color-border); + border-radius: var(--space-2); + max-width: 32rem; + + p { + font-size: var(--font-size-2xl); + font-weight: 500; + color: var(--color-text); + margin: 0; + } + } + + /* Payment and Usage Items */ + [data-slot="payment-item"], + [data-slot="usage-item"] { + display: flex; + align-items: center; + gap: var(--space-4); + padding: var(--space-3); + background-color: var(--color-bg-surface); + border: 1px solid var(--color-border); + border-radius: var(--space-2); + font-size: var(--font-size-sm); + font-family: var(--font-mono); + + @media (max-width: 30rem) { + flex-direction: column; + align-items: flex-start; + gap: var(--space-2); + } + } + + [data-slot="payment-id"], + [data-slot="payment-amount"], + [data-slot="payment-date"], + [data-slot="usage-model"], + [data-slot="usage-tokens"], + [data-slot="usage-cost"], + [data-slot="usage-date"] { + color: var(--color-text-muted); + } + + /* Buttons */ + button { + padding: var(--space-2) var(--space-4); + border: 1px solid var(--color-border); + border-radius: var(--space-2); + background-color: var(--color-bg); + color: var(--color-text); + font-size: var(--font-size-sm); + font-family: var(--font-sans); + cursor: pointer; + transition: all 0.15s ease; + + &:hover { + background-color: var(--color-surface-hover); + border-color: var(--color-accent); + } + + &:active { + transform: translateY(1px); + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + + &:hover { + background-color: var(--color-bg); + border-color: var(--color-border); + transform: none; + } + } + + &[color="primary"] { + background-color: var(--color-primary); + border-color: var(--color-primary); + color: var(--color-primary-text); + + &:hover { + background-color: var(--color-primary-hover); + border-color: var(--color-primary-hover); + } + } + + &[color="ghost"] { + background-color: transparent; + border-color: transparent; + color: var(--color-text-muted); + + &:hover { + background-color: var(--color-surface-hover); + border-color: var(--color-border); + color: var(--color-text); + } + } + } + + @media (prefers-color-scheme: dark) { + /* Dark mode specific adjustments if needed */ + } +} diff --git a/cloud/app/src/routes/workspace/workspace.css b/cloud/app/src/routes/workspace/workspace.css index 66ae46ed4..7ea96aed2 100644 --- a/cloud/app/src/routes/workspace/workspace.css +++ b/cloud/app/src/routes/workspace/workspace.css @@ -18,8 +18,7 @@ display: flex; justify-content: space-between; align-items: center; - height: 3.5rem; - padding: 0 var(--space-6); + padding: var(--space-4) var(--space-3); margin: calc(-1 * var(--space-6)); margin-bottom: var(--space-6); border-bottom: 1px solid var(--color-border); @@ -34,6 +33,7 @@ [data-slot="header-brand"] { flex: 0 0 auto; + padding-top: 4px; svg { width: 138px; @@ -48,300 +48,22 @@ } } - [data-slot="header-nav"] { - display: flex; - gap: var(--space-4); - align-items: center; - - @media (max-width: 50rem) { - display: none; - } - - a { - color: var(--color-text-secondary); - text-decoration: none; - font-size: var(--font-size-sm); - font-weight: 400; - text-transform: uppercase; - letter-spacing: 0.05em; - transition: color 0.15s ease; - - &:hover { - color: var(--color-text); - } - } - } - [data-slot="header-actions"] { display: flex; gap: var(--space-4); align-items: center; + font-size: var(--font-size-sm); - a { - display: flex; - align-items: center; - justify-content: center; + span { color: var(--color-text-muted); - text-decoration: none; - transition: color 0.15s ease; - - &:hover { - color: var(--color-text); - } - - svg { - display: block; - } - } - } - - /* Main content container */ - & > div { - display: flex; - flex-direction: column; - gap: var(--space-6); - } - - /* Adjust header spacing */ - [data-component="workspace-header"] + div { - margin-top: var(--space-2); - } - - /* Section headers */ - h1 { - font-size: var(--font-size-3xl); - font-weight: 500; - line-height: 1.2; - letter-spacing: -0.05em; - margin: 0; - color: var(--color-text); - - @media (max-width: 30rem) { - font-size: var(--font-size-2xl); - line-height: 1.25; - } - } - - /* Section descriptions */ - p { - margin: 0; - color: var(--color-text-secondary); - font-size: var(--font-size-md); - line-height: 1.5; - } - - /* API Keys Section */ - [data-slot="create-form"] { - display: flex; - flex-direction: column; - gap: var(--space-3); - padding: var(--space-4); - background-color: var(--color-bg-surface); - border: 1px solid var(--color-border); - border-radius: var(--space-2); - max-width: 32rem; - - input { - padding: var(--space-2) var(--space-3); - border: 1px solid var(--color-border); - border-radius: var(--space-2); - 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="key-list"] { - display: flex; - flex-direction: column; - gap: var(--space-2); - } - - [data-slot="key-item"] { - display: flex; - justify-content: space-between; - align-items: flex-start; - padding: var(--space-4); - background-color: var(--color-bg-surface); - border: 1px solid var(--color-border); - border-radius: var(--space-2); - gap: var(--space-4); - - @media (max-width: 30rem) { - flex-direction: column; - gap: var(--space-3); - } - } - [data-slot="key-info"] { - display: flex; - flex-direction: column; - gap: var(--space-1); - flex: 1; - } - - [data-slot="key-name"] { - font-size: var(--font-size-md); - font-weight: 500; - color: var(--color-text); - } - - [data-slot="key-value"] { - font-size: var(--font-size-xs); - font-family: var(--font-mono); - color: var(--color-text-secondary); - background-color: var(--color-bg); - padding: var(--space-1) var(--space-2); - border-radius: var(--space-1); - border: 1px solid var(--color-border-muted); - } - - [data-slot="key-meta"] { - font-size: var(--font-size-xs); - color: var(--color-text-disabled); - } - - [data-slot="key-actions"] { - display: flex; - gap: var(--space-2); - } - - [data-slot="empty-state"] { - padding: var(--space-8); - text-align: center; - color: var(--color-text-muted); - background-color: var(--color-bg-surface); - border: 1px solid var(--color-border); - border-radius: var(--space-2); - - p { - margin: 0; - font-size: var(--font-size-sm); - } - } - - /* Balance Section */ - [data-slot="balance"] { - display: flex; - flex-direction: column; - gap: var(--space-3); - padding: var(--space-4); - background-color: var(--color-bg-surface); - border: 1px solid var(--color-border); - border-radius: var(--space-2); - max-width: 32rem; - - p { - font-size: var(--font-size-2xl); - font-weight: 500; + a { color: var(--color-text); - margin: 0; - } - } - - /* Payment and Usage Items */ - [data-slot="payment-item"], - [data-slot="usage-item"] { - display: flex; - align-items: center; - gap: var(--space-4); - padding: var(--space-3); - background-color: var(--color-bg-surface); - border: 1px solid var(--color-border); - border-radius: var(--space-2); - font-size: var(--font-size-sm); - font-family: var(--font-mono); - - @media (max-width: 30rem) { - flex-direction: column; - align-items: flex-start; - gap: var(--space-2); - } - } - - [data-slot="payment-id"], - [data-slot="payment-amount"], - [data-slot="payment-date"], - [data-slot="usage-model"], - [data-slot="usage-tokens"], - [data-slot="usage-cost"], - [data-slot="usage-date"] { - color: var(--color-text-muted); - } - - /* Buttons */ - button { - padding: var(--space-2) var(--space-4); - border: 1px solid var(--color-border); - border-radius: var(--space-2); - background-color: var(--color-bg); - color: var(--color-text); - font-size: var(--font-size-sm); - font-family: var(--font-sans); - cursor: pointer; - transition: all 0.15s ease; - - &:hover { - background-color: var(--color-surface-hover); - border-color: var(--color-accent); - } - - &:active { - transform: translateY(1px); - } - - &:disabled { - opacity: 0.5; - cursor: not-allowed; - - &:hover { - background-color: var(--color-bg); - border-color: var(--color-border); - transform: none; - } - } - - &[color="primary"] { - background-color: var(--color-primary); - border-color: var(--color-primary); - color: var(--color-primary-text); - - &:hover { - background-color: var(--color-primary-hover); - border-color: var(--color-primary-hover); - } - } - - &[color="ghost"] { - background-color: transparent; - border-color: transparent; - color: var(--color-text-muted); - - &:hover { - background-color: var(--color-surface-hover); - border-color: var(--color-border); - color: var(--color-text); - } + text-decoration: underline; + text-underline-offset: var(--space-0-75); + text-decoration-thickness: 1px; + text-transform: uppercase; } } - - @media (prefers-color-scheme: dark) { - /* Dark mode specific adjustments if needed */ - } } diff --git a/cloud/app/src/style/token/color.css b/cloud/app/src/style/token/color.css index 675ffdde2..f1a097d2f 100644 --- a/cloud/app/src/style/token/color.css +++ b/cloud/app/src/style/token/color.css @@ -42,12 +42,11 @@ /* Surface colors */ --color-surface: var(--color-bg-surface); --color-surface-hover: var(--color-bg-elevated); - --color-border: var(--color-border); + --color-surface-border: var(--color-border); } @media (prefers-color-scheme: dark) { :root { - /* OpenCode dark theme colors */ --color-bg: #0c0c0e; --color-bg-surface: #161618; --color-bg-elevated: #1c1c1f; @@ -87,6 +86,6 @@ /* Surface colors */ --color-surface: var(--color-bg-surface); --color-surface-hover: var(--color-bg-elevated); - --color-border: var(--color-border); + --color-surface-border: var(--color-border); } } -- cgit v1.2.3