@webx-ui/module-auth 0.5.4 → 0.6.0

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.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/session.ts","../src/LoginCard.vue","../src/messages.ts","../src/admins.ts","../src/i18n.ts","../src/AdminDialog.vue","../src/AdminList.vue","../src/AdminsPage.vue","../src/module.ts","../src/AdminPicker.vue","../src/selectAdmin.ts","../src/UserMenu.vue","../src/index.ts"],"sourcesContent":["import { inject, type App, type InjectionKey } from 'vue'\nimport type { AdminContext, AdminUser } from '@webx-ui/module-admin'\n\nexport interface Credentials {\n email: string\n password: string\n remember?: boolean\n}\n\nexport interface AuthSession {\n /** Who is signed in, or null. A 401 is an answer, not a failure. */\n me(): Promise<AdminUser | null>\n /** Signs in and tells the panel to load itself. Throws `HttpError` on a refusal. */\n login(credentials: Credentials): Promise<AdminUser>\n logout(): Promise<void>\n /**\n * Change the language this administrator reads the panel in.\n *\n * Stored on the person rather than in this browser, so it follows them to the next machine\n * and so the server writes its own messages — a 422 under a field — in the same language as\n * the label above it.\n */\n setLocale(code: string): Promise<void>\n}\n\nexport const authKey: InjectionKey<AuthSession> = Symbol('webx-auth')\n\nexport function useAuth(): AuthSession {\n const session = inject(authKey, null)\n\n if (session === null) {\n throw new Error('useAuth() was called outside a panel that installed the auth plugin.')\n }\n\n return session\n}\n\n/**\n * The three calls `webx-ui/module-auth` answers, and nothing else.\n *\n * The session lives in a cookie, so there is nothing to keep here: the source of truth is the\n * server, and the panel asks it rather than remembering.\n */\nexport function createAuthSession(admin: AdminContext): AuthSession {\n const base = `${admin.apiPath}/auth`\n\n return {\n async me() {\n try {\n const body = await admin.http.get<{ data: AdminUser | null }>(`${base}/me`)\n\n return body.data\n } catch (error) {\n if (isUnauthenticated(error)) {\n return null\n }\n\n throw error\n }\n },\n\n async login(credentials) {\n const body = await admin.http.post<{ data: AdminUser }>(`${base}/login`, {\n email: credentials.email,\n password: credentials.password,\n remember: credentials.remember ?? false,\n })\n\n admin.setUser(body.data)\n\n // Signing in is the moment the panel becomes knowable: the manifest needs a session,\n // so it could not have been loaded before now.\n await admin.reload()\n\n return body.data\n },\n\n async setLocale(code) {\n // The panel redraws only after the server has accepted the choice: a language that\n // half took — menus switched, error messages not — is worse than one that did not.\n const body = await admin.http.put<{ data: AdminUser }>(`${base}/locale`, { locale: code })\n\n admin.setUser(body.data)\n\n await admin.setLocale(code)\n },\n\n async logout() {\n try {\n await admin.http.post(`${base}/logout`)\n } finally {\n // Whatever the server said, this browser is done: leaving the panel drawn behind a\n // failed sign-out would be worse than signing out optimistically.\n admin.setUser(null)\n admin.state.status = 'unauthenticated'\n admin.state.manifest = null\n }\n },\n }\n}\n\n/** Turns the key an avatar is stored under into an address the browser can load. */\nexport type AvatarResolver = (key: string) => Promise<string | null>\n\n/**\n * The resolver the panel handed to `auth()`, for whatever draws the signed-in person — the\n * menu in the corner, first of all. Null in a panel that has no library to resolve against,\n * which is what initials are for.\n */\nexport const avatarResolverKey: InjectionKey<AvatarResolver | null> = Symbol('webx-auth-avatar')\n\nexport function provideAuth(\n app: App,\n session: AuthSession,\n resolveAvatar: AvatarResolver | null = null,\n): void {\n app.provide(authKey, session)\n app.provide(avatarResolverKey, resolveAvatar)\n}\n\nfunction isUnauthenticated(error: unknown): boolean {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'status' in error &&\n (error as { status: unknown }).status === 401\n )\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, useTemplateRef } from 'vue'\nimport { HttpError, useTranslate } from '@webx-ui/module-admin'\nimport { useAuth } from './session'\n\n/**\n * The card a panel opens on.\n *\n * No logo, no product name, no \"welcome back\" — an admin panel's front door should say as\n * little as possible to somebody who has no business behind it, and the people who do have\n * business behind it already know where they are.\n *\n * Every string is still a prop and a prop given still wins. What changed is what happens when\n * one is not: the label comes from the panel's dictionary, which the server assembles from the\n * same `lang` files it answers its own messages from. A panel gets its language without being\n * told every label twice, and a card placed outside a panel still speaks English.\n */\nconst props = withDefaults(\n defineProps<{\n emailLabel?: string\n passwordLabel?: string\n rememberLabel?: string\n submitLabel?: string\n capsLockWarning?: string\n /** `:seconds` is replaced with however long the server said to wait. */\n throttleMessage?: string\n revealLabel?: string\n hideLabel?: string\n /** Offer to stay signed in. The server honours it with a long-lived cookie. */\n remember?: boolean\n /** Put the cursor in the email field. Off for a card that is not the only thing on screen. */\n autofocus?: boolean\n }>(),\n {\n // Undefined rather than a string, so \"not given\" is tellable from \"given as English\" and\n // the dictionary gets its turn.\n emailLabel: undefined,\n passwordLabel: undefined,\n rememberLabel: undefined,\n submitLabel: undefined,\n capsLockWarning: undefined,\n throttleMessage: undefined,\n revealLabel: undefined,\n hideLabel: undefined,\n remember: true,\n autofocus: true,\n },\n)\n\nconst emit = defineEmits<{ success: [] }>()\n\nconst auth = useAuth()\nconst t = useTranslate('webx-auth')\n\nconst labels = computed(() => ({\n email: props.emailLabel ?? t('card.email'),\n password: props.passwordLabel ?? t('card.password'),\n remember: props.rememberLabel ?? t('card.remember'),\n submit: props.submitLabel ?? t('card.submit'),\n capsLock: props.capsLockWarning ?? t('card.caps-lock'),\n reveal: props.revealLabel ?? t('card.reveal'),\n hide: props.hideLabel ?? t('card.hide'),\n}))\n\nconst email = ref('')\nconst password = ref('')\nconst rememberMe = ref(false)\n\nconst busy = ref(false)\nconst revealed = ref(false)\nconst capsLock = ref(false)\nconst message = ref('')\nconst errors = ref<Record<string, string[]>>({})\nconst secondsLeft = ref(0)\n\nconst emailField = useTemplateRef<HTMLInputElement>('emailField')\n\nlet countdown: ReturnType<typeof setInterval> | null = null\n\nonBeforeUnmount(stopCountdown)\n\nconst throttled = computed(() => secondsLeft.value > 0)\n\nconst throttleNotice = computed(() =>\n props.throttleMessage === undefined\n ? t('card.throttled', { seconds: secondsLeft.value })\n : props.throttleMessage.replace(':seconds', String(secondsLeft.value)),\n)\n\nasync function submit(): Promise<void> {\n if (busy.value || throttled.value) {\n return\n }\n\n busy.value = true\n message.value = ''\n errors.value = {}\n\n try {\n await auth.login({\n email: email.value,\n password: password.value,\n remember: rememberMe.value,\n })\n\n password.value = ''\n emit('success')\n } catch (error) {\n handle(error)\n } finally {\n busy.value = false\n }\n}\n\nfunction handle(error: unknown): void {\n if (!(error instanceof HttpError)) {\n message.value = error instanceof Error ? error.message : String(error)\n\n return\n }\n\n // A bad password comes back as a 422 against the email field, the same as an address\n // nobody owns — the server refuses to say which, and so does this.\n if (error.isValidation) {\n errors.value = error.errors\n\n return\n }\n\n if (error.isThrottled) {\n startCountdown(error.retryAfter ?? 60)\n\n return\n }\n\n message.value = error.message\n}\n\nfunction startCountdown(seconds: number): void {\n stopCountdown()\n secondsLeft.value = seconds\n\n countdown = setInterval(() => {\n secondsLeft.value -= 1\n\n if (secondsLeft.value <= 0) {\n stopCountdown()\n }\n }, 1000)\n}\n\nfunction stopCountdown(): void {\n if (countdown !== null) {\n clearInterval(countdown)\n countdown = null\n }\n\n secondsLeft.value = 0\n}\n\n/**\n * Worth saying out loud: a password typed in capitals fails with the same unhelpful message\n * as a wrong one, and people retype it three times before noticing.\n */\nfunction trackCapsLock(event: KeyboardEvent): void {\n capsLock.value = event.getModifierState('CapsLock')\n}\n\ndefineExpose({ focus: () => emailField.value?.focus() })\n</script>\n\n<template>\n <wx-card class=\"wx-login\" :padding=\"24\">\n <wx-form :errors=\"errors\" :disabled=\"busy\" gap=\"md\" @submit.prevent=\"submit\">\n <wx-alert\n v-if=\"message !== ''\"\n type=\"danger\"\n variant=\"soft\"\n live\n :description=\"message\"\n class=\"wx-login__message\"\n />\n\n <wx-alert\n v-else-if=\"throttled\"\n type=\"warning\"\n variant=\"soft\"\n live\n :description=\"throttleNotice\"\n class=\"wx-login__message\"\n />\n\n <wx-form-item name=\"email\">\n <wx-input\n ref=\"emailField\"\n v-model=\"email\"\n type=\"email\"\n :placeholder=\"labels.email\"\n :aria-label=\"labels.email\"\n autocomplete=\"username\"\n :autofocus=\"autofocus\"\n inputmode=\"email\"\n size=\"lg\"\n >\n <template #prefix><wx-icon name=\"mail\" /></template>\n </wx-input>\n </wx-form-item>\n\n <wx-form-item name=\"password\" :help=\"capsLock ? labels.capsLock : undefined\">\n <wx-input\n v-model=\"password\"\n :type=\"revealed ? 'text' : 'password'\"\n :placeholder=\"labels.password\"\n :aria-label=\"labels.password\"\n autocomplete=\"current-password\"\n size=\"lg\"\n @keydown=\"trackCapsLock\"\n @keyup=\"trackCapsLock\"\n >\n <template #prefix><wx-icon name=\"lock\" /></template>\n <template #suffix>\n <!-- A bare icon rather than a button with a surface: it sits inside the field, and\n a second box in there reads as a second control. Out of the tab order because\n it is a convenience, not a step. -->\n <button\n type=\"button\"\n class=\"wx-login__reveal\"\n :title=\"revealed ? labels.hide : labels.reveal\"\n :aria-label=\"revealed ? labels.hide : labels.reveal\"\n :aria-pressed=\"revealed\"\n tabindex=\"-1\"\n @click=\"revealed = !revealed\"\n >\n <wx-icon :name=\"revealed ? 'eye-off' : 'eye'\" />\n </button>\n </template>\n </wx-input>\n </wx-form-item>\n\n <wx-checkbox\n v-if=\"remember\"\n v-model=\"rememberMe\"\n :label=\"labels.remember\"\n class=\"wx-login__remember\"\n />\n\n <wx-button\n type=\"primary\"\n native-type=\"submit\"\n size=\"lg\"\n block\n :loading=\"busy\"\n :disabled=\"throttled\"\n >\n {{ labels.submit }}\n </wx-button>\n </wx-form>\n </wx-card>\n</template>\n\n<style scoped>\n.wx-login {\n width: 100%;\n /* Wide enough for an email address, narrow enough to stay a card rather than a page. */\n max-width: 26rem;\n}\n\n.wx-login__message {\n margin-block-end: var(--wx-space-4);\n}\n\n/* The checkbox belongs to the password above it, not to the form's row of fields: pulled up\n out of the form's gap so it reads as an option under the field rather than as a field of its\n own. */\n.wx-login__remember {\n margin-block-start: calc(-1 * var(--wx-space-12));\n}\n\n.wx-login__reveal {\n display: inline-flex;\n align-items: center;\n padding: 0;\n /* A button has a font size of its own — 13.3px in every browser — and the icon inside is\n sized in em, so without this it came out smaller than the lock at the other end. */\n font: inherit;\n color: var(--wx-text-muted);\n background: none;\n border: 0;\n cursor: pointer;\n}\n\n.wx-login__reveal:hover,\n.wx-login__reveal[aria-pressed='true'] {\n color: var(--wx-text-default);\n}\n</style>\n","import type { Messages } from '@webx-ui/module-admin'\n\n/**\n * What this package says, in English.\n *\n * The same keys `webx-ui/module-auth` ships as `lang/en/*.php`. Kept here so a card placed by\n * hand, with no WebX UI server behind it, still has words — and so a key never reaches the\n * screen when a translation is missing.\n */\nexport const authMessages: Record<string, Messages> = {\n card: {\n email: 'Email',\n password: 'Password',\n remember: 'Stay signed in',\n submit: 'Sign in',\n 'caps-lock': 'Caps Lock is on.',\n reveal: 'Show the password',\n hide: 'Hide the password',\n throttled: 'Too many attempts. Try again in :seconds s.',\n failed: 'Those details do not match an account.',\n },\n admins: {\n title: 'Administrators',\n search: 'Search administrators',\n new: 'New administrator',\n edit: 'Edit administrator',\n // The same two, short enough for a button on a phone and for a dialog's heading.\n 'new-short': 'Add',\n 'edit-short': 'Edit',\n name: 'Name',\n email: 'Email',\n password: 'Password',\n 'password-hint': 'At least 12 characters',\n 'password-keep': 'Leave blank to keep the current one',\n avatar: 'Photo',\n roles: 'Roles',\n 'no-roles': 'No roles',\n active: 'Active',\n super: 'Super administrator',\n 'super-hint': 'Answers yes to everything, whatever the roles say',\n locale: 'Panel language',\n 'locale-auto': 'Follow the browser',\n 'last-login': 'Last signed in',\n save: 'Save',\n cancel: 'Cancel',\n delete: 'Delete',\n 'delete-title': 'Delete :name?',\n 'delete-text': 'They lose access at once. What they have written stays.',\n 'all-roles': 'All roles',\n 'any-state': 'Active and not',\n 'only-active': 'Active only',\n 'only-inactive': 'Switched off only',\n empty: 'Nobody matches that',\n 'select-one': 'Choose an administrator',\n 'select-many': 'Choose administrators',\n chosen: 'Chosen: :count',\n created: 'Administrator created',\n updated: 'Administrator saved',\n deleted: 'Administrator deleted',\n },\n errors: {\n unauthenticated: 'Unauthenticated.',\n inactive: 'This account is no longer active.',\n forbidden: 'This account may not do that.',\n 'not-yourself': 'You cannot do that to your own account.',\n 'last-super': 'There has to be one super administrator left.',\n },\n menu: {\n 'sign-out': 'Sign out',\n language: 'Interface language',\n },\n}\n","import type { AdminContext } from '@webx-ui/module-admin'\nimport type { Admin, AdminInput, AdminPage, AdminQuery, Role } from './types'\n\nexport interface AdminsApi {\n list(query?: AdminQuery): Promise<AdminPage>\n get(id: number): Promise<Admin>\n create(input: AdminInput): Promise<Admin>\n update(id: number, input: AdminInput): Promise<Admin>\n remove(id: number): Promise<void>\n roles(): Promise<Role[]>\n}\n\n/**\n * The calls behind the administrators screen, and behind the picker that opens from code.\n *\n * Reading is separate from managing on the server, so a module that only wants to say who\n * wrote something — or offer a list of people to assign work to — can use `list` and `roles`\n * without being allowed anywhere near the rest.\n */\nexport function createAdminsApi(admin: AdminContext): AdminsApi {\n const base = `${admin.apiPath}/auth`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n list: (query = {}) =>\n admin.http\n .get<{ data: Admin[]; meta: Omit<AdminPage, 'data'> }>(`${base}/admins`, {\n query: {\n q: query.q || undefined,\n role: query.role ?? undefined,\n active: query.active ?? undefined,\n sort: query.sort,\n page: query.page,\n per_page: query.per_page,\n },\n })\n .then((body) => ({ ...body.meta, data: body.data })),\n\n get: (id) => admin.http.get<{ data: Admin }>(`${base}/admins/${id}`).then(data),\n\n create: (input) => admin.http.post<{ data: Admin }>(`${base}/admins`, input).then(data),\n\n update: (id, input) =>\n admin.http.patch<{ data: Admin }>(`${base}/admins/${id}`, input).then(data),\n\n remove: (id) => admin.http.delete<void>(`${base}/admins/${id}`),\n\n roles: () => admin.http.get<{ data: Role[] }>(`${base}/roles`).then(data),\n }\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { authMessages } from './messages'\n\nconst seeded = new WeakSet<object>()\n\n/**\n * Put this package's English under the panel's dictionary, once per panel.\n *\n * The plugin does this too, but a panel can use the administrators screen — or just the picker\n * — without installing the sign-in plugin at all, and a screen showing `admins.title` instead\n * of a word is not a thing to leave to chance.\n */\nexport function useAuthMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-auth', authMessages)\n seeded.add(i18n)\n }\n } catch {\n // Outside a panel there is no dictionary to seed, and `useTranslate` falls back on its own.\n }\n}\n","<script setup lang=\"ts\">\nimport { computed, ref, watch, type Component } from 'vue'\nimport { useAdmin, useErrorText, useI18n, useTranslate } from '@webx-ui/module-admin'\nimport {\n toast,\n useModal,\n WxButton,\n WxCheckbox,\n WxDialog,\n WxFormItem,\n WxInput,\n WxSelect,\n WxSpace,\n WxText,\n} from '@webx-ui/core'\n\nimport { createAdminsApi } from './admins'\nimport { useAuthMessages } from './i18n'\nimport type { Admin, AdminInput, Role } from './types'\n\n/**\n * Creating an administrator and editing one — the same form, because they differ in one field.\n *\n * The password is required the first time and optional afterwards, and a blank one on an edit\n * means \"leave it alone\" rather than \"set it to nothing\". That is the only conditional here.\n */\nconst props = withDefaults(\n defineProps<{\n /** Absent creates somebody; present edits them. */\n admin?: Admin | null\n /**\n * The field to pick the photograph with — `WxMediaField` in a panel that has the library.\n *\n * Handed in rather than imported: this package does not depend on the media one, and a\n * panel without a library still edits everything else about a person.\n */\n avatarField?: Component\n }>(),\n { admin: null, avatarField: undefined },\n)\n\nconst { resolve, dismiss, open } = useModal<Admin>()\n\nconst context = useAdmin()\nconst api = createAdminsApi(context)\nconst i18n = useI18n()\nuseAuthMessages()\n\nconst t = useTranslate('webx-auth')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\nconst roles = ref<Role[]>([])\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst form = ref<AdminInput & { avatar: string | null }>({\n name: '',\n email: '',\n password: '',\n avatar: null,\n is_active: true,\n is_super: false,\n locale: null,\n roles: [],\n})\n\n/** The field hands back `{ path, … }`; what is stored is the key. */\nconst photo = computed({\n get: () => (form.value.avatar ? { path: form.value.avatar } : null),\n set: (value: { path: string } | null) => {\n form.value.avatar = value?.path ?? null\n },\n})\n\nconst editing = computed(() => props.admin !== null)\n\nconst roleOptions = computed(() => roles.value.map((one) => ({ value: one.id, label: one.name })))\n\n/* A word, not an empty string: a select reads \"\" as nothing chosen and shows a blank control. */\nconst localeOptions = computed(() => [\n { value: 'auto', label: t('admins.locale-auto') },\n ...i18n.state.panelLocales.map((one) => ({ value: one.code, label: one.nativeName })),\n])\n\nvoid api.roles().then((all) => (roles.value = all))\n\nwatch(\n () => props.admin,\n (admin) => {\n form.value = {\n name: admin?.name ?? '',\n email: admin?.email ?? '',\n password: '',\n avatar: admin?.avatar ?? null,\n is_active: admin?.is_active ?? true,\n is_super: admin?.is_super ?? false,\n locale: admin?.locale ?? 'auto',\n roles: admin?.roles.map((one) => one.id) ?? [],\n }\n errors.value = {}\n },\n { immediate: true },\n)\n\nfunction errorOf(field: string): string | undefined {\n return errors.value[field]?.[0]\n}\n\nasync function save(): Promise<void> {\n saving.value = true\n errors.value = {}\n\n const payload: AdminInput = {\n name: form.value.name,\n email: form.value.email,\n avatar: form.value.avatar,\n is_active: form.value.is_active,\n is_super: form.value.is_super,\n locale: form.value.locale === 'auto' ? null : form.value.locale,\n roles: form.value.roles ?? [],\n }\n\n if (form.value.password) {\n payload.password = form.value.password\n }\n\n try {\n const saved = props.admin\n ? await api.update(props.admin.id, payload)\n : await api.create(payload)\n\n toast.success(t(editing.value ? 'admins.updated' : 'admins.created'))\n resolve(saved)\n } catch (error) {\n const body = (error as { body?: { errors?: Record<string, string[]>; message?: string } }).body\n\n if (body?.errors) {\n errors.value = body.errors\n } else {\n // The two refusals that keep a panel reachable — the last super administrator, and doing\n // it to yourself — arrive as a 422 with a message and no field, which is the one shape\n // the panel repeats back word for word.\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog\n v-model:open=\"open\"\n :title=\"editing ? t('admins.edit-short') : t('admins.new-short')\"\n :width=\"720\"\n >\n <!--\n The photograph beside the fields rather than above them: it is square, the fields are a\n column, and stacking the two makes somebody scroll past a picture to reach a name. Under\n `--wx-admin-form-wrap` the sidebar goes back on top, which is the only shape that fits a\n phone.\n -->\n <div class=\"wx-admin-form\">\n <div class=\"wx-admin-form__grid\">\n <div v-if=\"avatarField\" class=\"wx-admin-form__aside\">\n <component\n :is=\"avatarField\"\n v-model=\"photo\"\n :label=\"t('admins.avatar')\"\n aspect=\"1/1\"\n :captions=\"false\"\n />\n </div>\n\n <div class=\"wx-admin-form__fields\">\n <wx-form-item :label=\"t('admins.name')\" :error=\"errorOf('name')\">\n <wx-input v-model=\"form.name\" name=\"name\" />\n </wx-form-item>\n\n <wx-form-item :label=\"t('admins.email')\" :error=\"errorOf('email')\">\n <wx-input v-model=\"form.email\" type=\"email\" name=\"email\" autocomplete=\"off\" />\n </wx-form-item>\n\n <wx-form-item\n :label=\"t('admins.password')\"\n :error=\"errorOf('password')\"\n :help=\"editing ? t('admins.password-keep') : t('admins.password-hint')\"\n >\n <wx-input\n v-model=\"form.password\"\n type=\"password\"\n name=\"password\"\n autocomplete=\"new-password\"\n />\n </wx-form-item>\n\n <wx-form-item :label=\"t('admins.roles')\" :error=\"errorOf('roles')\">\n <wx-select v-model=\"form.roles\" :options=\"roleOptions\" multiple />\n </wx-form-item>\n\n <wx-form-item :label=\"t('admins.locale')\">\n <wx-select v-model=\"form.locale\" :options=\"localeOptions\" />\n </wx-form-item>\n\n <wx-space size=\"md\">\n <wx-checkbox v-model=\"form.is_active\" :label=\"t('admins.active')\" />\n <wx-checkbox v-model=\"form.is_super\" :label=\"t('admins.super')\" />\n </wx-space>\n\n <wx-text size=\"sm\" tone=\"muted\">{{ t('admins.super-hint') }}</wx-text>\n </div>\n </div>\n </div>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('admins.cancel') }}</wx-button>\n <wx-button type=\"primary\" :loading=\"saving\" @click=\"save\">\n {{ t('admins.save') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n\n<style>\n/* The container is the wrapper, not the grid: an element cannot query its own width. */\n.wx-admin-form {\n container-type: inline-size;\n}\n\n.wx-admin-form__grid {\n display: grid;\n grid-template-columns: 200px minmax(0, 1fr);\n gap: var(--wx-space-24);\n}\n\n.wx-admin-form__fields {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n min-width: 0;\n}\n\n/* The dialog's own width decides this, not the window's. */\n@container (max-width: 520px) {\n .wx-admin-form__grid {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .wx-admin-form__aside {\n max-width: 200px;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useAdmin, useTranslate, WxDate, WxRowMenu, type RowAction } from '@webx-ui/module-admin'\nimport {\n WxAvatar,\n WxBadge,\n WxSelect,\n WxSpace,\n WxTable,\n WxText,\n type TableColumn,\n type TableState,\n} from '@webx-ui/core'\nimport { createAdminsApi } from './admins'\nimport { useAuthMessages } from './i18n'\nimport type { Admin, AdminPage, Role } from './types'\n\n/**\n * The list of administrators — the screen, and the inside of the picker.\n *\n * One component for both so that searching, filtering and paging behave the same wherever\n * somebody is looking at these people. What changes is only what a row is for: opening it, or\n * choosing it.\n */\nconst props = withDefaults(\n defineProps<{\n /** Rows are chosen rather than opened. */\n picking?: boolean\n /** More than one may be chosen at a time. */\n multiple?: boolean\n /** Adds the column that removes somebody. */\n removable?: boolean\n /**\n * Offers the role and state filters.\n *\n * Off by default: a panel has half a dozen administrators, and a filter over six rows is a\n * control to read and decide about where a glance would have done.\n */\n filters?: boolean\n /** Turns an avatar key into an address. The panel wires this; this package cannot. */\n resolveAvatar?: (key: string) => Promise<string | null>\n }>(),\n {\n picking: false,\n multiple: false,\n removable: false,\n filters: false,\n resolveAvatar: undefined,\n },\n)\n\nconst emit = defineEmits<{\n open: [admin: Admin]\n chosen: [admins: Admin[]]\n /** Which rows are ticked, for a footer that counts them. */\n selection: [admins: Admin[]]\n remove: [admin: Admin]\n}>()\n\nconst admin = useAdmin()\nconst api = createAdminsApi(admin)\nuseAuthMessages()\n\nconst t = useTranslate('webx-auth')\n\nconst page = ref<AdminPage | null>(null)\nconst roles = ref<Role[]>([])\nconst loading = ref(false)\n/* A word rather than an empty string: a select reads \"\" as nothing chosen and shows a\n blank control where the option said \"all roles\". */\nconst role = ref('all')\nconst active = ref<'any' | 'yes' | 'no'>('any')\nconst selected = ref<Admin[]>([])\nconst avatars = ref<Record<string, string>>({})\n\nlet last: TableState = { page: 1, perPage: 20, sort: null, search: '' }\n\nconst roleOptions = computed(() => [\n { value: 'all', label: t('admins.all-roles') },\n ...roles.value.map((one) => ({ value: one.slug, label: one.name })),\n])\n\nconst stateOptions = computed(() => [\n { value: 'any', label: t('admins.any-state') },\n { value: 'yes', label: t('admins.only-active') },\n { value: 'no', label: t('admins.only-inactive') },\n])\n\n/*\n * No widths but the one the buttons need.\n *\n * A declared width is a promise the table has to keep, and five of them add up to more than the\n * screen — so it scrolls sideways while half the row is whitespace. Left alone, the columns take\n * what their contents need. What does not fit is dropped instead, in the order it can be spared.\n */\nconst columns = computed<TableColumn<Admin>[]>(() => [\n { key: 'name', label: t('admins.name'), sortable: true },\n { key: 'email', label: t('admins.email'), sortable: true, hideBelow: 560 },\n { key: 'roles', label: t('admins.roles'), hideBelow: 760 },\n { key: 'is_active', label: t('admins.active'), align: 'center', hideBelow: 660 },\n {\n key: 'last_login_at',\n label: t('admins.last-login'),\n sortable: true,\n hideBelow: 900,\n // The first thing to go when a row becomes a card: it is a date somebody scans down a\n // column, and a card has no column to scan.\n hideOnCards: true,\n },\n {\n key: 'actions',\n label: '',\n width: 56,\n align: 'right',\n hidden: !props.removable,\n // A card puts them along its top instead, through the `card-actions` slot.\n hideOnCards: true,\n },\n])\n\nvoid api.roles().then((all) => (roles.value = all))\n\nwatch([role, active], () => load(last))\n\nasync function load(state: TableState): Promise<void> {\n last = state\n loading.value = true\n\n try {\n page.value = await api.list({\n q: state.search,\n role: role.value === 'all' ? null : role.value,\n active: active.value === 'any' ? null : active.value,\n sort: state.sort ? `${state.sort.order === 'desc' ? '-' : ''}${state.sort.key}` : 'name',\n page: state.page,\n per_page: state.perPage,\n })\n\n void resolveAvatars()\n } finally {\n loading.value = false\n }\n}\n\n/**\n * A row stores the library's key, not an address, and this package has no business knowing how\n * to turn one into the other. The panel hands that in; without it a row shows initials, which\n * is what it would have shown anyway for everybody who never uploaded a photograph.\n */\nasync function resolveAvatars(): Promise<void> {\n const resolve = props.resolveAvatar\n\n if (!resolve) return\n\n for (const row of page.value?.data ?? []) {\n if (!row.avatar || avatars.value[row.avatar]) continue\n\n const url = await resolve(row.avatar)\n\n if (url) avatars.value = { ...avatars.value, [row.avatar]: url }\n }\n}\n\n/**\n * One line, and the same `···` every other list of the panel puts a record's actions behind.\n * A bare red bin in every row says deleting somebody is the thing this list is for (§20).\n */\nfunction actionsFor(row: Admin): RowAction[] {\n return [\n {\n key: 'delete',\n icon: 'trash',\n label: t('admins.delete'),\n danger: true,\n run: () => emit('remove', row),\n },\n ]\n}\n\nfunction onRow(row: Admin): void {\n if (props.picking) {\n emit('chosen', [row])\n\n return\n }\n\n emit('open', row)\n}\n\n/**\n * The listeners the table gets, rather than a handler that decides to do nothing (§13).\n *\n * Picking several is the one case where a row leads nowhere: the tick box is what chooses, and\n * a click on the row itself would have to either choose one and drop the rest or do nothing at\n * all. So it does nothing and says so — no cursor, no highlight, which is what `WxTable` reads\n * out of the missing listener. `v-bind`, not `v-on`: only that reaches it (CLAUDE.md §4).\n */\nconst clickable = computed(() => !(props.picking && props.multiple))\n\nfunction onSelection(_keys: unknown, rows: Admin[]): void {\n selected.value = rows\n emit('selection', rows)\n}\n\n/** Reload without losing where somebody was, for a caller that changed something. */\ndefineExpose({ reload: () => load(last), chosen: () => selected.value })\n</script>\n\n<template>\n <div class=\"wx-admin-list\">\n <wx-table\n :data=\"page\"\n :columns=\"columns\"\n row-key=\"id\"\n searchable\n :clickable=\"clickable\"\n :hover=\"clickable\"\n flush\n :loading=\"loading\"\n :search-placeholder=\"t('admins.search')\"\n :empty-text=\"t('admins.empty')\"\n :selectable=\"picking && multiple\"\n @row-click=\"onRow\"\n @state-change=\"load\"\n @selection-change=\"onSelection\"\n >\n <template v-if=\"filters\" #actions>\n <wx-space size=\"sm\">\n <wx-select v-model=\"role\" :options=\"roleOptions\" size=\"sm\" style=\"width: 180px\" />\n <wx-select v-model=\"active\" :options=\"stateOptions\" size=\"sm\" style=\"width: 180px\" />\n </wx-space>\n </template>\n\n <template #cell-name=\"{ row }\">\n <wx-space size=\"sm\" align=\"center\">\n <wx-avatar\n :name=\"row.name\"\n :src=\"row.avatar ? avatars[row.avatar] : undefined\"\n size=\"sm\"\n />\n <span>{{ row.name }}</span>\n <wx-badge v-if=\"row.is_super\" type=\"warning\">{{ t('admins.super') }}</wx-badge>\n </wx-space>\n </template>\n\n <template #cell-roles=\"{ row }\">\n <wx-space v-if=\"row.roles.length > 0\" size=\"xs\" wrap>\n <wx-badge v-for=\"one in row.roles\" :key=\"one.id\">{{ one.name }}</wx-badge>\n </wx-space>\n <wx-text v-else size=\"sm\" tone=\"muted\">{{ t('admins.no-roles') }}</wx-text>\n </template>\n\n <template #cell-is_active=\"{ row }\">\n <wx-badge :type=\"row.is_active ? 'success' : 'default'\" dot>\n {{ row.is_active ? t('admins.active') : t('admins.only-inactive') }}\n </wx-badge>\n </template>\n\n <template #card-actions=\"{ row }\">\n <wx-row-menu v-if=\"removable\" :actions=\"actionsFor(row)\" :label=\"row.name\" />\n </template>\n\n <template #cell-actions=\"{ row }\">\n <wx-row-menu :actions=\"actionsFor(row)\" :label=\"row.name\" />\n </template>\n\n <template #cell-last_login_at=\"{ row }\">\n <wx-date :value=\"row.last_login_at\" :tone=\"row.last_login_at ? 'default' : 'muted'\" />\n </template>\n </wx-table>\n </div>\n</template>\n\n<style>\n.wx-admin-list {\n min-width: 0;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, useTemplateRef, type Component } from 'vue'\nimport { useAdmin, useErrorText, useTranslate, WxListScreen } from '@webx-ui/module-admin'\nimport { confirm, createModal, toast, WxButton } from '@webx-ui/core'\nimport AdminDialog from './AdminDialog.vue'\nimport AdminList from './AdminList.vue'\nimport { createAdminsApi } from './admins'\nimport { useAuthMessages } from './i18n'\nimport type { Admin } from './types'\n\n/**\n * The administrators section: the list, and the form over it.\n *\n * A dialog rather than a second route. Editing somebody is half a dozen fields, and a screen\n * that takes over the page for that loses the list you were reading — which is where you\n * decide who to open next.\n *\n * The heading and `Add` used to live inside the card, which made this the one section of the\n * panel with no line of its own at the top. They stand outside it now, where every other list\n * keeps them (§19).\n */\nconst props = withDefaults(\n defineProps<{\n avatarField?: Component\n resolveAvatar?: (key: string) => Promise<string | null>\n }>(),\n { avatarField: undefined, resolveAvatar: undefined },\n)\n\nconst context = useAdmin()\nconst api = createAdminsApi(context)\nuseAuthMessages()\n\nconst t = useTranslate('webx-auth')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\nconst list = useTemplateRef<{ reload: () => void }>('list')\nconst editing = ref<Admin | null>(null)\n\nconst edit = createModal<Admin, { admin: Admin | null; avatarField?: Component }>(AdminDialog)\n\nconst canManage = context.can('admins.manage')\n\n/** The section's name as the server translated it; the built-in English until it arrives. */\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'admins')?.title ??\n t('admins.title'),\n)\n\nasync function open(admin: Admin | null): Promise<void> {\n editing.value = admin\n\n const saved = await edit({ admin, avatarField: props.avatarField })\n\n if (saved) {\n list.value?.reload()\n }\n}\n\nasync function remove(admin: Admin): Promise<void> {\n const agreed = await confirm({\n title: t('admins.delete-title', { name: admin.name }),\n message: t('admins.delete-text'),\n confirmText: t('admins.delete'),\n cancelText: t('admins.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(admin.id)\n toast.success(t('admins.deleted'))\n list.value?.reload()\n } catch (error) {\n toast.danger(message(error))\n }\n}\n</script>\n\n<template>\n <wx-list-screen :title=\"title\">\n <template v-if=\"canManage\" #actions>\n <!-- Short enough for a phone, where the heading and the button share one line. -->\n <wx-button type=\"primary\" icon=\"add\" @click=\"open(null)\">\n {{ t('admins.new-short') }}\n </wx-button>\n </template>\n\n <admin-list\n ref=\"list\"\n :removable=\"canManage\"\n :resolve-avatar=\"props.resolveAvatar\"\n @open=\"open\"\n @remove=\"remove\"\n />\n </wx-list-screen>\n</template>\n","import { h, type Component } from 'vue'\nimport type { AdminModule } from '@webx-ui/module-admin'\nimport AdminsPage from './AdminsPage.vue'\n\nexport interface AdminsOptions {\n /** Where the section lives inside the panel. */\n path?: string\n /**\n * The field the photograph is picked with — `WxMediaField` in a panel that has the library.\n *\n * Handed in rather than imported: this package does not depend on the media one, and a panel\n * without a library still edits everything else about a person.\n */\n avatarField?: Component\n /** Turns an avatar key into an address. Omitted, rows and forms show initials. */\n resolveAvatar?: (key: string) => Promise<string | null>\n}\n\n/**\n * The administrators, as a section of the panel.\n *\n * The id matches the module the server reports, which is what makes the entry appear in the\n * navigation: a section shows up when both halves are installed.\n */\nexport function admins(options: AdminsOptions = {}): AdminModule {\n const path = options.path ?? '/admins'\n\n return {\n id: 'admins',\n path,\n routes: [\n {\n path,\n name: 'webx.admins',\n component: {\n render: () =>\n h(AdminsPage, {\n avatarField: options.avatarField,\n resolveAvatar: options.resolveAvatar,\n }),\n },\n },\n ],\n }\n}\n","<script setup lang=\"ts\">\nimport { ref, useTemplateRef } from 'vue'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport { useModal, WxButton, WxDialog, WxSpace, WxText } from '@webx-ui/core'\nimport AdminList from './AdminList.vue'\nimport { useAuthMessages } from './i18n'\nimport type { Admin } from './types'\n\n/**\n * The list of administrators as a dialog, resolving with whoever was chosen.\n *\n * The same list as the screen — one component, so that searching and filtering behave the same\n * wherever these people are being looked at.\n */\nconst props = withDefaults(\n defineProps<{\n multiple?: boolean\n title?: string\n resolveAvatar?: (key: string) => Promise<string | null>\n }>(),\n { multiple: false, title: undefined, resolveAvatar: undefined },\n)\n\nconst { open, resolve, dismiss } = useModal<Admin[]>()\n\nuseAuthMessages()\n\nconst t = useTranslate('webx-auth')\n\nconst list = useTemplateRef<{ chosen: () => Admin[] }>('list')\nconst count = ref(0)\n\n/** One is chosen by clicking the row; several are ticked and then confirmed. */\nfunction chosen(admins: Admin[]): void {\n resolve(admins)\n}\n\nfunction track(admins: Admin[]): void {\n count.value = admins.length\n}\n</script>\n\n<template>\n <wx-dialog\n v-model:open=\"open\"\n :title=\"title ?? t(multiple ? 'admins.select-many' : 'admins.select-one')\"\n :width=\"880\"\n >\n <admin-list\n ref=\"list\"\n picking\n :multiple=\"multiple\"\n :resolve-avatar=\"props.resolveAvatar\"\n @chosen=\"chosen\"\n @selection=\"track\"\n />\n\n <template v-if=\"multiple\" #footer>\n <wx-space size=\"sm\" align=\"center\">\n <wx-text size=\"sm\" tone=\"muted\">{{ t('admins.chosen', { count }) }}</wx-text>\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('admins.cancel') }}</wx-button>\n <wx-button type=\"primary\" :disabled=\"count === 0\" @click=\"resolve(list?.chosen() ?? [])\">\n {{ t('admins.save') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","import { createModal } from '@webx-ui/core'\nimport AdminPicker from './AdminPicker.vue'\nimport type { Admin } from './types'\n\nexport interface AdminPickerOptions {\n /** Heading of the dialog, when the default is not specific enough. */\n title?: string\n /** Turns an avatar key into an address. Omitted, rows show initials. */\n resolveAvatar?: (key: string) => Promise<string | null>\n}\n\nconst pick = createModal<Admin[], AdminPickerOptions & { multiple?: boolean }>(AdminPicker)\n\n/**\n * One administrator, chosen from a dialog.\n *\n * ```ts\n * const owner = await selectAdmin()\n * if (owner) task.assignedTo = owner.id\n * ```\n *\n * Resolves with `undefined` when the dialog was closed without choosing. Needs `admins.view`,\n * which is deliberately separate from managing them: assigning work to somebody is not the\n * same as being allowed to edit their account.\n */\nexport async function selectAdmin(options: AdminPickerOptions = {}): Promise<Admin | undefined> {\n const chosen = await pick({ ...options, multiple: false })\n\n return chosen?.[0]\n}\n\n/**\n * Several administrators, ticked and confirmed.\n *\n * Resolves with `undefined` when the dialog was closed, and never with an empty list — the\n * confirming button is disabled until somebody is ticked, so \"none\" can only mean \"cancelled\".\n */\nexport function selectAdmins(options: AdminPickerOptions = {}): Promise<Admin[] | undefined> {\n return pick({ ...options, multiple: true })\n}\n","<script setup lang=\"ts\">\nimport { computed, inject, ref, watch } from 'vue'\nimport { useAdmin, useI18n, useTranslate } from '@webx-ui/module-admin'\nimport { avatarResolverKey, useAuth, type AvatarResolver } from './session'\n\n/**\n * The corner of the header: who this is, which language they read the panel in, and the way\n * out.\n *\n * The language picker lives here rather than in a settings screen because it is a property of\n * the person, not of the site — and because somebody who has landed in a language they cannot\n * read needs it within reach, not three clicks into a section they cannot navigate.\n */\nconst props = withDefaults(\n defineProps<{\n signOutLabel?: string\n /** Overrides the resolver `auth()` was given. Without either, initials. */\n resolveAvatar?: AvatarResolver\n }>(),\n { signOutLabel: undefined, resolveAvatar: undefined },\n)\n\nconst admin = useAdmin()\nconst auth = useAuth()\nconst i18n = useI18n()\nconst t = useTranslate('webx-auth')\n\nconst user = computed(() => admin.state.user)\n\n// One language is not a choice, and a menu that offers it is noise.\nconst languages = computed(() =>\n i18n.state.panelLocales.length > 1 ? i18n.state.panelLocales : [],\n)\n\nconst provided = inject(avatarResolverKey, null)\nconst avatarUrl = ref<string | undefined>(undefined)\n\n/*\n * The server sends the key the photograph is stored under, not a picture: turning one into\n * the other is the library's job, and the panel says which library. Resolved again whenever\n * the key changes — somebody editing their own photograph is told about it by the row, and\n * the corner should agree with the row.\n */\nwatch(\n () => (typeof user.value?.avatar === 'string' ? user.value.avatar : null),\n async (key) => {\n const resolve = props.resolveAvatar ?? provided\n\n if (key === null || key === '' || resolve === null) {\n avatarUrl.value = undefined\n\n return\n }\n\n const url = await resolve(key)\n\n // Only the answer to the key still on screen: a slow answer to a previous one is stale.\n if (user.value?.avatar === key) {\n avatarUrl.value = url ?? undefined\n }\n },\n { immediate: true },\n)\n\nasync function choose(code: string): Promise<void> {\n if (code === i18n.state.locale) {\n return\n }\n\n await auth.setLocale(code)\n}\n</script>\n\n<template>\n <wx-dropdown v-if=\"user !== null\">\n <!--\n The avatar is the button. Wrapped in an action it was a small picture inside a box the\n same size as every icon beside it, which reads as one more tool rather than as who is\n signed in.\n -->\n <template #trigger>\n <button type=\"button\" class=\"wx-user-menu__trigger\" :title=\"user.name\">\n <wx-avatar :name=\"user.name\" :src=\"avatarUrl\" size=\"lg\" />\n </button>\n </template>\n\n <wx-dropdown-item disabled>\n <wx-text size=\"sm\">{{ user.email }}</wx-text>\n </wx-dropdown-item>\n\n <template v-if=\"languages.length > 0\">\n <wx-divider spacing=\"sm\" />\n\n <wx-dropdown-item disabled>\n <wx-text size=\"sm\" tone=\"muted\">{{ t('menu.language') }}</wx-text>\n </wx-dropdown-item>\n\n <wx-dropdown-item\n v-for=\"language in languages\"\n :key=\"language.code\"\n :icon=\"language.code === i18n.state.locale ? 'check' : undefined\"\n @click=\"choose(language.code)\"\n >\n {{ language.nativeName }}\n </wx-dropdown-item>\n\n <wx-divider spacing=\"sm\" />\n </template>\n\n <wx-dropdown-item icon=\"logout\" @click=\"auth.logout()\">\n {{ signOutLabel ?? t('menu.sign-out') }}\n </wx-dropdown-item>\n </wx-dropdown>\n</template>\n\n<style scoped>\n/*\n * The avatar is the whole button: no border, no background, nothing of the browser's own —\n * a bordered box around a round picture reads as one more tool in the row of icons, which is\n * what wrapping it in an action looked like in the first place.\n */\n.wx-user-menu__trigger {\n display: inline-flex;\n padding: 0;\n border: none;\n border-radius: var(--wx-radius-full);\n background: transparent;\n cursor: pointer;\n transition: opacity var(--wx-duration-fast) var(--wx-easing-standard);\n}\n\n.wx-user-menu__trigger:hover {\n opacity: 0.85;\n}\n\n.wx-user-menu__trigger:focus-visible {\n outline: 2px solid var(--wx-border-focus);\n outline-offset: 2px;\n}\n</style>\n","import { h, watch } from 'vue'\nimport type { Admin, AdminPlugin } from '@webx-ui/module-admin'\nimport LoginCard from './LoginCard.vue'\nimport { authMessages } from './messages'\nimport { createAuthSession, provideAuth, type AvatarResolver } from './session'\n\nexport interface AuthOptions {\n /** Where the sign-in screen lives, inside the panel. */\n path?: string\n /** Props handed to the card — the labels, mostly. */\n card?: Record<string, unknown>\n /**\n * Turns the key the signed-in person's photograph is stored under into an address, so the\n * menu in the corner shows the picture rather than initials. The same function `admins()`\n * takes: this package does not depend on the media one, and the panel is the only place\n * that knows both are installed.\n */\n resolveAvatar?: AvatarResolver\n}\n\n/**\n * Signing in, as a plugin rather than a section: it owns a route and a guard, and it has no\n * place in the menu.\n *\n * Installing it also tells the panel how to find out who is signed in, which is why the panel\n * itself knows nothing about authentication — it holds the answer, not the question.\n */\nexport function auth(options: AuthOptions = {}): AdminPlugin {\n const path = options.path ?? '/login'\n\n return {\n install(admin: Admin) {\n const session = createAuthSession(admin.context)\n\n // The floor under this module's labels: what the server has not translated, and what\n // a panel assembled without a server has to fall back on.\n admin.i18n.defaults('webx-auth', authMessages)\n\n provideAuth(admin.app, session, options.resolveAvatar ?? null)\n admin.context.useSessionLoader(() => session.me())\n\n admin.router.addRoute({\n path,\n name: 'webx.login',\n meta: { public: true },\n component: { render: () => h(LoginCard, { ...options.card }) },\n })\n\n admin.router.beforeEach((to) => {\n if (to.meta.public === true) {\n return true\n }\n\n if (admin.context.state.status !== 'unauthenticated') {\n return true\n }\n\n return { path, query: to.fullPath === '/' ? {} : { next: to.fullPath } }\n })\n\n // Both directions are driven by the state rather than by an event, because the card\n // cannot be relied on to announce its own success: signing in flips the panel to\n // `ready`, the shell swaps the branch it renders, and the card is unmounted before the\n // line after `await` runs.\n watch(\n () => admin.context.state.status,\n (status) => {\n const current = admin.router.currentRoute.value\n\n // A session can also end between requests — a sign-out in another tab, an account\n // switched off. The panel learns that from a 401 on whatever it asked for next,\n // which is not a navigation, so nothing would move without this.\n if (status === 'unauthenticated' && current.path !== path) {\n void admin.router.replace({ path, query: { next: current.fullPath } })\n\n return\n }\n\n if (status === 'ready' && current.path === path) {\n const next = current.query.next\n\n void admin.router.replace(typeof next === 'string' ? next : '/')\n }\n },\n )\n },\n }\n}\n\nexport { admins, type AdminsOptions } from './module'\nexport { createAdminsApi, type AdminsApi } from './admins'\nexport { selectAdmin, selectAdmins, type AdminPickerOptions } from './selectAdmin'\nexport { default as WxAdminsPage } from './AdminsPage.vue'\nexport { default as WxAdminList } from './AdminList.vue'\nexport type { Admin, AdminInput, AdminPage, AdminQuery, AdminRole, Role } from './types'\nexport { createAuthSession, provideAuth, useAuth, authKey, avatarResolverKey } from './session'\nexport type { AuthSession, AvatarResolver, Credentials } from './session'\nexport { default as WxLoginCard } from './LoginCard.vue'\nexport { default as WxUserMenu } from './UserMenu.vue'\nexport { authMessages } from './messages'\n"],"names":["authKey","useAuth","session","inject","createAuthSession","admin","base","error","isUnauthenticated","credentials","body","code","avatarResolverKey","provideAuth","app","resolveAvatar","props","__props","emit","__emit","auth","t","useTranslate","labels","computed","email","ref","password","rememberMe","busy","revealed","capsLock","message","errors","secondsLeft","emailField","useTemplateRef","countdown","onBeforeUnmount","stopCountdown","throttled","throttleNotice","submit","handle","HttpError","startCountdown","seconds","trackCapsLock","event","__expose","_createBlock","_component_wx_card","_createVNode","_component_wx_form","_component_wx_alert","_component_wx_form_item","_component_wx_input","$event","_component_wx_icon","_createElementVNode","_cache","_component_wx_checkbox","_component_wx_button","_createTextVNode","_toDisplayString","authMessages","createAdminsApi","data","query","id","input","seeded","useAuthMessages","i18n","useAdmin","resolve","dismiss","open","useModal","context","api","useI18n","useErrorText","roles","saving","form","photo","value","editing","roleOptions","one","localeOptions","all","watch","errorOf","field","save","payload","saved","toast","_unref","WxDialog","WxSpace","WxButton","_hoisted_1","_hoisted_2","_openBlock","_createElementBlock","_hoisted_3","_resolveDynamicComponent","_hoisted_4","WxFormItem","WxInput","WxSelect","WxCheckbox","WxText","page","loading","role","active","selected","avatars","last","stateOptions","columns","load","state","resolveAvatars","row","url","actionsFor","onRow","clickable","onSelection","_keys","rows","WxTable","_withCtx","WxAvatar","WxBadge","_Fragment","_renderList","WxRowMenu","WxDate","list","edit","createModal","AdminDialog","canManage","title","module","remove","confirm","WxListScreen","_createSlots","AdminList","admins","options","path","h","AdminsPage","count","chosen","track","pick","AdminPicker","selectAdmin","selectAdmins","user","languages","provided","avatarUrl","key","choose","_component_wx_dropdown","_component_wx_avatar","_component_wx_dropdown_item","_component_wx_text","_component_wx_divider","language","LoginCard","to","status","current","next"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/session.ts","../src/LoginCard.vue","../src/messages.ts","../src/admins.ts","../src/i18n.ts","../src/AdminDialog.vue","../src/AdminList.vue","../src/AdminsPage.vue","../src/module.ts","../src/AdminPicker.vue","../src/selectAdmin.ts","../src/ProfileDialog.vue","../src/UserMenu.vue","../src/index.ts"],"sourcesContent":["import { inject, type App, type Component, type InjectionKey } from 'vue'\nimport type { AdminContext, AdminUser } from '@webx-ui/module-admin'\n\nexport interface Credentials {\n email: string\n password: string\n remember?: boolean\n}\n\nexport interface AuthSession {\n /** Who is signed in, or null. A 401 is an answer, not a failure. */\n me(): Promise<AdminUser | null>\n /** Signs in and tells the panel to load itself. Throws `HttpError` on a refusal. */\n login(credentials: Credentials): Promise<AdminUser>\n logout(): Promise<void>\n /**\n * Change the language this administrator reads the panel in.\n *\n * Stored on the person rather than in this browser, so it follows them to the next machine\n * and so the server writes its own messages — a 422 under a field — in the same language as\n * the label above it.\n */\n setLocale(code: string): Promise<void>\n /**\n * Change what is yours to change about yourself: your name, your photograph, your password.\n *\n * Not the administrators endpoint. That one is behind `admins.manage`, which is a permission\n * about other people — an editor who may not manage anybody still has a name to spell and\n * a password to rotate.\n */\n updateProfile(profile: ProfileInput): Promise<AdminUser>\n}\n\n/** What somebody may send about themselves. A blank password is \"leave it alone\". */\nexport interface ProfileInput {\n name: string\n avatar: string | null\n password?: string\n /** Required by the server whenever `password` is filled in. */\n current_password?: string\n}\n\nexport const authKey: InjectionKey<AuthSession> = Symbol('webx-auth')\n\nexport function useAuth(): AuthSession {\n const session = inject(authKey, null)\n\n if (session === null) {\n throw new Error('useAuth() was called outside a panel that installed the auth plugin.')\n }\n\n return session\n}\n\n/**\n * The three calls `webx-ui/module-auth` answers, and nothing else.\n *\n * The session lives in a cookie, so there is nothing to keep here: the source of truth is the\n * server, and the panel asks it rather than remembering.\n */\nexport function createAuthSession(admin: AdminContext): AuthSession {\n const base = `${admin.apiPath}/auth`\n\n return {\n async me() {\n try {\n const body = await admin.http.get<{ data: AdminUser | null }>(`${base}/me`)\n\n return body.data\n } catch (error) {\n if (isUnauthenticated(error)) {\n return null\n }\n\n throw error\n }\n },\n\n async login(credentials) {\n const body = await admin.http.post<{ data: AdminUser }>(`${base}/login`, {\n email: credentials.email,\n password: credentials.password,\n remember: credentials.remember ?? false,\n })\n\n admin.setUser(body.data)\n\n // Signing in is the moment the panel becomes knowable: the manifest needs a session,\n // so it could not have been loaded before now.\n await admin.reload()\n\n return body.data\n },\n\n async setLocale(code) {\n // The panel redraws only after the server has accepted the choice: a language that\n // half took — menus switched, error messages not — is worse than one that did not.\n const body = await admin.http.put<{ data: AdminUser }>(`${base}/locale`, { locale: code })\n\n admin.setUser(body.data)\n\n await admin.setLocale(code)\n },\n\n async updateProfile(profile) {\n const body = await admin.http.put<{ data: AdminUser }>(\n `${base}/me`,\n profile as unknown as Record<string, unknown>,\n )\n\n // The corner of the panel is drawn from this, so the new name and the new photograph\n // are on screen before the dialog has finished closing.\n admin.setUser(body.data)\n\n return body.data\n },\n\n async logout() {\n try {\n await admin.http.post(`${base}/logout`)\n } finally {\n // Whatever the server said, this browser is done: leaving the panel drawn behind a\n // failed sign-out would be worse than signing out optimistically.\n admin.setUser(null)\n admin.state.status = 'unauthenticated'\n admin.state.manifest = null\n }\n },\n }\n}\n\n/** Turns the key an avatar is stored under into an address the browser can load. */\nexport type AvatarResolver = (key: string) => Promise<string | null>\n\n/**\n * The resolver the panel handed to `auth()`, for whatever draws the signed-in person — the\n * menu in the corner, first of all. Null in a panel that has no library to resolve against,\n * which is what initials are for.\n */\nexport const avatarResolverKey: InjectionKey<AvatarResolver | null> = Symbol('webx-auth-avatar')\n\n/**\n * The field the panel picks photographs with, for the profile dialog behind the corner menu.\n *\n * Provided rather than passed: the shell renders the user menu itself, and a component it\n * builds is not a place to hand a component down through. Null in a panel with no library —\n * the profile still opens, with everything but the photograph.\n */\nexport const avatarFieldKey: InjectionKey<Component | null> = Symbol('webx-auth-avatar-field')\n\nexport function provideAuth(\n app: App,\n session: AuthSession,\n resolveAvatar: AvatarResolver | null = null,\n avatarField: Component | null = null,\n): void {\n app.provide(authKey, session)\n app.provide(avatarResolverKey, resolveAvatar)\n app.provide(avatarFieldKey, avatarField)\n}\n\nfunction isUnauthenticated(error: unknown): boolean {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'status' in error &&\n (error as { status: unknown }).status === 401\n )\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, useTemplateRef } from 'vue'\nimport { HttpError, useTranslate } from '@webx-ui/module-admin'\nimport { useAuth } from './session'\n\n/**\n * The card a panel opens on.\n *\n * No logo, no product name, no \"welcome back\" — an admin panel's front door should say as\n * little as possible to somebody who has no business behind it, and the people who do have\n * business behind it already know where they are.\n *\n * Every string is still a prop and a prop given still wins. What changed is what happens when\n * one is not: the label comes from the panel's dictionary, which the server assembles from the\n * same `lang` files it answers its own messages from. A panel gets its language without being\n * told every label twice, and a card placed outside a panel still speaks English.\n */\nconst props = withDefaults(\n defineProps<{\n emailLabel?: string\n passwordLabel?: string\n rememberLabel?: string\n submitLabel?: string\n capsLockWarning?: string\n /** `:seconds` is replaced with however long the server said to wait. */\n throttleMessage?: string\n revealLabel?: string\n hideLabel?: string\n /** Offer to stay signed in. The server honours it with a long-lived cookie. */\n remember?: boolean\n /** Put the cursor in the email field. Off for a card that is not the only thing on screen. */\n autofocus?: boolean\n }>(),\n {\n // Undefined rather than a string, so \"not given\" is tellable from \"given as English\" and\n // the dictionary gets its turn.\n emailLabel: undefined,\n passwordLabel: undefined,\n rememberLabel: undefined,\n submitLabel: undefined,\n capsLockWarning: undefined,\n throttleMessage: undefined,\n revealLabel: undefined,\n hideLabel: undefined,\n remember: true,\n autofocus: true,\n },\n)\n\nconst emit = defineEmits<{ success: [] }>()\n\nconst auth = useAuth()\nconst t = useTranslate('webx-auth')\n\nconst labels = computed(() => ({\n email: props.emailLabel ?? t('card.email'),\n password: props.passwordLabel ?? t('card.password'),\n remember: props.rememberLabel ?? t('card.remember'),\n submit: props.submitLabel ?? t('card.submit'),\n capsLock: props.capsLockWarning ?? t('card.caps-lock'),\n reveal: props.revealLabel ?? t('card.reveal'),\n hide: props.hideLabel ?? t('card.hide'),\n}))\n\nconst email = ref('')\nconst password = ref('')\nconst rememberMe = ref(false)\n\nconst busy = ref(false)\nconst revealed = ref(false)\nconst capsLock = ref(false)\nconst message = ref('')\nconst errors = ref<Record<string, string[]>>({})\nconst secondsLeft = ref(0)\n\nconst emailField = useTemplateRef<HTMLInputElement>('emailField')\n\nlet countdown: ReturnType<typeof setInterval> | null = null\n\nonBeforeUnmount(stopCountdown)\n\nconst throttled = computed(() => secondsLeft.value > 0)\n\nconst throttleNotice = computed(() =>\n props.throttleMessage === undefined\n ? t('card.throttled', { seconds: secondsLeft.value })\n : props.throttleMessage.replace(':seconds', String(secondsLeft.value)),\n)\n\nasync function submit(): Promise<void> {\n if (busy.value || throttled.value) {\n return\n }\n\n busy.value = true\n message.value = ''\n errors.value = {}\n\n try {\n await auth.login({\n email: email.value,\n password: password.value,\n remember: rememberMe.value,\n })\n\n password.value = ''\n emit('success')\n } catch (error) {\n handle(error)\n } finally {\n busy.value = false\n }\n}\n\nfunction handle(error: unknown): void {\n if (!(error instanceof HttpError)) {\n message.value = error instanceof Error ? error.message : String(error)\n\n return\n }\n\n // A bad password comes back as a 422 against the email field, the same as an address\n // nobody owns — the server refuses to say which, and so does this.\n if (error.isValidation) {\n errors.value = error.errors\n\n return\n }\n\n if (error.isThrottled) {\n startCountdown(error.retryAfter ?? 60)\n\n return\n }\n\n message.value = error.message\n}\n\nfunction startCountdown(seconds: number): void {\n stopCountdown()\n secondsLeft.value = seconds\n\n countdown = setInterval(() => {\n secondsLeft.value -= 1\n\n if (secondsLeft.value <= 0) {\n stopCountdown()\n }\n }, 1000)\n}\n\nfunction stopCountdown(): void {\n if (countdown !== null) {\n clearInterval(countdown)\n countdown = null\n }\n\n secondsLeft.value = 0\n}\n\n/**\n * Worth saying out loud: a password typed in capitals fails with the same unhelpful message\n * as a wrong one, and people retype it three times before noticing.\n */\nfunction trackCapsLock(event: KeyboardEvent): void {\n capsLock.value = event.getModifierState('CapsLock')\n}\n\ndefineExpose({ focus: () => emailField.value?.focus() })\n</script>\n\n<template>\n <wx-card class=\"wx-login\" :padding=\"24\">\n <wx-form :errors=\"errors\" :disabled=\"busy\" gap=\"md\" @submit.prevent=\"submit\">\n <wx-alert\n v-if=\"message !== ''\"\n type=\"danger\"\n variant=\"soft\"\n live\n :description=\"message\"\n class=\"wx-login__message\"\n />\n\n <wx-alert\n v-else-if=\"throttled\"\n type=\"warning\"\n variant=\"soft\"\n live\n :description=\"throttleNotice\"\n class=\"wx-login__message\"\n />\n\n <wx-form-item name=\"email\">\n <wx-input\n ref=\"emailField\"\n v-model=\"email\"\n type=\"email\"\n :placeholder=\"labels.email\"\n :aria-label=\"labels.email\"\n autocomplete=\"username\"\n :autofocus=\"autofocus\"\n inputmode=\"email\"\n size=\"lg\"\n >\n <template #prefix><wx-icon name=\"mail\" /></template>\n </wx-input>\n </wx-form-item>\n\n <wx-form-item name=\"password\" :help=\"capsLock ? labels.capsLock : undefined\">\n <wx-input\n v-model=\"password\"\n :type=\"revealed ? 'text' : 'password'\"\n :placeholder=\"labels.password\"\n :aria-label=\"labels.password\"\n autocomplete=\"current-password\"\n size=\"lg\"\n @keydown=\"trackCapsLock\"\n @keyup=\"trackCapsLock\"\n >\n <template #prefix><wx-icon name=\"lock\" /></template>\n <template #suffix>\n <!-- A bare icon rather than a button with a surface: it sits inside the field, and\n a second box in there reads as a second control. Out of the tab order because\n it is a convenience, not a step. -->\n <button\n type=\"button\"\n class=\"wx-login__reveal\"\n :title=\"revealed ? labels.hide : labels.reveal\"\n :aria-label=\"revealed ? labels.hide : labels.reveal\"\n :aria-pressed=\"revealed\"\n tabindex=\"-1\"\n @click=\"revealed = !revealed\"\n >\n <wx-icon :name=\"revealed ? 'eye-off' : 'eye'\" />\n </button>\n </template>\n </wx-input>\n </wx-form-item>\n\n <wx-checkbox\n v-if=\"remember\"\n v-model=\"rememberMe\"\n :label=\"labels.remember\"\n class=\"wx-login__remember\"\n />\n\n <wx-button\n type=\"primary\"\n native-type=\"submit\"\n size=\"lg\"\n block\n :loading=\"busy\"\n :disabled=\"throttled\"\n >\n {{ labels.submit }}\n </wx-button>\n </wx-form>\n </wx-card>\n</template>\n\n<style scoped>\n.wx-login {\n width: 100%;\n /* Wide enough for an email address, narrow enough to stay a card rather than a page. */\n max-width: 26rem;\n}\n\n.wx-login__message {\n margin-block-end: var(--wx-space-4);\n}\n\n/* The checkbox belongs to the password above it, not to the form's row of fields: pulled up\n out of the form's gap so it reads as an option under the field rather than as a field of its\n own. */\n.wx-login__remember {\n margin-block-start: calc(-1 * var(--wx-space-12));\n}\n\n.wx-login__reveal {\n display: inline-flex;\n align-items: center;\n padding: 0;\n /* A button has a font size of its own — 13.3px in every browser — and the icon inside is\n sized in em, so without this it came out smaller than the lock at the other end. */\n font: inherit;\n color: var(--wx-text-muted);\n background: none;\n border: 0;\n cursor: pointer;\n}\n\n.wx-login__reveal:hover,\n.wx-login__reveal[aria-pressed='true'] {\n color: var(--wx-text-default);\n}\n</style>\n","import type { Messages } from '@webx-ui/module-admin'\n\n/**\n * What this package says, in English.\n *\n * The same keys `webx-ui/module-auth` ships as `lang/en/*.php`. Kept here so a card placed by\n * hand, with no WebX UI server behind it, still has words — and so a key never reaches the\n * screen when a translation is missing.\n */\nexport const authMessages: Record<string, Messages> = {\n card: {\n email: 'Email',\n password: 'Password',\n remember: 'Stay signed in',\n submit: 'Sign in',\n 'caps-lock': 'Caps Lock is on.',\n reveal: 'Show the password',\n hide: 'Hide the password',\n throttled: 'Too many attempts. Try again in :seconds s.',\n failed: 'Those details do not match an account.',\n },\n admins: {\n title: 'Administrators',\n search: 'Search administrators',\n new: 'New administrator',\n edit: 'Edit administrator',\n // The same two, short enough for a button on a phone and for a dialog's heading.\n 'new-short': 'Add',\n 'edit-short': 'Edit',\n name: 'Name',\n email: 'Email',\n password: 'Password',\n 'password-hint': 'At least 12 characters',\n 'password-keep': 'Leave blank to keep the current one',\n avatar: 'Photo',\n roles: 'Roles',\n 'no-roles': 'No roles',\n active: 'Active',\n super: 'Super administrator',\n 'super-hint': 'Answers yes to everything, whatever the roles say',\n locale: 'Panel language',\n 'locale-auto': 'Follow the browser',\n 'last-login': 'Last signed in',\n save: 'Save',\n cancel: 'Cancel',\n delete: 'Delete',\n 'delete-title': 'Delete :name?',\n 'delete-text': 'They lose access at once. What they have written stays.',\n 'all-roles': 'All roles',\n 'any-state': 'Active and not',\n 'only-active': 'Active only',\n 'only-inactive': 'Switched off only',\n empty: 'Nobody matches that',\n 'select-one': 'Choose an administrator',\n 'select-many': 'Choose administrators',\n chosen: 'Chosen: :count',\n created: 'Administrator created',\n updated: 'Administrator saved',\n deleted: 'Administrator deleted',\n },\n errors: {\n unauthenticated: 'Unauthenticated.',\n inactive: 'This account is no longer active.',\n forbidden: 'This account may not do that.',\n 'not-yourself': 'You cannot do that to your own account.',\n 'last-super': 'There has to be one super administrator left.',\n },\n // Somebody editing themselves, which is a shorter list than an administrator editing them.\n profile: {\n title: 'Your profile',\n menu: 'Edit your profile',\n name: 'Name',\n avatar: 'Photo',\n password: 'New password',\n 'password-hint': 'At least 12 characters. Leave blank to keep the current one.',\n 'current-password': 'Current password',\n 'current-password-hint': 'Asked for before the password is changed.',\n 'wrong-password': 'That is not the current password.',\n saved: 'Your profile is saved.',\n save: 'Save',\n cancel: 'Cancel',\n },\n menu: {\n 'sign-out': 'Sign out',\n language: 'Interface language',\n },\n}\n","import type { AdminContext } from '@webx-ui/module-admin'\nimport type { Admin, AdminInput, AdminPage, AdminQuery, Role } from './types'\n\nexport interface AdminsApi {\n list(query?: AdminQuery): Promise<AdminPage>\n get(id: number): Promise<Admin>\n create(input: AdminInput): Promise<Admin>\n update(id: number, input: AdminInput): Promise<Admin>\n remove(id: number): Promise<void>\n roles(): Promise<Role[]>\n}\n\n/**\n * The calls behind the administrators screen, and behind the picker that opens from code.\n *\n * Reading is separate from managing on the server, so a module that only wants to say who\n * wrote something — or offer a list of people to assign work to — can use `list` and `roles`\n * without being allowed anywhere near the rest.\n */\nexport function createAdminsApi(admin: AdminContext): AdminsApi {\n const base = `${admin.apiPath}/auth`\n const data = <T>(body: { data: T }): T => body.data\n\n return {\n list: (query = {}) =>\n admin.http\n .get<{ data: Admin[]; meta: Omit<AdminPage, 'data'> }>(`${base}/admins`, {\n query: {\n q: query.q || undefined,\n role: query.role ?? undefined,\n active: query.active ?? undefined,\n sort: query.sort,\n page: query.page,\n per_page: query.per_page,\n },\n })\n .then((body) => ({ ...body.meta, data: body.data })),\n\n get: (id) => admin.http.get<{ data: Admin }>(`${base}/admins/${id}`).then(data),\n\n create: (input) => admin.http.post<{ data: Admin }>(`${base}/admins`, input).then(data),\n\n update: (id, input) =>\n admin.http.patch<{ data: Admin }>(`${base}/admins/${id}`, input).then(data),\n\n remove: (id) => admin.http.delete<void>(`${base}/admins/${id}`),\n\n roles: () => admin.http.get<{ data: Role[] }>(`${base}/roles`).then(data),\n }\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { authMessages } from './messages'\n\nconst seeded = new WeakSet<object>()\n\n/**\n * Put this package's English under the panel's dictionary, once per panel.\n *\n * The plugin does this too, but a panel can use the administrators screen — or just the picker\n * — without installing the sign-in plugin at all, and a screen showing `admins.title` instead\n * of a word is not a thing to leave to chance.\n */\nexport function useAuthMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-auth', authMessages)\n seeded.add(i18n)\n }\n } catch {\n // Outside a panel there is no dictionary to seed, and `useTranslate` falls back on its own.\n }\n}\n","<script setup lang=\"ts\">\nimport { computed, ref, watch, type Component } from 'vue'\nimport { useAdmin, useErrorText, useI18n, useTranslate } from '@webx-ui/module-admin'\nimport {\n toast,\n useModal,\n WxButton,\n WxCheckbox,\n WxDialog,\n WxFormItem,\n WxInput,\n WxSelect,\n WxSpace,\n WxText,\n} from '@webx-ui/core'\n\nimport { createAdminsApi } from './admins'\nimport { useAuthMessages } from './i18n'\nimport type { Admin, AdminInput, Role } from './types'\n\n/**\n * Creating an administrator and editing one — the same form, because they differ in one field.\n *\n * The password is required the first time and optional afterwards, and a blank one on an edit\n * means \"leave it alone\" rather than \"set it to nothing\". That is the only conditional here.\n */\nconst props = withDefaults(\n defineProps<{\n /** Absent creates somebody; present edits them. */\n admin?: Admin | null\n /**\n * The field to pick the photograph with — `WxMediaField` in a panel that has the library.\n *\n * Handed in rather than imported: this package does not depend on the media one, and a\n * panel without a library still edits everything else about a person.\n */\n avatarField?: Component\n }>(),\n { admin: null, avatarField: undefined },\n)\n\nconst { resolve, dismiss, open } = useModal<Admin>()\n\nconst context = useAdmin()\nconst api = createAdminsApi(context)\nconst i18n = useI18n()\nuseAuthMessages()\n\nconst t = useTranslate('webx-auth')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\nconst roles = ref<Role[]>([])\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst form = ref<AdminInput & { avatar: string | null }>({\n name: '',\n email: '',\n password: '',\n avatar: null,\n is_active: true,\n is_super: false,\n locale: null,\n roles: [],\n})\n\n/** The field hands back `{ path, … }`; what is stored is the key. */\nconst photo = computed({\n get: () => (form.value.avatar ? { path: form.value.avatar } : null),\n set: (value: { path: string } | null) => {\n form.value.avatar = value?.path ?? null\n },\n})\n\nconst editing = computed(() => props.admin !== null)\n\nconst roleOptions = computed(() => roles.value.map((one) => ({ value: one.id, label: one.name })))\n\n/* A word, not an empty string: a select reads \"\" as nothing chosen and shows a blank control. */\nconst localeOptions = computed(() => [\n { value: 'auto', label: t('admins.locale-auto') },\n ...i18n.state.panelLocales.map((one) => ({ value: one.code, label: one.nativeName })),\n])\n\nvoid api.roles().then((all) => (roles.value = all))\n\nwatch(\n () => props.admin,\n (admin) => {\n form.value = {\n name: admin?.name ?? '',\n email: admin?.email ?? '',\n password: '',\n avatar: admin?.avatar ?? null,\n is_active: admin?.is_active ?? true,\n is_super: admin?.is_super ?? false,\n locale: admin?.locale ?? 'auto',\n roles: admin?.roles.map((one) => one.id) ?? [],\n }\n errors.value = {}\n },\n { immediate: true },\n)\n\nfunction errorOf(field: string): string | undefined {\n return errors.value[field]?.[0]\n}\n\nasync function save(): Promise<void> {\n saving.value = true\n errors.value = {}\n\n const payload: AdminInput = {\n name: form.value.name,\n email: form.value.email,\n avatar: form.value.avatar,\n is_active: form.value.is_active,\n is_super: form.value.is_super,\n locale: form.value.locale === 'auto' ? null : form.value.locale,\n roles: form.value.roles ?? [],\n }\n\n if (form.value.password) {\n payload.password = form.value.password\n }\n\n try {\n const saved = props.admin\n ? await api.update(props.admin.id, payload)\n : await api.create(payload)\n\n toast.success(t(editing.value ? 'admins.updated' : 'admins.created'))\n resolve(saved)\n } catch (error) {\n const body = (error as { body?: { errors?: Record<string, string[]>; message?: string } }).body\n\n if (body?.errors) {\n errors.value = body.errors\n } else {\n // The two refusals that keep a panel reachable — the last super administrator, and doing\n // it to yourself — arrive as a 422 with a message and no field, which is the one shape\n // the panel repeats back word for word.\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog\n v-model:open=\"open\"\n :title=\"editing ? t('admins.edit-short') : t('admins.new-short')\"\n :width=\"720\"\n >\n <!--\n The photograph beside the fields rather than above them: it is square, the fields are a\n column, and stacking the two makes somebody scroll past a picture to reach a name. Under\n `--wx-admin-form-wrap` the sidebar goes back on top, which is the only shape that fits a\n phone.\n -->\n <div class=\"wx-admin-form\">\n <div class=\"wx-admin-form__grid\">\n <div v-if=\"avatarField\" class=\"wx-admin-form__aside\">\n <component\n :is=\"avatarField\"\n v-model=\"photo\"\n :label=\"t('admins.avatar')\"\n aspect=\"1/1\"\n :captions=\"false\"\n />\n </div>\n\n <div class=\"wx-admin-form__fields\">\n <wx-form-item :label=\"t('admins.name')\" :error=\"errorOf('name')\">\n <wx-input v-model=\"form.name\" name=\"name\" />\n </wx-form-item>\n\n <wx-form-item :label=\"t('admins.email')\" :error=\"errorOf('email')\">\n <wx-input v-model=\"form.email\" type=\"email\" name=\"email\" autocomplete=\"off\" />\n </wx-form-item>\n\n <wx-form-item\n :label=\"t('admins.password')\"\n :error=\"errorOf('password')\"\n :help=\"editing ? t('admins.password-keep') : t('admins.password-hint')\"\n >\n <wx-input\n v-model=\"form.password\"\n type=\"password\"\n name=\"password\"\n autocomplete=\"new-password\"\n />\n </wx-form-item>\n\n <wx-form-item :label=\"t('admins.roles')\" :error=\"errorOf('roles')\">\n <wx-select v-model=\"form.roles\" :options=\"roleOptions\" multiple />\n </wx-form-item>\n\n <wx-form-item :label=\"t('admins.locale')\">\n <wx-select v-model=\"form.locale\" :options=\"localeOptions\" />\n </wx-form-item>\n\n <wx-space size=\"md\">\n <wx-checkbox v-model=\"form.is_active\" :label=\"t('admins.active')\" />\n <wx-checkbox v-model=\"form.is_super\" :label=\"t('admins.super')\" />\n </wx-space>\n\n <wx-text size=\"sm\" tone=\"muted\">{{ t('admins.super-hint') }}</wx-text>\n </div>\n </div>\n </div>\n\n <template #footer>\n <wx-space size=\"sm\">\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('admins.cancel') }}</wx-button>\n <wx-button type=\"primary\" :loading=\"saving\" @click=\"save\">\n {{ t('admins.save') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n\n<style>\n/* The container is the wrapper, not the grid: an element cannot query its own width. */\n.wx-admin-form {\n container-type: inline-size;\n}\n\n.wx-admin-form__grid {\n display: grid;\n grid-template-columns: 200px minmax(0, 1fr);\n gap: var(--wx-space-24);\n}\n\n.wx-admin-form__fields {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n min-width: 0;\n}\n\n/* The dialog's own width decides this, not the window's. */\n@container (max-width: 520px) {\n .wx-admin-form__grid {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .wx-admin-form__aside {\n max-width: 200px;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useAdmin, useTranslate, WxDate, WxRowMenu, type RowAction } from '@webx-ui/module-admin'\nimport {\n WxAvatar,\n WxBadge,\n WxSelect,\n WxSpace,\n WxTable,\n WxText,\n type TableColumn,\n type TableState,\n} from '@webx-ui/core'\nimport { createAdminsApi } from './admins'\nimport { useAuthMessages } from './i18n'\nimport type { Admin, AdminPage, Role } from './types'\n\n/**\n * The list of administrators — the screen, and the inside of the picker.\n *\n * One component for both so that searching, filtering and paging behave the same wherever\n * somebody is looking at these people. What changes is only what a row is for: opening it, or\n * choosing it.\n */\nconst props = withDefaults(\n defineProps<{\n /** Rows are chosen rather than opened. */\n picking?: boolean\n /** More than one may be chosen at a time. */\n multiple?: boolean\n /** Adds the column that removes somebody. */\n removable?: boolean\n /**\n * Offers the role and state filters.\n *\n * Off by default: a panel has half a dozen administrators, and a filter over six rows is a\n * control to read and decide about where a glance would have done.\n */\n filters?: boolean\n /** Turns an avatar key into an address. The panel wires this; this package cannot. */\n resolveAvatar?: (key: string) => Promise<string | null>\n }>(),\n {\n picking: false,\n multiple: false,\n removable: false,\n filters: false,\n resolveAvatar: undefined,\n },\n)\n\nconst emit = defineEmits<{\n open: [admin: Admin]\n chosen: [admins: Admin[]]\n /** Which rows are ticked, for a footer that counts them. */\n selection: [admins: Admin[]]\n remove: [admin: Admin]\n}>()\n\nconst admin = useAdmin()\nconst api = createAdminsApi(admin)\nuseAuthMessages()\n\nconst t = useTranslate('webx-auth')\n\nconst page = ref<AdminPage | null>(null)\nconst roles = ref<Role[]>([])\nconst loading = ref(false)\n/* A word rather than an empty string: a select reads \"\" as nothing chosen and shows a\n blank control where the option said \"all roles\". */\nconst role = ref('all')\nconst active = ref<'any' | 'yes' | 'no'>('any')\nconst selected = ref<Admin[]>([])\nconst avatars = ref<Record<string, string>>({})\n\nlet last: TableState = { page: 1, perPage: 20, sort: null, search: '' }\n\nconst roleOptions = computed(() => [\n { value: 'all', label: t('admins.all-roles') },\n ...roles.value.map((one) => ({ value: one.slug, label: one.name })),\n])\n\nconst stateOptions = computed(() => [\n { value: 'any', label: t('admins.any-state') },\n { value: 'yes', label: t('admins.only-active') },\n { value: 'no', label: t('admins.only-inactive') },\n])\n\n/*\n * No widths but the one the buttons need.\n *\n * A declared width is a promise the table has to keep, and five of them add up to more than the\n * screen — so it scrolls sideways while half the row is whitespace. Left alone, the columns take\n * what their contents need. What does not fit is dropped instead, in the order it can be spared.\n */\nconst columns = computed<TableColumn<Admin>[]>(() => [\n { key: 'name', label: t('admins.name'), sortable: true },\n { key: 'email', label: t('admins.email'), sortable: true, hideBelow: 560 },\n { key: 'roles', label: t('admins.roles'), hideBelow: 760 },\n { key: 'is_active', label: t('admins.active'), align: 'center', hideBelow: 660 },\n {\n key: 'last_login_at',\n label: t('admins.last-login'),\n sortable: true,\n hideBelow: 900,\n // The first thing to go when a row becomes a card: it is a date somebody scans down a\n // column, and a card has no column to scan.\n hideOnCards: true,\n },\n {\n key: 'actions',\n label: '',\n width: 56,\n align: 'right',\n hidden: !props.removable,\n // A card puts them along its top instead, through the `card-actions` slot.\n hideOnCards: true,\n },\n])\n\nvoid api.roles().then((all) => (roles.value = all))\n\nwatch([role, active], () => load(last))\n\nasync function load(state: TableState): Promise<void> {\n last = state\n loading.value = true\n\n try {\n page.value = await api.list({\n q: state.search,\n role: role.value === 'all' ? null : role.value,\n active: active.value === 'any' ? null : active.value,\n sort: state.sort ? `${state.sort.order === 'desc' ? '-' : ''}${state.sort.key}` : 'name',\n page: state.page,\n per_page: state.perPage,\n })\n\n void resolveAvatars()\n } finally {\n loading.value = false\n }\n}\n\n/**\n * A row stores the library's key, not an address, and this package has no business knowing how\n * to turn one into the other. The panel hands that in; without it a row shows initials, which\n * is what it would have shown anyway for everybody who never uploaded a photograph.\n */\nasync function resolveAvatars(): Promise<void> {\n const resolve = props.resolveAvatar\n\n if (!resolve) return\n\n for (const row of page.value?.data ?? []) {\n if (!row.avatar || avatars.value[row.avatar]) continue\n\n const url = await resolve(row.avatar)\n\n if (url) avatars.value = { ...avatars.value, [row.avatar]: url }\n }\n}\n\n/**\n * One line, and the same `···` every other list of the panel puts a record's actions behind.\n * A bare red bin in every row says deleting somebody is the thing this list is for (§20).\n */\nfunction actionsFor(row: Admin): RowAction[] {\n return [\n {\n key: 'delete',\n icon: 'trash',\n label: t('admins.delete'),\n danger: true,\n run: () => emit('remove', row),\n },\n ]\n}\n\nfunction onRow(row: Admin): void {\n if (props.picking) {\n emit('chosen', [row])\n\n return\n }\n\n emit('open', row)\n}\n\n/**\n * The listeners the table gets, rather than a handler that decides to do nothing (§13).\n *\n * Picking several is the one case where a row leads nowhere: the tick box is what chooses, and\n * a click on the row itself would have to either choose one and drop the rest or do nothing at\n * all. So it does nothing and says so — no cursor, no highlight, which is what `WxTable` reads\n * out of the missing listener. `v-bind`, not `v-on`: only that reaches it (CLAUDE.md §4).\n */\nconst clickable = computed(() => !(props.picking && props.multiple))\n\nfunction onSelection(_keys: unknown, rows: Admin[]): void {\n selected.value = rows\n emit('selection', rows)\n}\n\n/** Reload without losing where somebody was, for a caller that changed something. */\ndefineExpose({ reload: () => load(last), chosen: () => selected.value })\n</script>\n\n<template>\n <div class=\"wx-admin-list\">\n <wx-table\n :data=\"page\"\n :columns=\"columns\"\n row-key=\"id\"\n searchable\n :clickable=\"clickable\"\n :hover=\"clickable\"\n flush\n :loading=\"loading\"\n :search-placeholder=\"t('admins.search')\"\n :empty-text=\"t('admins.empty')\"\n :selectable=\"picking && multiple\"\n @row-click=\"onRow\"\n @state-change=\"load\"\n @selection-change=\"onSelection\"\n >\n <template v-if=\"filters\" #actions>\n <wx-space size=\"sm\">\n <wx-select v-model=\"role\" :options=\"roleOptions\" size=\"sm\" style=\"width: 180px\" />\n <wx-select v-model=\"active\" :options=\"stateOptions\" size=\"sm\" style=\"width: 180px\" />\n </wx-space>\n </template>\n\n <template #cell-name=\"{ row }\">\n <wx-space size=\"sm\" align=\"center\">\n <wx-avatar\n :name=\"row.name\"\n :src=\"row.avatar ? avatars[row.avatar] : undefined\"\n size=\"sm\"\n />\n <span>{{ row.name }}</span>\n <wx-badge v-if=\"row.is_super\" type=\"warning\">{{ t('admins.super') }}</wx-badge>\n </wx-space>\n </template>\n\n <template #cell-roles=\"{ row }\">\n <wx-space v-if=\"row.roles.length > 0\" size=\"xs\" wrap>\n <wx-badge v-for=\"one in row.roles\" :key=\"one.id\">{{ one.name }}</wx-badge>\n </wx-space>\n <wx-text v-else size=\"sm\" tone=\"muted\">{{ t('admins.no-roles') }}</wx-text>\n </template>\n\n <template #cell-is_active=\"{ row }\">\n <wx-badge :type=\"row.is_active ? 'success' : 'default'\" dot>\n {{ row.is_active ? t('admins.active') : t('admins.only-inactive') }}\n </wx-badge>\n </template>\n\n <template #card-actions=\"{ row }\">\n <wx-row-menu v-if=\"removable\" :actions=\"actionsFor(row)\" :label=\"row.name\" />\n </template>\n\n <template #cell-actions=\"{ row }\">\n <wx-row-menu :actions=\"actionsFor(row)\" :label=\"row.name\" />\n </template>\n\n <template #cell-last_login_at=\"{ row }\">\n <wx-date :value=\"row.last_login_at\" :tone=\"row.last_login_at ? 'default' : 'muted'\" />\n </template>\n </wx-table>\n </div>\n</template>\n\n<style>\n.wx-admin-list {\n min-width: 0;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, ref, useTemplateRef, type Component } from 'vue'\nimport { useAdmin, useErrorText, useTranslate, WxListScreen } from '@webx-ui/module-admin'\nimport { confirm, createModal, toast, WxButton } from '@webx-ui/core'\nimport AdminDialog from './AdminDialog.vue'\nimport AdminList from './AdminList.vue'\nimport { createAdminsApi } from './admins'\nimport { useAuthMessages } from './i18n'\nimport type { Admin } from './types'\n\n/**\n * The administrators section: the list, and the form over it.\n *\n * A dialog rather than a second route. Editing somebody is half a dozen fields, and a screen\n * that takes over the page for that loses the list you were reading — which is where you\n * decide who to open next.\n *\n * The heading and `Add` used to live inside the card, which made this the one section of the\n * panel with no line of its own at the top. They stand outside it now, where every other list\n * keeps them (§19).\n */\nconst props = withDefaults(\n defineProps<{\n avatarField?: Component\n resolveAvatar?: (key: string) => Promise<string | null>\n }>(),\n { avatarField: undefined, resolveAvatar: undefined },\n)\n\nconst context = useAdmin()\nconst api = createAdminsApi(context)\nuseAuthMessages()\n\nconst t = useTranslate('webx-auth')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\nconst list = useTemplateRef<{ reload: () => void }>('list')\nconst editing = ref<Admin | null>(null)\n\nconst edit = createModal<Admin, { admin: Admin | null; avatarField?: Component }>(AdminDialog)\n\nconst canManage = context.can('admins.manage')\n\n/** The section's name as the server translated it; the built-in English until it arrives. */\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'admins')?.title ??\n t('admins.title'),\n)\n\nasync function open(admin: Admin | null): Promise<void> {\n editing.value = admin\n\n const saved = await edit({ admin, avatarField: props.avatarField })\n\n if (saved) {\n list.value?.reload()\n }\n}\n\nasync function remove(admin: Admin): Promise<void> {\n const agreed = await confirm({\n title: t('admins.delete-title', { name: admin.name }),\n message: t('admins.delete-text'),\n confirmText: t('admins.delete'),\n cancelText: t('admins.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(admin.id)\n toast.success(t('admins.deleted'))\n list.value?.reload()\n } catch (error) {\n toast.danger(message(error))\n }\n}\n</script>\n\n<template>\n <wx-list-screen :title=\"title\">\n <template v-if=\"canManage\" #actions>\n <!-- Short enough for a phone, where the heading and the button share one line. -->\n <wx-button type=\"primary\" icon=\"add\" @click=\"open(null)\">\n {{ t('admins.new-short') }}\n </wx-button>\n </template>\n\n <admin-list\n ref=\"list\"\n :removable=\"canManage\"\n :resolve-avatar=\"props.resolveAvatar\"\n @open=\"open\"\n @remove=\"remove\"\n />\n </wx-list-screen>\n</template>\n","import { h, type Component } from 'vue'\nimport type { AdminModule } from '@webx-ui/module-admin'\nimport AdminsPage from './AdminsPage.vue'\n\nexport interface AdminsOptions {\n /** Where the section lives inside the panel. */\n path?: string\n /**\n * The field the photograph is picked with — `WxMediaField` in a panel that has the library.\n *\n * Handed in rather than imported: this package does not depend on the media one, and a panel\n * without a library still edits everything else about a person.\n */\n avatarField?: Component\n /** Turns an avatar key into an address. Omitted, rows and forms show initials. */\n resolveAvatar?: (key: string) => Promise<string | null>\n}\n\n/**\n * The administrators, as a section of the panel.\n *\n * The id matches the module the server reports, which is what makes the entry appear in the\n * navigation: a section shows up when both halves are installed.\n */\nexport function admins(options: AdminsOptions = {}): AdminModule {\n const path = options.path ?? '/admins'\n\n return {\n id: 'admins',\n path,\n routes: [\n {\n path,\n name: 'webx.admins',\n component: {\n render: () =>\n h(AdminsPage, {\n avatarField: options.avatarField,\n resolveAvatar: options.resolveAvatar,\n }),\n },\n },\n ],\n }\n}\n","<script setup lang=\"ts\">\nimport { ref, useTemplateRef } from 'vue'\nimport { useTranslate } from '@webx-ui/module-admin'\nimport { useModal, WxButton, WxDialog, WxSpace, WxText } from '@webx-ui/core'\nimport AdminList from './AdminList.vue'\nimport { useAuthMessages } from './i18n'\nimport type { Admin } from './types'\n\n/**\n * The list of administrators as a dialog, resolving with whoever was chosen.\n *\n * The same list as the screen — one component, so that searching and filtering behave the same\n * wherever these people are being looked at.\n */\nconst props = withDefaults(\n defineProps<{\n multiple?: boolean\n title?: string\n resolveAvatar?: (key: string) => Promise<string | null>\n }>(),\n { multiple: false, title: undefined, resolveAvatar: undefined },\n)\n\nconst { open, resolve, dismiss } = useModal<Admin[]>()\n\nuseAuthMessages()\n\nconst t = useTranslate('webx-auth')\n\nconst list = useTemplateRef<{ chosen: () => Admin[] }>('list')\nconst count = ref(0)\n\n/** One is chosen by clicking the row; several are ticked and then confirmed. */\nfunction chosen(admins: Admin[]): void {\n resolve(admins)\n}\n\nfunction track(admins: Admin[]): void {\n count.value = admins.length\n}\n</script>\n\n<template>\n <wx-dialog\n v-model:open=\"open\"\n :title=\"title ?? t(multiple ? 'admins.select-many' : 'admins.select-one')\"\n :width=\"880\"\n >\n <admin-list\n ref=\"list\"\n picking\n :multiple=\"multiple\"\n :resolve-avatar=\"props.resolveAvatar\"\n @chosen=\"chosen\"\n @selection=\"track\"\n />\n\n <template v-if=\"multiple\" #footer>\n <wx-space size=\"sm\" align=\"center\">\n <wx-text size=\"sm\" tone=\"muted\">{{ t('admins.chosen', { count }) }}</wx-text>\n <wx-button variant=\"outline\" @click=\"dismiss()\">{{ t('admins.cancel') }}</wx-button>\n <wx-button type=\"primary\" :disabled=\"count === 0\" @click=\"resolve(list?.chosen() ?? [])\">\n {{ t('admins.save') }}\n </wx-button>\n </wx-space>\n </template>\n </wx-dialog>\n</template>\n","import { createModal } from '@webx-ui/core'\nimport AdminPicker from './AdminPicker.vue'\nimport type { Admin } from './types'\n\nexport interface AdminPickerOptions {\n /** Heading of the dialog, when the default is not specific enough. */\n title?: string\n /** Turns an avatar key into an address. Omitted, rows show initials. */\n resolveAvatar?: (key: string) => Promise<string | null>\n}\n\nconst pick = createModal<Admin[], AdminPickerOptions & { multiple?: boolean }>(AdminPicker)\n\n/**\n * One administrator, chosen from a dialog.\n *\n * ```ts\n * const owner = await selectAdmin()\n * if (owner) task.assignedTo = owner.id\n * ```\n *\n * Resolves with `undefined` when the dialog was closed without choosing. Needs `admins.view`,\n * which is deliberately separate from managing them: assigning work to somebody is not the\n * same as being allowed to edit their account.\n */\nexport async function selectAdmin(options: AdminPickerOptions = {}): Promise<Admin | undefined> {\n const chosen = await pick({ ...options, multiple: false })\n\n return chosen?.[0]\n}\n\n/**\n * Several administrators, ticked and confirmed.\n *\n * Resolves with `undefined` when the dialog was closed, and never with an empty list — the\n * confirming button is disabled until somebody is ticked, so \"none\" can only mean \"cancelled\".\n */\nexport function selectAdmins(options: AdminPickerOptions = {}): Promise<Admin[] | undefined> {\n return pick({ ...options, multiple: true })\n}\n","<script setup lang=\"ts\">\nimport { ref, type Component } from 'vue'\nimport { useAdmin, useErrorText, useTranslate } from '@webx-ui/module-admin'\nimport { toast, useModal, WxButton, WxDialog, WxFormItem, WxInput } from '@webx-ui/core'\nimport { useAuth } from './session'\nimport { useAuthMessages } from './i18n'\n\n/**\n * Somebody editing themselves.\n *\n * Not `AdminDialog` with the fields taken away. What a person may change about themselves and\n * what an administrator may change about them are different lists, and the difference is the\n * whole point: roles, super, active and the sign-in address are not here, which is what makes\n * this reachable by everybody rather than by whoever holds `admins.manage`.\n *\n * The address is shown and not editable: changing where somebody signs in has to be proved at\n * the new address first, and nothing here can do that yet.\n */\nconst props = withDefaults(\n defineProps<{\n /** The field to pick the photograph with — `WxMediaField` in a panel with the library. */\n avatarField?: Component\n }>(),\n { avatarField: undefined },\n)\n\nconst { resolve, dismiss, open } = useModal<true>()\n\nconst context = useAdmin()\nconst auth = useAuth()\nuseAuthMessages()\n\nconst t = useTranslate('webx-auth')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst form = ref({\n name: context.state.user?.name ?? '',\n avatar: (context.state.user?.avatar as string | null | undefined) ?? null,\n password: '',\n current_password: '',\n})\n\n/** The field hands back `{ path, … }`; what is stored is the key. */\nconst photo = ref<{ path: string } | null>(form.value.avatar ? { path: form.value.avatar } : null)\n\nfunction errorOf(field: string): string | undefined {\n return errors.value[field]?.[0]\n}\n\nasync function save(): Promise<void> {\n saving.value = true\n errors.value = {}\n\n try {\n await auth.updateProfile({\n name: form.value.name,\n avatar: photo.value?.path ?? null,\n // Blank is left out entirely rather than sent as an empty string: the server reads a\n // filled password as \"change it\", and an empty one would be a change to nothing.\n ...(form.value.password\n ? { password: form.value.password, current_password: form.value.current_password }\n : {}),\n })\n\n toast.success(t('profile.saved'))\n resolve(true)\n } catch (error) {\n const body = (error as { body?: { errors?: Record<string, string[]> } }).body\n\n if (body?.errors) {\n errors.value = body.errors\n } else {\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n</script>\n\n<template>\n <wx-dialog v-model:open=\"open\" :title=\"t('profile.title')\" :width=\"560\">\n <!-- The host is what the query below measures: a container query on an element's own\n class resolves against its nearest ancestor container, never against itself. -->\n <div class=\"wx-profile-host\">\n <div class=\"wx-profile\">\n <div v-if=\"props.avatarField\" class=\"wx-profile__aside\">\n <component\n :is=\"props.avatarField\"\n v-model=\"photo\"\n :label=\"t('profile.avatar')\"\n aspect=\"1/1\"\n :captions=\"false\"\n />\n </div>\n\n <div class=\"wx-profile__fields\">\n <wx-form-item :label=\"t('profile.name')\" :error=\"errorOf('name')\">\n <wx-input v-model=\"form.name\" name=\"name\" />\n </wx-form-item>\n\n <!-- Shown, not offered: see the note on the component. -->\n <wx-form-item :label=\"t('admins.email')\">\n <wx-input :model-value=\"context.state.user?.email ?? ''\" readonly />\n </wx-form-item>\n\n <wx-form-item\n :label=\"t('profile.password')\"\n :help=\"t('profile.password-hint')\"\n :error=\"errorOf('password')\"\n >\n <wx-input v-model=\"form.password\" type=\"password\" autocomplete=\"new-password\" />\n </wx-form-item>\n\n <wx-form-item\n v-if=\"form.password\"\n :label=\"t('profile.current-password')\"\n :help=\"t('profile.current-password-hint')\"\n :error=\"errorOf('current_password')\"\n >\n <wx-input\n v-model=\"form.current_password\"\n type=\"password\"\n autocomplete=\"current-password\"\n />\n </wx-form-item>\n </div>\n </div>\n </div>\n\n <template #footer>\n <wx-button @click=\"dismiss()\">{{ t('profile.cancel') }}</wx-button>\n <wx-button type=\"primary\" :loading=\"saving\" @click=\"save\">{{ t('profile.save') }}</wx-button>\n </template>\n </wx-dialog>\n</template>\n\n<style scoped>\n/*\n * The photograph beside the fields, the same shape as the administrators form — and on a\n * narrow dialog it goes back on top, which is the only arrangement that fits a phone.\n */\n.wx-profile-host {\n container-type: inline-size;\n}\n\n.wx-profile {\n display: flex;\n gap: var(--wx-space-16);\n}\n\n.wx-profile__aside {\n flex: 0 0 180px;\n}\n\n.wx-profile__fields {\n display: flex;\n flex-direction: column;\n gap: var(--wx-space-12);\n flex: 1 1 auto;\n min-width: 0;\n}\n\n@container (max-width: 480px) {\n .wx-profile {\n flex-direction: column;\n }\n\n .wx-profile__aside {\n flex: none;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, inject, ref, watch, type Component } from 'vue'\nimport { useAdmin, useI18n, useTranslate } from '@webx-ui/module-admin'\nimport { createModal } from '@webx-ui/core'\nimport ProfileDialog from './ProfileDialog.vue'\nimport { avatarFieldKey, avatarResolverKey, useAuth, type AvatarResolver } from './session'\n\n/**\n * The corner of the header: who this is, which language they read the panel in, and the way\n * out.\n *\n * The language picker lives here rather than in a settings screen because it is a property of\n * the person, not of the site — and because somebody who has landed in a language they cannot\n * read needs it within reach, not three clicks into a section they cannot navigate.\n */\nconst props = withDefaults(\n defineProps<{\n signOutLabel?: string\n /** Overrides the resolver `auth()` was given. Without either, initials. */\n resolveAvatar?: AvatarResolver\n /**\n * The field to pick a photograph with, handed down from the plugin. Without one the\n * profile still opens — a panel with no library edits everything else about a person.\n */\n avatarField?: Component\n /**\n * There is room beside the face for a name. The shell says so: the foot of an open\n * sidebar and the foot of the drawer have it, the icon rail and a phone's bar do not.\n */\n expanded?: boolean\n }>(),\n { signOutLabel: undefined, resolveAvatar: undefined, avatarField: undefined, expanded: false },\n)\n\nconst admin = useAdmin()\nconst auth = useAuth()\nconst i18n = useI18n()\nconst t = useTranslate('webx-auth')\n\nconst user = computed(() => admin.state.user)\n\n// One language is not a choice, and a menu that offers it is noise.\nconst languages = computed(() =>\n i18n.state.panelLocales.length > 1 ? i18n.state.panelLocales : [],\n)\n\nconst provided = inject(avatarResolverKey, null)\nconst providedField = inject(avatarFieldKey, null)\nconst avatarUrl = ref<string | undefined>(undefined)\n\n/*\n * The server sends the key the photograph is stored under, not a picture: turning one into\n * the other is the library's job, and the panel says which library. Resolved again whenever\n * the key changes — somebody editing their own photograph is told about it by the row, and\n * the corner should agree with the row.\n */\nwatch(\n () => (typeof user.value?.avatar === 'string' ? user.value.avatar : null),\n async (key) => {\n const resolve = props.resolveAvatar ?? provided\n\n if (key === null || key === '' || resolve === null) {\n avatarUrl.value = undefined\n\n return\n }\n\n const url = await resolve(key)\n\n // Only the answer to the key still on screen: a slow answer to a previous one is stale.\n if (user.value?.avatar === key) {\n avatarUrl.value = url ?? undefined\n }\n },\n { immediate: true },\n)\n\n/*\n * Opened from a menu, which is itself a dismissable layer: Reka builds the dialog's own layer\n * in the middle of handling this very click, then sees the click end outside it and treats\n * that as a click away. So it opens on the next turn of the loop instead.\n */\nconst editProfile = createModal<true, { avatarField?: Component }>(ProfileDialog)\n\nfunction openProfile(): void {\n setTimeout(\n () => void editProfile({ avatarField: props.avatarField ?? providedField ?? undefined }),\n 0,\n )\n}\n\nasync function choose(code: string): Promise<void> {\n if (code === i18n.state.locale) {\n return\n }\n\n await auth.setLocale(code)\n}\n</script>\n\n<template>\n <wx-dropdown v-if=\"user !== null\">\n <!--\n The avatar is the button. Wrapped in an action it was a small picture inside a box the\n same size as every icon beside it, which reads as one more tool rather than as who is\n signed in.\n -->\n <template #trigger>\n <button\n type=\"button\"\n class=\"wx-user-menu__trigger\"\n :class=\"{ 'wx-user-menu__trigger--named': expanded }\"\n :title=\"user.name\"\n >\n <wx-avatar :name=\"user.name\" :src=\"avatarUrl\" size=\"lg\" />\n\n <!--\n Initials are a way of telling two people apart, not of saying who somebody is. Where\n the corner is wide enough, it says it.\n -->\n <span v-if=\"expanded\" class=\"wx-user-menu__name\">{{ user.name }}</span>\n </button>\n </template>\n\n <wx-dropdown-item disabled>\n <wx-text size=\"sm\">{{ user.email }}</wx-text>\n </wx-dropdown-item>\n\n <wx-divider spacing=\"sm\" />\n\n <wx-dropdown-item icon=\"user\" @click=\"openProfile\">\n {{ t('profile.menu') }}\n </wx-dropdown-item>\n\n <template v-if=\"languages.length > 0\">\n <wx-divider spacing=\"sm\" />\n\n <wx-dropdown-item disabled>\n <wx-text size=\"sm\" tone=\"muted\">{{ t('menu.language') }}</wx-text>\n </wx-dropdown-item>\n\n <wx-dropdown-item\n v-for=\"language in languages\"\n :key=\"language.code\"\n :icon=\"language.code === i18n.state.locale ? 'check' : undefined\"\n @click=\"choose(language.code)\"\n >\n {{ language.nativeName }}\n </wx-dropdown-item>\n\n <wx-divider spacing=\"sm\" />\n </template>\n\n <wx-dropdown-item icon=\"logout\" @click=\"auth.logout()\">\n {{ signOutLabel ?? t('menu.sign-out') }}\n </wx-dropdown-item>\n </wx-dropdown>\n</template>\n\n<style scoped>\n/*\n * The avatar is the whole button: no border, no background, nothing of the browser's own —\n * a bordered box around a round picture reads as one more tool in the row of icons, which is\n * what wrapping it in an action looked like in the first place.\n */\n.wx-user-menu__trigger {\n display: inline-flex;\n padding: 0;\n border: none;\n border-radius: var(--wx-radius-full);\n background: transparent;\n cursor: pointer;\n transition: opacity var(--wx-duration-fast) var(--wx-easing-standard);\n}\n\n.wx-user-menu__trigger:hover {\n opacity: 0.85;\n}\n\n.wx-user-menu__trigger:focus-visible {\n outline: 2px solid var(--wx-border-focus);\n outline-offset: 2px;\n}\n\n/*\n * With a name beside it the button is a row rather than a circle, so it takes the width it is\n * given and rounds like the menu items above it instead of like the picture inside it.\n */\n.wx-user-menu__trigger--named {\n align-items: center;\n gap: var(--wx-space-8);\n width: 100%;\n min-width: 0;\n padding: var(--wx-space-4);\n border-radius: var(--wx-radius-control);\n text-align: start;\n}\n\n.wx-user-menu__trigger--named:hover {\n opacity: 1;\n background: var(--wx-bg-subtle);\n}\n\n.wx-user-menu__name {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n color: var(--wx-text-default);\n font-family: var(--wx-font-family-sans);\n font-size: var(--wx-font-size-sm);\n font-weight: var(--wx-font-weight-medium);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n</style>\n","import { h, watch, type Component } from 'vue'\nimport type { Admin, AdminPlugin } from '@webx-ui/module-admin'\nimport LoginCard from './LoginCard.vue'\nimport { authMessages } from './messages'\nimport { createAuthSession, provideAuth, type AvatarResolver } from './session'\n\nexport interface AuthOptions {\n /** Where the sign-in screen lives, inside the panel. */\n path?: string\n /** Props handed to the card — the labels, mostly. */\n card?: Record<string, unknown>\n /**\n * Turns the key the signed-in person's photograph is stored under into an address, so the\n * menu in the corner shows the picture rather than initials. The same function `admins()`\n * takes: this package does not depend on the media one, and the panel is the only place\n * that knows both are installed.\n */\n resolveAvatar?: AvatarResolver\n /**\n * The field that picks a photograph — `WxMediaField` in a panel with the library. Handed\n * in for the same reason as the resolver, and used by the profile dialog in the corner\n * menu. Without it a person edits everything about themselves except their picture.\n */\n avatarField?: Component\n}\n\n/**\n * Signing in, as a plugin rather than a section: it owns a route and a guard, and it has no\n * place in the menu.\n *\n * Installing it also tells the panel how to find out who is signed in, which is why the panel\n * itself knows nothing about authentication — it holds the answer, not the question.\n */\nexport function auth(options: AuthOptions = {}): AdminPlugin {\n const path = options.path ?? '/login'\n\n return {\n install(admin: Admin) {\n const session = createAuthSession(admin.context)\n\n // The floor under this module's labels: what the server has not translated, and what\n // a panel assembled without a server has to fall back on.\n admin.i18n.defaults('webx-auth', authMessages)\n\n provideAuth(admin.app, session, options.resolveAvatar ?? null, options.avatarField ?? null)\n admin.context.useSessionLoader(() => session.me())\n\n admin.router.addRoute({\n path,\n name: 'webx.login',\n meta: { public: true },\n component: { render: () => h(LoginCard, { ...options.card }) },\n })\n\n admin.router.beforeEach((to) => {\n if (to.meta.public === true) {\n return true\n }\n\n if (admin.context.state.status !== 'unauthenticated') {\n return true\n }\n\n return { path, query: to.fullPath === '/' ? {} : { next: to.fullPath } }\n })\n\n // Both directions are driven by the state rather than by an event, because the card\n // cannot be relied on to announce its own success: signing in flips the panel to\n // `ready`, the shell swaps the branch it renders, and the card is unmounted before the\n // line after `await` runs.\n watch(\n () => admin.context.state.status,\n (status) => {\n const current = admin.router.currentRoute.value\n\n // A session can also end between requests — a sign-out in another tab, an account\n // switched off. The panel learns that from a 401 on whatever it asked for next,\n // which is not a navigation, so nothing would move without this.\n if (status === 'unauthenticated' && current.path !== path) {\n void admin.router.replace({ path, query: { next: current.fullPath } })\n\n return\n }\n\n if (status === 'ready' && current.path === path) {\n const next = current.query.next\n\n void admin.router.replace(typeof next === 'string' ? next : '/')\n }\n },\n )\n },\n }\n}\n\nexport { admins, type AdminsOptions } from './module'\nexport { createAdminsApi, type AdminsApi } from './admins'\nexport { selectAdmin, selectAdmins, type AdminPickerOptions } from './selectAdmin'\nexport { default as WxAdminsPage } from './AdminsPage.vue'\nexport { default as WxAdminList } from './AdminList.vue'\nexport type { Admin, AdminInput, AdminPage, AdminQuery, AdminRole, Role } from './types'\nexport {\n createAuthSession,\n provideAuth,\n useAuth,\n authKey,\n avatarFieldKey,\n avatarResolverKey,\n type ProfileInput,\n} from './session'\nexport { default as WxProfileDialog } from './ProfileDialog.vue'\nexport type { AuthSession, AvatarResolver, Credentials } from './session'\nexport { default as WxLoginCard } from './LoginCard.vue'\nexport { default as WxUserMenu } from './UserMenu.vue'\nexport { authMessages } from './messages'\n"],"names":["authKey","useAuth","session","inject","createAuthSession","admin","base","error","isUnauthenticated","credentials","body","code","profile","avatarResolverKey","avatarFieldKey","provideAuth","app","resolveAvatar","avatarField","props","__props","emit","__emit","auth","t","useTranslate","labels","computed","email","ref","password","rememberMe","busy","revealed","capsLock","message","errors","secondsLeft","emailField","useTemplateRef","countdown","onBeforeUnmount","stopCountdown","throttled","throttleNotice","submit","handle","HttpError","startCountdown","seconds","trackCapsLock","event","__expose","_createBlock","_component_wx_card","_createVNode","_component_wx_form","_component_wx_alert","_component_wx_form_item","_component_wx_input","$event","_component_wx_icon","_createElementVNode","_cache","_component_wx_checkbox","_component_wx_button","_createTextVNode","_toDisplayString","authMessages","createAdminsApi","data","query","id","input","seeded","useAuthMessages","i18n","useAdmin","resolve","dismiss","open","useModal","context","api","useI18n","useErrorText","roles","saving","form","photo","value","editing","roleOptions","one","localeOptions","all","watch","errorOf","field","save","payload","saved","toast","_unref","WxDialog","WxSpace","WxButton","_hoisted_1","_hoisted_2","_openBlock","_createElementBlock","_hoisted_3","_resolveDynamicComponent","_hoisted_4","WxFormItem","WxInput","WxSelect","WxCheckbox","WxText","page","loading","role","active","selected","avatars","last","stateOptions","columns","load","state","resolveAvatars","row","url","actionsFor","onRow","clickable","onSelection","_keys","rows","WxTable","_withCtx","WxAvatar","WxBadge","_Fragment","_renderList","WxRowMenu","WxDate","list","edit","createModal","AdminDialog","canManage","title","module","remove","confirm","WxListScreen","_createSlots","AdminList","admins","options","path","h","AdminsPage","count","chosen","track","pick","AdminPicker","selectAdmin","selectAdmins","user","languages","provided","providedField","avatarUrl","key","editProfile","ProfileDialog","openProfile","choose","_component_wx_dropdown","_normalizeClass","_component_wx_avatar","_component_wx_dropdown_item","_component_wx_text","_component_wx_divider","language","LoginCard","to","status","current","next"],"mappings":"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"}
package/dist/session.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { App, InjectionKey } from 'vue';
1
+ import { App, Component, InjectionKey } from 'vue';
2
2
  import { AdminContext, AdminUser } from '@webx-ui/module-admin';
3
3
  export interface Credentials {
4
4
  email: string;
@@ -19,6 +19,22 @@ export interface AuthSession {
19
19
  * the label above it.
20
20
  */
21
21
  setLocale(code: string): Promise<void>;
22
+ /**
23
+ * Change what is yours to change about yourself: your name, your photograph, your password.
24
+ *
25
+ * Not the administrators endpoint. That one is behind `admins.manage`, which is a permission
26
+ * about other people — an editor who may not manage anybody still has a name to spell and
27
+ * a password to rotate.
28
+ */
29
+ updateProfile(profile: ProfileInput): Promise<AdminUser>;
30
+ }
31
+ /** What somebody may send about themselves. A blank password is "leave it alone". */
32
+ export interface ProfileInput {
33
+ name: string;
34
+ avatar: string | null;
35
+ password?: string;
36
+ /** Required by the server whenever `password` is filled in. */
37
+ current_password?: string;
22
38
  }
23
39
  export declare const authKey: InjectionKey<AuthSession>;
24
40
  export declare function useAuth(): AuthSession;
@@ -37,4 +53,12 @@ export type AvatarResolver = (key: string) => Promise<string | null>;
37
53
  * which is what initials are for.
38
54
  */
39
55
  export declare const avatarResolverKey: InjectionKey<AvatarResolver | null>;
40
- export declare function provideAuth(app: App, session: AuthSession, resolveAvatar?: AvatarResolver | null): void;
56
+ /**
57
+ * The field the panel picks photographs with, for the profile dialog behind the corner menu.
58
+ *
59
+ * Provided rather than passed: the shell renders the user menu itself, and a component it
60
+ * builds is not a place to hand a component down through. Null in a panel with no library —
61
+ * the profile still opens, with everything but the photograph.
62
+ */
63
+ export declare const avatarFieldKey: InjectionKey<Component | null>;
64
+ export declare function provideAuth(app: App, session: AuthSession, resolveAvatar?: AvatarResolver | null, avatarField?: Component | null): void;
package/dist/style.css CHANGED
@@ -1 +1 @@
1
- .wx-login[data-v-3c206e31]{width:100%;max-width:26rem}.wx-login__message[data-v-3c206e31]{margin-block-end:var(--wx-space-4)}.wx-login__remember[data-v-3c206e31]{margin-block-start:calc(-1 * var(--wx-space-12))}.wx-login__reveal[data-v-3c206e31]{display:inline-flex;align-items:center;padding:0;font:inherit;color:var(--wx-text-muted);background:none;border:0;cursor:pointer}.wx-login__reveal[data-v-3c206e31]:hover,.wx-login__reveal[aria-pressed=true][data-v-3c206e31]{color:var(--wx-text-default)}.wx-admin-form{container-type:inline-size}.wx-admin-form__grid{display:grid;grid-template-columns:200px minmax(0,1fr);gap:var(--wx-space-24)}.wx-admin-form__fields{display:flex;flex-direction:column;gap:var(--wx-space-12);min-width:0}@container (max-width: 520px){.wx-admin-form__grid{grid-template-columns:minmax(0,1fr)}.wx-admin-form__aside{max-width:200px}}.wx-admin-list{min-width:0}.wx-user-menu__trigger[data-v-d9c92170]{display:inline-flex;padding:0;border:none;border-radius:var(--wx-radius-full);background:transparent;cursor:pointer;transition:opacity var(--wx-duration-fast) var(--wx-easing-standard)}.wx-user-menu__trigger[data-v-d9c92170]:hover{opacity:.85}.wx-user-menu__trigger[data-v-d9c92170]:focus-visible{outline:2px solid var(--wx-border-focus);outline-offset:2px}
1
+ .wx-login[data-v-3c206e31]{width:100%;max-width:26rem}.wx-login__message[data-v-3c206e31]{margin-block-end:var(--wx-space-4)}.wx-login__remember[data-v-3c206e31]{margin-block-start:calc(-1 * var(--wx-space-12))}.wx-login__reveal[data-v-3c206e31]{display:inline-flex;align-items:center;padding:0;font:inherit;color:var(--wx-text-muted);background:none;border:0;cursor:pointer}.wx-login__reveal[data-v-3c206e31]:hover,.wx-login__reveal[aria-pressed=true][data-v-3c206e31]{color:var(--wx-text-default)}.wx-admin-form{container-type:inline-size}.wx-admin-form__grid{display:grid;grid-template-columns:200px minmax(0,1fr);gap:var(--wx-space-24)}.wx-admin-form__fields{display:flex;flex-direction:column;gap:var(--wx-space-12);min-width:0}@container (max-width: 520px){.wx-admin-form__grid{grid-template-columns:minmax(0,1fr)}.wx-admin-form__aside{max-width:200px}}.wx-admin-list{min-width:0}.wx-profile-host[data-v-9583a67a]{container-type:inline-size}.wx-profile[data-v-9583a67a]{display:flex;gap:var(--wx-space-16)}.wx-profile__aside[data-v-9583a67a]{flex:0 0 180px}.wx-profile__fields[data-v-9583a67a]{display:flex;flex-direction:column;gap:var(--wx-space-12);flex:1 1 auto;min-width:0}@container (max-width: 480px){.wx-profile[data-v-9583a67a]{flex-direction:column}.wx-profile__aside[data-v-9583a67a]{flex:none}}.wx-user-menu__trigger[data-v-d51d13e4]{display:inline-flex;padding:0;border:none;border-radius:var(--wx-radius-full);background:transparent;cursor:pointer;transition:opacity var(--wx-duration-fast) var(--wx-easing-standard)}.wx-user-menu__trigger[data-v-d51d13e4]:hover{opacity:.85}.wx-user-menu__trigger[data-v-d51d13e4]:focus-visible{outline:2px solid var(--wx-border-focus);outline-offset:2px}.wx-user-menu__trigger--named[data-v-d51d13e4]{align-items:center;gap:var(--wx-space-8);width:100%;min-width:0;padding:var(--wx-space-4);border-radius:var(--wx-radius-control);text-align:start}.wx-user-menu__trigger--named[data-v-d51d13e4]:hover{opacity:1;background:var(--wx-bg-subtle)}.wx-user-menu__name[data-v-d51d13e4]{flex:1 1 auto;min-width:0;overflow:hidden;color:var(--wx-text-default);font-family:var(--wx-font-family-sans);font-size:var(--wx-font-size-sm);font-weight:var(--wx-font-weight-medium);text-overflow:ellipsis;white-space:nowrap}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webx-ui/module-auth",
3
- "version": "0.5.4",
3
+ "version": "0.6.0",
4
4
  "description": "Signing in to a WebX UI admin panel: the screen, the session and the router guard. Pairs with webx-ui/module-auth on the server.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -44,8 +44,8 @@
44
44
  "vue-router": "^4.5.0"
45
45
  },
46
46
  "dependencies": {
47
- "@webx-ui/module-admin": "^0.5.0",
48
- "@webx-ui/core": "^0.21.0"
47
+ "@webx-ui/module-admin": "^0.6.0",
48
+ "@webx-ui/core": "^0.22.0"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/node": "^24.10.1",