diff options
| author | Frank <[email protected]> | 2025-10-10 13:48:56 -0400 |
|---|---|---|
| committer | Frank <[email protected]> | 2025-10-10 13:48:56 -0400 |
| commit | 94d0a3d888c3b5f5cfc4fcf5f275885128a968e6 (patch) | |
| tree | 5752ec1a32886c430efbcafef3ba352de501bbb4 | |
| parent | d83af721a6e6269481186df80d498aa1213a6e59 (diff) | |
| download | opencode-94d0a3d888c3b5f5cfc4fcf5f275885128a968e6.tar.gz opencode-94d0a3d888c3b5f5cfc4fcf5f275885128a968e6.zip | |
wip: zen style members
| -rw-r--r-- | packages/console/app/src/routes/workspace/[id]/members/member-section.module.css | 47 | ||||
| -rw-r--r-- | packages/console/app/src/routes/workspace/[id]/members/member-section.tsx | 144 |
2 files changed, 112 insertions, 79 deletions
diff --git a/packages/console/app/src/routes/workspace/[id]/members/member-section.module.css b/packages/console/app/src/routes/workspace/[id]/members/member-section.module.css index 16b6ff8d2..14246d585 100644 --- a/packages/console/app/src/routes/workspace/[id]/members/member-section.module.css +++ b/packages/console/app/src/routes/workspace/[id]/members/member-section.module.css @@ -1,4 +1,11 @@ .root { + [data-slot="title-row"] { + display: flex; + justify-content: space-between; + align-items: center; + gap: var(--space-4); + } + [data-component="empty-state"] { padding: var(--space-20) var(--space-6); text-align: center; @@ -64,6 +71,46 @@ margin-top: var(--space-1); line-height: 1.4; } + + [data-slot="role-selector"] { + display: flex; + flex-direction: column; + gap: var(--space-2); + + label { + display: flex; + gap: var(--space-3); + padding: var(--space-3); + border: 1px solid var(--color-border); + border-radius: var(--border-radius-sm); + cursor: pointer; + + &:hover { + background-color: var(--color-bg-surface); + } + + input[type="radio"] { + margin-top: var(--space-1); + } + + div { + flex: 1; + + strong { + display: block; + color: var(--color-text); + font-family: var(--font-sans); + margin-bottom: var(--space-1); + } + + p { + font-size: var(--font-size-xs); + color: var(--color-text-muted); + font-family: var(--font-sans); + } + } + } + } } [data-slot="members-table"] { diff --git a/packages/console/app/src/routes/workspace/[id]/members/member-section.tsx b/packages/console/app/src/routes/workspace/[id]/members/member-section.tsx index cfc8b5e71..e9f617248 100644 --- a/packages/console/app/src/routes/workspace/[id]/members/member-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/members/member-section.tsx @@ -81,83 +81,6 @@ const updateMember = action(async (form: FormData) => { ) }, "member.update") -export function MemberCreateForm() { - const params = useParams() - const submission = useSubmission(inviteMember) - const [store, setStore] = createStore({ show: false }) - - let input: HTMLInputElement - - createEffect(() => { - if (!submission.pending && submission.result && !submission.result.error) { - hide() - } - }) - - function show() { - // submission.clear() does not clear the result in some cases, ie. - // 1. Create key with empty name => error shows - // 2. Put in a key name and creates the key => form hides - // 3. Click add key button again => form shows with the same error if - // submission.clear() is called only once - while (true) { - submission.clear() - if (!submission.result) break - } - setStore("show", true) - input.focus() - } - - function hide() { - setStore("show", false) - } - - return ( - <Show - when={store.show} - fallback={ - <button data-color="primary" onClick={() => show()}> - Invite Member - </button> - } - > - <form action={inviteMember} method="post" data-slot="create-form"> - <div data-slot="input-container"> - <input ref={(r) => (input = r)} data-component="input" name="email" type="text" placeholder="Enter email" /> - <div data-slot="role-selector"> - <label> - <input type="radio" name="role" value="admin" checked /> - <div> - <strong>Admin</strong> - <p>Can manage models, members, and billing</p> - </div> - </label> - <label> - <input type="radio" name="role" value="member" /> - <div> - <strong>Member</strong> - <p>Can only generate API keys for themselves</p> - </div> - </label> - </div> - <Show when={submission.result && submission.result.error}> - {(err) => <div data-slot="form-error">{err()}</div>} - </Show> - </div> - <input type="hidden" name="workspaceID" value={params.id} /> - <div data-slot="form-actions"> - <button type="reset" data-color="ghost" onClick={() => hide()}> - Cancel - </button> - <button type="submit" data-color="primary" disabled={submission.pending}> - {submission.pending ? "Inviting..." : "Invite"} - </button> - </div> - </form> - </Show> - ) -} - function MemberRow(props: { member: any; workspaceID: string; actorID: string; actorRole: string }) { const [editing, setEditing] = createSignal(false) const submission = useSubmission(updateMember) @@ -289,14 +212,77 @@ function MemberRow(props: { member: any; workspaceID: string; actorID: string; a export function MemberSection() { const params = useParams() const data = createAsync(() => listMembers(params.id)) + const submission = useSubmission(inviteMember) + const [store, setStore] = createStore({ show: false }) + + let input: HTMLInputElement + + createEffect(() => { + if (!submission.pending && submission.result && !submission.result.error) { + setStore("show", false) + } + }) + + function show() { + while (true) { + submission.clear() + if (!submission.result) break + } + setStore("show", true) + setTimeout(() => input?.focus(), 0) + } + + function hide() { + setStore("show", false) + } return ( <section class={styles.root}> <div data-slot="section-title"> <h2>Members</h2> + <div data-slot="title-row"> + <p>Manage workspace members and their permissions.</p> + <Show when={data()?.actorRole === "admin"}> + <button data-color="primary" onClick={() => show()}> + Invite Member + </button> + </Show> + </div> </div> - <Show when={data()?.actorRole === "admin"}> - <MemberCreateForm /> + <Show when={store.show}> + <form action={inviteMember} method="post" data-slot="create-form"> + <div data-slot="input-container"> + <input ref={(r) => (input = r)} data-component="input" name="email" type="text" placeholder="Enter email" /> + <div data-slot="role-selector"> + <label> + <input type="radio" name="role" value="admin" checked /> + <div> + <strong>Admin</strong> + <p>Can manage models, members, and billing</p> + </div> + </label> + <label> + <input type="radio" name="role" value="member" /> + <div> + <strong>Member</strong> + <p>Can only generate API keys for themselves</p> + </div> + </label> + </div> + <Show when={submission.result && submission.result.error}> + {(err) => <div data-slot="form-error">{err()}</div>} + </Show> + </div> + <input type="hidden" name="workspaceID" value={params.id} /> + <div data-slot="form-actions"> + <button type="reset" data-color="ghost" onClick={() => hide()}> + Cancel + </button> + <button type="submit" data-color="primary" disabled={submission.pending}> + {submission.pending ? "Inviting..." : "Invite"} + </button> + </div> + </form> </Show> <div data-slot="members-table"> <table data-slot="members-table-element"> |
