@aiquants/auth-react-router 0.5.0 → 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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/admin/labels.ts","../src/admin/server.ts","../src/admin/ui.tsx","../src/admin/views.tsx"],"sourcesContent":["/**\n * Labels and i18n strings for the user administration UI.\n * ユーザー管理 UI で使用するラベルと i18n 文字列の定義。\n *\n * パッケージ既定は **英語** ([defaultUserAdminLabels])。日本語で使う場合はアプリ側が\n * [jaUserAdminLabels] を明示的に注入する (認可管理 `@aiquants/authz-react-router` と同じ規約)。\n * UI 文字列はすべて本モジュールに集約し、コンポーネント側へ直書きしないこと — 直書きすると\n * 日本語以外の利用者がその箇所を差し替えられなくなる。\n */\n\nexport type UserAdminLabels = {\n title: string\n /** シェル (タブ枠) の見出し。 */\n heading: string\n tabs: {\n users: string\n groups: string\n groupMembers: string\n allowlist: string\n }\n usersView: {\n title: string\n searchPlaceholder: string\n addUser: string\n editUser: string\n id: string\n displayName: string\n email: string\n status: string\n active: string\n inactive: string\n actions: string\n edit: string\n activate: string\n deactivate: string\n empty: string\n save: string\n cancel: string\n }\n groupsView: {\n title: string\n searchPlaceholder: string\n addGroup: string\n editGroup: string\n id: string\n groupKey: string\n name: string\n description: string\n memberCount: string\n status: string\n actions: string\n edit: string\n delete: string\n confirmDelete: string\n noDescription: string\n groupKeyPlaceholder: string\n namePlaceholder: string\n empty: string\n save: string\n cancel: string\n }\n groupMembersView: {\n selectGroup: string\n selectPrompt: string\n actionAdd: string\n actionRemove: string\n }\n allowlistView: {\n title: string\n addEntry: string\n pattern: string\n type: string\n emailType: string\n domainType: string\n /** 一覧のバッジ表示 (短縮形)。 */\n emailTypeShort: string\n domainTypeShort: string\n description: string\n actions: string\n /** 変更不能な供給元 (環境変数など) 由来の行に出す表示。 */\n readOnly: string\n delete: string\n empty: string\n save: string\n cancel: string\n }\n}\n\n/**\n * Package default labels (English).\n * パッケージ既定ラベル (英語)。\n */\nexport const defaultUserAdminLabels: UserAdminLabels = {\n title: \"Users & Groups\",\n heading: \"👤 User administration\",\n tabs: {\n users: \"👤 Users\",\n groups: \"👥 Groups\",\n groupMembers: \"🔗 Members\",\n allowlist: \"✉️ Allowlist\",\n },\n usersView: {\n title: \"Users\",\n searchPlaceholder: \"Search by name or email...\",\n addUser: \"+ Add user\",\n editUser: \"Edit user\",\n id: \"ID\",\n displayName: \"Name\",\n email: \"Email\",\n status: \"Status\",\n active: \"Active\",\n inactive: \"Inactive\",\n actions: \"Actions\",\n edit: \"Edit\",\n activate: \"Activate\",\n deactivate: \"Deactivate\",\n empty: \"No matching users\",\n save: \"Save\",\n cancel: \"Cancel\",\n },\n groupsView: {\n title: \"Groups\",\n searchPlaceholder: \"Search by group name or key...\",\n addGroup: \"+ Add group\",\n editGroup: \"Edit group\",\n id: \"ID\",\n groupKey: \"Group key\",\n name: \"Name\",\n description: \"Description\",\n memberCount: \"Members\",\n status: \"Status\",\n actions: \"Actions\",\n edit: \"Edit\",\n delete: \"Delete\",\n confirmDelete: \"Delete this group?\",\n noDescription: \"No description\",\n groupKeyPlaceholder: \"e.g. dev-team\",\n namePlaceholder: \"e.g. Development team\",\n empty: \"No groups registered\",\n save: \"Save\",\n cancel: \"Cancel\",\n },\n groupMembersView: {\n selectGroup: \"Select a group\",\n selectPrompt: \"Select a group to manage its members\",\n actionAdd: \"Add\",\n actionRemove: \"Remove\",\n },\n allowlistView: {\n title: \"Sign-in allowlist\",\n addEntry: \"+ Add pattern\",\n pattern: \"Pattern (e.g. @example.com or user@example.com)\",\n type: \"Type\",\n emailType: \"Exact email match\",\n domainType: \"Domain match\",\n emailTypeShort: \"Email\",\n domainTypeShort: \"Domain\",\n description: \"Description\",\n actions: \"Actions\",\n readOnly: \"Read-only\",\n delete: \"Delete\",\n empty: \"No allowlist entries registered\",\n save: \"Add\",\n cancel: \"Cancel\",\n },\n}\n\n/**\n * Standard Japanese labels for user administration (opt-in).\n * ユーザー管理の標準日本語ラベル (アプリ側で明示注入して使う)。\n */\nexport const jaUserAdminLabels: UserAdminLabels = {\n title: \"ユーザー・グループ管理\",\n heading: \"👤 ユーザー管理\",\n tabs: {\n users: \"👤 ユーザー一覧\",\n groups: \"👥 ユーザーグループ\",\n groupMembers: \"🔗 グループメンバー\",\n allowlist: \"✉️ アクセス許可リスト\",\n },\n usersView: {\n title: \"ユーザー管理\",\n searchPlaceholder: \"表示名や Email で検索...\",\n addUser: \"+ ユーザー追加\",\n editUser: \"ユーザーの編集\",\n id: \"ID\",\n displayName: \"表示名\",\n email: \"メールアドレス\",\n status: \"ステータス\",\n active: \"有効\",\n inactive: \"無効\",\n actions: \"操作\",\n edit: \"編集\",\n activate: \"有効化\",\n deactivate: \"無効化\",\n empty: \"該当するユーザーが見つかりません\",\n save: \"保存\",\n cancel: \"キャンセル\",\n },\n groupsView: {\n title: \"ユーザーグループ管理\",\n searchPlaceholder: \"グループ名やキーで検索...\",\n addGroup: \"+ グループ作成\",\n editGroup: \"グループの編集\",\n id: \"ID\",\n groupKey: \"グループキー\",\n name: \"グループ名\",\n description: \"説明\",\n memberCount: \"メンバー数\",\n status: \"ステータス\",\n actions: \"操作\",\n edit: \"編集\",\n delete: \"削除\",\n confirmDelete: \"このグループを削除してもよろしいですか?\",\n noDescription: \"説明なし\",\n groupKeyPlaceholder: \"例: dev-team\",\n namePlaceholder: \"例: 開発チーム\",\n empty: \"グループが登録されていません\",\n save: \"保存\",\n cancel: \"キャンセル\",\n },\n groupMembersView: {\n selectGroup: \"グループ選択\",\n selectPrompt: \"グループを選択してください\",\n actionAdd: \"追加\",\n actionRemove: \"除外\",\n },\n allowlistView: {\n title: \"アクセス許可リスト (Allowlist)\",\n addEntry: \"+ 許可パターン追加\",\n pattern: \"パターン (例: @example.com または user@example.com)\",\n type: \"種別\",\n emailType: \"メールアドレス完全一致\",\n domainType: \"ドメイン一致\",\n emailTypeShort: \"Email 完全一致\",\n domainTypeShort: \"ドメイン一致\",\n description: \"説明 / 備考\",\n actions: \"操作\",\n readOnly: \"変更不可\",\n delete: \"削除\",\n empty: \"アロワリストエントリが登録されていません\",\n save: \"登録\",\n cancel: \"キャンセル\",\n },\n}\n\n/** セクション単位の部分上書き。 */\nexport type PartialUserAdminLabels = {\n title?: string\n heading?: string\n tabs?: Partial<UserAdminLabels[\"tabs\"]>\n usersView?: Partial<UserAdminLabels[\"usersView\"]>\n groupsView?: Partial<UserAdminLabels[\"groupsView\"]>\n groupMembersView?: Partial<UserAdminLabels[\"groupMembersView\"]>\n allowlistView?: Partial<UserAdminLabels[\"allowlistView\"]>\n}\n\n/**\n * Shallow per-section merge over the English defaults.\n * 英語既定に対してセクション単位で浅くマージする処理。\n */\nexport function resolveUserAdminLabels(over?: PartialUserAdminLabels): UserAdminLabels {\n const d = defaultUserAdminLabels\n if (!over) return d\n return {\n title: over.title ?? d.title,\n heading: over.heading ?? d.heading,\n tabs: { ...d.tabs, ...over.tabs },\n usersView: { ...d.usersView, ...over.usersView },\n groupsView: { ...d.groupsView, ...over.groupsView },\n groupMembersView: { ...d.groupMembersView, ...over.groupMembersView },\n allowlistView: { ...d.allowlistView, ...over.allowlistView },\n }\n}\n","import type { AuthAllowlistEntry, AuthGroup, AuthGroupMember, AuthUser } from \"@aiquants/auth-core\"\nimport type { ActionFunctionArgs, LoaderFunctionArgs } from \"react-router\"\nimport { type PartialUserAdminLabels, resolveUserAdminLabels, type UserAdminLabels } from \"./labels\"\n\n/**\n * Server store interface for managing users, groups, memberships, and allowlists.\n * ユーザー・グループ・所属・アロワリストを管理するサーバー層ストアのインターフェース。\n */\nexport type UserAdminStore = {\n listUsers: () => Promise<AuthUser[]>\n createUser: (data: { displayName: string; email: string }) => Promise<AuthUser>\n updateUser: (id: number, data: { displayName: string; email: string }) => Promise<AuthUser>\n toggleUserActive: (id: number, isActive: boolean) => Promise<AuthUser>\n\n listGroups: () => Promise<AuthGroup[]>\n createGroup: (data: { groupKey: string; name: string; description?: string }) => Promise<AuthGroup>\n updateGroup: (id: number, data: { name: string; description?: string }) => Promise<AuthGroup>\n deleteGroup: (id: number) => Promise<void>\n\n listGroupMembers: () => Promise<AuthGroupMember[]>\n addGroupMember: (groupId: number, userId: number) => Promise<AuthGroupMember>\n removeGroupMember: (groupId: number, userId: number) => Promise<void>\n\n listAllowlist: () => Promise<AuthAllowlistEntry[]>\n addAllowlistEntry: (data: { pattern: string; type: \"email\" | \"domain\"; description?: string }) => Promise<AuthAllowlistEntry>\n removeAllowlistEntry: (id: number) => Promise<void>\n}\n\n/**\n * Creates an in-memory UserAdminStore initialized with optional seed data.\n * シードデータで初期化されるメモリ上の UserAdminStore を作成する。\n */\nexport function createInMemoryUserAdminStore(initial?: { users?: AuthUser[]; groups?: AuthGroup[]; groupMembers?: AuthGroupMember[]; allowlist?: AuthAllowlistEntry[] }): UserAdminStore {\n const users: AuthUser[] = initial?.users ?? [\n { id: 1, email: \"admin@example.com\", displayName: \"システム管理者\", isActive: true },\n { id: 2, email: \"user1@example.com\", displayName: \"開発 太郎\", isActive: true },\n { id: 3, email: \"user2@example.com\", displayName: \"営業 花子\", isActive: true },\n ]\n let groups: AuthGroup[] = initial?.groups ?? [\n { id: 1, groupKey: \"dev-team\", name: \"開発チーム\", description: \"システム開発担当グループ\", isActive: true },\n { id: 2, groupKey: \"sales-dept\", name: \"営業部\", description: \"営業ソリューション担当\", isActive: true },\n ]\n let groupMembers: AuthGroupMember[] = initial?.groupMembers ?? [\n { id: 1, groupId: 1, userId: 2 },\n { id: 2, groupId: 2, userId: 3 },\n ]\n let allowlist: AuthAllowlistEntry[] = initial?.allowlist ?? [\n { id: 1, pattern: \"admin@example.com\", type: \"email\", description: \"初期管理者\" },\n { id: 2, pattern: \"@example.com\", type: \"domain\", description: \"社内ドメイン\" },\n ]\n\n return {\n async listUsers() {\n return [...users]\n },\n async createUser(data) {\n const newUser: AuthUser = {\n id: Math.max(...users.map((u) => u.id), 0) + 1,\n email: data.email,\n displayName: data.displayName,\n isActive: true,\n }\n users.push(newUser)\n return newUser\n },\n async updateUser(id, data) {\n const idx = users.findIndex((u) => u.id === id)\n if (idx === -1) throw new Error(`User not found: ${id}`)\n users[idx] = { ...users[idx], displayName: data.displayName, email: data.email }\n return users[idx]\n },\n async toggleUserActive(id, isActive) {\n const idx = users.findIndex((u) => u.id === id)\n if (idx === -1) throw new Error(`User not found: ${id}`)\n users[idx] = { ...users[idx], isActive }\n return users[idx]\n },\n\n async listGroups() {\n return [...groups]\n },\n async createGroup(data) {\n const newGroup: AuthGroup = {\n id: Math.max(...groups.map((g) => g.id), 0) + 1,\n groupKey: data.groupKey,\n name: data.name,\n description: data.description,\n isActive: true,\n }\n groups.push(newGroup)\n return newGroup\n },\n async updateGroup(id, data) {\n const idx = groups.findIndex((g) => g.id === id)\n if (idx === -1) throw new Error(`Group not found: ${id}`)\n groups[idx] = { ...groups[idx], name: data.name, description: data.description }\n return groups[idx]\n },\n async deleteGroup(id) {\n groups = groups.filter((g) => g.id !== id)\n groupMembers = groupMembers.filter((m) => m.groupId !== id)\n },\n\n async listGroupMembers() {\n return [...groupMembers]\n },\n async addGroupMember(groupId, userId) {\n const existing = groupMembers.find((m) => m.groupId === groupId && m.userId === userId)\n if (existing) return existing\n const newMember: AuthGroupMember = {\n id: Math.max(...groupMembers.map((m) => m.id), 0) + 1,\n groupId,\n userId,\n }\n groupMembers.push(newMember)\n return newMember\n },\n async removeGroupMember(groupId, userId) {\n groupMembers = groupMembers.filter((m) => !(m.groupId === groupId && m.userId === userId))\n },\n\n async listAllowlist() {\n return [...allowlist]\n },\n async addAllowlistEntry(data) {\n const newEntry: AuthAllowlistEntry = {\n id: Math.max(...allowlist.map((a) => a.id), 0) + 1,\n pattern: data.pattern,\n type: data.type,\n description: data.description,\n }\n allowlist.push(newEntry)\n return newEntry\n },\n async removeAllowlistEntry(id) {\n allowlist = allowlist.filter((a) => a.id !== id)\n },\n }\n}\n\n/**\n * The CRUD verb an operation performs. Deliberately a plain string union so this package stays\n * independent of any particular authorization library.\n * 操作が行う CRUD 種別。特定の認可ライブラリに依存しないよう素の文字列 union で表現する。\n */\nexport type UserAdminAccess = \"read\" | \"create\" | \"update\" | \"delete\"\n\n/**\n * Access guard the user-admin surface is mounted behind. Deny by throwing\n * (a `Response`/`redirect` or an `Error`); returning normally means \"allow\".\n *\n * ユーザー管理面をマウントする際の必須アクセスガード。拒否は throw で表現する\n * (`Response` / `redirect` / `Error`)。正常 return は「許可」を意味する。\n *\n * 操作ごとの CRUD 種別を受け取るため、呼び出し側は「削除だけ別権限」といった細分化ができる。\n * `intent` は監査ログ用の補助情報(認可判断は `action` で行うこと)。\n */\nexport type UserAdminGuards = {\n /**\n * 許可は「正常 return」、拒否は「throw」で表す。返り値は判定に使わない (`void` 契約)。\n * 述語のつもりで `false` を返す実装を書くと素通りするため、返り値型で明示的に禁じる。\n */\n requireAccess: (request: Request, context: { action: UserAdminAccess; intent: string }) => Promise<void> | void\n}\n\n/**\n * Maps each action intent to the CRUD verb it actually performs.\n * 各 intent が実際に行う操作の CRUD 種別への対応表。\n *\n * 割当の付与・解除は行の追加・削除であるため、認可管理 UI (`/authz`) の\n * `assignUserRole` = create / `removeUserRole` = delete と同じ粒度に揃える。\n */\nconst INTENT_ACCESS = new Map<string, UserAdminAccess>([\n [\"createUser\", \"create\"],\n [\"updateUser\", \"update\"],\n [\"toggleUserActive\", \"update\"],\n [\"createGroup\", \"create\"],\n [\"updateGroup\", \"update\"],\n [\"deleteGroup\", \"delete\"],\n [\"addGroupMember\", \"create\"],\n [\"removeGroupMember\", \"delete\"],\n [\"addAllowlistEntry\", \"create\"],\n [\"removeAllowlistEntry\", \"delete\"],\n])\n\nexport type UserAdminAppConfig = {\n store: UserAdminStore\n /** セクション単位の部分上書き。既定は英語 (`defaultUserAdminLabels`)。 */\n labels?: PartialUserAdminLabels\n /**\n * Required — omitting a guard would expose the whole user directory and its write actions.\n * 必須。省略を許すとユーザー名簿と全更新操作が無防備に露出するため、型で強制する。\n */\n guards: UserAdminGuards\n}\n\n/**\n * Reads a required positive integer id from submitted form data.\n * 送信フォームから必須の正整数 ID を読み取る処理。\n *\n * @throws when the field is missing, non-numeric, or not a positive integer.\n * 欠落・非数値・正整数でない場合は throw。\n */\nfunction requireId(formData: FormData, field: string): number {\n const raw = formData.get(field)\n const n = Number(raw)\n // NaN / 小数 / 0 以下は不正な ID として弾く (Number(null) === 0 の取り違えも防ぐ)\n if (raw === null || String(raw).trim() === \"\" || !Number.isInteger(n) || n <= 0) {\n throw new Error(`Invalid or missing \"${field}\"`)\n }\n return n\n}\n\n/**\n * Reads a required non-empty trimmed string from submitted form data.\n * 送信フォームから必須の非空文字列 (トリム済み) を読み取る処理。\n *\n * @throws when the field is missing or blank. 欠落・空白のみの場合は throw。\n */\nfunction requireText(formData: FormData, field: string): string {\n const value = String(formData.get(field) ?? \"\").trim()\n if (value === \"\") throw new Error(`Invalid or missing \"${field}\"`)\n return value\n}\n\n/** Reads an optional trimmed string; blank becomes `undefined`. 任意文字列の読み取り (空は undefined)。 */\nfunction optionalText(formData: FormData, field: string): string | undefined {\n const value = String(formData.get(field) ?? \"\").trim()\n return value === \"\" ? undefined : value\n}\n\n/**\n * Creates React Router loader & action handler for user administration.\n * ユーザー管理用 React Router の loader および action ハンドラーを作成する。\n *\n * `config.guards` は必須。loader / action の双方が、処理を始める前にガードを通過させる。\n */\nexport function createUserAdminApp(config: UserAdminAppConfig) {\n const labels = resolveUserAdminLabels(config.labels)\n\n async function loader({ request }: LoaderFunctionArgs) {\n // 読取ガード: 拒否は throw されるためここで処理が打ち切られる\n await config.guards.requireAccess(request, { action: \"read\", intent: \"loader\" })\n const url = new URL(request.url)\n const pathSegments = url.pathname.split(\"/\").filter(Boolean)\n const segment = pathSegments[pathSegments.length - 1] ?? \"users\"\n\n const [users, groups, groupMembers, allowlist] = await Promise.all([config.store.listUsers(), config.store.listGroups(), config.store.listGroupMembers(), config.store.listAllowlist()])\n\n return {\n segment: [\"groups\", \"group_members\", \"allowlist\"].includes(segment) ? segment : \"users\",\n users,\n groups,\n groupMembers,\n allowlist,\n labels,\n }\n }\n\n async function action({ request }: ActionFunctionArgs) {\n // intent の判定に FormData が要るため、ここでだけ本文を先に読む。\n // 未知 intent は「何も実行しない」ため、ガード前に読んでも権限判断には影響しない。\n const formData = await request.formData()\n const intent = String(formData.get(\"intent\") ?? \"\")\n\n // 未知 intent はストアへ到達しない (ガードも走らせず 400 相当を返す)。\n // Map 参照のためプロトタイプ継承キー (\"constructor\" 等) を拾わない。\n const access = INTENT_ACCESS.get(intent)\n if (!access) return { ok: false, error: `Unknown intent: ${intent}` }\n\n // 操作ごとの CRUD 種別でガードする (削除は delete 権限が要る)\n await config.guards.requireAccess(request, { action: access, intent })\n\n try {\n return await dispatch(intent, formData)\n } catch (error) {\n // ガード拒否 (Response/redirect) はそのまま伝播させる。ストア由来の業務エラーだけ\n // {ok:false,error} に写して UI へ届ける (ロックアウト理由・重複パターン等)。\n if (error instanceof Response) throw error\n return { ok: false, error: error instanceof Error ? error.message : String(error) }\n }\n }\n\n /** intent ごとの実処理。認可・入力検証を通過した後にのみ呼ばれる。 */\n async function dispatch(intent: string, formData: FormData) {\n if (intent === \"createUser\") {\n await config.store.createUser({ displayName: requireText(formData, \"displayName\"), email: requireText(formData, \"email\") })\n return { ok: true }\n }\n\n if (intent === \"updateUser\") {\n await config.store.updateUser(requireId(formData, \"userId\"), { displayName: requireText(formData, \"displayName\"), email: requireText(formData, \"email\") })\n return { ok: true }\n }\n\n if (intent === \"toggleUserActive\") {\n // 破壊側 (無効化) を既定にしない。明示的な \"true\"/\"false\" 以外は不正入力として弾く。\n const rawActive = String(formData.get(\"isActive\") ?? \"\")\n if (rawActive !== \"true\" && rawActive !== \"false\") throw new Error('Invalid or missing \"isActive\"')\n await config.store.toggleUserActive(requireId(formData, \"userId\"), rawActive === \"true\")\n return { ok: true }\n }\n\n if (intent === \"createGroup\") {\n await config.store.createGroup({ groupKey: requireText(formData, \"groupKey\"), name: requireText(formData, \"name\"), description: optionalText(formData, \"description\") })\n return { ok: true }\n }\n\n if (intent === \"updateGroup\") {\n await config.store.updateGroup(requireId(formData, \"groupId\"), { name: requireText(formData, \"name\"), description: optionalText(formData, \"description\") })\n return { ok: true }\n }\n\n if (intent === \"deleteGroup\") {\n await config.store.deleteGroup(requireId(formData, \"groupId\"))\n return { ok: true }\n }\n\n if (intent === \"addGroupMember\") {\n await config.store.addGroupMember(requireId(formData, \"groupId\"), requireId(formData, \"userId\"))\n return { ok: true }\n }\n\n if (intent === \"removeGroupMember\") {\n await config.store.removeGroupMember(requireId(formData, \"groupId\"), requireId(formData, \"userId\"))\n return { ok: true }\n }\n\n if (intent === \"addAllowlistEntry\") {\n const type: \"email\" | \"domain\" = formData.get(\"type\") === \"domain\" ? \"domain\" : \"email\"\n await config.store.addAllowlistEntry({ pattern: requireText(formData, \"pattern\"), type, description: optionalText(formData, \"description\") })\n return { ok: true }\n }\n\n if (intent === \"removeAllowlistEntry\") {\n await config.store.removeAllowlistEntry(requireId(formData, \"entryId\"))\n return { ok: true }\n }\n\n // INTENT_ACCESS を通過した intent は上のいずれかで処理済み (到達しない安全網)\n return { ok: false, error: `Unknown intent: ${intent}` }\n }\n\n return { loader, action }\n}\n","import type React from \"react\"\nimport { Link, useLoaderData } from \"react-router\"\nimport { defaultUserAdminLabels, type UserAdminLabels } from \"./labels\"\n\nexport type UserAdminShellProps = {\n children?: React.ReactNode\n renderHeader?: (props: { title: string; annotation: React.ReactNode }) => React.ReactNode\n}\n\n/**\n * Shell layout for user administration pages with header annotation delegation.\n * ヘッダー注釈委譲を備えたユーザー管理ページのシェルレイアウト。\n */\nexport function UserAdminShell({ children, renderHeader }: UserAdminShellProps) {\n const data = useLoaderData() as { segment?: string; labels?: UserAdminLabels } | undefined\n const segment = data?.segment ?? \"users\"\n const labels = data?.labels ?? defaultUserAdminLabels\n\n const tabs = [\n { key: \"users\", label: labels.tabs.users, href: \"/users\" },\n { key: \"groups\", label: labels.tabs.groups, href: \"/users/groups\" },\n { key: \"group_members\", label: labels.tabs.groupMembers, href: \"/users/group_members\" },\n { key: \"allowlist\", label: labels.tabs.allowlist, href: \"/users/allowlist\" },\n ]\n\n const annotation = (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: \"0.5rem\" }} data-testid=\"user-admin-header-annotation\">\n <span style={{ fontSize: \"0.75rem\", backgroundColor: \"#e0f2fe\", color: \"#0369a1\", padding: \"2px 8px\", borderRadius: \"12px\", fontWeight: 600 }}>{labels.heading}</span>\n </div>\n )\n\n return (\n <div data-testid=\"user-admin-shell\" style={{ width: \"100%\", minHeight: \"100vh\", backgroundColor: \"#f8fafc\", padding: \"1.5rem\", boxSizing: \"border-box\" }}>\n {renderHeader ? (\n renderHeader({ title: labels.title, annotation })\n ) : (\n <div style={{ marginBottom: \"1.5rem\", display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\" }}>\n <h1 style={{ margin: 0, fontSize: \"1.5rem\", fontWeight: 700, color: \"#0f172a\" }}>{labels.title}</h1>\n {annotation}\n </div>\n )}\n\n <div style={{ display: \"flex\", gap: \"0.5rem\", marginBottom: \"1.2rem\", borderBottom: \"2px solid #e2e8f0\", paddingBottom: \"0.5rem\" }}>\n {tabs.map((tab) => {\n const isActive = segment === tab.key\n return (\n <Link\n key={tab.key}\n to={tab.href}\n data-testid={`user-admin-tab-${tab.key}`}\n style={{\n padding: \"0.5rem 1rem\",\n borderRadius: \"6px 6px 0 0\",\n fontSize: \"0.88rem\",\n fontWeight: isActive ? 700 : 500,\n color: isActive ? \"#0284c7\" : \"#64748b\",\n backgroundColor: isActive ? \"#ffffff\" : \"transparent\",\n textDecoration: \"none\",\n borderBottom: isActive ? \"3px solid #0284c7\" : \"3px solid transparent\",\n transition: \"all 0.15s ease\",\n }}>\n {tab.label}\n </Link>\n )\n })}\n </div>\n\n <main>{children}</main>\n </div>\n )\n}\n","import type { AuthAllowlistEntry, AuthGroup, AuthGroupMember, AuthUser } from \"@aiquants/auth-core\"\nimport type React from \"react\"\nimport { useState } from \"react\"\nimport { useFetcher, useLoaderData } from \"react-router\"\nimport { defaultUserAdminLabels, type UserAdminLabels } from \"./labels\"\nimport { UserAdminShell, type UserAdminShellProps } from \"./ui\"\n\nexport type UserAdminLoaderData = {\n segment: string\n users: AuthUser[]\n groups: AuthGroup[]\n groupMembers: AuthGroupMember[]\n allowlist: AuthAllowlistEntry[]\n labels?: UserAdminLabels\n}\n\n/**\n * View for managing users (list, search, toggle active, modal edit).\n * ユーザー管理ビュー (一覧・検索・アクティブ切替・ダイアログ編集)。\n */\n/**\n * Renders the error returned by the last action submission, if any.\n * 直近の action 送信が返したエラーを表示する部品。\n *\n * ロックアウト理由・重複パターン・入力不正などの文言は action が `{ok:false,error}` で返す。\n * 表示しなければ操作が黙って失敗したように見えるため、各ビューの先頭に必ず置く。\n */\nfunction ActionError({ fetcher }: { fetcher: { data?: unknown } }) {\n const result = fetcher.data as { ok?: boolean; error?: string } | undefined\n if (result?.ok !== false || !result.error) return null\n return (\n <div data-testid=\"user-admin-action-error\" role=\"alert\" style={{ padding: \"0.7rem 1rem\", borderRadius: \"6px\", border: \"1px solid #fecaca\", backgroundColor: \"#fef2f2\", color: \"#b91c1c\", fontSize: \"0.85rem\" }}>\n {result.error}\n </div>\n )\n}\n\nexport function UsersView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [searchQuery, setSearchQuery] = useState(\"\")\n const [editingUser, setEditingUser] = useState<AuthUser | null>(null)\n const [isCreateOpen, setIsCreateOpen] = useState(false)\n\n const [newDisplayName, setNewDisplayName] = useState(\"\")\n const [newEmail, setNewEmail] = useState(\"\")\n\n const filteredUsers = data.users.filter((u) => u.displayName.toLowerCase().includes(searchQuery.toLowerCase()) || u.email.toLowerCase().includes(searchQuery.toLowerCase()))\n\n const handleToggleActive = (user: AuthUser) => {\n fetcher.submit({ intent: \"toggleUserActive\", userId: String(user.id), isActive: String(!user.isActive) }, { method: \"post\" })\n }\n\n const handleCreateUser = (e: React.FormEvent) => {\n e.preventDefault()\n if (!(newDisplayName && newEmail)) return\n fetcher.submit({ intent: \"createUser\", displayName: newDisplayName, email: newEmail }, { method: \"post\" })\n setNewDisplayName(\"\")\n setNewEmail(\"\")\n setIsCreateOpen(false)\n }\n\n const handleUpdateUser = (e: React.FormEvent) => {\n e.preventDefault()\n if (!editingUser) return\n fetcher.submit({ intent: \"updateUser\", userId: String(editingUser.id), displayName: editingUser.displayName, email: editingUser.email }, { method: \"post\" })\n setEditingUser(null)\n }\n\n return (\n <section data-testid=\"users-view-section\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1rem\" }}>\n <ActionError fetcher={fetcher} />\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", backgroundColor: \"#ffffff\", padding: \"1rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n <input\n type=\"text\"\n placeholder={labels.usersView.searchPlaceholder}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n data-testid=\"users-search-input\"\n style={{ padding: \"6px 12px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", width: \"260px\" }}\n />\n <button type=\"button\" onClick={() => setIsCreateOpen(true)} data-testid=\"users-add-button\" style={{ padding: \"6px 14px\", borderRadius: \"6px\", backgroundColor: \"#0284c7\", color: \"#ffffff\", border: \"none\", fontSize: \"0.85rem\", fontWeight: 600, cursor: \"pointer\" }}>\n {labels.usersView.addUser}\n </button>\n </div>\n\n <div style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", overflow: \"hidden\" }}>\n <table style={{ width: \"100%\", borderCollapse: \"collapse\", fontSize: \"0.85rem\", textAlign: \"left\" }}>\n <thead>\n <tr style={{ backgroundColor: \"#f8fafc\", borderBottom: \"1px solid #e2e8f0\", color: \"#475569\" }}>\n <th style={{ padding: \"10px 14px\" }}>{labels.usersView.id}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.usersView.displayName}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.usersView.email}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.usersView.status}</th>\n <th style={{ padding: \"10px 14px\", textAlign: \"right\" }}>{labels.usersView.actions}</th>\n </tr>\n </thead>\n <tbody>\n {filteredUsers.map((u) => (\n <tr key={u.id} style={{ borderBottom: \"1px solid #f1f5f9\" }} data-testid={`user-row-${u.id}`}>\n <td style={{ padding: \"10px 14px\", color: \"#64748b\" }}>#{u.id}</td>\n <td style={{ padding: \"10px 14px\", fontWeight: 600, color: \"#0f172a\" }}>{u.displayName}</td>\n <td style={{ padding: \"10px 14px\", color: \"#475569\" }}>{u.email}</td>\n <td style={{ padding: \"10px 14px\" }}>\n <span style={{ fontSize: \"0.75rem\", padding: \"2px 8px\", borderRadius: \"10px\", backgroundColor: u.isActive ? \"#dcfce7\" : \"#fee2e2\", color: u.isActive ? \"#15803d\" : \"#b91c1c\", fontWeight: 600 }}>\n {u.isActive ? labels.usersView.active : labels.usersView.inactive}\n </span>\n </td>\n <td style={{ padding: \"10px 14px\", textAlign: \"right\" }}>\n <button\n type=\"button\"\n onClick={() => setEditingUser(u)}\n data-testid={`edit-user-btn-${u.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", fontSize: \"0.78rem\", cursor: \"pointer\", marginRight: \"6px\" }}>\n {labels.usersView.edit}\n </button>\n <button\n type=\"button\"\n onClick={() => handleToggleActive(u)}\n data-testid={`toggle-user-btn-${u.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", fontSize: \"0.78rem\", cursor: \"pointer\", color: u.isActive ? \"#b91c1c\" : \"#15803d\" }}>\n {u.isActive ? labels.usersView.deactivate : labels.usersView.activate}\n </button>\n </td>\n </tr>\n ))}\n {filteredUsers.length === 0 && (\n <tr>\n <td colSpan={5} style={{ padding: \"2rem\", textAlign: \"center\", color: \"#94a3b8\" }}>\n {labels.usersView.empty}\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n\n {/* Create Modal */}\n {isCreateOpen && (\n <div style={{ position: \"fixed\", inset: 0, backgroundColor: \"rgba(0,0,0,0.4)\", display: \"flex\", justifyContent: \"center\", alignItems: \"center\", zIndex: 100 }} data-testid=\"user-create-modal\">\n <form onSubmit={handleCreateUser} style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>{labels.usersView.addUser}</h3>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"create-user-display-name-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.usersView.displayName}\n </label>\n <input\n id=\"create-user-display-name-input\"\n type=\"text\"\n value={newDisplayName}\n onChange={(e) => setNewDisplayName(e.target.value)}\n required\n data-testid=\"create-user-display-name\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1.2rem\" }}>\n <label htmlFor=\"create-user-email-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.usersView.email}\n </label>\n <input\n id=\"create-user-email-input\"\n type=\"email\"\n value={newEmail}\n onChange={(e) => setNewEmail(e.target.value)}\n required\n data-testid=\"create-user-email\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: \"0.5rem\" }}>\n <button type=\"button\" onClick={() => setIsCreateOpen(false)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n キャンセル\n </button>\n <button type=\"submit\" data-testid=\"submit-create-user\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#0284c7\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n 作成\n </button>\n </div>\n </form>\n </div>\n )}\n\n {/* Edit Modal */}\n {editingUser && (\n <div style={{ position: \"fixed\", inset: 0, backgroundColor: \"rgba(0,0,0,0.4)\", display: \"flex\", justifyContent: \"center\", alignItems: \"center\", zIndex: 100 }} data-testid=\"user-edit-modal\">\n <form onSubmit={handleUpdateUser} style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>{labels.usersView.editUser}</h3>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"edit-user-display-name-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.usersView.displayName}\n </label>\n <input\n id=\"edit-user-display-name-input\"\n type=\"text\"\n value={editingUser.displayName}\n onChange={(e) => setEditingUser({ ...editingUser, displayName: e.target.value })}\n required\n data-testid=\"edit-user-display-name\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1.2rem\" }}>\n <label htmlFor=\"edit-user-email-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.usersView.email}\n </label>\n <input\n id=\"edit-user-email-input\"\n type=\"email\"\n value={editingUser.email}\n onChange={(e) => setEditingUser({ ...editingUser, email: e.target.value })}\n required\n data-testid=\"edit-user-email\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: \"0.5rem\" }}>\n <button type=\"button\" onClick={() => setEditingUser(null)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n {labels.usersView.cancel}\n </button>\n <button type=\"submit\" data-testid=\"submit-edit-user\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#0284c7\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n {labels.usersView.save}\n </button>\n </div>\n </form>\n </div>\n )}\n </section>\n )\n}\n\n/**\n * View for managing user groups (create, edit, delete).\n * ユーザーグループ管理ビュー (作成・編集・削除)。\n */\nexport function GroupsView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [searchQuery, setSearchQuery] = useState(\"\")\n const [isCreateOpen, setIsCreateOpen] = useState(false)\n const [editingGroup, setEditingGroup] = useState<AuthGroup | null>(null)\n\n const [newGroupKey, setNewGroupKey] = useState(\"\")\n const [newName, setNewName] = useState(\"\")\n const [newDescription, setNewDescription] = useState(\"\")\n\n const filteredGroups = data.groups.filter((g) => g.name.toLowerCase().includes(searchQuery.toLowerCase()) || g.groupKey.toLowerCase().includes(searchQuery.toLowerCase()))\n\n const handleCreateGroup = (e: React.FormEvent) => {\n e.preventDefault()\n if (!(newGroupKey && newName)) return\n fetcher.submit({ intent: \"createGroup\", groupKey: newGroupKey, name: newName, description: newDescription }, { method: \"post\" })\n setNewGroupKey(\"\")\n setNewName(\"\")\n setNewDescription(\"\")\n setIsCreateOpen(false)\n }\n\n const handleUpdateGroup = (e: React.FormEvent) => {\n e.preventDefault()\n if (!editingGroup) return\n fetcher.submit({ intent: \"updateGroup\", groupId: String(editingGroup.id), name: editingGroup.name, description: editingGroup.description ?? \"\" }, { method: \"post\" })\n setEditingGroup(null)\n }\n\n const handleDeleteGroup = (groupId: number) => {\n if (!window.confirm(labels.groupsView.confirmDelete)) return\n fetcher.submit({ intent: \"deleteGroup\", groupId: String(groupId) }, { method: \"post\" })\n }\n\n return (\n <section data-testid=\"groups-view-section\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1rem\" }}>\n <ActionError fetcher={fetcher} />\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", backgroundColor: \"#ffffff\", padding: \"1rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n <input\n type=\"text\"\n placeholder={labels.groupsView.searchPlaceholder}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n data-testid=\"groups-search-input\"\n style={{ padding: \"6px 12px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", width: \"260px\" }}\n />\n <button type=\"button\" onClick={() => setIsCreateOpen(true)} data-testid=\"groups-add-button\" style={{ padding: \"6px 14px\", borderRadius: \"6px\", backgroundColor: \"#4338ca\", color: \"#ffffff\", border: \"none\", fontSize: \"0.85rem\", fontWeight: 600, cursor: \"pointer\" }}>\n {labels.groupsView.addGroup}\n </button>\n </div>\n\n <div style={{ display: \"grid\", gridTemplateColumns: \"repeat(auto-fill, minmax(280px, 1fr))\", gap: \"1rem\" }}>\n {filteredGroups.map((g) => {\n const memberCount = data.groupMembers.filter((m) => m.groupId === g.id).length\n return (\n <div key={g.id} style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", padding: \"1rem\", display: \"flex\", flexDirection: \"column\", justifyContent: \"space-between\" }} data-testid={`group-card-${g.id}`}>\n <div>\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", marginBottom: \"0.5rem\" }}>\n <h4 style={{ margin: 0, fontSize: \"1rem\", color: \"#0f172a\" }}>{g.name}</h4>\n <span style={{ fontSize: \"0.72rem\", backgroundColor: \"#e0e7ff\", color: \"#4338ca\", padding: \"2px 6px\", borderRadius: \"4px\", fontFamily: \"monospace\" }}>{g.groupKey}</span>\n </div>\n <p style={{ margin: \"0 0 0.8rem 0\", fontSize: \"0.82rem\", color: \"#64748b\", minHeight: \"2.4em\" }}>{g.description || labels.groupsView.noDescription}</p>\n </div>\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", borderTop: \"1px solid #f1f5f9\", paddingTop: \"0.6rem\" }}>\n <span style={{ fontSize: \"0.78rem\", color: \"#475569\" }}>\n 👥 メンバー: <strong>{memberCount}</strong> 人\n </span>\n <div>\n <button\n type=\"button\"\n onClick={() => setEditingGroup(g)}\n data-testid={`edit-group-btn-${g.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", fontSize: \"0.75rem\", cursor: \"pointer\", marginRight: \"4px\" }}>\n {labels.usersView.edit}\n </button>\n <button\n type=\"button\"\n onClick={() => handleDeleteGroup(g.id)}\n data-testid={`delete-group-btn-${g.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #fee2e2\", backgroundColor: \"#fff5f5\", color: \"#b91c1c\", fontSize: \"0.75rem\", cursor: \"pointer\" }}>\n 削除\n </button>\n </div>\n </div>\n </div>\n )\n })}\n </div>\n\n {/* Create Group Modal */}\n {isCreateOpen && (\n <div style={{ position: \"fixed\", inset: 0, backgroundColor: \"rgba(0,0,0,0.4)\", display: \"flex\", justifyContent: \"center\", alignItems: \"center\", zIndex: 100 }} data-testid=\"group-create-modal\">\n <form onSubmit={handleCreateGroup} style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>{labels.groupsView.addGroup}</h3>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"create-group-key-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.groupKey}\n </label>\n <input\n id=\"create-group-key-input\"\n type=\"text\"\n value={newGroupKey}\n onChange={(e) => setNewGroupKey(e.target.value)}\n placeholder={labels.groupsView.groupKeyPlaceholder}\n required\n data-testid=\"create-group-key\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"create-group-name-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.name}\n </label>\n <input\n id=\"create-group-name-input\"\n type=\"text\"\n value={newName}\n onChange={(e) => setNewName(e.target.value)}\n placeholder={labels.groupsView.namePlaceholder}\n required\n data-testid=\"create-group-name\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1.2rem\" }}>\n <label htmlFor=\"create-group-desc-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.description}\n </label>\n <textarea\n id=\"create-group-desc-input\"\n value={newDescription}\n onChange={(e) => setNewDescription(e.target.value)}\n data-testid=\"create-group-description\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\", height: \"60px\" }}\n />\n </div>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: \"0.5rem\" }}>\n <button type=\"button\" onClick={() => setIsCreateOpen(false)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n キャンセル\n </button>\n <button type=\"submit\" data-testid=\"submit-create-group\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#4338ca\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n 作成\n </button>\n </div>\n </form>\n </div>\n )}\n\n {/* Edit Group Modal */}\n {editingGroup && (\n <div style={{ position: \"fixed\", inset: 0, backgroundColor: \"rgba(0,0,0,0.4)\", display: \"flex\", justifyContent: \"center\", alignItems: \"center\", zIndex: 100 }} data-testid=\"group-edit-modal\">\n <form onSubmit={handleUpdateGroup} style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>{labels.groupsView.editGroup}</h3>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"edit-group-name-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.name}\n </label>\n <input\n id=\"edit-group-name-input\"\n type=\"text\"\n value={editingGroup.name}\n onChange={(e) => setEditingGroup({ ...editingGroup, name: e.target.value })}\n required\n data-testid=\"edit-group-name\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1.2rem\" }}>\n <label htmlFor=\"edit-group-desc-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.description}\n </label>\n <textarea\n id=\"edit-group-desc-input\"\n value={editingGroup.description ?? \"\"}\n onChange={(e) => setEditingGroup({ ...editingGroup, description: e.target.value })}\n data-testid=\"edit-group-description\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\", height: \"60px\" }}\n />\n </div>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: \"0.5rem\" }}>\n <button type=\"button\" onClick={() => setEditingGroup(null)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n {labels.groupsView.cancel}\n </button>\n <button type=\"submit\" data-testid=\"submit-edit-group\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#4338ca\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n {labels.groupsView.save}\n </button>\n </div>\n </form>\n </div>\n )}\n </section>\n )\n}\n\n/**\n * View for managing group member assignments.\n * グループメンバー割り当て管理ビュー。\n */\nexport function GroupMembersView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [selectedGroupId, setSelectedGroupId] = useState<number>(data.groups[0]?.id ?? 0)\n\n const currentGroup = data.groups.find((g) => g.id === selectedGroupId)\n const currentMemberUserIds = new Set(data.groupMembers.filter((m) => m.groupId === selectedGroupId).map((m) => m.userId))\n\n const handleToggleMember = (userId: number, isMember: boolean) => {\n if (isMember) {\n fetcher.submit({ intent: \"removeGroupMember\", groupId: String(selectedGroupId), userId: String(userId) }, { method: \"post\" })\n } else {\n fetcher.submit({ intent: \"addGroupMember\", groupId: String(selectedGroupId), userId: String(userId) }, { method: \"post\" })\n }\n }\n\n return (\n <section data-testid=\"group-members-view-section\" style={{ display: \"grid\", gridTemplateColumns: \"240px 1fr\", gap: \"1.2rem\" }}>\n <ActionError fetcher={fetcher} />\n <div style={{ backgroundColor: \"#ffffff\", padding: \"1rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n <h4 style={{ margin: \"0 0 0.8rem 0\", fontSize: \"0.9rem\", color: \"#475569\" }}>{labels.groupMembersView.selectGroup}</h4>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.4rem\" }}>\n {data.groups.map((g) => {\n const isSelected = g.id === selectedGroupId\n return (\n <button\n type=\"button\"\n key={g.id}\n onClick={() => setSelectedGroupId(g.id)}\n data-testid={`select-group-btn-${g.id}`}\n style={{\n textAlign: \"left\",\n padding: \"8px 10px\",\n borderRadius: \"6px\",\n border: isSelected ? \"1px solid #6366f1\" : \"1px solid #e2e8f0\",\n backgroundColor: isSelected ? \"#e0e7ff\" : \"#ffffff\",\n color: isSelected ? \"#3730a3\" : \"#0f172a\",\n cursor: \"pointer\",\n fontWeight: isSelected ? 600 : 400,\n fontSize: \"0.85rem\",\n }}>\n {g.name}\n </button>\n )\n })}\n </div>\n </div>\n\n <div style={{ backgroundColor: \"#ffffff\", padding: \"1.2rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n {currentGroup ? (\n <>\n <div style={{ marginBottom: \"1rem\", borderBottom: \"1px solid #e2e8f0\", paddingBottom: \"0.8rem\" }}>\n <h3 style={{ margin: 0, fontSize: \"1.1rem\", color: \"#0f172a\" }}>{currentGroup.name} の所属メンバー</h3>\n <span style={{ fontSize: \"0.8rem\", color: \"#64748b\" }}>グループキー: {currentGroup.groupKey}</span>\n </div>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.5rem\" }}>\n {data.users.map((u) => {\n const isMember = currentMemberUserIds.has(u.id)\n return (\n <div\n key={u.id}\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n padding: \"8px 12px\",\n borderRadius: \"6px\",\n border: \"1px solid #f1f5f9\",\n backgroundColor: isMember ? \"#f0fdf4\" : \"#ffffff\",\n }}\n data-testid={`member-row-${u.id}`}>\n <div>\n <span style={{ fontWeight: 600, fontSize: \"0.88rem\", color: \"#0f172a\", marginRight: \"8px\" }}>{u.displayName}</span>\n <span style={{ fontSize: \"0.78rem\", color: \"#64748b\" }}>({u.email})</span>\n </div>\n <button\n type=\"button\"\n onClick={() => handleToggleMember(u.id, isMember)}\n data-testid={`toggle-member-btn-${u.id}`}\n style={{\n padding: \"4px 12px\",\n borderRadius: \"4px\",\n border: isMember ? \"1px solid #b91c1c\" : \"1px solid #0284c7\",\n backgroundColor: isMember ? \"#ffffff\" : \"#0284c7\",\n color: isMember ? \"#b91c1c\" : \"#ffffff\",\n fontSize: \"0.78rem\",\n fontWeight: 600,\n cursor: \"pointer\",\n }}>\n {isMember ? labels.groupMembersView.actionRemove : labels.groupMembersView.actionAdd}\n </button>\n </div>\n )\n })}\n </div>\n </>\n ) : (\n <div style={{ color: \"#94a3b8\", textAlign: \"center\", padding: \"2rem\" }}>{labels.groupMembersView.selectPrompt}</div>\n )}\n </div>\n </section>\n )\n}\n\n/**\n * View for managing authentication allowlist entries.\n * アクセス許可リスト (Allowlist) 管理ビュー。\n */\nexport function AllowlistView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [pattern, setPattern] = useState(\"\")\n const [type, setType] = useState<\"email\" | \"domain\">(\"email\")\n const [description, setDescription] = useState(\"\")\n\n const handleAddEntry = (e: React.FormEvent) => {\n e.preventDefault()\n if (!pattern) return\n fetcher.submit({ intent: \"addAllowlistEntry\", pattern, type, description }, { method: \"post\" })\n setPattern(\"\")\n setDescription(\"\")\n }\n\n const handleRemoveEntry = (id: number) => {\n fetcher.submit({ intent: \"removeAllowlistEntry\", entryId: String(id) }, { method: \"post\" })\n }\n\n return (\n <section data-testid=\"allowlist-view-section\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1.2rem\" }}>\n <ActionError fetcher={fetcher} />\n <form onSubmit={handleAddEntry} style={{ backgroundColor: \"#ffffff\", padding: \"1.2rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", display: \"flex\", flexWrap: \"wrap\", gap: \"0.8rem\", alignItems: \"flex-end\" }}>\n <div style={{ flex: 2, minWidth: \"220px\" }}>\n <label htmlFor=\"allowlist-pattern-input-field\" style={{ display: \"block\", fontSize: \"0.78rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.allowlistView.pattern}\n </label>\n <input\n id=\"allowlist-pattern-input-field\"\n type=\"text\"\n value={pattern}\n onChange={(e) => setPattern(e.target.value)}\n required\n data-testid=\"allowlist-pattern-input\"\n style={{ width: \"100%\", padding: \"6px 10px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ flex: 1, minWidth: \"140px\" }}>\n <label htmlFor=\"allowlist-type-select-field\" style={{ display: \"block\", fontSize: \"0.78rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.allowlistView.type}\n </label>\n <select\n id=\"allowlist-type-select-field\"\n value={type}\n onChange={(e) => setType(e.target.value as \"email\" | \"domain\")}\n data-testid=\"allowlist-type-select\"\n style={{ width: \"100%\", padding: \"6px 10px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", boxSizing: \"border-box\" }}>\n <option value=\"email\">{labels.allowlistView.emailType}</option>\n <option value=\"domain\">{labels.allowlistView.domainType}</option>\n </select>\n </div>\n <div style={{ flex: 2, minWidth: \"200px\" }}>\n <label htmlFor=\"allowlist-desc-input-field\" style={{ display: \"block\", fontSize: \"0.78rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.allowlistView.description}\n </label>\n <input\n id=\"allowlist-desc-input-field\"\n type=\"text\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n data-testid=\"allowlist-desc-input\"\n style={{ width: \"100%\", padding: \"6px 10px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", boxSizing: \"border-box\" }}\n />\n </div>\n <button type=\"submit\" data-testid=\"submit-allowlist-add\" style={{ padding: \"7px 16px\", borderRadius: \"6px\", backgroundColor: \"#047857\", color: \"#ffffff\", border: \"none\", fontSize: \"0.85rem\", fontWeight: 600, cursor: \"pointer\" }}>\n {labels.allowlistView.addEntry}\n </button>\n </form>\n\n <div style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", overflow: \"hidden\" }}>\n <table style={{ width: \"100%\", borderCollapse: \"collapse\", fontSize: \"0.85rem\", textAlign: \"left\" }}>\n <thead>\n <tr style={{ backgroundColor: \"#f8fafc\", borderBottom: \"1px solid #e2e8f0\", color: \"#475569\" }}>\n <th style={{ padding: \"10px 14px\" }}>ID</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.allowlistView.pattern}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.allowlistView.type}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.allowlistView.description}</th>\n <th style={{ padding: \"10px 14px\", textAlign: \"right\" }}>{labels.allowlistView.actions}</th>\n </tr>\n </thead>\n <tbody>\n {data.allowlist.map((item) => (\n <tr key={item.id} style={{ borderBottom: \"1px solid #f1f5f9\" }} data-testid={`allowlist-row-${item.id}`}>\n <td style={{ padding: \"10px 14px\", color: \"#64748b\" }}>#{item.id}</td>\n <td style={{ padding: \"10px 14px\", fontWeight: 600, color: \"#0f172a\", fontFamily: \"monospace\" }}>{item.pattern}</td>\n <td style={{ padding: \"10px 14px\" }}>\n <span style={{ fontSize: \"0.75rem\", padding: \"2px 8px\", borderRadius: \"10px\", backgroundColor: item.type === \"email\" ? \"#e0f2fe\" : \"#fef3c7\", color: item.type === \"email\" ? \"#0369a1\" : \"#b45309\", fontWeight: 600 }}>\n {item.type === \"email\" ? labels.allowlistView.emailTypeShort : labels.allowlistView.domainTypeShort}\n </span>\n </td>\n <td style={{ padding: \"10px 14px\", color: \"#475569\" }}>{item.description || \"-\"}</td>\n <td style={{ padding: \"10px 14px\", textAlign: \"right\" }}>\n {/* 変更不能な供給元 (環境変数など) は削除ボタンを出さない */}\n {item.readOnly ? (\n <span data-testid={`readonly-allowlist-${item.id}`} style={{ fontSize: \"0.75rem\", color: \"#94a3b8\" }}>\n {labels.allowlistView.readOnly}\n </span>\n ) : (\n <button\n type=\"button\"\n onClick={() => handleRemoveEntry(item.id)}\n data-testid={`remove-allowlist-btn-${item.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #fee2e2\", backgroundColor: \"#fff5f5\", color: \"#b91c1c\", fontSize: \"0.78rem\", cursor: \"pointer\" }}>\n 削除\n </button>\n )}\n </td>\n </tr>\n ))}\n {data.allowlist.length === 0 && (\n <tr>\n <td colSpan={5} style={{ padding: \"2rem\", textAlign: \"center\", color: \"#94a3b8\" }}>\n {labels.allowlistView.empty}\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n </section>\n )\n}\n\nconst SEGMENT_VIEWS: Record<string, () => React.ReactElement> = {\n users: UsersView,\n groups: GroupsView,\n group_members: GroupMembersView,\n allowlist: AllowlistView,\n}\n\nexport type AuthUserAdminAppViewProps = UserAdminShellProps\n\n/**\n * Top-level application view component for the user administration UI (`/users/*`).\n * ユーザー管理 UI 全体を描画するトップレベルコンポーネント。\n */\nexport function AuthUserAdminAppView({ renderHeader }: AuthUserAdminAppViewProps = {}) {\n const data = useLoaderData() as UserAdminLoaderData | undefined\n const segment = data?.segment ?? \"users\"\n const View = SEGMENT_VIEWS[segment] ?? UsersView\n\n return (\n <UserAdminShell renderHeader={renderHeader}>\n <View />\n </UserAdminShell>\n )\n}\n"],"mappings":"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","names":["defaultUserAdminLabels","jaUserAdminLabels","resolveUserAdminLabels","over","d","createInMemoryUserAdminStore","initial","users","groups","groupMembers","allowlist","data","newUser","u","id","idx","isActive","newGroup","g","m","groupId","userId","existing","newMember","newEntry","a","INTENT_ACCESS","requireId","formData","field","raw","n","requireText","value","optionalText","createUserAdminApp","config","labels","resolveUserAdminLabels","loader","request","pathSegments","segment","action","intent","access","dispatch","error","rawActive","type","Link","useLoaderData","jsx","jsxs","UserAdminShell","children","renderHeader","data","useLoaderData","segment","labels","defaultUserAdminLabels","tabs","annotation","tab","isActive","Link","useState","useFetcher","useLoaderData","Fragment","jsx","jsxs","ActionError","fetcher","result","UsersView","data","useLoaderData","useFetcher","labels","defaultUserAdminLabels","searchQuery","setSearchQuery","useState","editingUser","setEditingUser","isCreateOpen","setIsCreateOpen","newDisplayName","setNewDisplayName","newEmail","setNewEmail","filteredUsers","handleToggleActive","user","handleCreateUser","e","handleUpdateUser","GroupsView","editingGroup","setEditingGroup","newGroupKey","setNewGroupKey","newName","setNewName","newDescription","setNewDescription","filteredGroups","g","handleCreateGroup","handleUpdateGroup","handleDeleteGroup","groupId","memberCount","m","GroupMembersView","selectedGroupId","setSelectedGroupId","currentGroup","currentMemberUserIds","handleToggleMember","userId","isMember","isSelected","u","AllowlistView","pattern","setPattern","type","setType","description","setDescription","handleAddEntry","handleRemoveEntry","id","item","SEGMENT_VIEWS","AuthUserAdminAppView","renderHeader","segment","View","UserAdminShell"]}
1
+ {"version":3,"sources":["../src/admin/labels.ts","../src/admin/server.ts","../src/admin/ui.tsx","../src/admin/views.tsx"],"sourcesContent":["/**\n * Labels and i18n strings for the user administration UI.\n * ユーザー管理 UI で使用するラベルと i18n 文字列の定義。\n *\n * パッケージ既定は **英語** ([defaultUserAdminLabels])。日本語で使う場合はアプリ側が\n * [jaUserAdminLabels] を明示的に注入する (認可管理 `@aiquants/authz-react-router` と同じ規約)。\n * UI 文字列はすべて本モジュールに集約し、コンポーネント側へ直書きしないこと — 直書きすると\n * 日本語以外の利用者がその箇所を差し替えられなくなる。\n */\n\nexport type UserAdminLabels = {\n title: string\n /** シェル (タブ枠) の見出し。 */\n heading: string\n tabs: {\n users: string\n groups: string\n groupMembers: string\n allowlist: string\n }\n usersView: {\n title: string\n searchPlaceholder: string\n addUser: string\n editUser: string\n id: string\n displayName: string\n email: string\n status: string\n active: string\n inactive: string\n actions: string\n edit: string\n activate: string\n deactivate: string\n empty: string\n save: string\n cancel: string\n }\n groupsView: {\n title: string\n searchPlaceholder: string\n addGroup: string\n editGroup: string\n id: string\n groupKey: string\n name: string\n description: string\n memberCount: string\n status: string\n actions: string\n edit: string\n delete: string\n confirmDelete: string\n noDescription: string\n groupKeyPlaceholder: string\n namePlaceholder: string\n empty: string\n save: string\n cancel: string\n }\n groupMembersView: {\n selectGroup: string\n selectPrompt: string\n actionAdd: string\n actionRemove: string\n }\n allowlistView: {\n title: string\n addEntry: string\n pattern: string\n type: string\n emailType: string\n domainType: string\n /** 一覧のバッジ表示 (短縮形)。 */\n emailTypeShort: string\n domainTypeShort: string\n description: string\n actions: string\n /** 変更不能な供給元 (環境変数など) 由来の行に出す表示。 */\n readOnly: string\n delete: string\n empty: string\n save: string\n cancel: string\n }\n}\n\n/**\n * Package default labels (English).\n * パッケージ既定ラベル (英語)。\n */\nexport const defaultUserAdminLabels: UserAdminLabels = {\n title: \"Users & Groups\",\n heading: \"👤 User administration\",\n tabs: {\n users: \"👤 Users\",\n groups: \"👥 Groups\",\n groupMembers: \"🔗 Members\",\n allowlist: \"✉️ Allowlist\",\n },\n usersView: {\n title: \"Users\",\n searchPlaceholder: \"Search by name or email...\",\n addUser: \"+ Add user\",\n editUser: \"Edit user\",\n id: \"ID\",\n displayName: \"Name\",\n email: \"Email\",\n status: \"Status\",\n active: \"Active\",\n inactive: \"Inactive\",\n actions: \"Actions\",\n edit: \"Edit\",\n activate: \"Activate\",\n deactivate: \"Deactivate\",\n empty: \"No matching users\",\n save: \"Save\",\n cancel: \"Cancel\",\n },\n groupsView: {\n title: \"Groups\",\n searchPlaceholder: \"Search by group name or key...\",\n addGroup: \"+ Add group\",\n editGroup: \"Edit group\",\n id: \"ID\",\n groupKey: \"Group key\",\n name: \"Name\",\n description: \"Description\",\n memberCount: \"Members\",\n status: \"Status\",\n actions: \"Actions\",\n edit: \"Edit\",\n delete: \"Delete\",\n confirmDelete: \"Delete this group?\",\n noDescription: \"No description\",\n groupKeyPlaceholder: \"e.g. dev-team\",\n namePlaceholder: \"e.g. Development team\",\n empty: \"No groups registered\",\n save: \"Save\",\n cancel: \"Cancel\",\n },\n groupMembersView: {\n selectGroup: \"Select a group\",\n selectPrompt: \"Select a group to manage its members\",\n actionAdd: \"Add\",\n actionRemove: \"Remove\",\n },\n allowlistView: {\n title: \"Sign-in allowlist\",\n addEntry: \"+ Add pattern\",\n pattern: \"Pattern (e.g. @example.com or user@example.com)\",\n type: \"Type\",\n emailType: \"Exact email match\",\n domainType: \"Domain match\",\n emailTypeShort: \"Email\",\n domainTypeShort: \"Domain\",\n description: \"Description\",\n actions: \"Actions\",\n readOnly: \"Read-only\",\n delete: \"Delete\",\n empty: \"No allowlist entries registered\",\n save: \"Add\",\n cancel: \"Cancel\",\n },\n}\n\n/**\n * Standard Japanese labels for user administration (opt-in).\n * ユーザー管理の標準日本語ラベル (アプリ側で明示注入して使う)。\n */\nexport const jaUserAdminLabels: UserAdminLabels = {\n title: \"ユーザー・グループ管理\",\n heading: \"👤 ユーザー管理\",\n tabs: {\n users: \"👤 ユーザー一覧\",\n groups: \"👥 ユーザーグループ\",\n groupMembers: \"🔗 グループメンバー\",\n allowlist: \"✉️ アクセス許可リスト\",\n },\n usersView: {\n title: \"ユーザー管理\",\n searchPlaceholder: \"表示名や Email で検索...\",\n addUser: \"+ ユーザー追加\",\n editUser: \"ユーザーの編集\",\n id: \"ID\",\n displayName: \"表示名\",\n email: \"メールアドレス\",\n status: \"ステータス\",\n active: \"有効\",\n inactive: \"無効\",\n actions: \"操作\",\n edit: \"編集\",\n activate: \"有効化\",\n deactivate: \"無効化\",\n empty: \"該当するユーザーが見つかりません\",\n save: \"保存\",\n cancel: \"キャンセル\",\n },\n groupsView: {\n title: \"ユーザーグループ管理\",\n searchPlaceholder: \"グループ名やキーで検索...\",\n addGroup: \"+ グループ作成\",\n editGroup: \"グループの編集\",\n id: \"ID\",\n groupKey: \"グループキー\",\n name: \"グループ名\",\n description: \"説明\",\n memberCount: \"メンバー数\",\n status: \"ステータス\",\n actions: \"操作\",\n edit: \"編集\",\n delete: \"削除\",\n confirmDelete: \"このグループを削除してもよろしいですか?\",\n noDescription: \"説明なし\",\n groupKeyPlaceholder: \"例: dev-team\",\n namePlaceholder: \"例: 開発チーム\",\n empty: \"グループが登録されていません\",\n save: \"保存\",\n cancel: \"キャンセル\",\n },\n groupMembersView: {\n selectGroup: \"グループ選択\",\n selectPrompt: \"グループを選択してください\",\n actionAdd: \"追加\",\n actionRemove: \"除外\",\n },\n allowlistView: {\n title: \"アクセス許可リスト (Allowlist)\",\n addEntry: \"+ 許可パターン追加\",\n pattern: \"パターン (例: @example.com または user@example.com)\",\n type: \"種別\",\n emailType: \"メールアドレス完全一致\",\n domainType: \"ドメイン一致\",\n emailTypeShort: \"Email 完全一致\",\n domainTypeShort: \"ドメイン一致\",\n description: \"説明 / 備考\",\n actions: \"操作\",\n readOnly: \"変更不可\",\n delete: \"削除\",\n empty: \"アロワリストエントリが登録されていません\",\n save: \"登録\",\n cancel: \"キャンセル\",\n },\n}\n\n/** セクション単位の部分上書き。 */\nexport type PartialUserAdminLabels = {\n title?: string\n heading?: string\n tabs?: Partial<UserAdminLabels[\"tabs\"]>\n usersView?: Partial<UserAdminLabels[\"usersView\"]>\n groupsView?: Partial<UserAdminLabels[\"groupsView\"]>\n groupMembersView?: Partial<UserAdminLabels[\"groupMembersView\"]>\n allowlistView?: Partial<UserAdminLabels[\"allowlistView\"]>\n}\n\n/**\n * Shallow per-section merge over the English defaults.\n * 英語既定に対してセクション単位で浅くマージする処理。\n */\nexport function resolveUserAdminLabels(over?: PartialUserAdminLabels): UserAdminLabels {\n const d = defaultUserAdminLabels\n if (!over) return d\n return {\n title: over.title ?? d.title,\n heading: over.heading ?? d.heading,\n tabs: { ...d.tabs, ...over.tabs },\n usersView: { ...d.usersView, ...over.usersView },\n groupsView: { ...d.groupsView, ...over.groupsView },\n groupMembersView: { ...d.groupMembersView, ...over.groupMembersView },\n allowlistView: { ...d.allowlistView, ...over.allowlistView },\n }\n}\n","import type { AuthAllowlistEntry, AuthGroup, AuthGroupMember, AuthUser } from \"@aiquants/auth-core\"\nimport type { ActionFunctionArgs, LoaderFunctionArgs } from \"react-router\"\nimport { type PartialUserAdminLabels, resolveUserAdminLabels } from \"./labels\"\n\n/**\n * Server store interface for managing users, groups, memberships, and allowlists.\n * ユーザー・グループ・所属・アロワリストを管理するサーバー層ストアのインターフェース。\n */\nexport type UserAdminStore = {\n listUsers: () => Promise<AuthUser[]>\n createUser: (data: { displayName: string; email: string }) => Promise<AuthUser>\n updateUser: (id: number, data: { displayName: string; email: string }) => Promise<AuthUser>\n toggleUserActive: (id: number, isActive: boolean) => Promise<AuthUser>\n\n listGroups: () => Promise<AuthGroup[]>\n createGroup: (data: { groupKey: string; name: string; description?: string }) => Promise<AuthGroup>\n updateGroup: (id: number, data: { name: string; description?: string }) => Promise<AuthGroup>\n deleteGroup: (id: number) => Promise<void>\n\n listGroupMembers: () => Promise<AuthGroupMember[]>\n addGroupMember: (groupId: number, userId: number) => Promise<AuthGroupMember>\n removeGroupMember: (groupId: number, userId: number) => Promise<void>\n\n listAllowlist: () => Promise<AuthAllowlistEntry[]>\n addAllowlistEntry: (data: { pattern: string; type: \"email\" | \"domain\"; description?: string }) => Promise<AuthAllowlistEntry>\n removeAllowlistEntry: (id: number) => Promise<void>\n}\n\n/**\n * Creates an in-memory UserAdminStore initialized with optional seed data.\n * シードデータで初期化されるメモリ上の UserAdminStore を作成する。\n */\nexport function createInMemoryUserAdminStore(initial?: { users?: AuthUser[]; groups?: AuthGroup[]; groupMembers?: AuthGroupMember[]; allowlist?: AuthAllowlistEntry[] }): UserAdminStore {\n const users: AuthUser[] = initial?.users ?? [\n { id: 1, email: \"admin@example.com\", displayName: \"システム管理者\", isActive: true },\n { id: 2, email: \"user1@example.com\", displayName: \"開発 太郎\", isActive: true },\n { id: 3, email: \"user2@example.com\", displayName: \"営業 花子\", isActive: true },\n ]\n let groups: AuthGroup[] = initial?.groups ?? [\n { id: 1, groupKey: \"dev-team\", name: \"開発チーム\", description: \"システム開発担当グループ\", isActive: true },\n { id: 2, groupKey: \"sales-dept\", name: \"営業部\", description: \"営業ソリューション担当\", isActive: true },\n ]\n let groupMembers: AuthGroupMember[] = initial?.groupMembers ?? [\n { id: 1, groupId: 1, userId: 2 },\n { id: 2, groupId: 2, userId: 3 },\n ]\n let allowlist: AuthAllowlistEntry[] = initial?.allowlist ?? [\n { id: 1, pattern: \"admin@example.com\", type: \"email\", description: \"初期管理者\" },\n { id: 2, pattern: \"@example.com\", type: \"domain\", description: \"社内ドメイン\" },\n ]\n\n return {\n async listUsers() {\n return [...users]\n },\n async createUser(data) {\n const newUser: AuthUser = {\n id: Math.max(...users.map((u) => u.id), 0) + 1,\n email: data.email,\n displayName: data.displayName,\n isActive: true,\n }\n users.push(newUser)\n return newUser\n },\n async updateUser(id, data) {\n const idx = users.findIndex((u) => u.id === id)\n if (idx === -1) throw new Error(`User not found: ${id}`)\n users[idx] = { ...users[idx], displayName: data.displayName, email: data.email }\n return users[idx]\n },\n async toggleUserActive(id, isActive) {\n const idx = users.findIndex((u) => u.id === id)\n if (idx === -1) throw new Error(`User not found: ${id}`)\n users[idx] = { ...users[idx], isActive }\n return users[idx]\n },\n\n async listGroups() {\n return [...groups]\n },\n async createGroup(data) {\n const newGroup: AuthGroup = {\n id: Math.max(...groups.map((g) => g.id), 0) + 1,\n groupKey: data.groupKey,\n name: data.name,\n description: data.description,\n isActive: true,\n }\n groups.push(newGroup)\n return newGroup\n },\n async updateGroup(id, data) {\n const idx = groups.findIndex((g) => g.id === id)\n if (idx === -1) throw new Error(`Group not found: ${id}`)\n groups[idx] = { ...groups[idx], name: data.name, description: data.description }\n return groups[idx]\n },\n async deleteGroup(id) {\n groups = groups.filter((g) => g.id !== id)\n groupMembers = groupMembers.filter((m) => m.groupId !== id)\n },\n\n async listGroupMembers() {\n return [...groupMembers]\n },\n async addGroupMember(groupId, userId) {\n const existing = groupMembers.find((m) => m.groupId === groupId && m.userId === userId)\n if (existing) return existing\n const newMember: AuthGroupMember = {\n id: Math.max(...groupMembers.map((m) => m.id), 0) + 1,\n groupId,\n userId,\n }\n groupMembers.push(newMember)\n return newMember\n },\n async removeGroupMember(groupId, userId) {\n groupMembers = groupMembers.filter((m) => !(m.groupId === groupId && m.userId === userId))\n },\n\n async listAllowlist() {\n return [...allowlist]\n },\n async addAllowlistEntry(data) {\n const newEntry: AuthAllowlistEntry = {\n id: Math.max(...allowlist.map((a) => a.id), 0) + 1,\n pattern: data.pattern,\n type: data.type,\n description: data.description,\n }\n allowlist.push(newEntry)\n return newEntry\n },\n async removeAllowlistEntry(id) {\n allowlist = allowlist.filter((a) => a.id !== id)\n },\n }\n}\n\n/**\n * The CRUD verb an operation performs. Deliberately a plain string union so this package stays\n * independent of any particular authorization library.\n * 操作が行う CRUD 種別。特定の認可ライブラリに依存しないよう素の文字列 union で表現する。\n */\nexport type UserAdminAccess = \"read\" | \"create\" | \"update\" | \"delete\"\n\n/**\n * Access guard the user-admin surface is mounted behind. Deny by throwing\n * (a `Response`/`redirect` or an `Error`); returning normally means \"allow\".\n *\n * ユーザー管理面をマウントする際の必須アクセスガード。拒否は throw で表現する\n * (`Response` / `redirect` / `Error`)。正常 return は「許可」を意味する。\n *\n * 操作ごとの CRUD 種別を受け取るため、呼び出し側は「削除だけ別権限」といった細分化ができる。\n * `intent` は監査ログ用の補助情報(認可判断は `action` で行うこと)。\n */\nexport type UserAdminGuards = {\n /**\n * 許可は「正常 return」、拒否は「throw」で表す。返り値は判定に使わない (`void` 契約)。\n * 述語のつもりで `false` を返す実装を書くと素通りするため、返り値型で明示的に禁じる。\n */\n requireAccess: (request: Request, context: { action: UserAdminAccess; intent: string }) => Promise<void> | void\n}\n\n/**\n * Maps each action intent to the CRUD verb it actually performs.\n * 各 intent が実際に行う操作の CRUD 種別への対応表。\n *\n * 割当の付与・解除は行の追加・削除であるため、認可管理 UI (`/authz`) の\n * `assignUserRole` = create / `removeUserRole` = delete と同じ粒度に揃える。\n */\nconst INTENT_ACCESS = new Map<string, UserAdminAccess>([\n [\"createUser\", \"create\"],\n [\"updateUser\", \"update\"],\n [\"toggleUserActive\", \"update\"],\n [\"createGroup\", \"create\"],\n [\"updateGroup\", \"update\"],\n [\"deleteGroup\", \"delete\"],\n [\"addGroupMember\", \"create\"],\n [\"removeGroupMember\", \"delete\"],\n [\"addAllowlistEntry\", \"create\"],\n [\"removeAllowlistEntry\", \"delete\"],\n])\n\nexport type UserAdminAppConfig = {\n store: UserAdminStore\n /** セクション単位の部分上書き。既定は英語 (`defaultUserAdminLabels`)。 */\n labels?: PartialUserAdminLabels\n /**\n * Required — omitting a guard would expose the whole user directory and its write actions.\n * 必須。省略を許すとユーザー名簿と全更新操作が無防備に露出するため、型で強制する。\n */\n guards: UserAdminGuards\n /** ヘッダー注釈に自分の実効権限を表示するためのリクエスト別権限取得関数。 */\n getMyPermissions?: (request: Request) => Promise<Record<string, unknown>>\n}\n\n/**\n * Reads a required positive integer id from submitted form data.\n * 送信フォームから必須の正整数 ID を読み取る処理。\n *\n * @throws when the field is missing, non-numeric, or not a positive integer.\n * 欠落・非数値・正整数でない場合は throw。\n */\nfunction requireId(formData: FormData, field: string): number {\n const raw = formData.get(field)\n const n = Number(raw)\n // NaN / 小数 / 0 以下は不正な ID として弾く (Number(null) === 0 の取り違えも防ぐ)\n if (raw === null || String(raw).trim() === \"\" || !Number.isInteger(n) || n <= 0) {\n throw new Error(`Invalid or missing \"${field}\"`)\n }\n return n\n}\n\n/**\n * Reads a required non-empty trimmed string from submitted form data.\n * 送信フォームから必須の非空文字列 (トリム済み) を読み取る処理。\n *\n * @throws when the field is missing or blank. 欠落・空白のみの場合は throw。\n */\nfunction requireText(formData: FormData, field: string): string {\n const value = String(formData.get(field) ?? \"\").trim()\n if (value === \"\") throw new Error(`Invalid or missing \"${field}\"`)\n return value\n}\n\n/** Reads an optional trimmed string; blank becomes `undefined`. 任意文字列の読み取り (空は undefined)。 */\nfunction optionalText(formData: FormData, field: string): string | undefined {\n const value = String(formData.get(field) ?? \"\").trim()\n return value === \"\" ? undefined : value\n}\n\n/**\n * Creates React Router loader & action handler for user administration.\n * ユーザー管理用 React Router の loader および action ハンドラーを作成する。\n *\n * `config.guards` は必須。loader / action の双方が、処理を始める前にガードを通過させる。\n */\nexport function createUserAdminApp(config: UserAdminAppConfig) {\n const labels = resolveUserAdminLabels(config.labels)\n\n async function loader({ request }: LoaderFunctionArgs) {\n // 読取ガード: 拒否は throw されるためここで処理が打ち切られる\n await config.guards.requireAccess(request, { action: \"read\", intent: \"loader\" })\n const url = new URL(request.url)\n const pathSegments = url.pathname.split(\"/\").filter(Boolean)\n const segment = pathSegments[pathSegments.length - 1] ?? \"users\"\n\n const [users, groups, groupMembers, allowlist, myPermissions] = await Promise.all([\n config.store.listUsers(),\n config.store.listGroups(),\n config.store.listGroupMembers(),\n config.store.listAllowlist(),\n config.getMyPermissions ? config.getMyPermissions(request).catch(() => ({})) : Promise.resolve({}),\n ])\n\n return {\n segment: [\"groups\", \"group_members\", \"allowlist\"].includes(segment) ? segment : \"users\",\n users,\n groups,\n groupMembers,\n allowlist,\n myPermissions,\n labels,\n }\n }\n\n async function action({ request }: ActionFunctionArgs) {\n // intent の判定に FormData が要るため、ここでだけ本文を先に読む。\n // 未知 intent は「何も実行しない」ため、ガード前に読んでも権限判断には影響しない。\n const formData = await request.formData()\n const intent = String(formData.get(\"intent\") ?? \"\")\n\n // 未知 intent はストアへ到達しない (ガードも走らせず 400 相当を返す)。\n // Map 参照のためプロトタイプ継承キー (\"constructor\" 等) を拾わない。\n const access = INTENT_ACCESS.get(intent)\n if (!access) return { ok: false, error: `Unknown intent: ${intent}` }\n\n // 操作ごとの CRUD 種別でガードする (削除は delete 権限が要る)\n await config.guards.requireAccess(request, { action: access, intent })\n\n try {\n return await dispatch(intent, formData)\n } catch (error) {\n // ガード拒否 (Response/redirect) はそのまま伝播させる。ストア由来の業務エラーだけ\n // {ok:false,error} に写して UI へ届ける (ロックアウト理由・重複パターン等)。\n if (error instanceof Response) throw error\n return { ok: false, error: error instanceof Error ? error.message : String(error) }\n }\n }\n\n /** intent ごとの実処理。認可・入力検証を通過した後にのみ呼ばれる。 */\n async function dispatch(intent: string, formData: FormData) {\n if (intent === \"createUser\") {\n await config.store.createUser({ displayName: requireText(formData, \"displayName\"), email: requireText(formData, \"email\") })\n return { ok: true }\n }\n\n if (intent === \"updateUser\") {\n await config.store.updateUser(requireId(formData, \"userId\"), { displayName: requireText(formData, \"displayName\"), email: requireText(formData, \"email\") })\n return { ok: true }\n }\n\n if (intent === \"toggleUserActive\") {\n // 破壊側 (無効化) を既定にしない。明示的な \"true\"/\"false\" 以外は不正入力として弾く。\n const rawActive = String(formData.get(\"isActive\") ?? \"\")\n if (rawActive !== \"true\" && rawActive !== \"false\") throw new Error('Invalid or missing \"isActive\"')\n await config.store.toggleUserActive(requireId(formData, \"userId\"), rawActive === \"true\")\n return { ok: true }\n }\n\n if (intent === \"createGroup\") {\n await config.store.createGroup({ groupKey: requireText(formData, \"groupKey\"), name: requireText(formData, \"name\"), description: optionalText(formData, \"description\") })\n return { ok: true }\n }\n\n if (intent === \"updateGroup\") {\n await config.store.updateGroup(requireId(formData, \"groupId\"), { name: requireText(formData, \"name\"), description: optionalText(formData, \"description\") })\n return { ok: true }\n }\n\n if (intent === \"deleteGroup\") {\n await config.store.deleteGroup(requireId(formData, \"groupId\"))\n return { ok: true }\n }\n\n if (intent === \"addGroupMember\") {\n await config.store.addGroupMember(requireId(formData, \"groupId\"), requireId(formData, \"userId\"))\n return { ok: true }\n }\n\n if (intent === \"removeGroupMember\") {\n await config.store.removeGroupMember(requireId(formData, \"groupId\"), requireId(formData, \"userId\"))\n return { ok: true }\n }\n\n if (intent === \"addAllowlistEntry\") {\n const type: \"email\" | \"domain\" = formData.get(\"type\") === \"domain\" ? \"domain\" : \"email\"\n await config.store.addAllowlistEntry({ pattern: requireText(formData, \"pattern\"), type, description: optionalText(formData, \"description\") })\n return { ok: true }\n }\n\n if (intent === \"removeAllowlistEntry\") {\n await config.store.removeAllowlistEntry(requireId(formData, \"entryId\"))\n return { ok: true }\n }\n\n // INTENT_ACCESS を通過した intent は上のいずれかで処理済み (到達しない安全網)\n return { ok: false, error: `Unknown intent: ${intent}` }\n }\n\n return { loader, action }\n}\n","import type React from \"react\"\nimport { Link, useLoaderData } from \"react-router\"\nimport { defaultUserAdminLabels, type UserAdminLabels } from \"./labels\"\n\nexport type UserAdminShellProps = {\n children?: React.ReactNode\n renderHeader?: (props: { title: string; annotation: React.ReactNode }) => React.ReactNode\n}\n\n/**\n * Shell layout for user administration pages with header annotation delegation.\n * ヘッダー注釈委譲を備えたユーザー管理ページのシェルレイアウト。\n */\nfunction GroupPill({ label, summary }: { label: string; summary: { text: string; color: string; bgColor: string; borderColor: string } }) {\n return (\n <span\n style={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: \"0.18rem\",\n fontSize: \"0.65rem\",\n padding: \"0.08rem 0.35rem\",\n borderRadius: \"9999px\",\n backgroundColor: summary.bgColor,\n color: summary.color,\n border: `1px solid ${summary.borderColor}`,\n fontWeight: 600,\n lineHeight: \"1.2\",\n }}>\n <span>{label}:</span>\n <span>{summary.text}</span>\n </span>\n )\n}\n\nexport function UserAdminShell({ children, renderHeader }: UserAdminShellProps) {\n const data = useLoaderData() as { segment?: string; labels?: UserAdminLabels; myPermissions?: Record<string, unknown> } | undefined\n const segment = data?.segment ?? \"users\"\n const labels = data?.labels ?? defaultUserAdminLabels\n const myPermissions = data?.myPermissions ?? {}\n\n const tabs = [\n { key: \"users\", label: labels.tabs.users, href: \"/users\" },\n { key: \"groups\", label: labels.tabs.groups, href: \"/users/groups\" },\n { key: \"group_members\", label: labels.tabs.groupMembers, href: \"/users/group_members\" },\n { key: \"allowlist\", label: labels.tabs.allowlist, href: \"/users/allowlist\" },\n ]\n\n const permEntries = Object.entries(myPermissions)\n const displayEntries = permEntries.length > 0 ? permEntries : [[\"user_admin\", { read: true, write: true, delete: true, allowedActions: [\"read\", \"create\", \"update\", \"delete\"] }]]\n\n const getSummary = (perm: unknown) => {\n const p = perm as { read?: boolean; write?: boolean; delete?: boolean; allowedActions?: string[] } | undefined\n const allowed = p?.allowedActions ?? []\n const canWrite = p?.write ?? (allowed.includes(\"create\") || allowed.includes(\"update\") || allowed.includes(\"delete\"))\n const canRead = p?.read ?? allowed.includes(\"read\")\n\n if (canWrite) {\n return { text: \"編集可\", color: \"#047857\", bgColor: \"#ecfdf5\", borderColor: \"#a7f3d0\" }\n }\n if (canRead) {\n return { text: \"閲覧のみ\", color: \"#1d4ed8\", bgColor: \"#eff6ff\", borderColor: \"#bfdbfe\" }\n }\n return { text: \"権限なし\", color: \"#64748b\", bgColor: \"#f1f5f9\", borderColor: \"#cbd5e1\" }\n }\n\n const annotation = (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: \"0.4rem\" }} data-testid=\"user-admin-header-annotation\">\n <span style={{ fontSize: \"0.75rem\", backgroundColor: \"#e0f2fe\", color: \"#0369a1\", padding: \"2px 8px\", borderRadius: \"12px\", fontWeight: 600 }}>{labels.heading}</span>\n\n <div style={{ display: \"inline-flex\", alignItems: \"center\", gap: \"0.25rem\", fontSize: \"0.68rem\", color: \"#475569\" }} data-testid=\"user-admin-permissions-badge\">\n <span style={{ fontWeight: 600, color: \"#334155\" }}>あなたの権限:</span>\n <span style={{ display: \"inline-flex\", alignItems: \"center\", gap: \"0.18rem\" }}>\n {displayEntries.map(([resKey, perm], idx) => (\n <GroupPill key={String(resKey || idx)} label={resKey === \"user_admin\" ? \"ユーザー管理UI\" : String(resKey)} summary={getSummary(perm)} />\n ))}\n </span>\n </div>\n </div>\n )\n\n return (\n <div data-testid=\"user-admin-shell\" style={{ width: \"100%\", minHeight: \"100vh\", backgroundColor: \"#f8fafc\", padding: \"1.5rem\", boxSizing: \"border-box\" }}>\n {renderHeader ? (\n renderHeader({ title: labels.title, annotation })\n ) : (\n <div style={{ marginBottom: \"1.5rem\", display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\" }}>\n <h1 style={{ margin: 0, fontSize: \"1.5rem\", fontWeight: 700, color: \"#0f172a\" }}>{labels.title}</h1>\n {annotation}\n </div>\n )}\n\n <div style={{ display: \"flex\", gap: \"0.5rem\", marginBottom: \"1.2rem\", borderBottom: \"2px solid #e2e8f0\", paddingBottom: \"0.5rem\" }}>\n {tabs.map((tab) => {\n const isActive = segment === tab.key\n return (\n <Link\n key={tab.key}\n to={tab.href}\n data-testid={`user-admin-tab-${tab.key}`}\n style={{\n padding: \"0.5rem 1rem\",\n borderRadius: \"6px 6px 0 0\",\n fontSize: \"0.88rem\",\n fontWeight: isActive ? 700 : 500,\n color: isActive ? \"#0284c7\" : \"#64748b\",\n backgroundColor: isActive ? \"#ffffff\" : \"transparent\",\n textDecoration: \"none\",\n borderBottom: isActive ? \"3px solid #0284c7\" : \"3px solid transparent\",\n transition: \"all 0.15s ease\",\n }}>\n {tab.label}\n </Link>\n )\n })}\n </div>\n\n <main>{children}</main>\n </div>\n )\n}\n","import type { AuthAllowlistEntry, AuthGroup, AuthGroupMember, AuthUser } from \"@aiquants/auth-core\"\nimport type React from \"react\"\nimport { useState } from \"react\"\nimport { useFetcher, useLoaderData } from \"react-router\"\nimport { defaultUserAdminLabels, type UserAdminLabels } from \"./labels\"\nimport { UserAdminShell, type UserAdminShellProps } from \"./ui\"\n\nexport type UserAdminLoaderData = {\n segment: string\n users: AuthUser[]\n groups: AuthGroup[]\n groupMembers: AuthGroupMember[]\n allowlist: AuthAllowlistEntry[]\n labels?: UserAdminLabels\n}\n\n/**\n * View for managing users (list, search, toggle active, modal edit).\n * ユーザー管理ビュー (一覧・検索・アクティブ切替・ダイアログ編集)。\n */\n/**\n * Renders the error returned by the last action submission, if any.\n * 直近の action 送信が返したエラーを表示する部品。\n *\n * ロックアウト理由・重複パターン・入力不正などの文言は action が `{ok:false,error}` で返す。\n * 表示しなければ操作が黙って失敗したように見えるため、各ビューの先頭に必ず置く。\n */\nfunction ActionError({ fetcher }: { fetcher: { data?: unknown } }) {\n const result = fetcher.data as { ok?: boolean; error?: string } | undefined\n if (result?.ok !== false || !result.error) return null\n return (\n <div data-testid=\"user-admin-action-error\" role=\"alert\" style={{ padding: \"0.7rem 1rem\", borderRadius: \"6px\", border: \"1px solid #fecaca\", backgroundColor: \"#fef2f2\", color: \"#b91c1c\", fontSize: \"0.85rem\" }}>\n {result.error}\n </div>\n )\n}\n\nexport function UsersView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [searchQuery, setSearchQuery] = useState(\"\")\n const [editingUser, setEditingUser] = useState<AuthUser | null>(null)\n const [isCreateOpen, setIsCreateOpen] = useState(false)\n\n const [newDisplayName, setNewDisplayName] = useState(\"\")\n const [newEmail, setNewEmail] = useState(\"\")\n\n const filteredUsers = data.users.filter((u) => u.displayName.toLowerCase().includes(searchQuery.toLowerCase()) || u.email.toLowerCase().includes(searchQuery.toLowerCase()))\n\n const handleToggleActive = (user: AuthUser) => {\n fetcher.submit({ intent: \"toggleUserActive\", userId: String(user.id), isActive: String(!user.isActive) }, { method: \"post\" })\n }\n\n const handleCreateUser = (e: React.FormEvent) => {\n e.preventDefault()\n if (!(newDisplayName && newEmail)) return\n fetcher.submit({ intent: \"createUser\", displayName: newDisplayName, email: newEmail }, { method: \"post\" })\n setNewDisplayName(\"\")\n setNewEmail(\"\")\n setIsCreateOpen(false)\n }\n\n const handleUpdateUser = (e: React.FormEvent) => {\n e.preventDefault()\n if (!editingUser) return\n fetcher.submit({ intent: \"updateUser\", userId: String(editingUser.id), displayName: editingUser.displayName, email: editingUser.email }, { method: \"post\" })\n setEditingUser(null)\n }\n\n return (\n <section data-testid=\"users-view-section\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1rem\" }}>\n <ActionError fetcher={fetcher} />\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", backgroundColor: \"#ffffff\", padding: \"1rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n <input\n type=\"text\"\n placeholder={labels.usersView.searchPlaceholder}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n data-testid=\"users-search-input\"\n style={{ padding: \"6px 12px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", width: \"260px\" }}\n />\n <button type=\"button\" onClick={() => setIsCreateOpen(true)} data-testid=\"users-add-button\" style={{ padding: \"6px 14px\", borderRadius: \"6px\", backgroundColor: \"#0284c7\", color: \"#ffffff\", border: \"none\", fontSize: \"0.85rem\", fontWeight: 600, cursor: \"pointer\" }}>\n {labels.usersView.addUser}\n </button>\n </div>\n\n <div style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", overflow: \"hidden\" }}>\n <table style={{ width: \"100%\", borderCollapse: \"collapse\", fontSize: \"0.85rem\", textAlign: \"left\" }}>\n <thead>\n <tr style={{ backgroundColor: \"#f8fafc\", borderBottom: \"1px solid #e2e8f0\", color: \"#475569\" }}>\n <th style={{ padding: \"10px 14px\" }}>{labels.usersView.id}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.usersView.displayName}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.usersView.email}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.usersView.status}</th>\n <th style={{ padding: \"10px 14px\", textAlign: \"right\" }}>{labels.usersView.actions}</th>\n </tr>\n </thead>\n <tbody>\n {filteredUsers.map((u) => (\n <tr key={u.id} style={{ borderBottom: \"1px solid #f1f5f9\" }} data-testid={`user-row-${u.id}`}>\n <td style={{ padding: \"10px 14px\", color: \"#64748b\" }}>#{u.id}</td>\n <td style={{ padding: \"10px 14px\", fontWeight: 600, color: \"#0f172a\" }}>{u.displayName}</td>\n <td style={{ padding: \"10px 14px\", color: \"#475569\" }}>{u.email}</td>\n <td style={{ padding: \"10px 14px\" }}>\n <span style={{ fontSize: \"0.75rem\", padding: \"2px 8px\", borderRadius: \"10px\", backgroundColor: u.isActive ? \"#dcfce7\" : \"#fee2e2\", color: u.isActive ? \"#15803d\" : \"#b91c1c\", fontWeight: 600 }}>\n {u.isActive ? labels.usersView.active : labels.usersView.inactive}\n </span>\n </td>\n <td style={{ padding: \"10px 14px\", textAlign: \"right\" }}>\n <button\n type=\"button\"\n onClick={() => setEditingUser(u)}\n data-testid={`edit-user-btn-${u.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", fontSize: \"0.78rem\", cursor: \"pointer\", marginRight: \"6px\" }}>\n {labels.usersView.edit}\n </button>\n <button\n type=\"button\"\n onClick={() => handleToggleActive(u)}\n data-testid={`toggle-user-btn-${u.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", fontSize: \"0.78rem\", cursor: \"pointer\", color: u.isActive ? \"#b91c1c\" : \"#15803d\" }}>\n {u.isActive ? labels.usersView.deactivate : labels.usersView.activate}\n </button>\n </td>\n </tr>\n ))}\n {filteredUsers.length === 0 && (\n <tr>\n <td colSpan={5} style={{ padding: \"2rem\", textAlign: \"center\", color: \"#94a3b8\" }}>\n {labels.usersView.empty}\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n\n {/* Create Modal */}\n {isCreateOpen && (\n <div style={{ position: \"fixed\", inset: 0, backgroundColor: \"rgba(0,0,0,0.4)\", display: \"flex\", justifyContent: \"center\", alignItems: \"center\", zIndex: 100 }} data-testid=\"user-create-modal\">\n <form onSubmit={handleCreateUser} style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>{labels.usersView.addUser}</h3>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"create-user-display-name-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.usersView.displayName}\n </label>\n <input\n id=\"create-user-display-name-input\"\n type=\"text\"\n value={newDisplayName}\n onChange={(e) => setNewDisplayName(e.target.value)}\n required\n data-testid=\"create-user-display-name\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1.2rem\" }}>\n <label htmlFor=\"create-user-email-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.usersView.email}\n </label>\n <input\n id=\"create-user-email-input\"\n type=\"email\"\n value={newEmail}\n onChange={(e) => setNewEmail(e.target.value)}\n required\n data-testid=\"create-user-email\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: \"0.5rem\" }}>\n <button type=\"button\" onClick={() => setIsCreateOpen(false)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n キャンセル\n </button>\n <button type=\"submit\" data-testid=\"submit-create-user\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#0284c7\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n 作成\n </button>\n </div>\n </form>\n </div>\n )}\n\n {/* Edit Modal */}\n {editingUser && (\n <div style={{ position: \"fixed\", inset: 0, backgroundColor: \"rgba(0,0,0,0.4)\", display: \"flex\", justifyContent: \"center\", alignItems: \"center\", zIndex: 100 }} data-testid=\"user-edit-modal\">\n <form onSubmit={handleUpdateUser} style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>{labels.usersView.editUser}</h3>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"edit-user-display-name-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.usersView.displayName}\n </label>\n <input\n id=\"edit-user-display-name-input\"\n type=\"text\"\n value={editingUser.displayName}\n onChange={(e) => setEditingUser({ ...editingUser, displayName: e.target.value })}\n required\n data-testid=\"edit-user-display-name\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1.2rem\" }}>\n <label htmlFor=\"edit-user-email-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.usersView.email}\n </label>\n <input\n id=\"edit-user-email-input\"\n type=\"email\"\n value={editingUser.email}\n onChange={(e) => setEditingUser({ ...editingUser, email: e.target.value })}\n required\n data-testid=\"edit-user-email\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: \"0.5rem\" }}>\n <button type=\"button\" onClick={() => setEditingUser(null)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n {labels.usersView.cancel}\n </button>\n <button type=\"submit\" data-testid=\"submit-edit-user\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#0284c7\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n {labels.usersView.save}\n </button>\n </div>\n </form>\n </div>\n )}\n </section>\n )\n}\n\n/**\n * View for managing user groups (create, edit, delete).\n * ユーザーグループ管理ビュー (作成・編集・削除)。\n */\nexport function GroupsView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [searchQuery, setSearchQuery] = useState(\"\")\n const [isCreateOpen, setIsCreateOpen] = useState(false)\n const [editingGroup, setEditingGroup] = useState<AuthGroup | null>(null)\n\n const [newGroupKey, setNewGroupKey] = useState(\"\")\n const [newName, setNewName] = useState(\"\")\n const [newDescription, setNewDescription] = useState(\"\")\n\n const filteredGroups = data.groups.filter((g) => g.name.toLowerCase().includes(searchQuery.toLowerCase()) || g.groupKey.toLowerCase().includes(searchQuery.toLowerCase()))\n\n const handleCreateGroup = (e: React.FormEvent) => {\n e.preventDefault()\n if (!(newGroupKey && newName)) return\n fetcher.submit({ intent: \"createGroup\", groupKey: newGroupKey, name: newName, description: newDescription }, { method: \"post\" })\n setNewGroupKey(\"\")\n setNewName(\"\")\n setNewDescription(\"\")\n setIsCreateOpen(false)\n }\n\n const handleUpdateGroup = (e: React.FormEvent) => {\n e.preventDefault()\n if (!editingGroup) return\n fetcher.submit({ intent: \"updateGroup\", groupId: String(editingGroup.id), name: editingGroup.name, description: editingGroup.description ?? \"\" }, { method: \"post\" })\n setEditingGroup(null)\n }\n\n const handleDeleteGroup = (groupId: number) => {\n if (!window.confirm(labels.groupsView.confirmDelete)) return\n fetcher.submit({ intent: \"deleteGroup\", groupId: String(groupId) }, { method: \"post\" })\n }\n\n return (\n <section data-testid=\"groups-view-section\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1rem\" }}>\n <ActionError fetcher={fetcher} />\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", backgroundColor: \"#ffffff\", padding: \"1rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n <input\n type=\"text\"\n placeholder={labels.groupsView.searchPlaceholder}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n data-testid=\"groups-search-input\"\n style={{ padding: \"6px 12px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", width: \"260px\" }}\n />\n <button type=\"button\" onClick={() => setIsCreateOpen(true)} data-testid=\"groups-add-button\" style={{ padding: \"6px 14px\", borderRadius: \"6px\", backgroundColor: \"#4338ca\", color: \"#ffffff\", border: \"none\", fontSize: \"0.85rem\", fontWeight: 600, cursor: \"pointer\" }}>\n {labels.groupsView.addGroup}\n </button>\n </div>\n\n <div style={{ display: \"grid\", gridTemplateColumns: \"repeat(auto-fill, minmax(280px, 1fr))\", gap: \"1rem\" }}>\n {filteredGroups.map((g) => {\n const memberCount = data.groupMembers.filter((m) => m.groupId === g.id).length\n return (\n <div key={g.id} style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", padding: \"1rem\", display: \"flex\", flexDirection: \"column\", justifyContent: \"space-between\" }} data-testid={`group-card-${g.id}`}>\n <div>\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", marginBottom: \"0.5rem\" }}>\n <h4 style={{ margin: 0, fontSize: \"1rem\", color: \"#0f172a\" }}>{g.name}</h4>\n <span style={{ fontSize: \"0.72rem\", backgroundColor: \"#e0e7ff\", color: \"#4338ca\", padding: \"2px 6px\", borderRadius: \"4px\", fontFamily: \"monospace\" }}>{g.groupKey}</span>\n </div>\n <p style={{ margin: \"0 0 0.8rem 0\", fontSize: \"0.82rem\", color: \"#64748b\", minHeight: \"2.4em\" }}>{g.description || labels.groupsView.noDescription}</p>\n </div>\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", borderTop: \"1px solid #f1f5f9\", paddingTop: \"0.6rem\" }}>\n <span style={{ fontSize: \"0.78rem\", color: \"#475569\" }}>\n 👥 メンバー: <strong>{memberCount}</strong> 人\n </span>\n <div>\n <button\n type=\"button\"\n onClick={() => setEditingGroup(g)}\n data-testid={`edit-group-btn-${g.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", fontSize: \"0.75rem\", cursor: \"pointer\", marginRight: \"4px\" }}>\n {labels.usersView.edit}\n </button>\n <button\n type=\"button\"\n onClick={() => handleDeleteGroup(g.id)}\n data-testid={`delete-group-btn-${g.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #fee2e2\", backgroundColor: \"#fff5f5\", color: \"#b91c1c\", fontSize: \"0.75rem\", cursor: \"pointer\" }}>\n 削除\n </button>\n </div>\n </div>\n </div>\n )\n })}\n </div>\n\n {/* Create Group Modal */}\n {isCreateOpen && (\n <div style={{ position: \"fixed\", inset: 0, backgroundColor: \"rgba(0,0,0,0.4)\", display: \"flex\", justifyContent: \"center\", alignItems: \"center\", zIndex: 100 }} data-testid=\"group-create-modal\">\n <form onSubmit={handleCreateGroup} style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>{labels.groupsView.addGroup}</h3>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"create-group-key-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.groupKey}\n </label>\n <input\n id=\"create-group-key-input\"\n type=\"text\"\n value={newGroupKey}\n onChange={(e) => setNewGroupKey(e.target.value)}\n placeholder={labels.groupsView.groupKeyPlaceholder}\n required\n data-testid=\"create-group-key\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"create-group-name-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.name}\n </label>\n <input\n id=\"create-group-name-input\"\n type=\"text\"\n value={newName}\n onChange={(e) => setNewName(e.target.value)}\n placeholder={labels.groupsView.namePlaceholder}\n required\n data-testid=\"create-group-name\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1.2rem\" }}>\n <label htmlFor=\"create-group-desc-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.description}\n </label>\n <textarea\n id=\"create-group-desc-input\"\n value={newDescription}\n onChange={(e) => setNewDescription(e.target.value)}\n data-testid=\"create-group-description\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\", height: \"60px\" }}\n />\n </div>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: \"0.5rem\" }}>\n <button type=\"button\" onClick={() => setIsCreateOpen(false)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n キャンセル\n </button>\n <button type=\"submit\" data-testid=\"submit-create-group\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#4338ca\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n 作成\n </button>\n </div>\n </form>\n </div>\n )}\n\n {/* Edit Group Modal */}\n {editingGroup && (\n <div style={{ position: \"fixed\", inset: 0, backgroundColor: \"rgba(0,0,0,0.4)\", display: \"flex\", justifyContent: \"center\", alignItems: \"center\", zIndex: 100 }} data-testid=\"group-edit-modal\">\n <form onSubmit={handleUpdateGroup} style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>{labels.groupsView.editGroup}</h3>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"edit-group-name-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.name}\n </label>\n <input\n id=\"edit-group-name-input\"\n type=\"text\"\n value={editingGroup.name}\n onChange={(e) => setEditingGroup({ ...editingGroup, name: e.target.value })}\n required\n data-testid=\"edit-group-name\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1.2rem\" }}>\n <label htmlFor=\"edit-group-desc-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.description}\n </label>\n <textarea\n id=\"edit-group-desc-input\"\n value={editingGroup.description ?? \"\"}\n onChange={(e) => setEditingGroup({ ...editingGroup, description: e.target.value })}\n data-testid=\"edit-group-description\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\", height: \"60px\" }}\n />\n </div>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: \"0.5rem\" }}>\n <button type=\"button\" onClick={() => setEditingGroup(null)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n {labels.groupsView.cancel}\n </button>\n <button type=\"submit\" data-testid=\"submit-edit-group\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#4338ca\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n {labels.groupsView.save}\n </button>\n </div>\n </form>\n </div>\n )}\n </section>\n )\n}\n\n/**\n * View for managing group member assignments.\n * グループメンバー割り当て管理ビュー。\n */\nexport function GroupMembersView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [selectedGroupId, setSelectedGroupId] = useState<number>(data.groups[0]?.id ?? 0)\n\n const currentGroup = data.groups.find((g) => g.id === selectedGroupId)\n const currentMemberUserIds = new Set(data.groupMembers.filter((m) => m.groupId === selectedGroupId).map((m) => m.userId))\n\n const handleToggleMember = (userId: number, isMember: boolean) => {\n if (isMember) {\n fetcher.submit({ intent: \"removeGroupMember\", groupId: String(selectedGroupId), userId: String(userId) }, { method: \"post\" })\n } else {\n fetcher.submit({ intent: \"addGroupMember\", groupId: String(selectedGroupId), userId: String(userId) }, { method: \"post\" })\n }\n }\n\n return (\n <section data-testid=\"group-members-view-section\" style={{ display: \"grid\", gridTemplateColumns: \"240px 1fr\", gap: \"1.2rem\" }}>\n <ActionError fetcher={fetcher} />\n <div style={{ backgroundColor: \"#ffffff\", padding: \"1rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n <h4 style={{ margin: \"0 0 0.8rem 0\", fontSize: \"0.9rem\", color: \"#475569\" }}>{labels.groupMembersView.selectGroup}</h4>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.4rem\" }}>\n {data.groups.map((g) => {\n const isSelected = g.id === selectedGroupId\n return (\n <button\n type=\"button\"\n key={g.id}\n onClick={() => setSelectedGroupId(g.id)}\n data-testid={`select-group-btn-${g.id}`}\n style={{\n textAlign: \"left\",\n padding: \"8px 10px\",\n borderRadius: \"6px\",\n border: isSelected ? \"1px solid #6366f1\" : \"1px solid #e2e8f0\",\n backgroundColor: isSelected ? \"#e0e7ff\" : \"#ffffff\",\n color: isSelected ? \"#3730a3\" : \"#0f172a\",\n cursor: \"pointer\",\n fontWeight: isSelected ? 600 : 400,\n fontSize: \"0.85rem\",\n }}>\n {g.name}\n </button>\n )\n })}\n </div>\n </div>\n\n <div style={{ backgroundColor: \"#ffffff\", padding: \"1.2rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n {currentGroup ? (\n <>\n <div style={{ marginBottom: \"1rem\", borderBottom: \"1px solid #e2e8f0\", paddingBottom: \"0.8rem\" }}>\n <h3 style={{ margin: 0, fontSize: \"1.1rem\", color: \"#0f172a\" }}>{currentGroup.name} の所属メンバー</h3>\n <span style={{ fontSize: \"0.8rem\", color: \"#64748b\" }}>グループキー: {currentGroup.groupKey}</span>\n </div>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.5rem\" }}>\n {data.users.map((u) => {\n const isMember = currentMemberUserIds.has(u.id)\n return (\n <div\n key={u.id}\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n padding: \"8px 12px\",\n borderRadius: \"6px\",\n border: \"1px solid #f1f5f9\",\n backgroundColor: isMember ? \"#f0fdf4\" : \"#ffffff\",\n }}\n data-testid={`member-row-${u.id}`}>\n <div>\n <span style={{ fontWeight: 600, fontSize: \"0.88rem\", color: \"#0f172a\", marginRight: \"8px\" }}>{u.displayName}</span>\n <span style={{ fontSize: \"0.78rem\", color: \"#64748b\" }}>({u.email})</span>\n </div>\n <button\n type=\"button\"\n onClick={() => handleToggleMember(u.id, isMember)}\n data-testid={`toggle-member-btn-${u.id}`}\n style={{\n padding: \"4px 12px\",\n borderRadius: \"4px\",\n border: isMember ? \"1px solid #b91c1c\" : \"1px solid #0284c7\",\n backgroundColor: isMember ? \"#ffffff\" : \"#0284c7\",\n color: isMember ? \"#b91c1c\" : \"#ffffff\",\n fontSize: \"0.78rem\",\n fontWeight: 600,\n cursor: \"pointer\",\n }}>\n {isMember ? labels.groupMembersView.actionRemove : labels.groupMembersView.actionAdd}\n </button>\n </div>\n )\n })}\n </div>\n </>\n ) : (\n <div style={{ color: \"#94a3b8\", textAlign: \"center\", padding: \"2rem\" }}>{labels.groupMembersView.selectPrompt}</div>\n )}\n </div>\n </section>\n )\n}\n\n/**\n * View for managing authentication allowlist entries.\n * アクセス許可リスト (Allowlist) 管理ビュー。\n */\nexport function AllowlistView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [pattern, setPattern] = useState(\"\")\n const [type, setType] = useState<\"email\" | \"domain\">(\"email\")\n const [description, setDescription] = useState(\"\")\n\n const handleAddEntry = (e: React.FormEvent) => {\n e.preventDefault()\n if (!pattern) return\n fetcher.submit({ intent: \"addAllowlistEntry\", pattern, type, description }, { method: \"post\" })\n setPattern(\"\")\n setDescription(\"\")\n }\n\n const handleRemoveEntry = (id: number) => {\n fetcher.submit({ intent: \"removeAllowlistEntry\", entryId: String(id) }, { method: \"post\" })\n }\n\n return (\n <section data-testid=\"allowlist-view-section\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1.2rem\" }}>\n <ActionError fetcher={fetcher} />\n <form onSubmit={handleAddEntry} style={{ backgroundColor: \"#ffffff\", padding: \"1.2rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", display: \"flex\", flexWrap: \"wrap\", gap: \"0.8rem\", alignItems: \"flex-end\" }}>\n <div style={{ flex: 2, minWidth: \"220px\" }}>\n <label htmlFor=\"allowlist-pattern-input-field\" style={{ display: \"block\", fontSize: \"0.78rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.allowlistView.pattern}\n </label>\n <input\n id=\"allowlist-pattern-input-field\"\n type=\"text\"\n value={pattern}\n onChange={(e) => setPattern(e.target.value)}\n required\n data-testid=\"allowlist-pattern-input\"\n style={{ width: \"100%\", padding: \"6px 10px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ flex: 1, minWidth: \"140px\" }}>\n <label htmlFor=\"allowlist-type-select-field\" style={{ display: \"block\", fontSize: \"0.78rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.allowlistView.type}\n </label>\n <select\n id=\"allowlist-type-select-field\"\n value={type}\n onChange={(e) => setType(e.target.value as \"email\" | \"domain\")}\n data-testid=\"allowlist-type-select\"\n style={{ width: \"100%\", padding: \"6px 10px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", boxSizing: \"border-box\" }}>\n <option value=\"email\">{labels.allowlistView.emailType}</option>\n <option value=\"domain\">{labels.allowlistView.domainType}</option>\n </select>\n </div>\n <div style={{ flex: 2, minWidth: \"200px\" }}>\n <label htmlFor=\"allowlist-desc-input-field\" style={{ display: \"block\", fontSize: \"0.78rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.allowlistView.description}\n </label>\n <input\n id=\"allowlist-desc-input-field\"\n type=\"text\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n data-testid=\"allowlist-desc-input\"\n style={{ width: \"100%\", padding: \"6px 10px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", boxSizing: \"border-box\" }}\n />\n </div>\n <button type=\"submit\" data-testid=\"submit-allowlist-add\" style={{ padding: \"7px 16px\", borderRadius: \"6px\", backgroundColor: \"#047857\", color: \"#ffffff\", border: \"none\", fontSize: \"0.85rem\", fontWeight: 600, cursor: \"pointer\" }}>\n {labels.allowlistView.addEntry}\n </button>\n </form>\n\n <div style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", overflow: \"hidden\" }}>\n <table style={{ width: \"100%\", borderCollapse: \"collapse\", fontSize: \"0.85rem\", textAlign: \"left\" }}>\n <thead>\n <tr style={{ backgroundColor: \"#f8fafc\", borderBottom: \"1px solid #e2e8f0\", color: \"#475569\" }}>\n <th style={{ padding: \"10px 14px\" }}>ID</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.allowlistView.pattern}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.allowlistView.type}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.allowlistView.description}</th>\n <th style={{ padding: \"10px 14px\", textAlign: \"right\" }}>{labels.allowlistView.actions}</th>\n </tr>\n </thead>\n <tbody>\n {data.allowlist.map((item) => (\n <tr key={item.id} style={{ borderBottom: \"1px solid #f1f5f9\" }} data-testid={`allowlist-row-${item.id}`}>\n <td style={{ padding: \"10px 14px\", color: \"#64748b\" }}>#{item.id}</td>\n <td style={{ padding: \"10px 14px\", fontWeight: 600, color: \"#0f172a\", fontFamily: \"monospace\" }}>{item.pattern}</td>\n <td style={{ padding: \"10px 14px\" }}>\n <span style={{ fontSize: \"0.75rem\", padding: \"2px 8px\", borderRadius: \"10px\", backgroundColor: item.type === \"email\" ? \"#e0f2fe\" : \"#fef3c7\", color: item.type === \"email\" ? \"#0369a1\" : \"#b45309\", fontWeight: 600 }}>\n {item.type === \"email\" ? labels.allowlistView.emailTypeShort : labels.allowlistView.domainTypeShort}\n </span>\n </td>\n <td style={{ padding: \"10px 14px\", color: \"#475569\" }}>{item.description || \"-\"}</td>\n <td style={{ padding: \"10px 14px\", textAlign: \"right\" }}>\n {/* 変更不能な供給元 (環境変数など) は削除ボタンを出さない */}\n {item.readOnly ? (\n <span data-testid={`readonly-allowlist-${item.id}`} style={{ fontSize: \"0.75rem\", color: \"#94a3b8\" }}>\n {labels.allowlistView.readOnly}\n </span>\n ) : (\n <button\n type=\"button\"\n onClick={() => handleRemoveEntry(item.id)}\n data-testid={`remove-allowlist-btn-${item.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #fee2e2\", backgroundColor: \"#fff5f5\", color: \"#b91c1c\", fontSize: \"0.78rem\", cursor: \"pointer\" }}>\n 削除\n </button>\n )}\n </td>\n </tr>\n ))}\n {data.allowlist.length === 0 && (\n <tr>\n <td colSpan={5} style={{ padding: \"2rem\", textAlign: \"center\", color: \"#94a3b8\" }}>\n {labels.allowlistView.empty}\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n </section>\n )\n}\n\nconst SEGMENT_VIEWS: Record<string, () => React.ReactElement> = {\n users: UsersView,\n groups: GroupsView,\n group_members: GroupMembersView,\n allowlist: AllowlistView,\n}\n\nexport type AuthUserAdminAppViewProps = UserAdminShellProps\n\n/**\n * Top-level application view component for the user administration UI (`/users/*`).\n * ユーザー管理 UI 全体を描画するトップレベルコンポーネント。\n */\nexport function AuthUserAdminAppView({ renderHeader }: AuthUserAdminAppViewProps = {}) {\n const data = useLoaderData() as UserAdminLoaderData | undefined\n const segment = data?.segment ?? \"users\"\n const View = SEGMENT_VIEWS[segment] ?? UsersView\n\n return (\n <UserAdminShell renderHeader={renderHeader}>\n <View />\n </UserAdminShell>\n )\n}\n"],"mappings":"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","names":["defaultUserAdminLabels","jaUserAdminLabels","resolveUserAdminLabels","over","d","createInMemoryUserAdminStore","initial","users","groups","groupMembers","allowlist","data","newUser","u","id","idx","isActive","newGroup","g","m","groupId","userId","existing","newMember","newEntry","a","INTENT_ACCESS","requireId","formData","field","raw","n","requireText","value","optionalText","createUserAdminApp","config","labels","resolveUserAdminLabels","loader","request","pathSegments","segment","myPermissions","action","intent","access","dispatch","error","rawActive","type","Link","useLoaderData","jsx","jsxs","GroupPill","label","summary","UserAdminShell","children","renderHeader","data","useLoaderData","segment","labels","defaultUserAdminLabels","myPermissions","tabs","permEntries","displayEntries","getSummary","perm","p","allowed","canWrite","canRead","annotation","resKey","idx","tab","isActive","Link","useState","useFetcher","useLoaderData","Fragment","jsx","jsxs","ActionError","fetcher","result","UsersView","data","useLoaderData","useFetcher","labels","defaultUserAdminLabels","searchQuery","setSearchQuery","useState","editingUser","setEditingUser","isCreateOpen","setIsCreateOpen","newDisplayName","setNewDisplayName","newEmail","setNewEmail","filteredUsers","u","handleToggleActive","user","handleCreateUser","e","handleUpdateUser","GroupsView","editingGroup","setEditingGroup","newGroupKey","setNewGroupKey","newName","setNewName","newDescription","setNewDescription","filteredGroups","g","handleCreateGroup","handleUpdateGroup","handleDeleteGroup","groupId","memberCount","m","GroupMembersView","selectedGroupId","setSelectedGroupId","currentGroup","currentMemberUserIds","handleToggleMember","userId","isMember","isSelected","AllowlistView","pattern","setPattern","type","setType","description","setDescription","handleAddEntry","handleRemoveEntry","id","item","SEGMENT_VIEWS","AuthUserAdminAppView","renderHeader","segment","View","UserAdminShell"]}