@jonsoc/console-app 1.1.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/.opencode/agent/css.md +149 -0
  2. package/README.md +32 -0
  3. package/package.json +49 -0
  4. package/public/apple-touch-icon-v3.png +1 -0
  5. package/public/apple-touch-icon.png +1 -0
  6. package/public/email +1 -0
  7. package/public/favicon-96x96-v3.png +1 -0
  8. package/public/favicon-96x96.png +1 -0
  9. package/public/favicon-v3.ico +1 -0
  10. package/public/favicon-v3.svg +1 -0
  11. package/public/favicon.ico +1 -0
  12. package/public/favicon.svg +1 -0
  13. package/public/opencode-brand-assets.zip +0 -0
  14. package/public/robots.txt +6 -0
  15. package/public/site.webmanifest +1 -0
  16. package/public/social-share-black.png +1 -0
  17. package/public/social-share-zen.png +1 -0
  18. package/public/social-share.png +1 -0
  19. package/public/theme.json +182 -0
  20. package/public/web-app-manifest-192x192.png +1 -0
  21. package/public/web-app-manifest-512x512.png +1 -0
  22. package/script/generate-sitemap.ts +103 -0
  23. package/src/app.css +1 -0
  24. package/src/app.tsx +27 -0
  25. package/src/asset/black/hero.png +0 -0
  26. package/src/asset/brand/opencode-brand-assets.zip +0 -0
  27. package/src/asset/brand/opencode-logo-dark.png +0 -0
  28. package/src/asset/brand/opencode-logo-dark.svg +16 -0
  29. package/src/asset/brand/opencode-logo-light.png +0 -0
  30. package/src/asset/brand/opencode-logo-light.svg +16 -0
  31. package/src/asset/brand/opencode-wordmark-dark.png +0 -0
  32. package/src/asset/brand/opencode-wordmark-dark.svg +30 -0
  33. package/src/asset/brand/opencode-wordmark-light.png +0 -0
  34. package/src/asset/brand/opencode-wordmark-light.svg +30 -0
  35. package/src/asset/brand/opencode-wordmark-simple-dark.png +0 -0
  36. package/src/asset/brand/opencode-wordmark-simple-dark.svg +22 -0
  37. package/src/asset/brand/opencode-wordmark-simple-light.png +0 -0
  38. package/src/asset/brand/opencode-wordmark-simple-light.svg +22 -0
  39. package/src/asset/brand/preview-opencode-dark.png +0 -0
  40. package/src/asset/brand/preview-opencode-logo-dark.png +0 -0
  41. package/src/asset/brand/preview-opencode-logo-light.png +0 -0
  42. package/src/asset/brand/preview-opencode-wordmark-dark.png +0 -0
  43. package/src/asset/brand/preview-opencode-wordmark-light.png +0 -0
  44. package/src/asset/brand/preview-opencode-wordmark-simple-dark.png +0 -0
  45. package/src/asset/brand/preview-opencode-wordmark-simple-light.png +0 -0
  46. package/src/asset/lander/avatar-adam.png +0 -0
  47. package/src/asset/lander/avatar-david.png +0 -0
  48. package/src/asset/lander/avatar-dax.png +0 -0
  49. package/src/asset/lander/avatar-frank.png +0 -0
  50. package/src/asset/lander/avatar-jay.png +0 -0
  51. package/src/asset/lander/brand-assets-dark.svg +10 -0
  52. package/src/asset/lander/brand-assets-light.svg +10 -0
  53. package/src/asset/lander/brand.png +0 -0
  54. package/src/asset/lander/check.svg +3 -0
  55. package/src/asset/lander/copy.svg +3 -0
  56. package/src/asset/lander/desktop-app-icon.png +0 -0
  57. package/src/asset/lander/dock.png +0 -0
  58. package/src/asset/lander/logo-dark.svg +11 -0
  59. package/src/asset/lander/logo-light.svg +11 -0
  60. package/src/asset/lander/opencode-comparison-min.mp4 +0 -0
  61. package/src/asset/lander/opencode-comparison-poster.png +0 -0
  62. package/src/asset/lander/opencode-desktop-icon.png +0 -0
  63. package/src/asset/lander/opencode-logo-dark.svg +11 -0
  64. package/src/asset/lander/opencode-logo-light.svg +11 -0
  65. package/src/asset/lander/opencode-min.mp4 +0 -0
  66. package/src/asset/lander/opencode-poster.png +0 -0
  67. package/src/asset/lander/opencode-wordmark-dark.svg +25 -0
  68. package/src/asset/lander/opencode-wordmark-light.svg +25 -0
  69. package/src/asset/lander/screenshot-github.png +0 -0
  70. package/src/asset/lander/screenshot-splash.png +0 -0
  71. package/src/asset/lander/screenshot-vscode.png +0 -0
  72. package/src/asset/lander/screenshot.png +0 -0
  73. package/src/asset/lander/wordmark-dark.svg +3 -0
  74. package/src/asset/lander/wordmark-light.svg +3 -0
  75. package/src/asset/logo-ornate-dark.svg +18 -0
  76. package/src/asset/logo-ornate-light.svg +18 -0
  77. package/src/asset/logo.svg +18 -0
  78. package/src/asset/zen-ornate-dark.svg +8 -0
  79. package/src/asset/zen-ornate-light.svg +8 -0
  80. package/src/component/dropdown.css +80 -0
  81. package/src/component/dropdown.tsx +79 -0
  82. package/src/component/email-signup.tsx +48 -0
  83. package/src/component/faq.tsx +33 -0
  84. package/src/component/footer.tsx +38 -0
  85. package/src/component/header-context-menu.css +63 -0
  86. package/src/component/header.tsx +279 -0
  87. package/src/component/icon.tsx +257 -0
  88. package/src/component/legal.tsx +20 -0
  89. package/src/component/modal.css +66 -0
  90. package/src/component/modal.tsx +24 -0
  91. package/src/component/spotlight.css +15 -0
  92. package/src/component/spotlight.tsx +820 -0
  93. package/src/config.ts +29 -0
  94. package/src/context/auth.session.ts +0 -0
  95. package/src/context/auth.ts +116 -0
  96. package/src/context/auth.withActor.ts +7 -0
  97. package/src/entry-client.tsx +4 -0
  98. package/src/entry-server.tsx +30 -0
  99. package/src/global.d.ts +5 -0
  100. package/src/lib/github.ts +38 -0
  101. package/src/middleware.ts +5 -0
  102. package/src/routes/[...404].css +130 -0
  103. package/src/routes/[...404].tsx +38 -0
  104. package/src/routes/api/enterprise.ts +47 -0
  105. package/src/routes/auth/[...callback].ts +41 -0
  106. package/src/routes/auth/authorize.ts +10 -0
  107. package/src/routes/auth/index.ts +12 -0
  108. package/src/routes/auth/logout.ts +17 -0
  109. package/src/routes/auth/status.ts +7 -0
  110. package/src/routes/bench/[id].tsx +365 -0
  111. package/src/routes/bench/index.tsx +86 -0
  112. package/src/routes/bench/submission.ts +29 -0
  113. package/src/routes/black/common.tsx +62 -0
  114. package/src/routes/black/index.tsx +108 -0
  115. package/src/routes/black/subscribe/[plan].tsx +449 -0
  116. package/src/routes/black/workspace.css +214 -0
  117. package/src/routes/black/workspace.tsx +229 -0
  118. package/src/routes/black.css +828 -0
  119. package/src/routes/black.tsx +285 -0
  120. package/src/routes/brand/index.css +555 -0
  121. package/src/routes/brand/index.tsx +252 -0
  122. package/src/routes/changelog/index.css +477 -0
  123. package/src/routes/changelog/index.tsx +147 -0
  124. package/src/routes/debug/index.ts +13 -0
  125. package/src/routes/desktop-feedback.ts +5 -0
  126. package/src/routes/discord.ts +5 -0
  127. package/src/routes/docs/[...path].ts +20 -0
  128. package/src/routes/docs/index.ts +20 -0
  129. package/src/routes/download/[platform].ts +38 -0
  130. package/src/routes/download/index.css +750 -0
  131. package/src/routes/download/index.tsx +482 -0
  132. package/src/routes/download/types.ts +4 -0
  133. package/src/routes/enterprise/index.css +578 -0
  134. package/src/routes/enterprise/index.tsx +251 -0
  135. package/src/routes/index.css +1251 -0
  136. package/src/routes/index.tsx +840 -0
  137. package/src/routes/legal/privacy-policy/index.css +343 -0
  138. package/src/routes/legal/privacy-policy/index.tsx +1512 -0
  139. package/src/routes/legal/terms-of-service/index.css +254 -0
  140. package/src/routes/legal/terms-of-service/index.tsx +512 -0
  141. package/src/routes/openapi.json.ts +7 -0
  142. package/src/routes/s/[id].ts +20 -0
  143. package/src/routes/stripe/webhook.ts +532 -0
  144. package/src/routes/t/[...path].tsx +20 -0
  145. package/src/routes/temp.tsx +172 -0
  146. package/src/routes/user-menu.css +18 -0
  147. package/src/routes/user-menu.tsx +32 -0
  148. package/src/routes/workspace/[id]/billing/billing-section.module.css +185 -0
  149. package/src/routes/workspace/[id]/billing/billing-section.tsx +240 -0
  150. package/src/routes/workspace/[id]/billing/black-section.module.css +142 -0
  151. package/src/routes/workspace/[id]/billing/black-section.tsx +269 -0
  152. package/src/routes/workspace/[id]/billing/black-waitlist-section.module.css +23 -0
  153. package/src/routes/workspace/[id]/billing/index.tsx +32 -0
  154. package/src/routes/workspace/[id]/billing/monthly-limit-section.module.css +96 -0
  155. package/src/routes/workspace/[id]/billing/monthly-limit-section.tsx +133 -0
  156. package/src/routes/workspace/[id]/billing/payment-section.module.css +93 -0
  157. package/src/routes/workspace/[id]/billing/payment-section.tsx +122 -0
  158. package/src/routes/workspace/[id]/billing/reload-section.module.css +261 -0
  159. package/src/routes/workspace/[id]/billing/reload-section.tsx +213 -0
  160. package/src/routes/workspace/[id]/graph-section.module.css +145 -0
  161. package/src/routes/workspace/[id]/graph-section.tsx +475 -0
  162. package/src/routes/workspace/[id]/index.tsx +81 -0
  163. package/src/routes/workspace/[id]/keys/index.tsx +11 -0
  164. package/src/routes/workspace/[id]/keys/key-section.module.css +197 -0
  165. package/src/routes/workspace/[id]/keys/key-section.tsx +176 -0
  166. package/src/routes/workspace/[id]/members/index.tsx +11 -0
  167. package/src/routes/workspace/[id]/members/member-section.module.css +249 -0
  168. package/src/routes/workspace/[id]/members/member-section.tsx +343 -0
  169. package/src/routes/workspace/[id]/members/role-dropdown.css +72 -0
  170. package/src/routes/workspace/[id]/members/role-dropdown.tsx +43 -0
  171. package/src/routes/workspace/[id]/model-section.module.css +173 -0
  172. package/src/routes/workspace/[id]/model-section.tsx +174 -0
  173. package/src/routes/workspace/[id]/new-user-section.module.css +143 -0
  174. package/src/routes/workspace/[id]/new-user-section.tsx +104 -0
  175. package/src/routes/workspace/[id]/provider-section.module.css +138 -0
  176. package/src/routes/workspace/[id]/provider-section.tsx +188 -0
  177. package/src/routes/workspace/[id]/settings/index.tsx +11 -0
  178. package/src/routes/workspace/[id]/settings/settings-section.module.css +94 -0
  179. package/src/routes/workspace/[id]/settings/settings-section.tsx +122 -0
  180. package/src/routes/workspace/[id]/usage-section.module.css +185 -0
  181. package/src/routes/workspace/[id]/usage-section.tsx +200 -0
  182. package/src/routes/workspace/[id].css +308 -0
  183. package/src/routes/workspace/[id].tsx +62 -0
  184. package/src/routes/workspace/common.tsx +120 -0
  185. package/src/routes/workspace-picker.css +74 -0
  186. package/src/routes/workspace-picker.tsx +122 -0
  187. package/src/routes/workspace.css +107 -0
  188. package/src/routes/workspace.tsx +38 -0
  189. package/src/routes/zen/index.css +866 -0
  190. package/src/routes/zen/index.tsx +343 -0
  191. package/src/routes/zen/util/dataDumper.ts +44 -0
  192. package/src/routes/zen/util/error.ts +13 -0
  193. package/src/routes/zen/util/handler.ts +784 -0
  194. package/src/routes/zen/util/logger.ts +12 -0
  195. package/src/routes/zen/util/provider/anthropic.ts +752 -0
  196. package/src/routes/zen/util/provider/google.ts +75 -0
  197. package/src/routes/zen/util/provider/openai-compatible.ts +546 -0
  198. package/src/routes/zen/util/provider/openai.ts +630 -0
  199. package/src/routes/zen/util/provider/provider.ts +210 -0
  200. package/src/routes/zen/util/rateLimiter.ts +41 -0
  201. package/src/routes/zen/util/stickyProviderTracker.ts +16 -0
  202. package/src/routes/zen/util/trialLimiter.ts +49 -0
  203. package/src/routes/zen/v1/chat/completions.ts +11 -0
  204. package/src/routes/zen/v1/messages.ts +11 -0
  205. package/src/routes/zen/v1/models/[model].ts +13 -0
  206. package/src/routes/zen/v1/models.ts +60 -0
  207. package/src/routes/zen/v1/responses.ts +11 -0
  208. package/src/style/base.css +21 -0
  209. package/src/style/component/button.css +102 -0
  210. package/src/style/index.css +8 -0
  211. package/src/style/reset.css +76 -0
  212. package/src/style/token/color.css +91 -0
  213. package/src/style/token/font.css +21 -0
  214. package/src/style/token/space.css +46 -0
  215. package/sst-env.d.ts +9 -0
  216. package/tsconfig.json +21 -0
  217. package/vite.config.ts +25 -0
