wip: zen style members

This commit is contained in:
Frank
2025-10-10 13:48:56 -04:00
parent d83af721a6
commit 94d0a3d888
2 changed files with 112 additions and 79 deletions

View File

@@ -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"] {

View File

@@ -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">