From 9a330b4f0f5a24a26e859c7a56daeedb397c3c67 Mon Sep 17 00:00:00 2001 From: Jay V Date: Fri, 29 Aug 2025 20:04:57 -0400 Subject: [PATCH] ignore: cloud keys section --- cloud/app/src/routes/workspace/[id].tsx | 111 ++++++++++++++------ cloud/app/src/routes/workspace/index.css | 123 ++++++++++++++--------- 2 files changed, 158 insertions(+), 76 deletions(-) diff --git a/cloud/app/src/routes/workspace/[id].tsx b/cloud/app/src/routes/workspace/[id].tsx index 07f4fc0d..cf6c0bdd 100644 --- a/cloud/app/src/routes/workspace/[id].tsx +++ b/cloud/app/src/routes/workspace/[id].tsx @@ -4,6 +4,7 @@ import { action, createAsync, revalidate, query, useAction, useSubmission, json import { createEffect, createSignal, For, onMount, Show } from "solid-js" import { getActor } from "~/context/auth" import { withActor } from "~/context/auth.withActor" +import { IconCopy, IconCheck } from "~/component/icon" import "./index.css" import { User } from "@opencode/cloud-core/user.js" import { Actor } from "@opencode/cloud-core/actor.js" @@ -142,7 +143,31 @@ const dummyPaymentData = [ }, ] -export default function () { +const dummyApiKeyData = [ + { + id: "key_1Ab2Cd3Ef4Gh5678", + name: "Production API", + key: "oc_live_sk_1Ab2Cd3Ef4Gh567890123456789012345678901234567890", + timeCreated: new Date("2025-01-28T14:32:00Z"), + timeUsed: new Date("2025-01-29T09:15:00Z"), + }, + { + id: "key_9Ij8Kl7Mn6Op5432", + name: "Development Key", + key: "oc_test_sk_9Ij8Kl7Mn6Op543210987654321098765432109876543210", + timeCreated: new Date("2025-01-25T09:18:00Z"), + timeUsed: null, + }, + { + id: "key_5Qr4St3Uv2Wx1098", + name: "CI/CD Pipeline", + key: "oc_live_sk_5Qr4St3Uv2Wx109876543210987654321098765432109876", + timeCreated: new Date("2025-01-20T16:45:00Z"), + timeUsed: new Date("2025-01-28T12:30:00Z"), + }, +] + +export default function() { const actor = createAsync(() => getActor()) onMount(() => { console.log("MOUNTED", actor()) @@ -157,6 +182,7 @@ export default function () { const createKeySubmission = useSubmission(createKey) const [showCreateForm, setShowCreateForm] = createSignal(false) const [keyName, setKeyName] = createSignal("") + const [copiedKeyId, setCopiedKeyId] = createSignal(null) const formatDate = (date: Date) => { return date.toLocaleDateString() @@ -201,6 +227,16 @@ export default function () { } } + const copyKeyToClipboard = async (text: string, keyId: string) => { + try { + await navigator.clipboard.writeText(text) + setCopiedKeyId(keyId) + setTimeout(() => setCopiedKeyId(null), 1500) + } catch (error) { + console.error("Failed to copy to clipboard:", error) + } + } + const handleCreateKey = async () => { if (!keyName().trim()) return @@ -214,7 +250,7 @@ export default function () { } const handleDeleteKey = async (keyId: string) => { - if (!confirm("Are you sure you want to delete this API key? This action cannot be undone.")) { + if (!confirm("Are you sure you want to delete this API key?")) { return } @@ -291,7 +327,7 @@ export default function () { {/* API Keys Section */} -
+

API Keys

Manage your API keys for accessing opencode services.

@@ -339,36 +375,55 @@ export default function () { Create API Key -
- + 0} fallback={
-

Create an API key to access opencode gateway

+

Create an opencode Gateway API key

} > - {(key) => ( -
-
-
{key.name}
-
{formatKey(key.key)}
-
- Created: {formatDate(key.timeCreated)} - {key.timeUsed && ` • Last used: ${formatDate(key.timeUsed)}`} -
-
-
- - -
-
- )} -
+ + + + + + + + + + + + {/* Real data: keys() */} + {(key) => ( + + + + + + + )} + + +
NameKeyCreated
{key.name} +
copyKeyToClipboard(key.key, key.id)} title="Click to copy API key"> + {formatKey(key.key)} + } + > + + +
+
+ {formatDateForTable(key.timeCreated)} + + +
+
diff --git a/cloud/app/src/routes/workspace/index.css b/cloud/app/src/routes/workspace/index.css index 0d04ea25..887469e3 100644 --- a/cloud/app/src/routes/workspace/index.css +++ b/cloud/app/src/routes/workspace/index.css @@ -203,59 +203,84 @@ a { } } - [data-slot="key-list"] { - display: flex; - flex-direction: column; - gap: var(--space-2); + [data-slot="api-keys-table"] { + overflow-x: auto; } - [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(--border-radius-sm); - gap: var(--space-4); + [data-slot="api-keys-table-element"] { + width: 100%; + border-collapse: collapse; + font-size: var(--font-size-sm); - @media (max-width: 30rem) { - flex-direction: column; - gap: var(--space-3); + thead { + border-bottom: 1px solid var(--color-border); } - } - [data-slot="key-info"] { - display: flex; - flex-direction: column; - gap: var(--space-1); - flex: 1; - } + th { + padding: var(--space-3) var(--space-4); + text-align: left; + font-weight: normal; + color: var(--color-text-muted); + text-transform: uppercase; + } - [data-slot="key-name"] { - font-size: var(--font-size-md); - font-weight: 500; - color: var(--color-text); - } + 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-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(--border-radius-sm); - border: 1px solid var(--color-border-muted); - } + &[data-slot="key-name"] { + color: var(--color-text); + font-family: var(--font-sans); + font-weight: 500; + } - [data-slot="key-meta"] { - font-size: var(--font-size-xs); - color: var(--color-text-disabled); - } + &[data-slot="key-value"] { + font-family: var(--font-mono); - [data-slot="key-actions"] { - display: flex; - gap: var(--space-2); + div { + cursor: pointer; + display: flex; + align-items: center; + gap: var(--space-2); + } + } + + &[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; + } + } + } } } @@ -321,8 +346,9 @@ a { th { padding: var(--space-3) var(--space-4); text-align: left; - font-weight: 600; - color: var(--color-text-secondary); + font-weight: normal; + color: var(--color-text-muted); + text-transform: uppercase; } td { @@ -394,8 +420,9 @@ a { th { padding: var(--space-3) var(--space-4); text-align: left; - font-weight: 600; - color: var(--color-text-secondary); + font-weight: normal; + color: var(--color-text-muted); + text-transform: uppercase; } td {