Przeglądaj źródła

wip: zen style members

Frank 4 miesięcy temu
rodzic
commit
94d0a3d888

+ 47 - 0
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"] {

+ 65 - 79
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">