@@ -0,0 +1,11 @@
1
+ import { KeySection } from "./key-section"
2
+
3
+ export default function () {
4
+ return (
5
+ <div data-page="workspace-[id]">
6
+ <div data-slot="sections">
7
+ <KeySection />
8
+ </div>
9
+ </div>
10
+ )
11
+ }
@@ -0,0 +1,197 @@
1
+ .root {
2
+ [data-slot="title-row"] {
3
+ display: flex;
4
+ justify-content: space-between;
5
+ align-items: center;
6
+ gap: var(--space-4);
7
+ }
8
+
9
+ [data-component="empty-state"] {
10
+ padding: var(--space-20) var(--space-6);
11
+ text-align: center;
12
+ border: 1px dashed var(--color-border);
13
+ border-radius: var(--border-radius-sm);
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: var(--space-2);
17
+
18
+ p {
19
+ line-height: 1.5;
20
+ font-size: var(--font-size-sm);
21
+ color: var(--color-text-muted);
22
+ }
23
+ }
24
+
25
+ [data-slot="create-form"] {
26
+ display: flex;
27
+ flex-direction: column;
28
+ gap: var(--space-3);
29
+ padding: var(--space-4);
30
+ border: 1px solid var(--color-border);
31
+ border-radius: var(--border-radius-sm);
32
+
33
+ [data-slot="input-container"] {
34
+ display: flex;
35
+ flex-direction: column;
36
+ gap: var(--space-1);
37
+ }
38
+
39
+ @media (max-width: 30rem) {
40
+ gap: var(--space-2);
41
+ }
42
+
43
+ input {
44
+ flex: 1;
45
+ padding: var(--space-2) var(--space-3);
46
+ border: 1px solid var(--color-border);
47
+ border-radius: var(--border-radius-sm);
48
+ background-color: var(--color-bg);
49
+ color: var(--color-text);
50
+ font-size: var(--font-size-sm);
51
+ font-family: var(--font-mono);
52
+
53
+ &:focus {
54
+ outline: none;
55
+ border-color: var(--color-accent);
56
+ }
57
+
58
+ &::placeholder {
59
+ color: var(--color-text-disabled);
60
+ }
61
+ }
62
+
63
+ [data-slot="form-actions"] {
64
+ display: flex;
65
+ gap: var(--space-2);
66
+ }
67
+
68
+ [data-slot="form-error"] {
69
+ color: var(--color-danger);
70
+ font-size: var(--font-size-sm);
71
+ margin-top: var(--space-1);
72
+ line-height: 1.4;
73
+ }
74
+ }
75
+
76
+ [data-slot="api-keys-table"] {
77
+ overflow-x: auto;
78
+ }
79
+
80
+ [data-slot="api-keys-table-element"] {
81
+ width: 100%;
82
+ border-collapse: collapse;
83
+ font-size: var(--font-size-sm);
84
+
85
+ thead {
86
+ border-bottom: 1px solid var(--color-border);
87
+ }
88
+
89
+ th {
90
+ padding: var(--space-3) var(--space-4);
91
+ text-align: left;
92
+ font-weight: normal;
93
+ color: var(--color-text-muted);
94
+ text-transform: uppercase;
95
+ }
96
+
97
+ td {
98
+ padding: var(--space-3) var(--space-4);
99
+ border-bottom: 1px solid var(--color-border-muted);
100
+ color: var(--color-text-muted);
101
+ font-family: var(--font-mono);
102
+
103
+ &[data-slot="key-name"] {
104
+ color: var(--color-text);
105
+ font-family: var(--font-sans);
106
+ font-weight: 500;
107
+ }
108
+
109
+ &[data-slot="key-value"] {
110
+ font-family: var(--font-mono);
111
+
112
+ button {
113
+ display: flex;
114
+ align-items: center;
115
+ gap: var(--space-2);
116
+ padding: var(--space-2) var(--space-3);
117
+ margin-left: calc(-1 * var(--space-3));
118
+ font-size: var(--font-size-sm);
119
+ font-weight: 400;
120
+ border: none;
121
+ background-color: transparent;
122
+ color: var(--color-text-muted);
123
+ font-family: var(--font-mono);
124
+ border-radius: var(--border-radius-sm);
125
+ cursor: pointer;
126
+ transition: all 0.15s ease;
127
+ text-transform: none;
128
+
129
+ &:hover:not(:disabled) {
130
+ background-color: var(--color-bg-surface);
131
+ color: var(--color-text);
132
+ }
133
+
134
+ &:disabled {
135
+ cursor: default;
136
+ color: var(--color-text);
137
+ }
138
+
139
+ span {
140
+ font-family: inherit;
141
+ }
142
+ }
143
+ }
144
+
145
+ &[data-slot="key-date"] {
146
+ color: var(--color-text);
147
+ }
148
+
149
+ &[data-slot="key-actions"] {
150
+ font-family: var(--font-sans);
151
+
152
+ button {
153
+ opacity: 0;
154
+ pointer-events: none;
155
+ transition: opacity 0.15s ease;
156
+ }
157
+ }
158
+ }
159
+
160
+ tbody tr {
161
+ &:hover {
162
+ [data-slot="key-actions"] button {
163
+ opacity: 1;
164
+ pointer-events: auto;
165
+ }
166
+ }
167
+
168
+ &:last-child td {
169
+ border-bottom: none;
170
+ }
171
+ }
172
+
173
+ @media (max-width: 40rem) {
174
+ th,
175
+ td {
176
+ padding: var(--space-2) var(--space-3);
177
+ font-size: var(--font-size-xs);
178
+ }
179
+
180
+ th {
181
+ &:nth-child(3)
182
+
183
+ /* Date */ {
184
+ display: none;
185
+ }
186
+ }
187
+
188
+ td {
189
+ &:nth-child(3)
190
+
191
+ /* Date */ {
192
+ display: none;
193
+ }
194
+ }
195
+ }
196
+ }
197
+ }
@@ -0,0 +1,176 @@
1
+ import { json, query, action, useParams, createAsync, useSubmission } from "@solidjs/router"
2
+ import { createEffect, createSignal, For, Show } from "solid-js"
3
+ import { IconCopy, IconCheck } from "~/component/icon"
4
+ import { Key } from "@jonsoc/console-core/key.js"
5
+ import { withActor } from "~/context/auth.withActor"
6
+ import { createStore } from "solid-js/store"
7
+ import { formatDateUTC, formatDateForTable } from "../../common"
8
+ import styles from "./key-section.module.css"
9
+ import { Actor } from "@jonsoc/console-core/actor.js"
10
+
11
+ const removeKey = action(async (form: FormData) => {
12
+ "use server"
13
+ const id = form.get("id")?.toString()
14
+ if (!id) return { error: "ID is required" }
15
+ const workspaceID = form.get("workspaceID")?.toString()
16
+ if (!workspaceID) return { error: "Workspace ID is required" }
17
+ return json(await withActor(() => Key.remove({ id }), workspaceID), { revalidate: listKeys.key })
18
+ }, "key.remove")
19
+
20
+ const createKey = action(async (form: FormData) => {
21
+ "use server"
22
+ const name = form.get("name")?.toString().trim()
23
+ if (!name) return { error: "Name is required" }
24
+ const workspaceID = form.get("workspaceID")?.toString()
25
+ if (!workspaceID) return { error: "Workspace ID is required" }
26
+ return json(
27
+ await withActor(
28
+ () =>
29
+ Key.create({
30
+ userID: Actor.assert("user").properties.userID,
31
+ name,
32
+ })
33
+ .then((data) => ({ error: undefined, data }))
34
+ .catch((e) => ({ error: e.message as string })),
35
+ workspaceID,
36
+ ),
37
+ { revalidate: listKeys.key },
38
+ )
39
+ }, "key.create")
40
+
41
+ const listKeys = query(async (workspaceID: string) => {
42
+ "use server"
43
+ return withActor(() => Key.list(), workspaceID)
44
+ }, "key.list")
45
+
46
+ export function KeySection() {
47
+ const params = useParams()
48
+ const keys = createAsync(() => listKeys(params.id!))
49
+ const submission = useSubmission(createKey)
50
+ const [store, setStore] = createStore({ show: false })
51
+
52
+ let input: HTMLInputElement
53
+
54
+ createEffect(() => {
55
+ if (!submission.pending && submission.result && !submission.result.error) {
56
+ setStore("show", false)
57
+ }
58
+ })
59
+
60
+ function show() {
61
+ while (true) {
62
+ submission.clear()
63
+ if (!submission.result) break
64
+ }
65
+ setStore("show", true)
66
+ setTimeout(() => input?.focus(), 0)
67
+ }
68
+
69
+ function hide() {
70
+ setStore("show", false)
71
+ }
72
+
73
+ return (
74
+ <section class={styles.root}>
75
+ <div data-slot="section-title">
76
+ <h2>API Keys</h2>
77
+ <div data-slot="title-row">
78
+ <p>Manage your API keys for accessing jonsoc services.</p>
79
+ <button data-color="primary" onClick={() => show()}>
80
+ Create API Key
81
+ </button>
82
+ </div>
83
+ </div>
84
+ <Show when={store.show}>
85
+ <form action={createKey} method="post" data-slot="create-form">
86
+ <div data-slot="input-container">
87
+ <input
88
+ ref={(r) => (input = r)}
89
+ data-component="input"
90
+ name="name"
91
+ type="text"
92
+ placeholder="Enter key name"
93
+ />
94
+ <Show when={submission.result && submission.result.error}>
95
+ {(err) => <div data-slot="form-error">{err()}</div>}
96
+ </Show>
97
+ </div>
98
+ <input type="hidden" name="workspaceID" value={params.id} />
99
+ <div data-slot="form-actions">
100
+ <button type="reset" data-color="ghost" onClick={() => hide()}>
101
+ Cancel
102
+ </button>
103
+ <button type="submit" data-color="primary" disabled={submission.pending}>
104
+ {submission.pending ? "Creating..." : "Create"}
105
+ </button>
106
+ </div>
107
+ </form>
108
+ </Show>
109
+ <div data-slot="api-keys-table">
110
+ <Show
111
+ when={keys()?.length}
112
+ fallback={
113
+ <div data-component="empty-state">
114
+ <p>Create an jonsoc Gateway API key</p>
115
+ </div>
116
+ }
117
+ >
118
+ <table data-slot="api-keys-table-element">
119
+ <thead>
120
+ <tr>
121
+ <th>Name</th>
122
+ <th>Key</th>
123
+ <th>Created By</th>
124
+ <th>Last Used</th>
125
+ <th></th>
126
+ </tr>
127
+ </thead>
128
+ <tbody>
129
+ <For each={keys()!}>
130
+ {(key) => {
131
+ const [copied, setCopied] = createSignal(false)
132
+ // const submission = useSubmission(removeKey, ([fd]) => fd.get("id")?.toString() === key.id)
133
+ return (
134
+ <tr>
135
+ <td data-slot="key-name">{key.name}</td>
136
+ <td data-slot="key-value">
137
+ <Show when={key.key} fallback={<span>{key.keyDisplay}</span>}>
138
+ <button
139
+ data-color="ghost"
140
+ disabled={copied()}
141
+ onClick={async () => {
142
+ await navigator.clipboard.writeText(key.key!)
143
+ setCopied(true)
144
+ setTimeout(() => setCopied(false), 1000)
145
+ }}
146
+ title="Copy API key"
147
+ >
148
+ <span>{key.keyDisplay}</span>
149
+ <Show when={copied()} fallback={<IconCopy style={{ width: "14px", height: "14px" }} />}>
150
+ <IconCheck style={{ width: "14px", height: "14px" }} />
151
+ </Show>
152
+ </button>
153
+ </Show>
154
+ </td>
155
+ <td data-slot="key-user-email">{key.email}</td>
156
+ <td data-slot="key-last-used" title={key.timeUsed ? formatDateUTC(key.timeUsed) : undefined}>
157
+ {key.timeUsed ? formatDateForTable(key.timeUsed) : "-"}
158
+ </td>
159
+ <td data-slot="key-actions">
160
+ <form action={removeKey} method="post">
161
+ <input type="hidden" name="id" value={key.id} />
162
+ <input type="hidden" name="workspaceID" value={params.id} />
163
+ <button data-color="ghost">Delete</button>
164
+ </form>
165
+ </td>
166
+ </tr>
167
+ )
168
+ }}
169
+ </For>
170
+ </tbody>
171
+ </table>
172
+ </Show>
173
+ </div>
174
+ </section>
175
+ )
176
+ }
@@ -0,0 +1,11 @@
1
+ import { MemberSection } from "./member-section"
2
+
3
+ export default function () {
4
+ return (
5
+ <div data-page="workspace-[id]">
6
+ <div data-slot="sections">
7
+ <MemberSection />
8
+ </div>
9
+ </div>
10
+ )
11
+ }
@@ -0,0 +1,249 @@
1
+ .root {
2
+ [data-slot="title-row"] {
3
+ display: flex;
4
+ justify-content: space-between;
5
+ align-items: center;
6
+ gap: var(--space-4);
7
+ }
8
+
9
+ [data-component="empty-state"] {
10
+ padding: var(--space-20) var(--space-6);
11
+ text-align: center;
12
+ border: 1px dashed var(--color-border);
13
+ border-radius: var(--border-radius-sm);
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: var(--space-2);
17
+
18
+ p {
19
+ line-height: 1.5;
20
+ font-size: var(--font-size-sm);
21
+ color: var(--color-text-muted);
22
+ }
23
+ }
24
+
25
+ [data-slot="beta-notice"] {
26
+ padding: var(--space-3) var(--space-4);
27
+ border: 1px solid var(--color-border);
28
+ border-radius: var(--border-radius-sm);
29
+ background-color: var(--color-bg-surface);
30
+ font-size: var(--font-size-sm);
31
+ color: var(--color-text-secondary);
32
+ line-height: 1.5;
33
+ margin-bottom: var(--space-3);
34
+
35
+ a {
36
+ color: var(--color-accent);
37
+ text-decoration: none;
38
+ }
39
+ }
40
+
41
+ [data-slot="create-form"] {
42
+ display: flex;
43
+ flex-direction: column;
44
+ gap: var(--space-3);
45
+ padding: var(--space-4);
46
+ border: 1px solid var(--color-border);
47
+ border-radius: var(--border-radius-sm);
48
+
49
+ [data-slot="input-row"] {
50
+ display: flex;
51
+ flex-direction: row;
52
+ gap: var(--space-3);
53
+
54
+ @media (max-width: 40rem) {
55
+ flex-direction: column;
56
+ gap: var(--space-2);
57
+ }
58
+ }
59
+
60
+ [data-slot="input-field"] {
61
+ display: flex;
62
+ flex-direction: column;
63
+ gap: var(--space-1);
64
+ flex: 1;
65
+
66
+ p {
67
+ line-height: 1.2;
68
+ margin: 0;
69
+ color: var(--color-text-muted);
70
+ font-size: var(--font-size-sm);
71
+ }
72
+
73
+ input {
74
+ flex: 1;
75
+ padding: var(--space-2) var(--space-3);
76
+ border: 1px solid var(--color-border);
77
+ border-radius: var(--border-radius-sm);
78
+ background-color: var(--color-bg);
79
+ color: var(--color-text);
80
+ font-size: var(--font-size-sm);
81
+ line-height: 1.5;
82
+ min-width: 0;
83
+
84
+ &:focus {
85
+ outline: none;
86
+ border-color: var(--color-accent);
87
+ box-shadow: 0 0 0 3px var(--color-accent-alpha);
88
+ }
89
+
90
+ &::placeholder {
91
+ color: var(--color-text-disabled);
92
+ }
93
+ }
94
+ }
95
+
96
+ [data-slot="form-actions"] {
97
+ display: flex;
98
+ gap: var(--space-2);
99
+
100
+ > button[type="reset"] {
101
+ align-self: flex-start;
102
+ }
103
+ }
104
+
105
+ [data-slot="form-error"] {
106
+ color: var(--color-danger);
107
+ font-size: var(--font-size-sm);
108
+ line-height: 1.4;
109
+ margin-top: calc(var(--space-1) * -1);
110
+ }
111
+ }
112
+
113
+ [data-slot="members-table"] {
114
+ overflow-x: auto;
115
+ padding-bottom: 200px;
116
+ margin-bottom: -200px;
117
+ }
118
+
119
+ [data-slot="members-table-element"] {
120
+ width: 100%;
121
+ border-collapse: collapse;
122
+ font-size: var(--font-size-sm);
123
+
124
+ thead {
125
+ border-bottom: 1px solid var(--color-border);
126
+ }
127
+
128
+ th {
129
+ padding: var(--space-3) var(--space-4);
130
+ text-align: left;
131
+ font-weight: normal;
132
+ color: var(--color-text-muted);
133
+ text-transform: uppercase;
134
+
135
+ &:nth-child(2) {
136
+ width: 180px;
137
+ }
138
+
139
+ &:nth-child(3) {
140
+ width: 200px;
141
+ }
142
+ }
143
+
144
+ td {
145
+ padding: var(--space-3) var(--space-4);
146
+ border-bottom: 1px solid var(--color-border-muted);
147
+ color: var(--color-text-muted);
148
+ font-family: var(--font-mono);
149
+
150
+ &[data-slot="member-email"] {
151
+ color: var(--color-text);
152
+ font-family: var(--font-sans);
153
+ font-weight: 500;
154
+ }
155
+
156
+ &[data-slot="member-role"] {
157
+ font-family: var(--font-mono);
158
+ text-transform: capitalize;
159
+ }
160
+
161
+ &[data-slot="member-usage"] {
162
+ input {
163
+ width: 100%;
164
+ padding: var(--space-2) var(--space-3);
165
+ border: 1px solid var(--color-border);
166
+ border-radius: var(--border-radius-sm);
167
+ background-color: var(--color-bg);
168
+ color: var(--color-text);
169
+ font-size: var(--font-size-sm);
170
+ line-height: 1.5;
171
+ font-family: var(--font-mono);
172
+
173
+ &:focus {
174
+ outline: none;
175
+ border-color: var(--color-accent);
176
+ box-shadow: 0 0 0 3px var(--color-accent-alpha);
177
+ }
178
+
179
+ &::placeholder {
180
+ color: var(--color-text-disabled);
181
+ }
182
+ }
183
+ }
184
+
185
+ &[data-slot="member-date"] {
186
+ color: var(--color-text);
187
+ }
188
+
189
+ &[data-slot="member-actions"] {
190
+ font-family: var(--font-sans);
191
+ display: flex;
192
+ gap: var(--space-2);
193
+
194
+ [data-slot="inline-edit-form"] {
195
+ display: flex;
196
+ gap: var(--space-2);
197
+
198
+ button {
199
+ opacity: 1;
200
+ pointer-events: auto;
201
+ }
202
+ }
203
+
204
+ form:not([data-slot="inline-edit-form"]) button {
205
+ opacity: 0;
206
+ pointer-events: none;
207
+ transition: opacity 0.15s ease;
208
+ }
209
+ }
210
+ }
211
+
212
+ tbody tr {
213
+ &:hover {
214
+ [data-slot="member-actions"] form:not([data-slot="inline-edit-form"]) button {
215
+ opacity: 1;
216
+ pointer-events: auto;
217
+ }
218
+ }
219
+
220
+ &:last-child td {
221
+ border-bottom: none;
222
+ }
223
+ }
224
+
225
+ @media (max-width: 40rem) {
226
+ th,
227
+ td {
228
+ padding: var(--space-2) var(--space-3);
229
+ font-size: var(--font-size-xs);
230
+ }
231
+
232
+ th {
233
+ &:nth-child(3)
234
+
235
+ /* Date */ {
236
+ display: none;
237
+ }
238
+ }
239
+
240
+ td {
241
+ &:nth-child(3)
242
+
243
+ /* Date */ {
244
+ display: none;
245
+ }
246
+ }
247
+ }
248
+ }
249
+ }