summaryrefslogtreecommitdiffhomepage
path: root/cloud/app/src
diff options
context:
space:
mode:
authorFrank <[email protected]>2025-08-28 16:44:55 -0400
committerFrank <[email protected]>2025-08-28 16:44:55 -0400
commitc6ef92634d0ae026a59e023e69847b481975462b (patch)
treec88bfbbabf1f46ca922e3212dd929b34d6229a1a /cloud/app/src
parentf97fdceb01c69ca563e755c6d50312ef7352f663 (diff)
downloadopencode-c6ef92634d0ae026a59e023e69847b481975462b.tar.gz
opencode-c6ef92634d0ae026a59e023e69847b481975462b.zip
wip cloud
Diffstat (limited to 'cloud/app/src')
-rw-r--r--cloud/app/src/routes/[workspaceID].tsx186
-rw-r--r--cloud/app/src/style/token/color.css4
2 files changed, 181 insertions, 9 deletions
diff --git a/cloud/app/src/routes/[workspaceID].tsx b/cloud/app/src/routes/[workspaceID].tsx
index 706a64323..98d03753f 100644
--- a/cloud/app/src/routes/[workspaceID].tsx
+++ b/cloud/app/src/routes/[workspaceID].tsx
@@ -1,15 +1,187 @@
-import { createAsync, query } from "@solidjs/router"
+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 { createSignal, For, Show } from "solid-js"
import { getActor, withActor } from "~/context/auth"
-const getPosts = query(async () => {
+/////////////////////////////////////
+// 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(() => {
- return "ok"
+ 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 }
})
-}, "posts")
+}, "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 const route = {
+// preload: () => listKeys(),
+//}
export default function () {
- const actor = createAsync(async () => getActor())
- return <div>{JSON.stringify(actor())}</div>
+ const actor = createAsync(() => getActor())
+ 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)
+ }
+ }
+
+ return (
+ <div>
+ <h1>Actor</h1>
+ <div>{JSON.stringify(actor())}</div>
+ <h1>API Keys</h1>
+ <Show
+ when={!showCreateForm()}
+ fallback={
+ <div data-slot="create-form">
+ <input
+ data-component="input"
+ type="text"
+ placeholder="Enter key name"
+ value={keyName()}
+ onInput={(e) => setKeyName(e.currentTarget.value)}
+ onKeyPress={(e) => e.key === "Enter" && handleCreateKey()}
+ />
+ <div data-slot="form-actions">
+ <button
+ color="primary"
+ disabled={createKeySubmission.pending || !keyName().trim()}
+ onClick={handleCreateKey}
+ >
+ {createKeySubmission.pending ? "Creating..." : "Create"}
+ </button>
+ <button
+ color="ghost"
+ onClick={() => {
+ setShowCreateForm(false)
+ setKeyName("")
+ }}
+ >
+ Cancel
+ </button>
+ </div>
+ </div>
+ }
+ >
+ <button
+ color="primary"
+ onClick={() => {
+ console.log("clicked")
+ setShowCreateForm(true)
+ }}
+ >
+ Create API Key
+ </button>
+ </Show>
+ <div data-slot="key-list">
+ <For
+ each={keys()}
+ fallback={
+ <div data-slot="empty-state">
+ <p>Create an API key to access opencode gateway</p>
+ </div>
+ }
+ >
+ {(key) => (
+ <div data-slot="key-item">
+ <div data-slot="key-info">
+ <div data-slot="key-name">{key.name}</div>
+ <div data-slot="key-value">{formatKey(key.key)}</div>
+ <div data-slot="key-meta">
+ Created: {formatDate(key.timeCreated)}
+ {key.timeUsed && ` • Last used: ${formatDate(key.timeUsed)}`}
+ </div>
+ </div>
+ <div data-slot="key-actions">
+ <button color="ghost" onClick={() => copyToClipboard(key.key)} title="Copy API key">
+ Copy
+ </button>
+ <button color="ghost" onClick={() => handleDeleteKey(key.id)} title="Delete API key">
+ Delete
+ </button>
+ </div>
+ </div>
+ )}
+ </For>
+ </div>
+ </div>
+ )
}
diff --git a/cloud/app/src/style/token/color.css b/cloud/app/src/style/token/color.css
index 35846acd9..31d11e2dd 100644
--- a/cloud/app/src/style/token/color.css
+++ b/cloud/app/src/style/token/color.css
@@ -47,7 +47,7 @@
@media (prefers-color-scheme: dark) {
:root {
/* OpenCode dark theme colors */
- --color-bg: #0c0c0e;
+ /*--color-bg: #0c0c0e;*/
--color-bg-surface: #161618;
--color-bg-elevated: #1c1c1f;
@@ -87,4 +87,4 @@
--color-surface-hover: var(--color-bg-elevated);
--color-border: var(--color-border);
}
-}
+} \ No newline at end of file