@aiquants/auth-react-router 0.13.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/admin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/admin.ts","../src/admin/labels.ts","../src/admin/server.ts","../src/admin/ui.tsx","../src/admin/format.ts","../src/admin/views.tsx"],"sourcesContent":["/**\n * @aiquants/auth-react-router/admin — user administration UI and store adapters.\n * ユーザー管理 UI およびストアアダプタのバレル。\n */\nexport * from \"./admin/app\"\n","/**\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 * BCP 47 locale used to format dates. 日付整形に使う BCP 47 ロケール。\n *\n * 実行環境任せにするとサーバーとブラウザで別の文字列になり、水和が壊れる。\n */\n locale: string\n /** IANA time zone used to format dates. 日付整形に使う IANA タイムゾーン。 */\n timeZone: string\n /**\n * Strings shared by more than one view. 複数のビューが共有する文言。\n *\n * ビューごとに同じ語を持たせると、ある画面の「削除」だけを言い換えることが **できなく\n * なる** (実装がどのビューのキーを読んでいるかは呼び出し側から見えない)。共有する語は\n * ここ 1 か所に置く。\n */\n common: {\n create: string\n edit: string\n delete: string\n save: string\n cancel: string\n actions: string\n status: string\n id: string\n /** 変更不能な供給元 (環境変数など) 由来の行に出す表示。 */\n readOnly: string\n /** 上流所有のグループに出す編集不可の表示。 */\n managedUpstream: string\n /** `#{id}` の表示。記号の位置は言語で変わる。 */\n idTemplate: string\n /** 値が無いことの表示 (説明欄など)。 */\n none: string\n }\n /** アクションが拒否・失敗したときの文言。 */\n errors: {\n /**\n * Shown when the actor lacks permission for the attempted operation.\n * 操作の権限が無いときに出す文言。\n *\n * ガードの例外メッセージをそのまま返してはならない。テナント ID・アプリキー・\n * 拒否理由といった内部の語彙が、権限を持たない相手にそのまま渡る。\n */\n forbidden: string\n }\n /** シェル (タブ枠) の見出し。 */\n heading: string\n /** シェルが描く自己権限バッジの文言。 */\n permissions: {\n label: string\n canWrite: string\n canRead: string\n none: string\n /** 未結線・取得失敗・付与ゼロのいずれも「不明」であり、許可ではない。 */\n unknown: string\n }\n tabs: {\n users: string\n groups: string\n groupMembers: string\n allowlist: string\n directory: string\n }\n usersView: {\n title: string\n searchPlaceholder: string\n /** 検索欄の読み上げ名。プレースホルダは入力すると消えるため名前にならない。 */\n searchLabel: string\n addUser: string\n editUser: string\n displayName: string\n email: string\n active: string\n inactive: string\n activate: string\n deactivate: string\n /** 行ごとの読み上げ名。`{user}` を利用者名へ置換する。 */\n editUserAria: string\n activateAria: string\n deactivateAria: string\n /** 無効化はサインインを止める。確認を取る。 */\n confirmDeactivate: string\n empty: string\n }\n groupsView: {\n /** 検索語に一致しないときの説明。空状態と混ぜると「消えた」と誤読される。 */\n emptyFiltered: string\n title: string\n searchPlaceholder: string\n searchLabel: string\n addGroup: string\n editGroup: string\n groupKey: string\n name: string\n description: string\n /** 所属人数。`{count}` を人数へ置換する。 */\n memberCountTemplate: string\n confirmDelete: string\n /** 行ごとの読み上げ名。`{group}` をグループ名へ置換する。 */\n editGroupAria: string\n deleteGroupAria: string\n noDescription: string\n groupKeyPlaceholder: string\n namePlaceholder: string\n empty: string\n }\n groupMembersView: {\n title: string\n selectGroup: string\n selectPrompt: string\n actionAdd: string\n actionRemove: string\n /** 行ごとの読み上げ名。`{user}` を利用者名へ置換する。 */\n actionAddAria: string\n actionRemoveAria: string\n /** 上流所有グループで編集を出さない理由。押せない理由を書かないと故障に見える。 */\n upstreamNotice: string\n /** 見出し。`{group}` をグループ名へ置換する。語順が言語で変わるため位置指定にする。 */\n membersOfTemplate: string\n groupKeyLabel: 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 /** グループ単位の許可。上流ディレクトリの所属で判定される。 */\n groupTypeShort: string\n /** 対象列の見出し。パターンとグループが混在するため「パターン」ではない。 */\n target: string\n description: string\n /** 配備全体が所有する行 (環境変数由来) の所有者表示。 */\n deploymentOwner: string\n /** 許可の取り消しはサインインを止める。確認を取る。 */\n confirmDelete: string\n /** 行ごとの読み上げ名。`{target}` を対象へ置換する。 */\n deleteAria: string\n empty: string\n }\n directoryView: {\n title: string\n membershipMode: string\n directMode: string\n transitiveMode: string\n lastSynced: string\n neverSynced: string\n /** 上流に居るがローカルユーザーへ解決できていない人数。`{count}` を人数へ置換する。 */\n unresolvedTemplate: string\n syncPaused: string\n syncEnabled: string\n syncNow: string\n syncQueued: string\n /** 同期が滞っていることの警告。停止はセキュリティ事象である。 */\n staleWarning: string\n disabledWarning: string\n /** 停止件数。`{paused}` / `{total}` を件数へ置換する。 */\n pausedTemplate: string\n /** 失敗したグループの列挙。`{groups}` を名前の並びへ置換する。 */\n erroredTemplate: string\n /** 失敗理由付きの 1 件。`{group}` / `{reason}`。 */\n erroredItemTemplate: string\n /** 列挙の区切り。言語で変わる。 */\n listSeparator: string\n /** 滞留警告に添える最終成功時刻。`{at}`。 */\n oldestSyncedTemplate: string\n /** 未処理の同期要求。`{count}`。 */\n pendingTemplate: string\n /** 連携先が 1 つも残っていないときの説明。 */\n nothingToLink: string\n /** 未結線の配備で操作を出さない理由。 */\n notWiredNotice: string\n /** 行ごとの読み上げ名。`{group}` をグループ名へ置換する。 */\n syncNowAria: string\n syncQueuedAria: string\n pauseAria: string\n resumeAria: string\n allowGroupAria: string\n disallowGroupAria: string\n unlinkAria: string\n /** グループ単位のサインイン許可を外す確認。 */\n confirmDisallow: string\n link: string\n unlink: string\n externalKey: string\n empty: string\n /** 連携するグループを選ぶ欄。 */\n selectGroup: string\n /** 上流グループのアドレス入力。 */\n externalKeyPlaceholder: string\n /** 連携解除の確認。台帳と許可設定が消える。 */\n confirmUnlink: string\n /** 同期の一時停止・再開。 */\n pause: string\n resume: string\n /** 許可グループに加える / 外す。 */\n allowGroup: string\n disallowGroup: string\n /** グループ名と鍵の併記。`{name}` / `{key}`。 */\n groupOptionTemplate: string\n }\n}\n\n/**\n * Package default labels (English).\n * パッケージ既定ラベル (英語)。\n */\nexport const defaultUserAdminLabels: UserAdminLabels = {\n title: \"Users & Groups\",\n locale: \"en-US\",\n timeZone: \"UTC\",\n heading: \"\\u{1F464} User administration\",\n common: {\n create: \"Create\",\n edit: \"Edit\",\n delete: \"Delete\",\n save: \"Save\",\n cancel: \"Cancel\",\n actions: \"Actions\",\n status: \"Status\",\n id: \"ID\",\n readOnly: \"Read-only\",\n managedUpstream: \"Managed upstream\",\n idTemplate: \"#{id}\",\n none: \"-\",\n },\n errors: {\n forbidden: \"You do not have permission for this operation.\",\n },\n permissions: {\n label: \"Your permissions:\",\n canWrite: \"Editable\",\n canRead: \"Read only\",\n none: \"No access\",\n unknown: \"Unknown\",\n },\n tabs: {\n users: \"\\u{1F464} Users\",\n groups: \"\\u{1F465} Groups\",\n groupMembers: \"\\u{1F517} Members\",\n allowlist: \"\\u2709\\uFE0F Allowlist\",\n directory: \"\\u{1F504} Directory\",\n },\n usersView: {\n title: \"Users\",\n searchPlaceholder: \"Search by name or email...\",\n searchLabel: \"Search users\",\n addUser: \"\\uFF0B Add user\",\n editUser: \"Edit user\",\n displayName: \"Name\",\n email: \"Email\",\n active: \"Active\",\n inactive: \"Inactive\",\n activate: \"Activate\",\n deactivate: \"Deactivate\",\n editUserAria: \"Edit {user}\",\n activateAria: \"Activate {user}\",\n deactivateAria: \"Deactivate {user}\",\n confirmDeactivate: \"Deactivate this user? They will no longer be able to sign in.\",\n empty: \"No matching users\",\n },\n groupsView: {\n emptyFiltered: \"No group matches this search\",\n title: \"Groups\",\n searchPlaceholder: \"Search by group name or key...\",\n searchLabel: \"Search groups\",\n addGroup: \"\\uFF0B Add group\",\n editGroup: \"Edit group\",\n groupKey: \"Group key\",\n name: \"Name\",\n description: \"Description\",\n memberCountTemplate: \"{count} members\",\n confirmDelete: \"Delete this group?\",\n editGroupAria: \"Edit {group}\",\n deleteGroupAria: \"Delete {group}\",\n noDescription: \"No description\",\n groupKeyPlaceholder: \"e.g. dev-team\",\n namePlaceholder: \"e.g. Development team\",\n empty: \"No groups registered\",\n },\n groupMembersView: {\n title: \"Group members\",\n selectGroup: \"Select a group\",\n selectPrompt: \"Select a group to manage its members\",\n actionAdd: \"Add\",\n actionRemove: \"Remove\",\n actionAddAria: \"Add {user} to this group\",\n actionRemoveAria: \"Remove {user} from this group\",\n upstreamNotice: \"Membership of this group is owned by the upstream directory. Edit it there \\u2014 a local change would be reverted at the next synchronization.\",\n membersOfTemplate: \"Members of {group}\",\n groupKeyLabel: \"Group key\",\n },\n allowlistView: {\n title: \"Sign-in allowlist\",\n addEntry: \"\\uFF0B 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 groupTypeShort: \"Group\",\n target: \"Target\",\n description: \"Description\",\n deploymentOwner: \"Deployment-wide\",\n confirmDelete: \"Remove this allowlist entry? Anyone who could sign in only through it will be locked out.\",\n deleteAria: \"Remove the allowlist entry for {target}\",\n empty: \"No allowlist entries registered\",\n },\n directoryView: {\n title: \"Directory synchronization\",\n membershipMode: \"Membership\",\n directMode: \"Direct only\",\n transitiveMode: \"Nested expanded\",\n lastSynced: \"Last synced\",\n neverSynced: \"Never\",\n unresolvedTemplate: \"{count} unresolved\",\n syncPaused: \"Paused\",\n syncEnabled: \"Active\",\n syncNow: \"Sync now\",\n syncQueued: \"Queued\",\n staleWarning: \"Synchronization is overdue. Until it runs, people removed upstream keep their access.\",\n disabledWarning: \"Directory synchronization is not wired for this deployment. Existing memberships remain in force and are no longer being refreshed.\",\n pausedTemplate: \"{paused} of {total} links are paused\",\n erroredTemplate: \"Last synchronization failed for {groups}\",\n erroredItemTemplate: \"{group}: {reason}\",\n listSeparator: \", \",\n oldestSyncedTemplate: \"oldest successful sync: {at}\",\n pendingTemplate: \"{count} sync requests are queued\",\n nothingToLink: \"Every group is already linked to a directory.\",\n notWiredNotice: \"Directory synchronization is not wired for this deployment, so links cannot be created or changed here.\",\n syncNowAria: \"Synchronize {group} now\",\n syncQueuedAria: \"A synchronization of {group} is queued\",\n pauseAria: \"Pause synchronization of {group}\",\n resumeAria: \"Resume synchronization of {group}\",\n allowGroupAria: \"Allow members of {group} to sign in\",\n disallowGroupAria: \"Stop allowing members of {group} to sign in\",\n unlinkAria: \"Unlink {group} from the directory\",\n confirmDisallow: \"Stop allowing this group to sign in? Anyone who could sign in only through it will be locked out.\",\n link: \"Link to a directory group\",\n unlink: \"Unlink\",\n externalKey: \"Upstream group\",\n empty: \"No groups are linked to a directory\",\n selectGroup: \"Group to link\",\n externalKeyPlaceholder: \"e.g. dev-team@example.com\",\n confirmUnlink: \"Unlink this group? Its member ledger and group-based sign-in permission are removed.\",\n pause: \"Pause\",\n resume: \"Resume\",\n allowGroup: \"Allow sign-in\",\n disallowGroup: \"Stop allowing sign-in\",\n groupOptionTemplate: \"{name} ({key})\",\n },\n}\n\n/**\n * Standard Japanese labels for user administration (opt-in).\n * ユーザー管理の標準日本語ラベル (アプリ側で明示注入して使う)。\n */\nexport const jaUserAdminLabels: UserAdminLabels = {\n title: \"ユーザー・グループ管理\",\n locale: \"ja-JP\",\n timeZone: \"Asia/Tokyo\",\n heading: \"\\u{1F464} ユーザー管理\",\n common: {\n create: \"作成\",\n edit: \"編集\",\n delete: \"削除\",\n save: \"保存\",\n cancel: \"キャンセル\",\n actions: \"操作\",\n status: \"状態\",\n id: \"ID\",\n readOnly: \"変更不可\",\n managedUpstream: \"上流が所有\",\n idTemplate: \"#{id}\",\n none: \"-\",\n },\n errors: {\n forbidden: \"この操作を行う権限がありません。\",\n },\n permissions: {\n label: \"あなたの権限:\",\n canWrite: \"編集可\",\n canRead: \"閲覧のみ\",\n none: \"権限なし\",\n unknown: \"権限不明\",\n },\n tabs: {\n users: \"\\u{1F464} ユーザー一覧\",\n groups: \"\\u{1F465} ユーザーグループ\",\n groupMembers: \"\\u{1F517} グループメンバー\",\n allowlist: \"\\u2709\\uFE0F アクセス許可リスト\",\n directory: \"\\u{1F504} ディレクトリ連携\",\n },\n usersView: {\n title: \"ユーザー管理\",\n searchPlaceholder: \"表示名や Email で検索...\",\n searchLabel: \"ユーザーを検索\",\n addUser: \"\\uFF0B ユーザー追加\",\n editUser: \"ユーザー編集\",\n displayName: \"表示名\",\n email: \"Email\",\n active: \"有効\",\n inactive: \"無効\",\n activate: \"有効化\",\n deactivate: \"無効化\",\n editUserAria: \"{user} を編集\",\n activateAria: \"{user} を有効化\",\n deactivateAria: \"{user} を無効化\",\n confirmDeactivate: \"このユーザーを無効化しますか? 以後サインインできなくなります。\",\n empty: \"該当するユーザーが見つかりません\",\n },\n groupsView: {\n emptyFiltered: \"検索条件に一致するグループはありません\",\n title: \"ユーザーグループ管理\",\n searchPlaceholder: \"グループ名やキーで検索...\",\n searchLabel: \"グループを検索\",\n addGroup: \"\\uFF0B グループ追加\",\n editGroup: \"グループ編集\",\n groupKey: \"グループキー\",\n name: \"名称\",\n description: \"説明\",\n memberCountTemplate: \"所属 {count} 名\",\n confirmDelete: \"このグループを削除しますか?\",\n editGroupAria: \"{group} を編集\",\n deleteGroupAria: \"{group} を削除\",\n noDescription: \"説明なし\",\n groupKeyPlaceholder: \"例: dev-team\",\n namePlaceholder: \"例: 開発チーム\",\n empty: \"ユーザーグループが登録されていません\",\n },\n groupMembersView: {\n title: \"グループメンバー管理\",\n selectGroup: \"グループ選択\",\n selectPrompt: \"グループを選択してください\",\n actionAdd: \"追加\",\n actionRemove: \"除外\",\n actionAddAria: \"{user} をこのグループへ追加\",\n actionRemoveAria: \"{user} をこのグループから除外\",\n upstreamNotice: \"このグループの所属は上流ディレクトリが所有しています。変更は上流で行ってください (ここで変えても次の同期で元に戻ります)。\",\n membersOfTemplate: \"{group} の所属メンバー\",\n groupKeyLabel: \"グループキー\",\n },\n allowlistView: {\n title: \"サインイン許可リスト\",\n addEntry: \"\\uFF0B パターン追加\",\n pattern: \"パターン (例: @example.com または user@example.com)\",\n type: \"種別\",\n emailType: \"メールアドレス完全一致\",\n domainType: \"ドメイン一致\",\n emailTypeShort: \"メール\",\n domainTypeShort: \"ドメイン\",\n groupTypeShort: \"グループ\",\n target: \"対象\",\n description: \"説明 / 備考\",\n deploymentOwner: \"配備共通\",\n confirmDelete: \"この許可を削除しますか? これでしかサインインできない人はログインできなくなります。\",\n deleteAria: \"{target} の許可を削除\",\n empty: \"アロワリストエントリが登録されていません\",\n },\n directoryView: {\n title: \"ディレクトリ同期\",\n membershipMode: \"所属の展開\",\n directMode: \"直接所属のみ\",\n transitiveMode: \"入れ子を展開\",\n lastSynced: \"最終同期\",\n neverSynced: \"未実行\",\n unresolvedTemplate: \"{count} 件が未解決\",\n syncPaused: \"停止中\",\n syncEnabled: \"有効\",\n syncNow: \"今すぐ同期\",\n syncQueued: \"同期を要求しました\",\n staleWarning: \"同期が滞っています。実行されるまで、上流から外れた人のアクセスは残り続けます。\",\n disabledWarning: \"この配備ではディレクトリ同期が結線されていません。既存の所属はそのまま有効で、更新だけが止まっています。\",\n pausedTemplate: \"連携 {total} 件のうち {paused} 件が停止中\",\n erroredTemplate: \"直近の同期に失敗したグループ: {groups}\",\n erroredItemTemplate: \"{group}: {reason}\",\n listSeparator: \" / \",\n oldestSyncedTemplate: \"最も古い同期成功: {at}\",\n pendingTemplate: \"{count} 件の同期要求が未処理\",\n nothingToLink: \"すべてのグループが既にディレクトリと連携しています。\",\n notWiredNotice: \"この配備ではディレクトリ同期が結線されていないため、ここから連携の作成・変更はできません。\",\n syncNowAria: \"{group} を今すぐ同期\",\n syncQueuedAria: \"{group} の同期を要求済み\",\n pauseAria: \"{group} の同期を一時停止\",\n resumeAria: \"{group} の同期を再開\",\n allowGroupAria: \"{group} のメンバーのサインインを許可\",\n disallowGroupAria: \"{group} のメンバーのサインイン許可を外す\",\n unlinkAria: \"{group} のディレクトリ連携を解除\",\n confirmDisallow: \"このグループのサインイン許可を外しますか? これでしかサインインできない人はログインできなくなります。\",\n link: \"ディレクトリのグループと連携する\",\n unlink: \"連携を解除\",\n externalKey: \"上流グループ\",\n empty: \"ディレクトリと連携しているグループはありません\",\n selectGroup: \"連携するグループ\",\n externalKeyPlaceholder: \"例: dev-team@example.com\",\n confirmUnlink: \"このグループの連携を解除しますか? メンバー台帳とグループ単位のサインイン許可が失われます。\",\n pause: \"一時停止\",\n resume: \"再開\",\n allowGroup: \"サインインを許可\",\n disallowGroup: \"サインイン許可を外す\",\n groupOptionTemplate: \"{name} ({key})\",\n },\n}\n\n/** セクション単位の部分上書き。 */\nexport type PartialUserAdminLabels = {\n title?: string\n locale?: string\n timeZone?: string\n common?: Partial<UserAdminLabels[\"common\"]>\n errors?: Partial<UserAdminLabels[\"errors\"]>\n heading?: string\n permissions?: Partial<UserAdminLabels[\"permissions\"]>\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 directoryView?: Partial<UserAdminLabels[\"directoryView\"]>\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 locale: over.locale ?? d.locale,\n timeZone: over.timeZone ?? d.timeZone,\n heading: over.heading ?? d.heading,\n common: { ...d.common, ...over.common },\n errors: { ...d.errors, ...over.errors },\n permissions: { ...d.permissions, ...over.permissions },\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 directoryView: { ...d.directoryView, ...over.directoryView },\n }\n}\n","import type { AuthAllowlistEntry, AuthDirectoryMembershipMode, AuthDirectoryStatus, AuthGroup, AuthGroupMember, AuthUser } from \"@aiquants/auth-core\"\nimport { data } 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 * Allows every member of an externally-sourced group to sign in.\n * 外部供給グループのメンバー全員にサインインを許可する処理。\n *\n * 判定は上流メンバー台帳を読むため、ローカル管理のグループを渡しても誰にも何も与えない。\n * ストアはそれを拒否すること (無言の no-op を作らない)。\n */\n addAllowlistGroup: (groupId: number, description?: string) => Promise<void>\n removeAllowlistGroup: (id: number) => Promise<void>\n\n /**\n * Links a local group to a group in the upstream directory.\n * ローカルグループを上流ディレクトリのグループへ結び付ける処理。\n *\n * リンク後、そのグループの名称と所属は上流が所有する。ロール割当はローカルのままである。\n */\n linkDirectoryGroup: (groupId: number, data: { provider: string; externalKey: string; membershipMode: AuthDirectoryMembershipMode }) => Promise<void>\n /** Stops following the upstream, keeping the memberships already projected. 連携の解除。 */\n unlinkDirectoryGroup: (groupId: number) => Promise<void>\n /** Pauses or resumes synchronization for one group. 1 グループの同期を停止・再開する処理。 */\n setDirectorySyncEnabled: (groupId: number, isSyncEnabled: boolean) => Promise<void>\n /**\n * Queues a synchronization request. 同期要求をキューへ積む処理。\n *\n * 上流へ到達できるのは同期ジョブだけなので、web からは要求を残すことしかできない。\n *\n * @param groupId `null` requests every linked group of the tenant. `null` はテナント全件。\n */\n requestDirectorySync: (groupId: number | null) => Promise<void>\n /** Health of the synchronization, for the header warning. 同期の健全性 (警告表示用)。 */\n getDirectoryStatus: () => Promise<AuthDirectoryStatus>\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, readOnly: false },\n { id: 2, groupKey: \"sales-dept\", name: \"営業部\", description: \"営業ソリューション担当\", isActive: true, readOnly: false },\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: \"初期管理者\", owner: { kind: \"tenant\", tenantId: \"default\" }, readOnly: false },\n { id: 2, pattern: \"@example.com\", type: \"domain\", description: \"社内ドメイン\", owner: { kind: \"tenant\", tenantId: \"default\" }, readOnly: false },\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 readOnly: false,\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 owner: { kind: \"tenant\", tenantId: \"default\" },\n readOnly: false,\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 // このメモリ実装はパッケージ自身の spec 用のテストシームであり、上流を持たない。\n // 連携面は「結線されていない配備」を表現する形で応答する\n async addAllowlistGroup(groupId, description) {\n const group = groups.find((g) => g.id === groupId)\n if (!group) throw new Error(`Group not found: ${groupId}`)\n allowlist = [...allowlist, { id: Math.max(...allowlist.map((a) => a.id), 0) + 1, type: \"group\", groupId, groupKey: group.groupKey, groupName: group.name, description, owner: { kind: \"tenant\", tenantId: \"default\" }, readOnly: false }]\n },\n async removeAllowlistGroup(id) {\n allowlist = allowlist.filter((a) => !(a.type === \"group\" && a.id === id))\n },\n async linkDirectoryGroup() {\n throw new Error(\"This in-memory store has no upstream directory; inject a real store to link groups.\")\n },\n async unlinkDirectoryGroup() {\n throw new Error(\"This in-memory store has no upstream directory; inject a real store to unlink groups.\")\n },\n async setDirectorySyncEnabled() {\n throw new Error(\"This in-memory store has no upstream directory; inject a real store to pause synchronization.\")\n },\n async requestDirectorySync() {\n throw new Error(\"This in-memory store has no upstream directory; inject a real store to request a sync.\")\n },\n async getDirectoryStatus() {\n return { isEnabled: false, provider: \"\", linkedGroupCount: 0, isStale: false, pausedGroupCount: 0, stalenessThresholdSec: 0, erroredGroupIds: [], pendingRequestCount: 0 }\n },\n }\n}\n\n/**\n * Tells a per-operation authorization denial apart from any other failure, without importing an authz library.\n * 認可ライブラリを import せずに、操作単位の認可拒否を他の失敗と見分ける判定。\n *\n * 特定の実装の例外クラスへ `instanceof` を張ると、そのライブラリに縛られる (`AGENTS.md` の\n * クロスパッケージ・ハードコード禁止)。`status === 403` は HTTP の共通語彙なので、これだけを見る。\n *\n * @param error The thrown value. 送出された値。\n * @returns True when it carries HTTP 403. HTTP 403 を名乗る場合に true。\n */\nfunction isForbidden(error: unknown): boolean {\n // Response でも素の例外でも `status` は共通語彙である。特定の実装の例外クラスへ\n // `instanceof` を張ると、そのライブラリに縛られる\n return typeof error === \"object\" && error !== null && (error as { status?: unknown }).status === 403\n}\n\n/**\n * Reads the allowlist kind, refusing anything but the two declared values.\n * 許可の種別を読み取る処理 (宣言された 2 値以外を拒否する)。\n *\n * 指定外を既定値へ倒すと、綴り違い (`Domain`) や欠落が **完全一致の許可** として登録される。\n * 「ドメイン全体を許可したつもりが 1 アドレスだけだった」は、逆向きより静かに壊れる。\n *\n * @param formData Submitted form. 送信されたフォーム。\n * @returns The declared kind. 宣言された種別。\n * @throws {Error} When the value is absent or unknown. 未指定・未知の値の場合。\n */\nfunction requireAllowlistType(formData: FormData): \"email\" | \"domain\" {\n const raw = String(formData.get(\"type\") ?? \"\")\n if (raw === \"email\" || raw === \"domain\") return raw\n throw new Error(`type は \"email\" または \"domain\" のいずれかで指定してください (受け取った値: ${JSON.stringify(raw)})`)\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 */\n/**\n * The only route-args field this module reads. React Router's full `LoaderFunctionArgs` satisfies it, so\n * route wiring is unchanged — but declaring the minimum keeps callers (and tests) from fabricating fields\n * that are never looked at.\n * このモジュールが実際に読むルート引数の全て。React Router の完全な引数はこれを満たすため結線は不変。\n */\ntype RouteArgs = { request: Request }\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 * Observes a denial that was converted into an in-page error. 画面内エラーへ変換した拒否の観測点。\n *\n * 拒否を画面内に返すと、既定のエラー経路が拾っていたログが消える。管理面への総当たりが\n * 200 番台の列に紛れて見えなくなるため、宿主が記録できる口を開けておく。\n */\n onDenied?: (context: { intent: string; actions: readonly UserAdminAccess[]; error: unknown }) => 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, readonly 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 [\"addAllowlistGroup\", [\"create\"]],\n [\"removeAllowlistGroup\", [\"delete\"]],\n // 連携の開始は、以後の所属の **追加と削除の両方** を上流へ委ねる行為である。所属を足せる\n // だけの主体が「足すことも消すこともできる仕掛け」を据えられてはならない。属性変更でもある\n // ため update も要る\n [\"linkDirectoryGroup\", [\"update\", \"create\", \"delete\"]],\n // 解除は台帳とグループ単位のサインイン許可を **消す**。属性を戻すだけではないので、\n // 削除権限を持たない主体には許さない\n [\"unlinkDirectoryGroup\", [\"update\", \"delete\"]],\n [\"setDirectorySyncEnabled\", [\"update\"]],\n // 書き込みを伴わないが update を要求する。読取権限だけの主体が上流問い合わせを誘発できると、\n // レート制限を外部から枯渇させられる\n [\"requestDirectorySync\", [\"update\"]],\n])\n/**\n * Neutral per-resource permission summary the admin shell renders in its header badge.\n * 管理シェルのヘッダーバッジが描画する、リソース単位の中立な権限サマリ。\n *\n * この形はこのパッケージ自身の契約であり、特定の認可ライブラリの型を import しない\n * (`AGENTS.md` のクロスパッケージ・ハードコード禁止)。`canRead` / `canWrite` / `canDelete` を持つ\n * ビューであれば構造的にそのまま渡せる。\n */\nexport type UserAdminPermission = {\n canRead: boolean\n canWrite: boolean\n canDelete: boolean\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 * Per-request self-permission lookup for the header annotation. Resolving to a map of\n * {@link UserAdminPermission} shows the badge; rejecting shows an explicit \"unknown\" badge — never a\n * permissive one. Any authz library whose view carries `canRead` / `canWrite` / `canDelete` satisfies it.\n * ヘッダー注釈に自分の実効権限を表示するためのリクエスト別権限取得関数。失敗時は「権限不明」表示。\n */\n getMyPermissions?: (request: Request) => Promise<Record<string, UserAdminPermission | undefined>>\n /**\n * Display names for the resource keys the badge shows, e.g. `{ authz_admin: \"ユーザー管理 UI\" }`.\n * バッジに出すリソースキーの表示名。未指定のキーはキー文字列をそのまま表示。\n *\n * 表示名をこのパッケージ側に埋め込むと、別パッケージのリソースキーを名指しすることになる\n * (`AGENTS.md` のクロスパッケージ・ハードコード禁止)。中立な差し込み口として受け取る。\n */\n resourceLabels?: Record<string, string>\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/**\n * Reads an explicit boolean. 明示的な真偽値の読み取り。\n *\n * `\"true\"` / `\"false\"` のみを受け付ける。欠落を既定へ倒すと、破壊側 (停止) が既定方向になる。\n *\n * @throws when the field is absent or not one of the two literals. 明示指定でない場合。\n */\nfunction requireBoolean(formData: FormData, field: string): boolean {\n const raw = String(formData.get(field) ?? \"\")\n if (raw !== \"true\" && raw !== \"false\") throw new Error(`Invalid or missing \"${field}\"`)\n return raw === \"true\"\n}\n\n/**\n * Reads the membership expansion mode. 所属展開モードの読み取り。\n *\n * 既定へ倒さない。入れ子展開が黙って有効になると権限が静かに広がり、黙って無効になると\n * 入れ子側の全員が削除候補になる。どちらの向きにも倒さず、明示させる。\n *\n * @throws when the value is not one of the two modes. 2 値でない場合。\n */\nfunction requireMembershipMode(formData: FormData): AuthDirectoryMembershipMode {\n const raw = String(formData.get(\"membershipMode\") ?? \"\")\n if (raw !== \"direct\" && raw !== \"transitive\") throw new Error('Invalid or missing \"membershipMode\"')\n return raw\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 }: RouteArgs) {\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, directoryStatus, myPermissions] = await Promise.all([\n config.store.listUsers(),\n config.store.listGroups(),\n config.store.listGroupMembers(),\n config.store.listAllowlist(),\n config.store.getDirectoryStatus(),\n // 取得できない場合は null (= 権限不明)。空マップに倒すと「付与ゼロ」と区別できなくなる\n config.getMyPermissions\n ? config.getMyPermissions(request).catch((e) => {\n console.error(\"[@aiquants/auth-react-router] getMyPermissions failed; rendering the permission badge as unknown:\", e)\n return null\n })\n : Promise.resolve(null),\n ])\n\n return {\n resourceLabels: config.resourceLabels,\n segment: [\"groups\", \"group_members\", \"allowlist\", \"directory\"].includes(segment) ? segment : \"users\",\n users,\n groups,\n groupMembers,\n allowlist,\n directoryStatus,\n myPermissions,\n labels,\n }\n }\n\n async function action({ request }: RouteArgs) {\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 /** 403 かどうか。Response でも素の例外でも `status` は共通語彙である。 */\n const isDenied = (error: unknown) => isForbidden(error)\n /**\n * Turns a denial into an in-page error without leaking the guard's own message.\n * 拒否を、ガードの内部文言を漏らさずに画面内エラーへ変える処理。\n */\n const denied = (error: unknown) => {\n try {\n config.guards.onDenied?.({ intent, actions: access, error })\n } catch {\n // 記録に失敗しても拒否の応答は返す。ログの都合で操作の結果が変わってはならない\n }\n // 監視から消えないよう、状態は 403 のまま返す。画面は data() なので生き残る\n return data({ ok: false, error: labels.errors.forbidden }, { status: 403 })\n }\n\n // 操作ごとの CRUD 種別でガードする。複数を要求する intent は **すべて** 通ること\n try {\n for (const action of access) await config.guards.requireAccess(request, { action, intent })\n } catch (error) {\n // 403 は「この操作だけができない」であり、画面ごと落とすと **他の操作まで巻き添え** に\n // なる (閲覧と作成はできるのに削除だけ拒否された、で一覧が消える)。401 や再認証の\n // リダイレクト、それ以外の障害はそのまま伝播させる — 握り潰すとログインし直せない\n if (!isDenied(error)) throw error\n return denied(error)\n }\n\n try {\n return await dispatch(intent, formData)\n } catch (error) {\n // ガードの後 (ストアの中) で投げられた拒否も同じ扱いにする。ここを素通りさせると、\n // 拒否理由がそのまま利用者へ渡り、しかも HTTP は 200 になって監視から消える\n if (isDenied(error)) return denied(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 = requireAllowlistType(formData)\n await config.store.addAllowlistEntry({ pattern: requireText(formData, \"pattern\"), type, description: optionalText(formData, \"description\") })\n return { ok: true }\n }\n\n if (intent === \"addAllowlistGroup\") {\n await config.store.addAllowlistGroup(requireId(formData, \"groupId\"), optionalText(formData, \"description\"))\n return { ok: true }\n }\n if (intent === \"removeAllowlistGroup\") {\n await config.store.removeAllowlistGroup(requireId(formData, \"entryId\"))\n return { ok: true }\n }\n if (intent === \"linkDirectoryGroup\") {\n await config.store.linkDirectoryGroup(requireId(formData, \"groupId\"), {\n provider: requireText(formData, \"provider\"),\n externalKey: requireText(formData, \"externalKey\"),\n membershipMode: requireMembershipMode(formData),\n })\n return { ok: true }\n }\n if (intent === \"unlinkDirectoryGroup\") {\n await config.store.unlinkDirectoryGroup(requireId(formData, \"groupId\"))\n return { ok: true }\n }\n if (intent === \"setDirectorySyncEnabled\") {\n await config.store.setDirectorySyncEnabled(requireId(formData, \"groupId\"), requireBoolean(formData, \"isSyncEnabled\"))\n return { ok: true }\n }\n if (intent === \"requestDirectorySync\") {\n const raw = String(formData.get(\"groupId\") ?? \"\").trim()\n await config.store.requestDirectorySync(raw === \"\" ? null : requireId(formData, \"groupId\"))\n return { ok: true }\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 { AuthDirectoryStatus, AuthGroup } from \"@aiquants/auth-core\"\nimport type React from \"react\"\nimport { Link, useLoaderData } from \"react-router\"\nimport { fill, formatDateTime } from \"./format\"\nimport { defaultUserAdminLabels, type UserAdminLabels } from \"./labels\"\nimport type { UserAdminPermission } from \"./server\"\n\nexport type UserAdminShellProps = {\n children?: React.ReactNode\n renderHeader?: (props: { title: string; annotation: React.ReactNode }) => React.ReactNode\n}\n\n/**\n * Render one resource's permission summary as a compact pill.\n * 1 リソース分の権限サマリをコンパクトなタグとして描画するコンポーネント。\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\n/**\n * Warns when synchronization is not actually keeping the mirror fresh.\n * 同期が実際には鏡を新しく保てていないことを警告する部品。\n *\n * 同期の停止は「上流から外れた人がテナントに居続ける」というセキュリティ事象である。件数と\n * エラー数だけを見ると何も起きていないように読めるため、**滞留・全停止・未結線の 3 つを\n * それぞれ名指しで** 出す。出さなければ、状態を計算している意味がない。\n *\n * どのタブに居ても見えるようにシェル側で描く。ディレクトリの停止はグループ一覧を見ている\n * ときだけ起きる事象ではない。\n */\nfunction DirectorySyncBanner({ status, labels, groups }: { status?: AuthDirectoryStatus; labels: UserAdminLabels; groups: AuthGroup[] }) {\n if (!status || status.linkedGroupCount === 0) return null\n const L = labels.directoryView\n const warnings: Array<{ id: string; text: string }> = []\n if (!status.isEnabled) warnings.push({ id: \"disabled\", text: L.disabledWarning })\n if (status.pausedGroupCount > 0) warnings.push({ id: \"paused\", text: fill(L.pausedTemplate, { paused: status.pausedGroupCount, total: status.linkedGroupCount }) })\n if (status.isStale) {\n const at = status.oldestSyncedAt ? formatDateTime(status.oldestSyncedAt, labels) : L.neverSynced\n warnings.push({ id: \"stale\", text: `${L.staleWarning} ${fill(L.oldestSyncedTemplate, { at })}` })\n }\n if (status.erroredGroupIds.length > 0) {\n // ID を並べても運用者には何のグループか判らない。名前と、あれば実際の失敗理由を出す\n const described = status.erroredGroupIds.map((id) => {\n const group = groups.find((g) => g.id === id)\n if (!group) return fill(labels.common.idTemplate, { id })\n const reason = group.externalLink?.syncError\n return reason ? fill(L.erroredItemTemplate, { group: group.name, reason }) : group.name\n })\n warnings.push({ id: \"errored\", text: fill(L.erroredTemplate, { groups: described.join(L.listSeparator) }) })\n }\n if (status.pendingRequestCount > 0) warnings.push({ id: \"pending\", text: fill(L.pendingTemplate, { count: status.pendingRequestCount }) })\n if (warnings.length === 0) return null\n return (\n // ライブリージョンにはしない。初期描画から在る内容は読み上げられず、見出しも読み順も\n // 失われる。**読める本文** として置き、見出しから辿れるようにする\n <section\n aria-labelledby=\"directory-sync-heading\"\n data-testid=\"directory-sync-banner\"\n style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.35rem\", padding: \"0.7rem 1rem\", marginBottom: \"1rem\", borderRadius: \"6px\", border: \"1px solid #fde68a\", backgroundColor: \"#fffbeb\", color: \"#92400e\", fontSize: \"0.85rem\" }}>\n <h2 id=\"directory-sync-heading\" style={{ margin: 0, fontSize: \"0.9rem\", fontWeight: 700 }}>\n {L.title}\n </h2>\n {warnings.map((w) => (\n <span key={w.id} data-directory-warning={w.id}>\n {w.text}\n </span>\n ))}\n </section>\n )\n}\n\n/**\n * Shell layout for user administration pages with header annotation delegation.\n * ヘッダー注釈委譲を備えたユーザー管理ページのシェルレイアウト。\n */\nexport function UserAdminShell({ children, renderHeader }: UserAdminShellProps) {\n // ⚠️ ここは `UserAdminLoaderData` と違い **すべて任意** で受ける。シェルは loader を持たない\n // 呼び出し (エラー境界の外側、テスト、部分描画) からも描かれるため、欠落を落ちる理由にしない\n const data = useLoaderData() as { segment?: string; labels?: UserAdminLabels; myPermissions?: Record<string, UserAdminPermission | undefined> | null; resourceLabels?: Record<string, string>; directoryStatus?: AuthDirectoryStatus; groups?: AuthGroup[] } | undefined\n const segment = data?.segment ?? \"users\"\n const labels = data?.labels ?? defaultUserAdminLabels\n // null = 未結線または取得失敗。空 {} も「分からない」扱いで、いずれも許可として描画しない\n const myPermissions = data?.myPermissions ?? null\n // リソースキーの表示名は呼び出し側が差し込む (キーをこのパッケージに埋め込まない)\n const resourceLabels = data?.resourceLabels ?? {}\n const labelFor = (resourceKey: string) => (Object.hasOwn(resourceLabels, resourceKey) ? resourceLabels[resourceKey] : resourceKey)\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 // 面だけが消えるのは最悪の挙動である。連携も結線も無い配備でだけ隠す。\n // 判定は状態の件数と一覧の実体の **両方** を見る — 片方だけだと、件数が 0 なのに\n // 「上流が所有」と描かれるグループが取り残される\n const hasLinkedGroup = (data?.groups ?? []).some((g) => g.externalLink)\n if (data?.directoryStatus?.isEnabled || (data?.directoryStatus?.linkedGroupCount ?? 0) > 0 || hasLinkedGroup) {\n tabs.push({ key: \"directory\", label: labels.tabs.directory, href: \"/users/directory\" })\n }\n\n const permEntries = Object.entries(myPermissions ?? {})\n\n /**\n * Pick the badge text and colours for one resource, never rendering \"unknown\" as \"allowed\".\n * 1 リソース分のバッジ文言と配色を決める処理。**未知を許可として描かない** ことが要件。\n *\n * 以前は権限マップが空のとき「編集可」の固定値を描いていたため、ポート未結線・取得失敗・\n * 付与ゼロのいずれでも「全操作できます」と表示されていた (実際には操作するとガードで拒否される)。\n */\n const getSummary = (perm: UserAdminPermission | undefined) => {\n if (perm?.canWrite) {\n return { text: labels.permissions.canWrite, color: \"#047857\", bgColor: \"#ecfdf5\", borderColor: \"#a7f3d0\" }\n }\n if (perm?.canRead) {\n return { text: labels.permissions.canRead, color: \"#1d4ed8\", bgColor: \"#eff6ff\", borderColor: \"#bfdbfe\" }\n }\n return { text: labels.permissions.none, color: \"#475569\", bgColor: \"#f1f5f9\", borderColor: \"#cbd5e1\" }\n }\n const unavailableSummary = { text: labels.permissions.unknown, color: \"#92400e\", bgColor: \"#fffbeb\", borderColor: \"#fde68a\" }\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\" }}>{labels.permissions.label}</span>\n <span style={{ display: \"inline-flex\", alignItems: \"center\", gap: \"0.18rem\" }}>\n {permEntries.length === 0 ? (\n <span data-testid=\"user-admin-permission-unavailable\">\n <GroupPill label={labels.heading} summary={unavailableSummary} />\n </span>\n ) : (\n permEntries.map(([resKey, perm]) => <GroupPill key={resKey} label={labelFor(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 <nav aria-label={labels.title} 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 aria-current={isActive ? \"page\" : undefined}\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 </nav>\n\n <DirectorySyncBanner status={data?.directoryStatus} labels={labels} groups={data?.groups ?? []} />\n\n <main>{children}</main>\n </div>\n )\n}\n","/**\n * @module admin/format\n * @description Text helpers shared by the shell and the views.\n * シェルとビューが共有する文言整形の補助。\n *\n * 数や名前を JSX で連結すると語順が英語に固定され、日付を実行環境任せで整形すると\n * サーバーとブラウザで別の文字列になる (水和が壊れる)。どちらもラベル側の宣言に従わせる。\n */\nimport type { UserAdminLabels } from \"./labels\"\n\n/**\n * Substitutes `{key}` placeholders in a label template.\n * ラベルの雛形に含まれる `{key}` を差し替える処理。\n *\n * @param template Label text containing `{key}` placeholders. `{key}` を含むラベル文言。\n * @param values Replacement values by key. キーごとの差し替え値。\n * @returns The filled text. 差し替え済みの文言。\n */\nexport function fill(template: string, values: Record<string, string | number>): string {\n return Object.entries(values).reduce((text, [key, value]) => text.replaceAll(`{${key}}`, String(value)), template)\n}\n\n/**\n * Formats an ISO timestamp with the locale the labels declare, degrading instead of throwing.\n * ISO 時刻を **ラベルが宣言したロケール** で整形する処理 (例外を投げず、読める形へ退避する)。\n *\n * `Intl.DateTimeFormat().format()` は不正な日時で `RangeError` を投げる (`toLocaleString()` が\n * 「Invalid Date」を返すのとは違う)。この関数を呼ぶのはシェルの同期バナーであり、バナーは\n * **全タブで描かれる**。上流の 1 行が壊れた時刻を持っていただけで、ユーザー管理面が丸ごと\n * エラー境界へ落ちる。ロケール文字列が空でも同じく投げるため、そこも退避する。\n *\n * オフセットの無い ISO 文字列 (`2026-03-01T09:00:00`) は仕様上 **実行環境の時間帯** で解釈される。\n * サーバーとブラウザで別の時刻になり水和が壊れるため、UTC として読む。\n *\n * @param iso ISO 8601 timestamp. ISO 8601 の時刻 (オフセット無しは UTC として解釈)。\n * @param labels Resolved label set carrying locale and time zone. ロケールと時間帯を持つラベル。\n * @returns Formatted text, or the input itself when it cannot be formatted. 整形済みの文言 (整形できない場合は入力そのもの)。\n */\nexport function formatDateTime(iso: string, labels: UserAdminLabels): string {\n // オフセットが無ければ UTC を補う (実行環境の時間帯で解釈させない)\n const normalized = /(?:Z|[+-]\\d{2}:?\\d{2})$/.test(iso) ? iso : `${iso}Z`\n try {\n return new Intl.DateTimeFormat(labels.locale, { timeZone: labels.timeZone, dateStyle: \"medium\", timeStyle: \"short\" }).format(new Date(normalized))\n } catch {\n // 不正な日時 (`RangeError: Invalid time value`) も、不正なロケール・時間帯も、ここで\n // 受け止める。画面を落とすより生の値を見せる方がまだ役に立つ\n return iso\n }\n}\n","import type { AuthAllowlistEntry, AuthDirectoryMembershipMode, AuthDirectoryStatus, AuthGroup, AuthGroupMember, AuthUser } from \"@aiquants/auth-core\"\nimport type React from \"react\"\nimport { useEffect, useRef, useState } from \"react\"\nimport { useFetcher, useLoaderData } from \"react-router\"\nimport { fill, formatDateTime } from \"./format\"\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 directoryStatus: AuthDirectoryStatus\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({ result: raw, id }: { result: unknown; id?: string }) {\n const result = raw as { ok?: boolean; error?: string } | undefined\n if (result?.ok !== false || !result.error) return null\n return (\n // グリッド配下でも行を跨ぐ。空のときは描かないので、余分な行も生まれない\n <div id={id} data-testid=\"user-admin-action-error\" role=\"alert\" style={{ gridColumn: \"1 / -1\", 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\n/**\n * Submits through the view's single fetcher and defers \"close and clear\" until the server accepts.\n * ビュー唯一の fetcher で送信し、**サーバーが受理してから** 「閉じて消す」を行うためのフック。\n *\n * 送信直後に閉じて入力を消すと、認可拒否や検証エラーで戻ったときに打ち込んだ内容が丸ごと\n * 失われ、利用者は全部打ち直すことになる。成功応答 (`ok === true`) を待って初めて破棄する。\n *\n * ⚠️ **このビューの送信はすべてここを通すこと。** fetcher は 1 つを共有しており、後の送信は\n * 前の送信を打ち切る。素の `fetcher.submit` を混ぜると、打ち切られた送信の後始末が **後の\n * 送信の成功応答で** 実行される (行の操作を挟んだだけで、フォームの入力が消える)。ここを\n * 通せば後の送信が `pending` を必ず上書きするため、後始末は常に最後の送信のものになる。\n */\nfunction useSubmitThen(fetcher: ReturnType<typeof useFetcher>) {\n const pending = useRef<(() => void) | null>(null)\n // 破棄した結果の世代。`fetcher.data` は宣言的に消せないため、こちらで無効化する\n const [discarded, setDiscarded] = useState<unknown>(undefined)\n const result = fetcher.data === discarded ? undefined : fetcher.data\n\n useEffect(() => {\n if (fetcher.state !== \"idle\" || fetcher.data === undefined) return\n const onSuccess = pending.current\n if (!onSuccess) return\n // 同じ応答で 2 度実行しないよう、消費した時点で捨てる。ここは **冗長な防御** である —\n // すべての送信がこのフックを通る限り、後の送信が必ず `pending` を上書きするため。\n // 素の `fetcher.submit` が 1 つでも混ざった瞬間に効き始める\n pending.current = null\n if ((fetcher.data as { ok?: boolean }).ok === true) onSuccess()\n }, [fetcher.state, fetcher.data])\n\n const submit = (payload: Record<string, string>, onSuccess?: () => void) => {\n pending.current = onSuccess ?? null\n setDiscarded(undefined)\n fetcher.submit(payload, { method: \"post\" })\n }\n /**\n * Forgets the last result so an unrelated failure cannot describe a freshly opened dialog.\n * 直前の結果を忘れる処理 (無関係な失敗が、開いたばかりのダイアログの説明文にならないように)。\n */\n const resetResult = () => setDiscarded(fetcher.data)\n\n return { submit, resetResult, result }\n}\n\n/**\n * Gives a dialog the behavior its ARIA attributes promise: focus, trap, Escape, restore.\n * ダイアログに、ARIA 属性が約束している挙動 (初期焦点・封じ込め・Escape・復帰) を与えるフック。\n *\n * 役割属性だけを付けても、焦点は背後に残り、Tab は外へ出て行き、Escape は効かない。\n * キーボード利用者は「閉じる」に到達できず、支援技術は背後を読み続ける。\n *\n * @param isOpen Whether the dialog is mounted and visible. ダイアログが開いているかどうか。\n * @param onClose Invoked when Escape is pressed. Escape が押されたときに呼ばれる処理。\n * @returns Ref to attach to the dialog panel. ダイアログ本体へ付ける ref。\n */\nfunction useModalBehavior(isOpen: boolean, onClose: () => void, openerRef?: React.RefObject<HTMLElement | null>) {\n const ref = useRef<HTMLFormElement>(null)\n // 再描画のたびに実行されると入力中に焦点が先頭へ飛ぶため、依存は開閉だけにする\n const closeRef = useRef(onClose)\n closeRef.current = onClose\n\n useEffect(() => {\n if (!isOpen) return\n const panel = ref.current\n // `activeElement` を見るだけでは戻し先にならない。押下で焦点が乗らないブラウザ\n // (Safari / macOS の Firefox) では body へ戻ってしまう。開いた要素を明示的に覚える\n const opener = openerRef?.current ?? (document.activeElement as HTMLElement | null)\n const focusables = () => Array.from(panel?.querySelectorAll<HTMLElement>('a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex=\"-1\"])') ?? [])\n focusables()[0]?.focus()\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n // 変換中の Escape は「候補を取り消す」であって「閉じる」ではない。ここで\n // 閉じると、編集中の下書きは状態ごと捨てられ、打ち直しになる\n if (event.isComposing || event.keyCode === 229) return\n event.preventDefault()\n closeRef.current()\n return\n }\n if (event.key !== \"Tab\") return\n const items = focusables()\n if (items.length === 0) return\n const first = items[0]\n const last = items[items.length - 1]\n const active = document.activeElement\n const outside = !panel?.contains(active)\n if (event.shiftKey && (active === first || outside)) {\n event.preventDefault()\n last.focus()\n } else if (!event.shiftKey && (active === last || outside)) {\n event.preventDefault()\n first.focus()\n }\n }\n document.addEventListener(\"keydown\", onKeyDown)\n return () => {\n document.removeEventListener(\"keydown\", onKeyDown)\n opener?.focus?.()\n }\n }, [isOpen, openerRef])\n\n return ref\n}\n\nexport function UsersView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const { submit: submitThen, resetResult, result: actionResult } = useSubmitThen(fetcher)\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [searchQuery, setSearchQuery] = useState(\"\")\n // 2 つの独立した状態にすると「両方開いている」状態が表現でき、エラー欄が 2 つ描かれる。\n // 排他であることを型で持つ\n const [modal, setModal] = useState<{ kind: \"create\" } | { kind: \"edit\"; user: AuthUser } | null>(null)\n const editingUser = modal?.kind === \"edit\" ? modal.user : null\n const isCreateOpen = modal?.kind === \"create\"\n const openerRef = useRef<HTMLElement | null>(null)\n /** ダイアログを開く。直前の操作の結果を持ち越さない (別の操作の拒否が説明文になる)。 */\n const openModal = (next: { kind: \"create\" } | { kind: \"edit\"; user: AuthUser }, event: React.MouseEvent<HTMLElement>) => {\n openerRef.current = event.currentTarget\n resetResult()\n setModal(next)\n }\n\n const [newDisplayName, setNewDisplayName] = useState(\"\")\n const [newEmail, setNewEmail] = useState(\"\")\n const createUserRef = useModalBehavior(isCreateOpen, () => setModal(null), openerRef)\n const editUserRef = useModalBehavior(editingUser !== null, () => setModal(null), openerRef)\n\n const busy = fetcher.state !== \"idle\"\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 // 無効化はサインインを止める。有効化は元に戻すだけなので確認しない\n if (user.isActive && !window.confirm(labels.usersView.confirmDeactivate)) return\n submitThen({ intent: \"toggleUserActive\", userId: String(user.id), isActive: String(!user.isActive) })\n }\n\n const handleCreateUser = (e: React.FormEvent) => {\n e.preventDefault()\n if (!(newDisplayName && newEmail)) return\n // 送信した値と今の値が違うなら、往復の間に打ち込まれたものである。消してはならない\n const sentName = newDisplayName\n const sentEmail = newEmail\n submitThen({ intent: \"createUser\", displayName: sentName, email: sentEmail }, () => {\n setNewDisplayName((current) => (current === sentName ? \"\" : current))\n setNewEmail((current) => (current === sentEmail ? \"\" : current))\n setModal(null)\n })\n }\n\n const handleUpdateUser = (e: React.FormEvent) => {\n e.preventDefault()\n if (!editingUser) return\n submitThen({ intent: \"updateUser\", userId: String(editingUser.id), displayName: editingUser.displayName, email: editingUser.email }, () => setModal(null))\n }\n\n return (\n <section data-testid=\"users-view-section\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1rem\" }}>\n <h2 style={heading}>{labels.usersView.title}</h2>\n {/* モーダルが開いている間はモーダル内に描く。両方描くと同じ hook が 2 つになる */}\n {!modal && <ActionError result={actionResult} />}\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 aria-label={labels.usersView.searchLabel}\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\n type=\"button\"\n onClick={(event) => openModal({ kind: \"create\" }, event)}\n data-testid=\"users-add-button\"\n 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.common.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.common.status}</th>\n <th style={{ padding: \"10px 14px\", textAlign: \"right\" }}>{labels.common.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: \"#475569\" }}>{fill(labels.common.idTemplate, { id: 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={(event) => openModal({ kind: \"edit\", user: u }, event)}\n aria-label={fill(labels.usersView.editUserAria, { user: u.displayName })}\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.common.edit}\n </button>\n <button\n type=\"button\"\n onClick={() => handleToggleActive(u)}\n disabled={busy}\n aria-busy={busy}\n aria-label={fill(u.isActive ? labels.usersView.deactivateAria : labels.usersView.activateAria, { user: u.displayName })}\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: busy ? \"default\" : \"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: \"#475569\" }}>\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 }}>\n <form\n ref={createUserRef}\n onSubmit={handleCreateUser}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"create-user-title\"\n aria-describedby=\"create-user-error\"\n data-testid=\"user-create-modal\"\n style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 id=\"create-user-title\" style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>\n {labels.usersView.addUser}\n </h3>\n <ActionError result={actionResult} id=\"create-user-error\" />\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={() => setModal(null)} data-testid=\"cancel-create-user\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n {labels.common.cancel}\n </button>\n <button type=\"submit\" data-testid=\"submit-create-user\" disabled={busy} aria-busy={busy} style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#0284c7\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n {labels.common.create}\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 }}>\n <form\n ref={editUserRef}\n onSubmit={handleUpdateUser}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"edit-user-title\"\n aria-describedby=\"edit-user-error\"\n data-testid=\"user-edit-modal\"\n style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 id=\"edit-user-title\" style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>\n {labels.usersView.editUser}\n </h3>\n <ActionError result={actionResult} id=\"edit-user-error\" />\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) => setModal({ kind: \"edit\", user: { ...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) => setModal({ kind: \"edit\", user: { ...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={() => setModal(null)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n {labels.common.cancel}\n </button>\n <button type=\"submit\" data-testid=\"submit-edit-user\" disabled={busy} aria-busy={busy} style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#0284c7\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n {labels.common.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 { submit: submitThen, resetResult, result: actionResult } = useSubmitThen(fetcher)\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [searchQuery, setSearchQuery] = useState(\"\")\n const [modal, setModal] = useState<{ kind: \"create\" } | { kind: \"edit\"; group: AuthGroup } | null>(null)\n const editingGroup = modal?.kind === \"edit\" ? modal.group : null\n const isCreateOpen = modal?.kind === \"create\"\n const openerRef = useRef<HTMLElement | null>(null)\n /** ダイアログを開く。直前の操作の結果を持ち越さない (別の操作の拒否が説明文になる)。 */\n const openModal = (next: { kind: \"create\" } | { kind: \"edit\"; group: AuthGroup }, event: React.MouseEvent<HTMLElement>) => {\n openerRef.current = event.currentTarget\n resetResult()\n setModal(next)\n }\n\n const busy = fetcher.state !== \"idle\"\n const [newGroupKey, setNewGroupKey] = useState(\"\")\n const [newName, setNewName] = useState(\"\")\n const [newDescription, setNewDescription] = useState(\"\")\n const createGroupRef = useModalBehavior(isCreateOpen, () => setModal(null), openerRef)\n const editGroupRef = useModalBehavior(editingGroup !== null, () => setModal(null), openerRef)\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 // 送信した値と今の値が違うなら、往復の間に打ち込まれたものである。消してはならない\n const sent = { key: newGroupKey, name: newName, description: newDescription }\n submitThen({ intent: \"createGroup\", groupKey: sent.key, name: sent.name, description: sent.description }, () => {\n setNewGroupKey((current) => (current === sent.key ? \"\" : current))\n setNewName((current) => (current === sent.name ? \"\" : current))\n setNewDescription((current) => (current === sent.description ? \"\" : current))\n setModal(null)\n })\n }\n\n const handleUpdateGroup = (e: React.FormEvent) => {\n e.preventDefault()\n if (!editingGroup) return\n submitThen({ intent: \"updateGroup\", groupId: String(editingGroup.id), name: editingGroup.name, description: editingGroup.description ?? \"\" }, () => setModal(null))\n }\n\n const handleDeleteGroup = (groupId: number) => {\n if (!window.confirm(labels.groupsView.confirmDelete)) return\n submitThen({ intent: \"deleteGroup\", groupId: String(groupId) })\n }\n\n return (\n <section data-testid=\"groups-view-section\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1rem\" }}>\n <h2 style={heading}>{labels.groupsView.title}</h2>\n {!modal && <ActionError result={actionResult} />}\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 aria-label={labels.groupsView.searchLabel}\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\n type=\"button\"\n onClick={(event) => openModal({ kind: \"create\" }, event)}\n data-testid=\"groups-add-button\"\n 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\n key={g.id}\n style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", padding: \"1rem\", display: \"flex\", flexDirection: \"column\", justifyContent: \"space-between\" }}\n data-testid={`group-card-${g.id}`}\n data-group-readonly={g.readOnly ? \"true\" : \"false\"}>\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 {/* 所属も名称も上流が所有していることを、操作を試す前に伝える */}\n {g.externalLink && (\n <div data-testid={`group-source-${g.id}`} style={{ display: \"flex\", alignItems: \"center\", gap: \"6px\", flexWrap: \"wrap\", marginBottom: \"0.6rem\" }}>\n <span style={{ fontSize: \"0.72rem\", padding: \"2px 8px\", borderRadius: \"10px\", backgroundColor: \"#ede9fe\", color: \"#6d28d9\", fontWeight: 600 }}>{labels.common.managedUpstream}</span>\n <span style={{ fontSize: \"0.72rem\", fontFamily: \"monospace\", color: \"#64748b\" }}>{g.externalLink.externalKey}</span>\n {g.externalLink.unresolvedCount > 0 && (\n <span data-testid={`group-unresolved-${g.id}`} style={{ fontSize: \"0.72rem\", color: \"#b45309\" }}>\n {fill(labels.directoryView.unresolvedTemplate, { count: g.externalLink.unresolvedCount })}\n </span>\n )}\n </div>\n )}\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\" }} data-testid={`group-member-count-${g.id}`}>\n {fill(labels.groupsView.memberCountTemplate, { count: memberCount })}\n </span>\n {/* 上流が所有する行では操作を **描かない**。無効化したボタンを出すと、\n 押せる操作と見分けがつかず、押せない理由も伝わらない */}\n {g.readOnly ? (\n <span data-testid={`readonly-group-${g.id}`} style={{ fontSize: \"0.75rem\", color: \"#475569\" }}>\n {labels.common.readOnly}\n </span>\n ) : (\n <div>\n <button\n type=\"button\"\n onClick={(event) => openModal({ kind: \"edit\", group: g }, event)}\n disabled={busy}\n aria-busy={busy}\n aria-label={fill(labels.groupsView.editGroupAria, { group: g.name })}\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.common.edit}\n </button>\n <button\n type=\"button\"\n onClick={() => handleDeleteGroup(g.id)}\n disabled={busy}\n aria-busy={busy}\n aria-label={fill(labels.groupsView.deleteGroupAria, { group: g.name })}\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 {labels.common.delete}\n </button>\n </div>\n )}\n </div>\n </div>\n )\n })}\n </div>\n {filteredGroups.length === 0 && (\n <div data-testid=\"groups-empty\" style={{ padding: \"2rem\", textAlign: \"center\", color: \"#475569\", backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n {data.groups.length === 0 ? labels.groupsView.empty : labels.groupsView.emptyFiltered}\n </div>\n )}\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 }}>\n <form\n ref={createGroupRef}\n onSubmit={handleCreateGroup}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"create-group-title\"\n aria-describedby=\"create-group-error\"\n data-testid=\"group-create-modal\"\n style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 id=\"create-group-title\" style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>\n {labels.groupsView.addGroup}\n </h3>\n <ActionError result={actionResult} id=\"create-group-error\" />\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={() => setModal(null)} data-testid=\"cancel-create-group\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n {labels.common.cancel}\n </button>\n <button type=\"submit\" data-testid=\"submit-create-group\" disabled={busy} aria-busy={busy} style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#4338ca\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n {labels.common.create}\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 }}>\n <form\n ref={editGroupRef}\n onSubmit={handleUpdateGroup}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"edit-group-title\"\n aria-describedby=\"edit-group-error\"\n data-testid=\"group-edit-modal\"\n style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 id=\"edit-group-title\" style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>\n {labels.groupsView.editGroup}\n </h3>\n <ActionError result={actionResult} id=\"edit-group-error\" />\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) => setModal({ kind: \"edit\", group: { ...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) => setModal({ kind: \"edit\", group: { ...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={() => setModal(null)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n {labels.common.cancel}\n </button>\n <button type=\"submit\" data-testid=\"submit-edit-group\" disabled={busy} aria-busy={busy} style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#4338ca\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n {labels.common.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 { submit: submitThen, result: actionResult } = useSubmitThen(fetcher)\n const busy = fetcher.state !== \"idle\"\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [selectedGroupId, setSelectedGroupId] = useState<number>(data.groups[0]?.id ?? 0)\n\n // 再検証で対象が消えることがある。消えた ID を握り続けると空の面になる\n const currentGroup = data.groups.find((g) => g.id === selectedGroupId) ?? data.groups[0]\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 submitThen({ intent: \"removeGroupMember\", groupId: String(selectedGroupId), userId: String(userId) })\n } else {\n submitThen({ intent: \"addGroupMember\", groupId: String(selectedGroupId), userId: String(userId) })\n }\n }\n\n return (\n <section data-testid=\"group-members-view-section\" style={{ display: \"grid\", gridTemplateColumns: \"240px 1fr\", gap: \"1.2rem\" }}>\n <h2 style={{ ...heading, gridColumn: \"1 / -1\" }}>{labels.groupMembersView.title}</h2>\n <ActionError result={actionResult} />\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 {g.readOnly ? (\n <span data-testid={`group-upstream-marker-${g.id}`} style={{ marginLeft: \"6px\", fontSize: \"0.7rem\", color: \"#7c3aed\" }}>\n {labels.common.managedUpstream}\n </span>\n ) : null}\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\" }}>{fill(labels.groupMembersView.membersOfTemplate, { group: currentGroup.name })}</h3>\n <span style={{ fontSize: \"0.8rem\", color: \"#64748b\" }}>\n {labels.groupMembersView.groupKeyLabel}: {currentGroup.groupKey}\n </span>\n {currentGroup.readOnly ? (\n <div data-testid=\"group-members-upstream-notice\" role=\"note\" style={{ marginTop: \"0.5rem\", padding: \"0.5rem 0.7rem\", borderRadius: \"6px\", backgroundColor: \"#f5f3ff\", border: \"1px solid #ddd6fe\", color: \"#5b21b6\", fontSize: \"0.8rem\" }}>\n {labels.groupMembersView.upstreamNotice}\n </div>\n ) : null}\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 {currentGroup.readOnly ? (\n isMember ? (\n <span data-testid={`member-readonly-${u.id}`} style={{ fontSize: \"0.75rem\", color: \"#475569\" }}>\n {labels.common.managedUpstream}\n </span>\n ) : null\n ) : (\n <button\n type=\"button\"\n onClick={() => handleToggleMember(u.id, isMember)}\n disabled={busy}\n aria-busy={busy}\n aria-label={fill(isMember ? labels.groupMembersView.actionRemoveAria : labels.groupMembersView.actionAddAria, { user: u.displayName })}\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 )}\n </div>\n )\n })}\n </div>\n </>\n ) : (\n <div style={{ color: \"#475569\", textAlign: \"center\", padding: \"2rem\" }}>{labels.groupMembersView.selectPrompt}</div>\n )}\n </div>\n </section>\n )\n}\n\n/** Badge colours per allowlist kind. 許可種別ごとのバッジ配色。 */\nfunction allowlistBadgeStyle(type: AuthAllowlistEntry[\"type\"]): { backgroundColor: string; color: string } {\n if (type === \"email\") return { backgroundColor: \"#e0f2fe\", color: \"#0369a1\" }\n if (type === \"domain\") return { backgroundColor: \"#fef3c7\", color: \"#b45309\" }\n return { backgroundColor: \"#ede9fe\", color: \"#6d28d9\" }\n}\n\n/** Badge text per allowlist kind. 許可種別ごとのバッジ文言。 */\nfunction allowlistTypeLabel(type: AuthAllowlistEntry[\"type\"], labels: UserAdminLabels): string {\n if (type === \"email\") return labels.allowlistView.emailTypeShort\n if (type === \"domain\") return labels.allowlistView.domainTypeShort\n return labels.allowlistView.groupTypeShort\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 { submit: submitThen, result: actionResult } = useSubmitThen(fetcher)\n const busy = fetcher.state !== \"idle\"\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 const sent = { pattern, description }\n submitThen({ intent: \"addAllowlistEntry\", pattern: sent.pattern, type, description: sent.description }, () => {\n setPattern((current) => (current === sent.pattern ? \"\" : current))\n setDescription((current) => (current === sent.description ? \"\" : current))\n })\n }\n\n const handleRemoveEntry = (id: number) => {\n submitThen({ intent: \"removeAllowlistEntry\", entryId: String(id) })\n }\n\n /** グループ許可は別の表に住むため、別の intent で消す。 */\n const handleRemoveGroupEntry = (id: number) => {\n submitThen({ intent: \"removeAllowlistGroup\", entryId: String(id) })\n }\n\n return (\n <section data-testid=\"allowlist-view-section\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1.2rem\" }}>\n <h2 style={heading}>{labels.allowlistView.title}</h2>\n <ActionError result={actionResult} />\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\" disabled={busy} aria-busy={busy} 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\" }}>{labels.common.id}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.allowlistView.target}</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.common.actions}</th>\n </tr>\n </thead>\n <tbody>\n {data.allowlist.map((item) => (\n <tr key={`${item.type}-${item.id}`} style={{ borderBottom: \"1px solid #f1f5f9\" }} data-testid={`allowlist-row-${item.type}-${item.id}`} data-allowlist-type={item.type}>\n <td style={{ padding: \"10px 14px\", color: \"#475569\" }}>{fill(labels.common.idTemplate, { id: item.id })}</td>\n <td style={{ padding: \"10px 14px\", fontWeight: 600, color: \"#0f172a\" }}>\n {/* パターン許可とグループ許可では正準値が違う。グループはアドレスではなく\n グループそのものが対象なので、名前とキーを見せる */}\n {item.type === \"group\" ? (\n <span data-testid={`allowlist-group-${item.type}-${item.id}`}>\n {item.groupName}\n <span style={{ marginLeft: \"6px\", fontFamily: \"monospace\", fontSize: \"0.78rem\", color: \"#64748b\" }}>{item.groupKey}</span>\n </span>\n ) : (\n <span style={{ fontFamily: \"monospace\" }}>{item.pattern}</span>\n )}\n </td>\n <td style={{ padding: \"10px 14px\" }}>\n <span style={{ fontSize: \"0.75rem\", padding: \"2px 8px\", borderRadius: \"10px\", ...allowlistBadgeStyle(item.type), fontWeight: 600 }}>{allowlistTypeLabel(item.type, labels)}</span>\n </td>\n <td style={{ padding: \"10px 14px\", color: \"#475569\" }}>\n {item.description || labels.common.none}\n {/* 配備全体が所有する行は、どのテナントの管理者でも編集できない */}\n {item.owner.kind === \"deployment\" && (\n <span data-testid={`allowlist-owner-${item.type}-${item.id}`} style={{ marginLeft: \"8px\", fontSize: \"0.72rem\", color: \"#475569\" }}>\n {labels.allowlistView.deploymentOwner}\n </span>\n )}\n </td>\n <td style={{ padding: \"10px 14px\", textAlign: \"right\" }}>\n {/* 変更不能な供給元 (環境変数など) は削除ボタンを出さない。無効化ではなく\n 非描画にするのは、押せないボタンが押せる操作と見分けられないため */}\n {item.readOnly ? (\n <span data-testid={`readonly-allowlist-${item.type}-${item.id}`} style={{ fontSize: \"0.75rem\", color: \"#475569\" }}>\n {labels.common.readOnly}\n </span>\n ) : (\n <button\n type=\"button\"\n onClick={() => {\n // これでしかサインインできない人はログインできなくなる。取り消せない\n if (!window.confirm(labels.allowlistView.confirmDelete)) return\n if (item.type === \"group\") handleRemoveGroupEntry(item.id)\n else handleRemoveEntry(item.id)\n }}\n disabled={busy}\n aria-busy={busy}\n aria-label={fill(labels.allowlistView.deleteAria, { target: item.type === \"group\" ? item.groupName : item.pattern })}\n data-testid={`remove-allowlist-btn-${item.type}-${item.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #fee2e2\", backgroundColor: \"#fff5f5\", color: \"#b91c1c\", fontSize: \"0.78rem\", cursor: \"pointer\" }}>\n {labels.common.delete}\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: \"#475569\" }}>\n {labels.allowlistView.empty}\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n </section>\n )\n}\n\n/**\n * View for the upstream directory: what is linked, how fresh it is, and the controls that change it.\n * 上流ディレクトリのビュー。何が連携され、どれだけ新しく、それを変える操作を提供する。\n *\n * このタブが無いと、連携の作成・解除・一時停止・即時同期・グループ単位のサインイン許可という\n * 5 つの操作にどこからも到達できない。実装されているのに押せる場所が無い状態は、実装が無いのと\n * 変わらないうえ、「許可を消せるが作れない」という片肺の運用を強いる。\n */\nexport function DirectoryView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const { submit: submitThen, result: actionResult } = useSubmitThen(fetcher)\n const labels = data.labels ?? defaultUserAdminLabels\n const L = labels.directoryView\n\n const linkable = data.groups.filter((g) => !g.externalLink)\n const [groupId, setGroupId] = useState(\"\")\n const [externalKey, setExternalKey] = useState(\"\")\n const [membershipMode, setMembershipMode] = useState<AuthDirectoryMembershipMode>(\"direct\")\n\n const linked = data.groups.filter((g) => g.externalLink)\n const allowedGroupIds = new Set(data.allowlist.filter((e) => e.type === \"group\").map((e) => e.groupId))\n const allowlistEntryOf = (id: number) => data.allowlist.find((e) => e.type === \"group\" && e.groupId === id)\n const busy = fetcher.state !== \"idle\"\n // 「要求しました」を出す対象。押しても何も変わらないと、何度も押される\n const [queuedGroupId, setQueuedGroupId] = useState<number | null>(null)\n // 供給元が判らないまま欄を出すと、送信は必ずサーバー側の内部文言で失敗する\n const canLink = data.directoryStatus.isEnabled && data.directoryStatus.provider !== \"\"\n\n /** 連携を作る。上流キーは運用者が知っている値 (グループアドレス) で受け取る。 */\n const handleLink = (e: React.FormEvent) => {\n e.preventDefault()\n if (!(groupId && externalKey)) return\n // 供給元は配備の設定から来る。ここに特定の値を書くと、別の上流を使う配備で作った\n // リンクが同期側で恒久的に拒否される\n const sentKey = externalKey\n submitThen({ intent: \"linkDirectoryGroup\", groupId, provider: data.directoryStatus.provider, externalKey: sentKey, membershipMode }, () => {\n setExternalKey((current) => (current === sentKey ? \"\" : current))\n // 連携済みになった行は候補から消える。選択を残すと、表示は空欄なのに前の\n // グループへ送信される\n setGroupId(\"\")\n })\n }\n\n return (\n <section data-testid=\"directory-view-section\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1rem\" }}>\n <h2 style={heading}>{L.title}</h2>\n <ActionError result={actionResult} />\n\n {!canLink && (\n <p data-testid=\"directory-not-wired\" style={{ margin: 0, padding: \"0.7rem 1rem\", borderRadius: \"6px\", border: \"1px solid #e2e8f0\", backgroundColor: \"#f8fafc\", color: \"#475569\", fontSize: \"0.85rem\" }}>\n {L.notWiredNotice}\n </p>\n )}\n\n {canLink && linkable.length === 0 && (\n <p data-testid=\"directory-nothing-to-link\" style={{ margin: 0, padding: \"0.7rem 1rem\", borderRadius: \"6px\", border: \"1px solid #e2e8f0\", backgroundColor: \"#f8fafc\", color: \"#475569\", fontSize: \"0.85rem\" }}>\n {L.nothingToLink}\n </p>\n )}\n\n {canLink && linkable.length > 0 && (\n <form onSubmit={handleLink} data-testid=\"directory-link-form\" style={{ display: \"flex\", gap: \"0.75rem\", flexWrap: \"wrap\", alignItems: \"flex-end\", backgroundColor: \"#ffffff\", padding: \"1rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.25rem\" }}>\n <label htmlFor=\"directory-group-select-input\" style={fieldLabel}>\n {L.selectGroup}\n </label>\n <select id=\"directory-group-select-input\" data-testid=\"directory-group-select\" value={groupId} onChange={(e) => setGroupId(e.target.value)} style={{ padding: \"0.5rem\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\" }}>\n <option value=\"\">{L.selectGroup}</option>\n {linkable.map((g) => (\n <option key={g.id} value={String(g.id)}>\n {fill(L.groupOptionTemplate, { name: g.name, key: g.groupKey })}\n </option>\n ))}\n </select>\n </div>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.25rem\", flex: 1, minWidth: \"220px\" }}>\n <label htmlFor=\"directory-external-key-input\" style={fieldLabel}>\n {L.externalKey}\n </label>\n <input\n id=\"directory-external-key-input\"\n type=\"email\"\n required\n data-testid=\"directory-external-key\"\n placeholder={L.externalKeyPlaceholder}\n value={externalKey}\n onChange={(e) => setExternalKey(e.target.value)}\n style={{ padding: \"0.5rem\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\" }}\n />\n </div>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.25rem\" }}>\n <label htmlFor=\"directory-membership-mode-input\" style={fieldLabel}>\n {L.membershipMode}\n </label>\n <select\n id=\"directory-membership-mode-input\"\n data-testid=\"directory-membership-mode\"\n value={membershipMode}\n onChange={(e) => setMembershipMode(e.target.value as AuthDirectoryMembershipMode)}\n style={{ padding: \"0.5rem\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\" }}>\n <option value=\"direct\">{L.directMode}</option>\n <option value=\"transitive\">{L.transitiveMode}</option>\n </select>\n </div>\n <button\n type=\"submit\"\n data-testid=\"directory-link-submit\"\n disabled={!(groupId && externalKey) || busy}\n aria-busy={busy}\n style={{ padding: \"0.5rem 1rem\", borderRadius: \"6px\", border: \"none\", backgroundColor: groupId && externalKey && !busy ? \"#2563eb\" : \"#94a3b8\", color: \"#fff\", fontWeight: 600 }}>\n {L.link}\n </button>\n </form>\n )}\n\n {linked.length === 0 ? (\n <div data-testid=\"directory-empty\" style={{ padding: \"2rem\", textAlign: \"center\", color: \"#64748b\", backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n {L.empty}\n </div>\n ) : (\n <table data-testid=\"directory-table\" style={{ width: \"100%\", borderCollapse: \"collapse\", backgroundColor: \"#ffffff\", borderRadius: \"8px\", overflow: \"hidden\", border: \"1px solid #e2e8f0\" }}>\n <thead>\n <tr style={{ backgroundColor: \"#f8fafc\", textAlign: \"left\" }}>\n <th style={cell}>{labels.groupsView.name}</th>\n <th style={cell}>{L.externalKey}</th>\n <th style={cell}>{L.membershipMode}</th>\n <th style={cell}>{L.lastSynced}</th>\n <th style={cell}>{labels.common.status}</th>\n <th style={cell}>{labels.common.actions}</th>\n </tr>\n </thead>\n <tbody>\n {linked.map((g) => {\n const link = g.externalLink as NonNullable<AuthGroup[\"externalLink\"]>\n const allowed = allowedGroupIds.has(g.id)\n return (\n <tr key={g.id} data-testid={`directory-row-${g.id}`} data-sync-enabled={String(link.isSyncEnabled)} style={{ borderTop: \"1px solid #e2e8f0\" }}>\n <td style={cell}>\n {g.name}\n <div style={{ fontSize: \"0.75rem\", color: \"#64748b\" }}>{g.groupKey}</div>\n </td>\n <td style={cell} data-testid={`directory-external-${g.id}`}>\n {link.externalKey}\n </td>\n <td style={cell}>{link.membershipMode === \"direct\" ? L.directMode : L.transitiveMode}</td>\n <td style={cell} data-testid={`directory-synced-${g.id}`}>\n {link.syncedAt ? formatDateTime(link.syncedAt, labels) : L.neverSynced}\n {link.unresolvedCount > 0 ? (\n <div style={{ fontSize: \"0.75rem\", color: \"#b45309\" }} data-testid={`directory-unresolved-${g.id}`}>\n {fill(L.unresolvedTemplate, { count: link.unresolvedCount })}\n </div>\n ) : null}\n </td>\n <td style={cell}>\n <span\n data-testid={`directory-status-${g.id}`}\n style={{ fontSize: \"0.75rem\", padding: \"2px 8px\", borderRadius: \"10px\", backgroundColor: link.isSyncEnabled ? \"#dcfce7\" : \"#fee2e2\", color: link.isSyncEnabled ? \"#166534\" : \"#b91c1c\", fontWeight: 600 }}>\n {link.isSyncEnabled ? L.syncEnabled : L.syncPaused}\n </span>\n {link.syncError ? (\n <div style={{ fontSize: \"0.75rem\", color: \"#b91c1c\", marginTop: \"0.25rem\" }} data-testid={`directory-error-${g.id}`}>\n {link.syncError}\n </div>\n ) : null}\n </td>\n {/* td 自体を flex にすると表セルの役割が失われ、列と見出しの対応が切れる */}\n <td style={cell}>\n <div style={{ display: \"flex\", gap: \"0.35rem\", flexWrap: \"wrap\" }}>\n <button\n type=\"button\"\n data-testid={`directory-sync-now-${g.id}`}\n disabled={busy}\n aria-busy={busy}\n aria-label={fill(queuedGroupId === g.id ? L.syncQueuedAria : L.syncNowAria, { group: g.name })}\n onClick={() => submitThen({ intent: \"requestDirectorySync\", groupId: String(g.id) }, () => setQueuedGroupId(g.id))}\n style={smallButton}>\n {queuedGroupId === g.id ? L.syncQueued : L.syncNow}\n </button>\n <button\n type=\"button\"\n data-testid={`directory-toggle-sync-${g.id}`}\n disabled={busy}\n aria-busy={busy}\n aria-label={fill(link.isSyncEnabled ? L.pauseAria : L.resumeAria, { group: g.name })}\n onClick={() => {\n setQueuedGroupId(null)\n submitThen({ intent: \"setDirectorySyncEnabled\", groupId: String(g.id), isSyncEnabled: String(!link.isSyncEnabled) })\n }}\n style={smallButton}>\n {link.isSyncEnabled ? L.pause : L.resume}\n </button>\n <button\n type=\"button\"\n data-testid={`directory-allow-${g.id}`}\n disabled={busy}\n aria-busy={busy}\n aria-label={fill(allowed ? L.disallowGroupAria : L.allowGroupAria, { group: g.name })}\n onClick={() => {\n const entry = allowlistEntryOf(g.id)\n if (allowed && entry) {\n // 取り消しはサインインを止める。追加は元に戻すだけなので確認しない\n if (!window.confirm(L.confirmDisallow)) return\n submitThen({ intent: \"removeAllowlistGroup\", entryId: String(entry.id) })\n } else {\n submitThen({ intent: \"addAllowlistGroup\", groupId: String(g.id) })\n }\n }}\n style={smallButton}>\n {allowed ? L.disallowGroup : L.allowGroup}\n </button>\n <button\n type=\"button\"\n data-testid={`directory-unlink-${g.id}`}\n disabled={busy}\n aria-busy={busy}\n aria-label={fill(L.unlinkAria, { group: g.name })}\n onClick={() => {\n // 台帳とグループ単位の許可が失われる。取り消せないので必ず確認する\n if (!window.confirm(L.confirmUnlink)) return\n submitThen({ intent: \"unlinkDirectoryGroup\", groupId: String(g.id) })\n }}\n style={{ ...smallButton, color: \"#b91c1c\", borderColor: \"#fecaca\" }}>\n {L.unlink}\n </button>\n </div>\n </td>\n </tr>\n )\n })}\n </tbody>\n </table>\n )}\n </section>\n )\n}\n\n/** フォーム項目の見出し。プレースホルダは入力すると消えるため名前にならない。 */\nconst fieldLabel: React.CSSProperties = { fontSize: \"0.78rem\", color: \"#475569\", fontWeight: 600 }\n/** 各ビューの見出し。段階を飛ばさないよう h2 に固定する。 */\nconst heading: React.CSSProperties = { margin: 0, fontSize: \"1.05rem\", fontWeight: 700, color: \"#0f172a\" }\n/** 表セルの共通スタイル。 */\nconst cell: React.CSSProperties = { padding: \"0.6rem 0.75rem\", fontSize: \"0.85rem\", verticalAlign: \"top\" }\n/** 行内の小さな操作ボタン。 */\nconst smallButton: React.CSSProperties = { padding: \"0.25rem 0.6rem\", fontSize: \"0.75rem\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#fff\", cursor: \"pointer\" }\n\nconst SEGMENT_VIEWS: Record<string, () => React.ReactElement> = {\n users: UsersView,\n groups: GroupsView,\n group_members: GroupMembersView,\n allowlist: AllowlistView,\n directory: DirectoryView,\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 // 素のオブジェクト参照だと `segment=\"constructor\"` が真値を返し、画面が描けなくなる\n const View = Object.hasOwn(SEGMENT_VIEWS, segment) ? SEGMENT_VIEWS[segment] : UsersView\n\n return (\n <UserAdminShell renderHeader={renderHeader}>\n <View />\n </UserAdminShell>\n )\n}\n"],"mappings":"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","names":["admin_exports","__export","AllowlistView","AuthUserAdminAppView","DirectoryView","GroupMembersView","GroupsView","UserAdminShell","UsersView","createInMemoryUserAdminStore","createUserAdminApp","defaultUserAdminLabels","jaUserAdminLabels","resolveUserAdminLabels","__toCommonJS","defaultUserAdminLabels","jaUserAdminLabels","resolveUserAdminLabels","over","d","import_react_router","createInMemoryUserAdminStore","initial","users","groups","groupMembers","allowlist","data","newUser","u","id","idx","isActive","newGroup","g","m","groupId","userId","existing","newMember","newEntry","a","description","group","isForbidden","error","requireAllowlistType","formData","raw","INTENT_ACCESS","requireId","field","n","requireText","value","requireBoolean","requireMembershipMode","optionalText","createUserAdminApp","config","labels","resolveUserAdminLabels","loader","request","pathSegments","segment","directoryStatus","myPermissions","e","action","intent","access","isDenied","denied","dispatch","rawActive","type","import_react_router","fill","template","values","text","key","value","formatDateTime","iso","labels","normalized","import_jsx_runtime","GroupPill","label","summary","DirectorySyncBanner","status","labels","groups","L","warnings","fill","at","formatDateTime","described","id","group","g","reason","w","UserAdminShell","children","renderHeader","data","segment","defaultUserAdminLabels","myPermissions","resourceLabels","labelFor","resourceKey","tabs","hasLinkedGroup","permEntries","getSummary","perm","unavailableSummary","annotation","resKey","tab","isActive","import_react","import_react_router","import_jsx_runtime","ActionError","raw","id","result","useSubmitThen","fetcher","pending","discarded","setDiscarded","onSuccess","payload","useModalBehavior","isOpen","onClose","openerRef","ref","closeRef","panel","opener","focusables","onKeyDown","event","items","first","last","active","outside","UsersView","data","submitThen","resetResult","actionResult","labels","defaultUserAdminLabels","searchQuery","setSearchQuery","modal","setModal","editingUser","isCreateOpen","openModal","next","newDisplayName","setNewDisplayName","newEmail","setNewEmail","createUserRef","editUserRef","busy","filteredUsers","u","handleToggleActive","user","handleCreateUser","e","sentName","sentEmail","current","handleUpdateUser","heading","fill","GroupsView","editingGroup","newGroupKey","setNewGroupKey","newName","setNewName","newDescription","setNewDescription","createGroupRef","editGroupRef","filteredGroups","handleCreateGroup","sent","handleUpdateGroup","handleDeleteGroup","groupId","memberCount","m","GroupMembersView","selectedGroupId","setSelectedGroupId","currentGroup","g","currentMemberUserIds","handleToggleMember","userId","isMember","isSelected","allowlistBadgeStyle","type","allowlistTypeLabel","AllowlistView","pattern","setPattern","setType","description","setDescription","handleAddEntry","handleRemoveEntry","handleRemoveGroupEntry","item","DirectoryView","L","linkable","setGroupId","externalKey","setExternalKey","membershipMode","setMembershipMode","linked","allowedGroupIds","allowlistEntryOf","queuedGroupId","setQueuedGroupId","canLink","handleLink","sentKey","fieldLabel","cell","link","allowed","formatDateTime","smallButton","entry","SEGMENT_VIEWS","AuthUserAdminAppView","renderHeader","segment","View","UserAdminShell"]}
1
+ {"version":3,"sources":["../src/admin.ts","../src/admin/labels.ts","../src/admin/server.ts","../src/admin/ui.tsx","../src/admin/format.ts","../src/admin/views.tsx"],"sourcesContent":["/**\n * @aiquants/auth-react-router/admin — user administration UI and store adapters.\n * ユーザー管理 UI およびストアアダプタのバレル。\n */\nexport * from \"./admin/app\"\n","/**\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 * BCP 47 locale used to format dates. 日付整形に使う BCP 47 ロケール。\n *\n * 実行環境任せにするとサーバーとブラウザで別の文字列になり、水和が壊れる。\n */\n locale: string\n /** IANA time zone used to format dates. 日付整形に使う IANA タイムゾーン。 */\n timeZone: string\n /**\n * Strings shared by more than one view. 複数のビューが共有する文言。\n *\n * ビューごとに同じ語を持たせると、ある画面の「削除」だけを言い換えることが **できなく\n * なる** (実装がどのビューのキーを読んでいるかは呼び出し側から見えない)。共有する語は\n * ここ 1 か所に置く。\n */\n common: {\n create: string\n edit: string\n delete: string\n save: string\n cancel: string\n actions: string\n status: string\n id: string\n /** 変更不能な供給元 (環境変数など) 由来の行に出す表示。 */\n readOnly: string\n /** 上流所有のグループに出す編集不可の表示。 */\n managedUpstream: string\n /** `#{id}` の表示。記号の位置は言語で変わる。 */\n idTemplate: string\n /** 値が無いことの表示 (説明欄など)。 */\n none: string\n }\n /** アクションが拒否・失敗したときの文言。 */\n errors: {\n /**\n * Shown when the actor lacks permission for the attempted operation.\n * 操作の権限が無いときに出す文言。\n *\n * ガードの例外メッセージをそのまま返してはならない。テナント ID・アプリキー・\n * 拒否理由といった内部の語彙が、権限を持たない相手にそのまま渡る。\n */\n forbidden: string\n }\n /** シェル (タブ枠) の見出し。 */\n heading: string\n /** シェルが描く自己権限バッジの文言。 */\n permissions: {\n label: string\n canWrite: string\n canRead: string\n none: string\n /** 未結線・取得失敗・付与ゼロのいずれも「不明」であり、許可ではない。 */\n unknown: string\n }\n tabs: {\n users: string\n groups: string\n groupMembers: string\n allowlist: string\n directory: string\n }\n usersView: {\n title: string\n searchPlaceholder: string\n /** 検索欄の読み上げ名。プレースホルダは入力すると消えるため名前にならない。 */\n searchLabel: string\n addUser: string\n editUser: string\n displayName: string\n email: string\n active: string\n inactive: string\n activate: string\n deactivate: string\n /** 行ごとの読み上げ名。`{user}` を利用者名へ置換する。 */\n editUserAria: string\n activateAria: string\n deactivateAria: string\n /** 無効化はサインインを止める。確認を取る。 */\n confirmDeactivate: string\n empty: string\n }\n groupsView: {\n /** 検索語に一致しないときの説明。空状態と混ぜると「消えた」と誤読される。 */\n emptyFiltered: string\n title: string\n searchPlaceholder: string\n searchLabel: string\n addGroup: string\n editGroup: string\n groupKey: string\n name: string\n description: string\n /** 所属人数。`{count}` を人数へ置換する。 */\n memberCountTemplate: string\n confirmDelete: string\n /** 行ごとの読み上げ名。`{group}` をグループ名へ置換する。 */\n editGroupAria: string\n deleteGroupAria: string\n noDescription: string\n groupKeyPlaceholder: string\n namePlaceholder: string\n empty: string\n }\n groupMembersView: {\n title: string\n selectGroup: string\n selectPrompt: string\n actionAdd: string\n actionRemove: string\n /** 行ごとの読み上げ名。`{user}` を利用者名へ置換する。 */\n actionAddAria: string\n actionRemoveAria: string\n /** 上流所有グループで編集を出さない理由。押せない理由を書かないと故障に見える。 */\n upstreamNotice: string\n /** 見出し。`{group}` をグループ名へ置換する。語順が言語で変わるため位置指定にする。 */\n membersOfTemplate: string\n groupKeyLabel: 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 /** グループ単位の許可。上流ディレクトリの所属で判定される。 */\n groupTypeShort: string\n /** 対象列の見出し。パターンとグループが混在するため「パターン」ではない。 */\n target: string\n description: string\n /** 配備全体が所有する行 (環境変数由来) の所有者表示。 */\n deploymentOwner: string\n /** 許可の取り消しはサインインを止める。確認を取る。 */\n confirmDelete: string\n /** 行ごとの読み上げ名。`{target}` を対象へ置換する。 */\n deleteAria: string\n empty: string\n }\n directoryView: {\n title: string\n membershipMode: string\n directMode: string\n transitiveMode: string\n lastSynced: string\n neverSynced: string\n /** 上流に居るがローカルユーザーへ解決できていない人数。`{count}` を人数へ置換する。 */\n unresolvedTemplate: string\n syncPaused: string\n syncEnabled: string\n syncNow: string\n syncQueued: string\n /** 同期が滞っていることの警告。停止はセキュリティ事象である。 */\n staleWarning: string\n disabledWarning: string\n /** 停止件数。`{paused}` / `{total}` を件数へ置換する。 */\n pausedTemplate: string\n /** 失敗したグループの列挙。`{groups}` を名前の並びへ置換する。 */\n erroredTemplate: string\n /** 失敗理由付きの 1 件。`{group}` / `{reason}`。 */\n erroredItemTemplate: string\n /** 列挙の区切り。言語で変わる。 */\n listSeparator: string\n /** 滞留警告に添える最終成功時刻。`{at}`。 */\n oldestSyncedTemplate: string\n /** 未処理の同期要求。`{count}`。 */\n pendingTemplate: string\n /** 連携先が 1 つも残っていないときの説明。 */\n nothingToLink: string\n /** 未結線の配備で操作を出さない理由。 */\n notWiredNotice: string\n /** 行ごとの読み上げ名。`{group}` をグループ名へ置換する。 */\n syncNowAria: string\n syncQueuedAria: string\n pauseAria: string\n resumeAria: string\n allowGroupAria: string\n disallowGroupAria: string\n unlinkAria: string\n /** グループ単位のサインイン許可を外す確認。 */\n confirmDisallow: string\n link: string\n unlink: string\n externalKey: string\n empty: string\n /** 連携するグループを選ぶ欄。 */\n selectGroup: string\n /** 上流グループのアドレス入力。 */\n externalKeyPlaceholder: string\n /** 連携解除の確認。台帳と許可設定が消える。 */\n confirmUnlink: string\n /** 同期の一時停止・再開。 */\n pause: string\n resume: string\n /** 許可グループに加える / 外す。 */\n allowGroup: string\n disallowGroup: string\n /** グループ名と鍵の併記。`{name}` / `{key}`。 */\n groupOptionTemplate: string\n }\n}\n\n/**\n * Package default labels (English).\n * パッケージ既定ラベル (英語)。\n */\nexport const defaultUserAdminLabels: UserAdminLabels = {\n title: \"Users & Groups\",\n locale: \"en-US\",\n timeZone: \"UTC\",\n heading: \"\\u{1F464} User administration\",\n common: {\n create: \"Create\",\n edit: \"Edit\",\n delete: \"Delete\",\n save: \"Save\",\n cancel: \"Cancel\",\n actions: \"Actions\",\n status: \"Status\",\n id: \"ID\",\n readOnly: \"Read-only\",\n managedUpstream: \"Managed upstream\",\n idTemplate: \"#{id}\",\n none: \"-\",\n },\n errors: {\n forbidden: \"You do not have permission for this operation.\",\n },\n permissions: {\n label: \"Your permissions:\",\n canWrite: \"Editable\",\n canRead: \"Read only\",\n none: \"No access\",\n unknown: \"Unknown\",\n },\n tabs: {\n users: \"\\u{1F464} Users\",\n groups: \"\\u{1F465} Groups\",\n groupMembers: \"\\u{1F517} Members\",\n allowlist: \"\\u2709\\uFE0F Allowlist\",\n directory: \"\\u{1F504} Directory\",\n },\n usersView: {\n title: \"Users\",\n searchPlaceholder: \"Search by name or email...\",\n searchLabel: \"Search users\",\n addUser: \"\\uFF0B Add user\",\n editUser: \"Edit user\",\n displayName: \"Name\",\n email: \"Email\",\n active: \"Active\",\n inactive: \"Inactive\",\n activate: \"Activate\",\n deactivate: \"Deactivate\",\n editUserAria: \"Edit {user}\",\n activateAria: \"Activate {user}\",\n deactivateAria: \"Deactivate {user}\",\n confirmDeactivate: \"Deactivate this user? They will no longer be able to sign in.\",\n empty: \"No matching users\",\n },\n groupsView: {\n emptyFiltered: \"No group matches this search\",\n title: \"Groups\",\n searchPlaceholder: \"Search by group name or key...\",\n searchLabel: \"Search groups\",\n addGroup: \"\\uFF0B Add group\",\n editGroup: \"Edit group\",\n groupKey: \"Group key\",\n name: \"Name\",\n description: \"Description\",\n memberCountTemplate: \"{count} members\",\n confirmDelete: \"Delete this group?\",\n editGroupAria: \"Edit {group}\",\n deleteGroupAria: \"Delete {group}\",\n noDescription: \"No description\",\n groupKeyPlaceholder: \"e.g. dev-team\",\n namePlaceholder: \"e.g. Development team\",\n empty: \"No groups registered\",\n },\n groupMembersView: {\n title: \"Group members\",\n selectGroup: \"Select a group\",\n selectPrompt: \"Select a group to manage its members\",\n actionAdd: \"Add\",\n actionRemove: \"Remove\",\n actionAddAria: \"Add {user} to this group\",\n actionRemoveAria: \"Remove {user} from this group\",\n upstreamNotice: \"Membership of this group is owned by the upstream directory. Edit it there \\u2014 a local change would be reverted at the next synchronization.\",\n membersOfTemplate: \"Members of {group}\",\n groupKeyLabel: \"Group key\",\n },\n allowlistView: {\n title: \"Sign-in allowlist\",\n addEntry: \"\\uFF0B 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 groupTypeShort: \"Group\",\n target: \"Target\",\n description: \"Description\",\n deploymentOwner: \"Deployment-wide\",\n confirmDelete: \"Remove this allowlist entry? Anyone who could sign in only through it will be locked out.\",\n deleteAria: \"Remove the allowlist entry for {target}\",\n empty: \"No allowlist entries registered\",\n },\n directoryView: {\n title: \"Directory synchronization\",\n membershipMode: \"Membership\",\n directMode: \"Direct only\",\n transitiveMode: \"Nested expanded\",\n lastSynced: \"Last synced\",\n neverSynced: \"Never\",\n unresolvedTemplate: \"{count} unresolved\",\n syncPaused: \"Paused\",\n syncEnabled: \"Active\",\n syncNow: \"Sync now\",\n syncQueued: \"Queued\",\n staleWarning: \"Synchronization is overdue. Until it runs, people removed upstream keep their access.\",\n disabledWarning: \"Directory synchronization is not wired for this deployment. Existing memberships remain in force and are no longer being refreshed.\",\n pausedTemplate: \"{paused} of {total} links are paused\",\n erroredTemplate: \"Last synchronization failed for {groups}\",\n erroredItemTemplate: \"{group}: {reason}\",\n listSeparator: \", \",\n oldestSyncedTemplate: \"oldest successful sync: {at}\",\n pendingTemplate: \"{count} sync requests are queued\",\n nothingToLink: \"Every group is already linked to a directory.\",\n notWiredNotice: \"Directory synchronization is not wired for this deployment, so links cannot be created or changed here.\",\n syncNowAria: \"Synchronize {group} now\",\n syncQueuedAria: \"A synchronization of {group} is queued\",\n pauseAria: \"Pause synchronization of {group}\",\n resumeAria: \"Resume synchronization of {group}\",\n allowGroupAria: \"Allow members of {group} to sign in\",\n disallowGroupAria: \"Stop allowing members of {group} to sign in\",\n unlinkAria: \"Unlink {group} from the directory\",\n confirmDisallow: \"Stop allowing this group to sign in? Anyone who could sign in only through it will be locked out.\",\n link: \"Link to a directory group\",\n unlink: \"Unlink\",\n externalKey: \"Upstream group\",\n empty: \"No groups are linked to a directory\",\n selectGroup: \"Group to link\",\n externalKeyPlaceholder: \"e.g. dev-team@example.com\",\n confirmUnlink: \"Unlink this group? Its member ledger and group-based sign-in permission are removed.\",\n pause: \"Pause\",\n resume: \"Resume\",\n allowGroup: \"Allow sign-in\",\n disallowGroup: \"Stop allowing sign-in\",\n groupOptionTemplate: \"{name} ({key})\",\n },\n}\n\n/**\n * Standard Japanese labels for user administration (opt-in).\n * ユーザー管理の標準日本語ラベル (アプリ側で明示注入して使う)。\n */\nexport const jaUserAdminLabels: UserAdminLabels = {\n title: \"ユーザー・グループ管理\",\n locale: \"ja-JP\",\n timeZone: \"Asia/Tokyo\",\n heading: \"\\u{1F464} ユーザー管理\",\n common: {\n create: \"作成\",\n edit: \"編集\",\n delete: \"削除\",\n save: \"保存\",\n cancel: \"キャンセル\",\n actions: \"操作\",\n status: \"状態\",\n id: \"ID\",\n readOnly: \"変更不可\",\n managedUpstream: \"上流が所有\",\n idTemplate: \"#{id}\",\n none: \"-\",\n },\n errors: {\n forbidden: \"この操作を行う権限がありません。\",\n },\n permissions: {\n label: \"あなたの権限:\",\n canWrite: \"編集可\",\n canRead: \"閲覧のみ\",\n none: \"権限なし\",\n unknown: \"権限不明\",\n },\n tabs: {\n users: \"\\u{1F464} ユーザー一覧\",\n groups: \"\\u{1F465} ユーザーグループ\",\n groupMembers: \"\\u{1F517} グループメンバー\",\n allowlist: \"\\u2709\\uFE0F アクセス許可リスト\",\n directory: \"\\u{1F504} ディレクトリ連携\",\n },\n usersView: {\n title: \"ユーザー管理\",\n searchPlaceholder: \"表示名や Email で検索...\",\n searchLabel: \"ユーザーを検索\",\n addUser: \"\\uFF0B ユーザー追加\",\n editUser: \"ユーザー編集\",\n displayName: \"表示名\",\n email: \"Email\",\n active: \"有効\",\n inactive: \"無効\",\n activate: \"有効化\",\n deactivate: \"無効化\",\n editUserAria: \"{user} を編集\",\n activateAria: \"{user} を有効化\",\n deactivateAria: \"{user} を無効化\",\n confirmDeactivate: \"このユーザーを無効化しますか? 以後サインインできなくなります。\",\n empty: \"該当するユーザーが見つかりません\",\n },\n groupsView: {\n emptyFiltered: \"検索条件に一致するグループはありません\",\n title: \"ユーザーグループ管理\",\n searchPlaceholder: \"グループ名やキーで検索...\",\n searchLabel: \"グループを検索\",\n addGroup: \"\\uFF0B グループ追加\",\n editGroup: \"グループ編集\",\n groupKey: \"グループキー\",\n name: \"名称\",\n description: \"説明\",\n memberCountTemplate: \"所属 {count} 名\",\n confirmDelete: \"このグループを削除しますか?\",\n editGroupAria: \"{group} を編集\",\n deleteGroupAria: \"{group} を削除\",\n noDescription: \"説明なし\",\n groupKeyPlaceholder: \"例: dev-team\",\n namePlaceholder: \"例: 開発チーム\",\n empty: \"ユーザーグループが登録されていません\",\n },\n groupMembersView: {\n title: \"グループメンバー管理\",\n selectGroup: \"グループ選択\",\n selectPrompt: \"グループを選択してください\",\n actionAdd: \"追加\",\n actionRemove: \"除外\",\n actionAddAria: \"{user} をこのグループへ追加\",\n actionRemoveAria: \"{user} をこのグループから除外\",\n upstreamNotice: \"このグループの所属は上流ディレクトリが所有しています。変更は上流で行ってください (ここで変えても次の同期で元に戻ります)。\",\n membersOfTemplate: \"{group} の所属メンバー\",\n groupKeyLabel: \"グループキー\",\n },\n allowlistView: {\n title: \"サインイン許可リスト\",\n addEntry: \"\\uFF0B パターン追加\",\n pattern: \"パターン (例: @example.com または user@example.com)\",\n type: \"種別\",\n emailType: \"メールアドレス完全一致\",\n domainType: \"ドメイン一致\",\n emailTypeShort: \"メール\",\n domainTypeShort: \"ドメイン\",\n groupTypeShort: \"グループ\",\n target: \"対象\",\n description: \"説明 / 備考\",\n deploymentOwner: \"配備共通\",\n confirmDelete: \"この許可を削除しますか? これでしかサインインできない人はログインできなくなります。\",\n deleteAria: \"{target} の許可を削除\",\n empty: \"アロワリストエントリが登録されていません\",\n },\n directoryView: {\n title: \"ディレクトリ同期\",\n membershipMode: \"所属の展開\",\n directMode: \"直接所属のみ\",\n transitiveMode: \"入れ子を展開\",\n lastSynced: \"最終同期\",\n neverSynced: \"未実行\",\n unresolvedTemplate: \"{count} 件が未解決\",\n syncPaused: \"停止中\",\n syncEnabled: \"有効\",\n syncNow: \"今すぐ同期\",\n syncQueued: \"同期を要求しました\",\n staleWarning: \"同期が滞っています。実行されるまで、上流から外れた人のアクセスは残り続けます。\",\n disabledWarning: \"この配備ではディレクトリ同期が結線されていません。既存の所属はそのまま有効で、更新だけが止まっています。\",\n pausedTemplate: \"連携 {total} 件のうち {paused} 件が停止中\",\n erroredTemplate: \"直近の同期に失敗したグループ: {groups}\",\n erroredItemTemplate: \"{group}: {reason}\",\n listSeparator: \" / \",\n oldestSyncedTemplate: \"最も古い同期成功: {at}\",\n pendingTemplate: \"{count} 件の同期要求が未処理\",\n nothingToLink: \"すべてのグループが既にディレクトリと連携しています。\",\n notWiredNotice: \"この配備ではディレクトリ同期が結線されていないため、ここから連携の作成・変更はできません。\",\n syncNowAria: \"{group} を今すぐ同期\",\n syncQueuedAria: \"{group} の同期を要求済み\",\n pauseAria: \"{group} の同期を一時停止\",\n resumeAria: \"{group} の同期を再開\",\n allowGroupAria: \"{group} のメンバーのサインインを許可\",\n disallowGroupAria: \"{group} のメンバーのサインイン許可を外す\",\n unlinkAria: \"{group} のディレクトリ連携を解除\",\n confirmDisallow: \"このグループのサインイン許可を外しますか? これでしかサインインできない人はログインできなくなります。\",\n link: \"ディレクトリのグループと連携する\",\n unlink: \"連携を解除\",\n externalKey: \"上流グループ\",\n empty: \"ディレクトリと連携しているグループはありません\",\n selectGroup: \"連携するグループ\",\n externalKeyPlaceholder: \"例: dev-team@example.com\",\n confirmUnlink: \"このグループの連携を解除しますか? メンバー台帳とグループ単位のサインイン許可が失われます。\",\n pause: \"一時停止\",\n resume: \"再開\",\n allowGroup: \"サインインを許可\",\n disallowGroup: \"サインイン許可を外す\",\n groupOptionTemplate: \"{name} ({key})\",\n },\n}\n\n/** セクション単位の部分上書き。 */\nexport type PartialUserAdminLabels = {\n title?: string\n locale?: string\n timeZone?: string\n common?: Partial<UserAdminLabels[\"common\"]>\n errors?: Partial<UserAdminLabels[\"errors\"]>\n heading?: string\n permissions?: Partial<UserAdminLabels[\"permissions\"]>\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 directoryView?: Partial<UserAdminLabels[\"directoryView\"]>\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 locale: over.locale ?? d.locale,\n timeZone: over.timeZone ?? d.timeZone,\n heading: over.heading ?? d.heading,\n common: { ...d.common, ...over.common },\n errors: { ...d.errors, ...over.errors },\n permissions: { ...d.permissions, ...over.permissions },\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 directoryView: { ...d.directoryView, ...over.directoryView },\n }\n}\n","import type { AuthAllowlistEntry, AuthDirectoryMembershipMode, AuthDirectoryStatus, AuthGroup, AuthGroupMember, AuthUser } from \"@aiquants/auth-core\"\nimport { data } 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 * Allows every member of an externally-sourced group to sign in.\n * 外部供給グループのメンバー全員にサインインを許可する処理。\n *\n * 判定は上流メンバー台帳を読むため、ローカル管理のグループを渡しても誰にも何も与えない。\n * ストアはそれを拒否すること (無言の no-op を作らない)。\n */\n addAllowlistGroup: (groupId: number, description?: string) => Promise<void>\n removeAllowlistGroup: (id: number) => Promise<void>\n\n /**\n * Links a local group to a group in the upstream directory.\n * ローカルグループを上流ディレクトリのグループへ結び付ける処理。\n *\n * リンク後、そのグループの名称と所属は上流が所有する。ロール割当はローカルのままである。\n */\n linkDirectoryGroup: (groupId: number, data: { provider: string; externalKey: string; membershipMode: AuthDirectoryMembershipMode }) => Promise<void>\n /** Stops following the upstream, keeping the memberships already projected. 連携の解除。 */\n unlinkDirectoryGroup: (groupId: number) => Promise<void>\n /** Pauses or resumes synchronization for one group. 1 グループの同期を停止・再開する処理。 */\n setDirectorySyncEnabled: (groupId: number, isSyncEnabled: boolean) => Promise<void>\n /**\n * Queues a synchronization request. 同期要求をキューへ積む処理。\n *\n * 上流へ到達できるのは同期ジョブだけなので、web からは要求を残すことしかできない。\n *\n * @param groupId `null` requests every linked group of the tenant. `null` はテナント全件。\n */\n requestDirectorySync: (groupId: number | null) => Promise<void>\n /** Health of the synchronization, for the header warning. 同期の健全性 (警告表示用)。 */\n getDirectoryStatus: () => Promise<AuthDirectoryStatus>\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, readOnly: false },\n { id: 2, groupKey: \"sales-dept\", name: \"営業部\", description: \"営業ソリューション担当\", isActive: true, readOnly: false },\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: \"初期管理者\", owner: { kind: \"tenant\", tenantId: \"default\" }, readOnly: false },\n { id: 2, pattern: \"@example.com\", type: \"domain\", description: \"社内ドメイン\", owner: { kind: \"tenant\", tenantId: \"default\" }, readOnly: false },\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 readOnly: false,\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 owner: { kind: \"tenant\", tenantId: \"default\" },\n readOnly: false,\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 // このメモリ実装はパッケージ自身の spec 用のテストシームであり、上流を持たない。\n // 連携面は「結線されていない配備」を表現する形で応答する\n async addAllowlistGroup(groupId, description) {\n const group = groups.find((g) => g.id === groupId)\n if (!group) throw new Error(`Group not found: ${groupId}`)\n allowlist = [...allowlist, { id: Math.max(...allowlist.map((a) => a.id), 0) + 1, type: \"group\", groupId, groupKey: group.groupKey, groupName: group.name, description, owner: { kind: \"tenant\", tenantId: \"default\" }, readOnly: false }]\n },\n async removeAllowlistGroup(id) {\n allowlist = allowlist.filter((a) => !(a.type === \"group\" && a.id === id))\n },\n async linkDirectoryGroup() {\n throw new Error(\"This in-memory store has no upstream directory; inject a real store to link groups.\")\n },\n async unlinkDirectoryGroup() {\n throw new Error(\"This in-memory store has no upstream directory; inject a real store to unlink groups.\")\n },\n async setDirectorySyncEnabled() {\n throw new Error(\"This in-memory store has no upstream directory; inject a real store to pause synchronization.\")\n },\n async requestDirectorySync() {\n throw new Error(\"This in-memory store has no upstream directory; inject a real store to request a sync.\")\n },\n async getDirectoryStatus() {\n return { isEnabled: false, provider: \"\", linkedGroupCount: 0, isStale: false, pausedGroupCount: 0, stalenessThresholdSec: 0, erroredGroupIds: [], pendingRequestCount: 0 }\n },\n }\n}\n\n/**\n * Tells a per-operation authorization denial apart from any other failure, without importing an authz library.\n * 認可ライブラリを import せずに、操作単位の認可拒否を他の失敗と見分ける判定。\n *\n * 特定の実装の例外クラスへ `instanceof` を張ると、そのライブラリに縛られる (`AGENTS.md` の\n * クロスパッケージ・ハードコード禁止)。`status === 403` は HTTP の共通語彙なので、これだけを見る。\n *\n * @param error The thrown value. 送出された値。\n * @returns True when it carries HTTP 403. HTTP 403 を名乗る場合に true。\n */\nfunction isForbidden(error: unknown): boolean {\n // Response でも素の例外でも `status` は共通語彙である。特定の実装の例外クラスへ\n // `instanceof` を張ると、そのライブラリに縛られる\n return typeof error === \"object\" && error !== null && (error as { status?: unknown }).status === 403\n}\n\n/**\n * Reads the allowlist kind, refusing anything but the two declared values.\n * 許可の種別を読み取る処理 (宣言された 2 値以外を拒否する)。\n *\n * 指定外を既定値へ倒すと、綴り違い (`Domain`) や欠落が **完全一致の許可** として登録される。\n * 「ドメイン全体を許可したつもりが 1 アドレスだけだった」は、逆向きより静かに壊れる。\n *\n * @param formData Submitted form. 送信されたフォーム。\n * @returns The declared kind. 宣言された種別。\n * @throws {Error} When the value is absent or unknown. 未指定・未知の値の場合。\n */\nfunction requireAllowlistType(formData: FormData): \"email\" | \"domain\" {\n const raw = String(formData.get(\"type\") ?? \"\")\n if (raw === \"email\" || raw === \"domain\") return raw\n throw new Error(`type は \"email\" または \"domain\" のいずれかで指定してください (受け取った値: ${JSON.stringify(raw)})`)\n}\n\n/** 表示するタブ。未知の値は先頭のタブへ寄せる。 */\nconst SEGMENTS = [\"users\", \"groups\", \"group_members\", \"allowlist\", \"directory\"] as const\n\n/**\n * Derives the segment from a request path, tolerating the router's single-fetch suffix.\n * リクエストパスからセグメントを決める処理 (ルーターの単一フェッチ用の接尾辞を許容する)。\n *\n * ⚠️ クライアント側の遷移では、ルーターは画面の URL ではなく **`<path>.data`** を取りに来る\n * (単一フェッチ)。素朴に末尾セグメントを見ると `\"groups.data\"` になり、どのタブにも一致せず\n * 既定へ落ちる。結果、**URL は変わるのに中身が変わらない** — 全画面再読込でだけ正しく描かれる\n * ため、動作確認では気付きにくい。接尾辞を落としてから判定する。\n *\n * @param pathname Request pathname. リクエストのパス。\n * @returns One of the known segments. 既知のセグメントのいずれか。\n */\nfunction segmentFromPathname(pathname: string): (typeof SEGMENTS)[number] {\n const parts = pathname\n .replace(/\\.data$/, \"\")\n .split(\"/\")\n .filter(Boolean)\n const last = parts[parts.length - 1] ?? \"\"\n return (SEGMENTS as readonly string[]).includes(last) ? (last as (typeof SEGMENTS)[number]) : \"users\"\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 */\n/**\n * The only route-args field this module reads. React Router's full `LoaderFunctionArgs` satisfies it, so\n * route wiring is unchanged — but declaring the minimum keeps callers (and tests) from fabricating fields\n * that are never looked at.\n * このモジュールが実際に読むルート引数の全て。React Router の完全な引数はこれを満たすため結線は不変。\n */\ntype RouteArgs = { request: Request }\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 * Observes a denial that was converted into an in-page error. 画面内エラーへ変換した拒否の観測点。\n *\n * 拒否を画面内に返すと、既定のエラー経路が拾っていたログが消える。管理面への総当たりが\n * 200 番台の列に紛れて見えなくなるため、宿主が記録できる口を開けておく。\n */\n onDenied?: (context: { intent: string; actions: readonly UserAdminAccess[]; error: unknown }) => 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, readonly 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 [\"addAllowlistGroup\", [\"create\"]],\n [\"removeAllowlistGroup\", [\"delete\"]],\n // 連携の開始は、以後の所属の **追加と削除の両方** を上流へ委ねる行為である。所属を足せる\n // だけの主体が「足すことも消すこともできる仕掛け」を据えられてはならない。属性変更でもある\n // ため update も要る\n [\"linkDirectoryGroup\", [\"update\", \"create\", \"delete\"]],\n // 解除は台帳とグループ単位のサインイン許可を **消す**。属性を戻すだけではないので、\n // 削除権限を持たない主体には許さない\n [\"unlinkDirectoryGroup\", [\"update\", \"delete\"]],\n [\"setDirectorySyncEnabled\", [\"update\"]],\n // 書き込みを伴わないが update を要求する。読取権限だけの主体が上流問い合わせを誘発できると、\n // レート制限を外部から枯渇させられる\n [\"requestDirectorySync\", [\"update\"]],\n])\n/**\n * Neutral per-resource permission summary the admin shell renders in its header badge.\n * 管理シェルのヘッダーバッジが描画する、リソース単位の中立な権限サマリ。\n *\n * この形はこのパッケージ自身の契約であり、特定の認可ライブラリの型を import しない\n * (`AGENTS.md` のクロスパッケージ・ハードコード禁止)。`canRead` / `canWrite` / `canDelete` を持つ\n * ビューであれば構造的にそのまま渡せる。\n */\nexport type UserAdminPermission = {\n canRead: boolean\n canWrite: boolean\n canDelete: boolean\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 * Per-request self-permission lookup for the header annotation. Resolving to a map of\n * {@link UserAdminPermission} shows the badge; rejecting shows an explicit \"unknown\" badge — never a\n * permissive one. Any authz library whose view carries `canRead` / `canWrite` / `canDelete` satisfies it.\n * ヘッダー注釈に自分の実効権限を表示するためのリクエスト別権限取得関数。失敗時は「権限不明」表示。\n */\n getMyPermissions?: (request: Request) => Promise<Record<string, UserAdminPermission | undefined>>\n /**\n * Display names for the resource keys the badge shows, e.g. `{ authz_admin: \"ユーザー管理 UI\" }`.\n * バッジに出すリソースキーの表示名。未指定のキーはキー文字列をそのまま表示。\n *\n * 表示名をこのパッケージ側に埋め込むと、別パッケージのリソースキーを名指しすることになる\n * (`AGENTS.md` のクロスパッケージ・ハードコード禁止)。中立な差し込み口として受け取る。\n */\n resourceLabels?: Record<string, string>\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/**\n * Reads an explicit boolean. 明示的な真偽値の読み取り。\n *\n * `\"true\"` / `\"false\"` のみを受け付ける。欠落を既定へ倒すと、破壊側 (停止) が既定方向になる。\n *\n * @throws when the field is absent or not one of the two literals. 明示指定でない場合。\n */\nfunction requireBoolean(formData: FormData, field: string): boolean {\n const raw = String(formData.get(field) ?? \"\")\n if (raw !== \"true\" && raw !== \"false\") throw new Error(`Invalid or missing \"${field}\"`)\n return raw === \"true\"\n}\n\n/**\n * Reads the membership expansion mode. 所属展開モードの読み取り。\n *\n * 既定へ倒さない。入れ子展開が黙って有効になると権限が静かに広がり、黙って無効になると\n * 入れ子側の全員が削除候補になる。どちらの向きにも倒さず、明示させる。\n *\n * @throws when the value is not one of the two modes. 2 値でない場合。\n */\nfunction requireMembershipMode(formData: FormData): AuthDirectoryMembershipMode {\n const raw = String(formData.get(\"membershipMode\") ?? \"\")\n if (raw !== \"direct\" && raw !== \"transitive\") throw new Error('Invalid or missing \"membershipMode\"')\n return raw\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 }: RouteArgs) {\n // 読取ガード: 拒否は throw されるためここで処理が打ち切られる\n await config.guards.requireAccess(request, { action: \"read\", intent: \"loader\" })\n const segment = segmentFromPathname(new URL(request.url).pathname)\n\n const [users, groups, groupMembers, allowlist, directoryStatus, myPermissions] = await Promise.all([\n config.store.listUsers(),\n config.store.listGroups(),\n config.store.listGroupMembers(),\n config.store.listAllowlist(),\n config.store.getDirectoryStatus(),\n // 取得できない場合は null (= 権限不明)。空マップに倒すと「付与ゼロ」と区別できなくなる\n config.getMyPermissions\n ? config.getMyPermissions(request).catch((e) => {\n console.error(\"[@aiquants/auth-react-router] getMyPermissions failed; rendering the permission badge as unknown:\", e)\n return null\n })\n : Promise.resolve(null),\n ])\n\n return {\n resourceLabels: config.resourceLabels,\n segment,\n users,\n groups,\n groupMembers,\n allowlist,\n directoryStatus,\n myPermissions,\n labels,\n }\n }\n\n async function action({ request }: RouteArgs) {\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 /** 403 かどうか。Response でも素の例外でも `status` は共通語彙である。 */\n const isDenied = (error: unknown) => isForbidden(error)\n /**\n * Turns a denial into an in-page error without leaking the guard's own message.\n * 拒否を、ガードの内部文言を漏らさずに画面内エラーへ変える処理。\n */\n const denied = (error: unknown) => {\n try {\n config.guards.onDenied?.({ intent, actions: access, error })\n } catch {\n // 記録に失敗しても拒否の応答は返す。ログの都合で操作の結果が変わってはならない\n }\n // 監視から消えないよう、状態は 403 のまま返す。画面は data() なので生き残る\n return data({ ok: false, error: labels.errors.forbidden }, { status: 403 })\n }\n\n // 操作ごとの CRUD 種別でガードする。複数を要求する intent は **すべて** 通ること\n try {\n for (const action of access) await config.guards.requireAccess(request, { action, intent })\n } catch (error) {\n // 403 は「この操作だけができない」であり、画面ごと落とすと **他の操作まで巻き添え** に\n // なる (閲覧と作成はできるのに削除だけ拒否された、で一覧が消える)。401 や再認証の\n // リダイレクト、それ以外の障害はそのまま伝播させる — 握り潰すとログインし直せない\n if (!isDenied(error)) throw error\n return denied(error)\n }\n\n try {\n return await dispatch(intent, formData)\n } catch (error) {\n // ガードの後 (ストアの中) で投げられた拒否も同じ扱いにする。ここを素通りさせると、\n // 拒否理由がそのまま利用者へ渡り、しかも HTTP は 200 になって監視から消える\n if (isDenied(error)) return denied(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 = requireAllowlistType(formData)\n await config.store.addAllowlistEntry({ pattern: requireText(formData, \"pattern\"), type, description: optionalText(formData, \"description\") })\n return { ok: true }\n }\n\n if (intent === \"addAllowlistGroup\") {\n await config.store.addAllowlistGroup(requireId(formData, \"groupId\"), optionalText(formData, \"description\"))\n return { ok: true }\n }\n if (intent === \"removeAllowlistGroup\") {\n await config.store.removeAllowlistGroup(requireId(formData, \"entryId\"))\n return { ok: true }\n }\n if (intent === \"linkDirectoryGroup\") {\n await config.store.linkDirectoryGroup(requireId(formData, \"groupId\"), {\n provider: requireText(formData, \"provider\"),\n externalKey: requireText(formData, \"externalKey\"),\n membershipMode: requireMembershipMode(formData),\n })\n return { ok: true }\n }\n if (intent === \"unlinkDirectoryGroup\") {\n await config.store.unlinkDirectoryGroup(requireId(formData, \"groupId\"))\n return { ok: true }\n }\n if (intent === \"setDirectorySyncEnabled\") {\n await config.store.setDirectorySyncEnabled(requireId(formData, \"groupId\"), requireBoolean(formData, \"isSyncEnabled\"))\n return { ok: true }\n }\n if (intent === \"requestDirectorySync\") {\n const raw = String(formData.get(\"groupId\") ?? \"\").trim()\n await config.store.requestDirectorySync(raw === \"\" ? null : requireId(formData, \"groupId\"))\n return { ok: true }\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 { AuthDirectoryStatus, AuthGroup } from \"@aiquants/auth-core\"\nimport type React from \"react\"\nimport { Link, useLoaderData } from \"react-router\"\nimport { fill, formatDateTime } from \"./format\"\nimport { defaultUserAdminLabels, type UserAdminLabels } from \"./labels\"\nimport type { UserAdminPermission } from \"./server\"\n\nexport type UserAdminShellProps = {\n children?: React.ReactNode\n renderHeader?: (props: { title: string; annotation: React.ReactNode }) => React.ReactNode\n}\n\n/**\n * Render one resource's permission summary as a compact pill.\n * 1 リソース分の権限サマリをコンパクトなタグとして描画するコンポーネント。\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\n/**\n * Warns when synchronization is not actually keeping the mirror fresh.\n * 同期が実際には鏡を新しく保てていないことを警告する部品。\n *\n * 同期の停止は「上流から外れた人がテナントに居続ける」というセキュリティ事象である。件数と\n * エラー数だけを見ると何も起きていないように読めるため、**滞留・全停止・未結線の 3 つを\n * それぞれ名指しで** 出す。出さなければ、状態を計算している意味がない。\n *\n * どのタブに居ても見えるようにシェル側で描く。ディレクトリの停止はグループ一覧を見ている\n * ときだけ起きる事象ではない。\n */\nfunction DirectorySyncBanner({ status, labels, groups }: { status?: AuthDirectoryStatus; labels: UserAdminLabels; groups: AuthGroup[] }) {\n if (!status || status.linkedGroupCount === 0) return null\n const L = labels.directoryView\n const warnings: Array<{ id: string; text: string }> = []\n if (!status.isEnabled) warnings.push({ id: \"disabled\", text: L.disabledWarning })\n if (status.pausedGroupCount > 0) warnings.push({ id: \"paused\", text: fill(L.pausedTemplate, { paused: status.pausedGroupCount, total: status.linkedGroupCount }) })\n if (status.isStale) {\n const at = status.oldestSyncedAt ? formatDateTime(status.oldestSyncedAt, labels) : L.neverSynced\n warnings.push({ id: \"stale\", text: `${L.staleWarning} ${fill(L.oldestSyncedTemplate, { at })}` })\n }\n if (status.erroredGroupIds.length > 0) {\n // ID を並べても運用者には何のグループか判らない。名前と、あれば実際の失敗理由を出す\n const described = status.erroredGroupIds.map((id) => {\n const group = groups.find((g) => g.id === id)\n if (!group) return fill(labels.common.idTemplate, { id })\n const reason = group.externalLink?.syncError\n return reason ? fill(L.erroredItemTemplate, { group: group.name, reason }) : group.name\n })\n warnings.push({ id: \"errored\", text: fill(L.erroredTemplate, { groups: described.join(L.listSeparator) }) })\n }\n if (status.pendingRequestCount > 0) warnings.push({ id: \"pending\", text: fill(L.pendingTemplate, { count: status.pendingRequestCount }) })\n if (warnings.length === 0) return null\n return (\n // ライブリージョンにはしない。初期描画から在る内容は読み上げられず、見出しも読み順も\n // 失われる。**読める本文** として置き、見出しから辿れるようにする\n <section\n aria-labelledby=\"directory-sync-heading\"\n data-testid=\"directory-sync-banner\"\n style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.35rem\", padding: \"0.7rem 1rem\", marginBottom: \"1rem\", borderRadius: \"6px\", border: \"1px solid #fde68a\", backgroundColor: \"#fffbeb\", color: \"#92400e\", fontSize: \"0.85rem\" }}>\n <h2 id=\"directory-sync-heading\" style={{ margin: 0, fontSize: \"0.9rem\", fontWeight: 700 }}>\n {L.title}\n </h2>\n {warnings.map((w) => (\n <span key={w.id} data-directory-warning={w.id}>\n {w.text}\n </span>\n ))}\n </section>\n )\n}\n\n/**\n * Shell layout for user administration pages with header annotation delegation.\n * ヘッダー注釈委譲を備えたユーザー管理ページのシェルレイアウト。\n */\nexport function UserAdminShell({ children, renderHeader }: UserAdminShellProps) {\n // ⚠️ ここは `UserAdminLoaderData` と違い **すべて任意** で受ける。シェルは loader を持たない\n // 呼び出し (エラー境界の外側、テスト、部分描画) からも描かれるため、欠落を落ちる理由にしない\n const data = useLoaderData() as { segment?: string; labels?: UserAdminLabels; myPermissions?: Record<string, UserAdminPermission | undefined> | null; resourceLabels?: Record<string, string>; directoryStatus?: AuthDirectoryStatus; groups?: AuthGroup[] } | undefined\n const segment = data?.segment ?? \"users\"\n const labels = data?.labels ?? defaultUserAdminLabels\n // null = 未結線または取得失敗。空 {} も「分からない」扱いで、いずれも許可として描画しない\n const myPermissions = data?.myPermissions ?? null\n // リソースキーの表示名は呼び出し側が差し込む (キーをこのパッケージに埋め込まない)\n const resourceLabels = data?.resourceLabels ?? {}\n const labelFor = (resourceKey: string) => (Object.hasOwn(resourceLabels, resourceKey) ? resourceLabels[resourceKey] : resourceKey)\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 // 面だけが消えるのは最悪の挙動である。連携も結線も無い配備でだけ隠す。\n // 判定は状態の件数と一覧の実体の **両方** を見る — 片方だけだと、件数が 0 なのに\n // 「上流が所有」と描かれるグループが取り残される\n const hasLinkedGroup = (data?.groups ?? []).some((g) => g.externalLink)\n if (data?.directoryStatus?.isEnabled || (data?.directoryStatus?.linkedGroupCount ?? 0) > 0 || hasLinkedGroup) {\n tabs.push({ key: \"directory\", label: labels.tabs.directory, href: \"/users/directory\" })\n }\n\n const permEntries = Object.entries(myPermissions ?? {})\n\n /**\n * Pick the badge text and colours for one resource, never rendering \"unknown\" as \"allowed\".\n * 1 リソース分のバッジ文言と配色を決める処理。**未知を許可として描かない** ことが要件。\n *\n * 以前は権限マップが空のとき「編集可」の固定値を描いていたため、ポート未結線・取得失敗・\n * 付与ゼロのいずれでも「全操作できます」と表示されていた (実際には操作するとガードで拒否される)。\n */\n const getSummary = (perm: UserAdminPermission | undefined) => {\n if (perm?.canWrite) {\n return { text: labels.permissions.canWrite, color: \"#047857\", bgColor: \"#ecfdf5\", borderColor: \"#a7f3d0\" }\n }\n if (perm?.canRead) {\n return { text: labels.permissions.canRead, color: \"#1d4ed8\", bgColor: \"#eff6ff\", borderColor: \"#bfdbfe\" }\n }\n return { text: labels.permissions.none, color: \"#475569\", bgColor: \"#f1f5f9\", borderColor: \"#cbd5e1\" }\n }\n const unavailableSummary = { text: labels.permissions.unknown, color: \"#92400e\", bgColor: \"#fffbeb\", borderColor: \"#fde68a\" }\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\" }}>{labels.permissions.label}</span>\n <span style={{ display: \"inline-flex\", alignItems: \"center\", gap: \"0.18rem\" }}>\n {permEntries.length === 0 ? (\n <span data-testid=\"user-admin-permission-unavailable\">\n <GroupPill label={labels.heading} summary={unavailableSummary} />\n </span>\n ) : (\n permEntries.map(([resKey, perm]) => <GroupPill key={resKey} label={labelFor(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 <nav aria-label={labels.title} 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 aria-current={isActive ? \"page\" : undefined}\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 </nav>\n\n <DirectorySyncBanner status={data?.directoryStatus} labels={labels} groups={data?.groups ?? []} />\n\n <main>{children}</main>\n </div>\n )\n}\n","/**\n * @module admin/format\n * @description Text helpers shared by the shell and the views.\n * シェルとビューが共有する文言整形の補助。\n *\n * 数や名前を JSX で連結すると語順が英語に固定され、日付を実行環境任せで整形すると\n * サーバーとブラウザで別の文字列になる (水和が壊れる)。どちらもラベル側の宣言に従わせる。\n */\nimport type { UserAdminLabels } from \"./labels\"\n\n/**\n * Substitutes `{key}` placeholders in a label template.\n * ラベルの雛形に含まれる `{key}` を差し替える処理。\n *\n * @param template Label text containing `{key}` placeholders. `{key}` を含むラベル文言。\n * @param values Replacement values by key. キーごとの差し替え値。\n * @returns The filled text. 差し替え済みの文言。\n */\nexport function fill(template: string, values: Record<string, string | number>): string {\n return Object.entries(values).reduce((text, [key, value]) => text.replaceAll(`{${key}}`, String(value)), template)\n}\n\n/**\n * Formats an ISO timestamp with the locale the labels declare, degrading instead of throwing.\n * ISO 時刻を **ラベルが宣言したロケール** で整形する処理 (例外を投げず、読める形へ退避する)。\n *\n * `Intl.DateTimeFormat().format()` は不正な日時で `RangeError` を投げる (`toLocaleString()` が\n * 「Invalid Date」を返すのとは違う)。この関数を呼ぶのはシェルの同期バナーであり、バナーは\n * **全タブで描かれる**。上流の 1 行が壊れた時刻を持っていただけで、ユーザー管理面が丸ごと\n * エラー境界へ落ちる。ロケール文字列が空でも同じく投げるため、そこも退避する。\n *\n * オフセットの無い ISO 文字列 (`2026-03-01T09:00:00`) は仕様上 **実行環境の時間帯** で解釈される。\n * サーバーとブラウザで別の時刻になり水和が壊れるため、UTC として読む。\n *\n * @param iso ISO 8601 timestamp. ISO 8601 の時刻 (オフセット無しは UTC として解釈)。\n * @param labels Resolved label set carrying locale and time zone. ロケールと時間帯を持つラベル。\n * @returns Formatted text, or the input itself when it cannot be formatted. 整形済みの文言 (整形できない場合は入力そのもの)。\n */\nexport function formatDateTime(iso: string, labels: UserAdminLabels): string {\n // オフセットが無ければ UTC を補う (実行環境の時間帯で解釈させない)\n const normalized = /(?:Z|[+-]\\d{2}:?\\d{2})$/.test(iso) ? iso : `${iso}Z`\n try {\n return new Intl.DateTimeFormat(labels.locale, { timeZone: labels.timeZone, dateStyle: \"medium\", timeStyle: \"short\" }).format(new Date(normalized))\n } catch {\n // 不正な日時 (`RangeError: Invalid time value`) も、不正なロケール・時間帯も、ここで\n // 受け止める。画面を落とすより生の値を見せる方がまだ役に立つ\n return iso\n }\n}\n","import type { AuthAllowlistEntry, AuthDirectoryMembershipMode, AuthDirectoryStatus, AuthGroup, AuthGroupMember, AuthUser } from \"@aiquants/auth-core\"\nimport type React from \"react\"\nimport { useEffect, useRef, useState } from \"react\"\nimport { useFetcher, useLoaderData } from \"react-router\"\nimport { fill, formatDateTime } from \"./format\"\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 directoryStatus: AuthDirectoryStatus\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({ result: raw, id }: { result: unknown; id?: string }) {\n const result = raw as { ok?: boolean; error?: string } | undefined\n if (result?.ok !== false || !result.error) return null\n return (\n // グリッド配下でも行を跨ぐ。空のときは描かないので、余分な行も生まれない\n <div id={id} data-testid=\"user-admin-action-error\" role=\"alert\" style={{ gridColumn: \"1 / -1\", 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\n/**\n * Submits through the view's single fetcher and defers \"close and clear\" until the server accepts.\n * ビュー唯一の fetcher で送信し、**サーバーが受理してから** 「閉じて消す」を行うためのフック。\n *\n * 送信直後に閉じて入力を消すと、認可拒否や検証エラーで戻ったときに打ち込んだ内容が丸ごと\n * 失われ、利用者は全部打ち直すことになる。成功応答 (`ok === true`) を待って初めて破棄する。\n *\n * ⚠️ **このビューの送信はすべてここを通すこと。** fetcher は 1 つを共有しており、後の送信は\n * 前の送信を打ち切る。素の `fetcher.submit` を混ぜると、打ち切られた送信の後始末が **後の\n * 送信の成功応答で** 実行される (行の操作を挟んだだけで、フォームの入力が消える)。ここを\n * 通せば後の送信が `pending` を必ず上書きするため、後始末は常に最後の送信のものになる。\n */\nfunction useSubmitThen(fetcher: ReturnType<typeof useFetcher>) {\n const pending = useRef<(() => void) | null>(null)\n // 破棄した結果の世代。`fetcher.data` は宣言的に消せないため、こちらで無効化する\n const [discarded, setDiscarded] = useState<unknown>(undefined)\n const result = fetcher.data === discarded ? undefined : fetcher.data\n\n useEffect(() => {\n if (fetcher.state !== \"idle\" || fetcher.data === undefined) return\n const onSuccess = pending.current\n if (!onSuccess) return\n // 同じ応答で 2 度実行しないよう、消費した時点で捨てる。ここは **冗長な防御** である —\n // すべての送信がこのフックを通る限り、後の送信が必ず `pending` を上書きするため。\n // 素の `fetcher.submit` が 1 つでも混ざった瞬間に効き始める\n pending.current = null\n if ((fetcher.data as { ok?: boolean }).ok === true) onSuccess()\n }, [fetcher.state, fetcher.data])\n\n const submit = (payload: Record<string, string>, onSuccess?: () => void) => {\n pending.current = onSuccess ?? null\n setDiscarded(undefined)\n fetcher.submit(payload, { method: \"post\" })\n }\n /**\n * Forgets the last result so an unrelated failure cannot describe a freshly opened dialog.\n * 直前の結果を忘れる処理 (無関係な失敗が、開いたばかりのダイアログの説明文にならないように)。\n */\n const resetResult = () => setDiscarded(fetcher.data)\n\n return { submit, resetResult, result }\n}\n\n/**\n * Gives a dialog the behavior its ARIA attributes promise: focus, trap, Escape, restore.\n * ダイアログに、ARIA 属性が約束している挙動 (初期焦点・封じ込め・Escape・復帰) を与えるフック。\n *\n * 役割属性だけを付けても、焦点は背後に残り、Tab は外へ出て行き、Escape は効かない。\n * キーボード利用者は「閉じる」に到達できず、支援技術は背後を読み続ける。\n *\n * @param isOpen Whether the dialog is mounted and visible. ダイアログが開いているかどうか。\n * @param onClose Invoked when Escape is pressed. Escape が押されたときに呼ばれる処理。\n * @returns Ref to attach to the dialog panel. ダイアログ本体へ付ける ref。\n */\nfunction useModalBehavior(isOpen: boolean, onClose: () => void, openerRef?: React.RefObject<HTMLElement | null>) {\n const ref = useRef<HTMLFormElement>(null)\n // 再描画のたびに実行されると入力中に焦点が先頭へ飛ぶため、依存は開閉だけにする\n const closeRef = useRef(onClose)\n closeRef.current = onClose\n\n useEffect(() => {\n if (!isOpen) return\n const panel = ref.current\n // `activeElement` を見るだけでは戻し先にならない。押下で焦点が乗らないブラウザ\n // (Safari / macOS の Firefox) では body へ戻ってしまう。開いた要素を明示的に覚える\n const opener = openerRef?.current ?? (document.activeElement as HTMLElement | null)\n const focusables = () => Array.from(panel?.querySelectorAll<HTMLElement>('a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex=\"-1\"])') ?? [])\n focusables()[0]?.focus()\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n // 変換中の Escape は「候補を取り消す」であって「閉じる」ではない。ここで\n // 閉じると、編集中の下書きは状態ごと捨てられ、打ち直しになる\n if (event.isComposing || event.keyCode === 229) return\n event.preventDefault()\n closeRef.current()\n return\n }\n if (event.key !== \"Tab\") return\n const items = focusables()\n if (items.length === 0) return\n const first = items[0]\n const last = items[items.length - 1]\n const active = document.activeElement\n const outside = !panel?.contains(active)\n if (event.shiftKey && (active === first || outside)) {\n event.preventDefault()\n last.focus()\n } else if (!event.shiftKey && (active === last || outside)) {\n event.preventDefault()\n first.focus()\n }\n }\n document.addEventListener(\"keydown\", onKeyDown)\n return () => {\n document.removeEventListener(\"keydown\", onKeyDown)\n opener?.focus?.()\n }\n }, [isOpen, openerRef])\n\n return ref\n}\n\nexport function UsersView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const { submit: submitThen, resetResult, result: actionResult } = useSubmitThen(fetcher)\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [searchQuery, setSearchQuery] = useState(\"\")\n // 2 つの独立した状態にすると「両方開いている」状態が表現でき、エラー欄が 2 つ描かれる。\n // 排他であることを型で持つ\n const [modal, setModal] = useState<{ kind: \"create\" } | { kind: \"edit\"; user: AuthUser } | null>(null)\n const editingUser = modal?.kind === \"edit\" ? modal.user : null\n const isCreateOpen = modal?.kind === \"create\"\n const openerRef = useRef<HTMLElement | null>(null)\n /** ダイアログを開く。直前の操作の結果を持ち越さない (別の操作の拒否が説明文になる)。 */\n const openModal = (next: { kind: \"create\" } | { kind: \"edit\"; user: AuthUser }, event: React.MouseEvent<HTMLElement>) => {\n openerRef.current = event.currentTarget\n resetResult()\n setModal(next)\n }\n\n const [newDisplayName, setNewDisplayName] = useState(\"\")\n const [newEmail, setNewEmail] = useState(\"\")\n const createUserRef = useModalBehavior(isCreateOpen, () => setModal(null), openerRef)\n const editUserRef = useModalBehavior(editingUser !== null, () => setModal(null), openerRef)\n\n const busy = fetcher.state !== \"idle\"\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 // 無効化はサインインを止める。有効化は元に戻すだけなので確認しない\n if (user.isActive && !window.confirm(labels.usersView.confirmDeactivate)) return\n submitThen({ intent: \"toggleUserActive\", userId: String(user.id), isActive: String(!user.isActive) })\n }\n\n const handleCreateUser = (e: React.FormEvent) => {\n e.preventDefault()\n if (!(newDisplayName && newEmail)) return\n // 送信した値と今の値が違うなら、往復の間に打ち込まれたものである。消してはならない\n const sentName = newDisplayName\n const sentEmail = newEmail\n submitThen({ intent: \"createUser\", displayName: sentName, email: sentEmail }, () => {\n setNewDisplayName((current) => (current === sentName ? \"\" : current))\n setNewEmail((current) => (current === sentEmail ? \"\" : current))\n setModal(null)\n })\n }\n\n const handleUpdateUser = (e: React.FormEvent) => {\n e.preventDefault()\n if (!editingUser) return\n submitThen({ intent: \"updateUser\", userId: String(editingUser.id), displayName: editingUser.displayName, email: editingUser.email }, () => setModal(null))\n }\n\n return (\n <section data-testid=\"users-view-section\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1rem\" }}>\n <h2 style={heading}>{labels.usersView.title}</h2>\n {/* モーダルが開いている間はモーダル内に描く。両方描くと同じ hook が 2 つになる */}\n {!modal && <ActionError result={actionResult} />}\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 aria-label={labels.usersView.searchLabel}\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\n type=\"button\"\n onClick={(event) => openModal({ kind: \"create\" }, event)}\n data-testid=\"users-add-button\"\n 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.common.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.common.status}</th>\n <th style={{ padding: \"10px 14px\", textAlign: \"right\" }}>{labels.common.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: \"#475569\" }}>{fill(labels.common.idTemplate, { id: 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={(event) => openModal({ kind: \"edit\", user: u }, event)}\n aria-label={fill(labels.usersView.editUserAria, { user: u.displayName })}\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.common.edit}\n </button>\n <button\n type=\"button\"\n onClick={() => handleToggleActive(u)}\n disabled={busy}\n aria-busy={busy}\n aria-label={fill(u.isActive ? labels.usersView.deactivateAria : labels.usersView.activateAria, { user: u.displayName })}\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: busy ? \"default\" : \"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: \"#475569\" }}>\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 }}>\n <form\n ref={createUserRef}\n onSubmit={handleCreateUser}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"create-user-title\"\n aria-describedby=\"create-user-error\"\n data-testid=\"user-create-modal\"\n style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 id=\"create-user-title\" style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>\n {labels.usersView.addUser}\n </h3>\n <ActionError result={actionResult} id=\"create-user-error\" />\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={() => setModal(null)} data-testid=\"cancel-create-user\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n {labels.common.cancel}\n </button>\n <button type=\"submit\" data-testid=\"submit-create-user\" disabled={busy} aria-busy={busy} style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#0284c7\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n {labels.common.create}\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 }}>\n <form\n ref={editUserRef}\n onSubmit={handleUpdateUser}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"edit-user-title\"\n aria-describedby=\"edit-user-error\"\n data-testid=\"user-edit-modal\"\n style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 id=\"edit-user-title\" style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>\n {labels.usersView.editUser}\n </h3>\n <ActionError result={actionResult} id=\"edit-user-error\" />\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) => setModal({ kind: \"edit\", user: { ...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) => setModal({ kind: \"edit\", user: { ...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={() => setModal(null)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n {labels.common.cancel}\n </button>\n <button type=\"submit\" data-testid=\"submit-edit-user\" disabled={busy} aria-busy={busy} style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#0284c7\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n {labels.common.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 { submit: submitThen, resetResult, result: actionResult } = useSubmitThen(fetcher)\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [searchQuery, setSearchQuery] = useState(\"\")\n const [modal, setModal] = useState<{ kind: \"create\" } | { kind: \"edit\"; group: AuthGroup } | null>(null)\n const editingGroup = modal?.kind === \"edit\" ? modal.group : null\n const isCreateOpen = modal?.kind === \"create\"\n const openerRef = useRef<HTMLElement | null>(null)\n /** ダイアログを開く。直前の操作の結果を持ち越さない (別の操作の拒否が説明文になる)。 */\n const openModal = (next: { kind: \"create\" } | { kind: \"edit\"; group: AuthGroup }, event: React.MouseEvent<HTMLElement>) => {\n openerRef.current = event.currentTarget\n resetResult()\n setModal(next)\n }\n\n const busy = fetcher.state !== \"idle\"\n const [newGroupKey, setNewGroupKey] = useState(\"\")\n const [newName, setNewName] = useState(\"\")\n const [newDescription, setNewDescription] = useState(\"\")\n const createGroupRef = useModalBehavior(isCreateOpen, () => setModal(null), openerRef)\n const editGroupRef = useModalBehavior(editingGroup !== null, () => setModal(null), openerRef)\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 // 送信した値と今の値が違うなら、往復の間に打ち込まれたものである。消してはならない\n const sent = { key: newGroupKey, name: newName, description: newDescription }\n submitThen({ intent: \"createGroup\", groupKey: sent.key, name: sent.name, description: sent.description }, () => {\n setNewGroupKey((current) => (current === sent.key ? \"\" : current))\n setNewName((current) => (current === sent.name ? \"\" : current))\n setNewDescription((current) => (current === sent.description ? \"\" : current))\n setModal(null)\n })\n }\n\n const handleUpdateGroup = (e: React.FormEvent) => {\n e.preventDefault()\n if (!editingGroup) return\n submitThen({ intent: \"updateGroup\", groupId: String(editingGroup.id), name: editingGroup.name, description: editingGroup.description ?? \"\" }, () => setModal(null))\n }\n\n const handleDeleteGroup = (groupId: number) => {\n if (!window.confirm(labels.groupsView.confirmDelete)) return\n submitThen({ intent: \"deleteGroup\", groupId: String(groupId) })\n }\n\n return (\n <section data-testid=\"groups-view-section\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1rem\" }}>\n <h2 style={heading}>{labels.groupsView.title}</h2>\n {!modal && <ActionError result={actionResult} />}\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 aria-label={labels.groupsView.searchLabel}\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\n type=\"button\"\n onClick={(event) => openModal({ kind: \"create\" }, event)}\n data-testid=\"groups-add-button\"\n 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\n key={g.id}\n style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", padding: \"1rem\", display: \"flex\", flexDirection: \"column\", justifyContent: \"space-between\" }}\n data-testid={`group-card-${g.id}`}\n data-group-readonly={g.readOnly ? \"true\" : \"false\"}>\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 {/* 所属も名称も上流が所有していることを、操作を試す前に伝える */}\n {g.externalLink && (\n <div data-testid={`group-source-${g.id}`} style={{ display: \"flex\", alignItems: \"center\", gap: \"6px\", flexWrap: \"wrap\", marginBottom: \"0.6rem\" }}>\n <span style={{ fontSize: \"0.72rem\", padding: \"2px 8px\", borderRadius: \"10px\", backgroundColor: \"#ede9fe\", color: \"#6d28d9\", fontWeight: 600 }}>{labels.common.managedUpstream}</span>\n <span style={{ fontSize: \"0.72rem\", fontFamily: \"monospace\", color: \"#64748b\" }}>{g.externalLink.externalKey}</span>\n {g.externalLink.unresolvedCount > 0 && (\n <span data-testid={`group-unresolved-${g.id}`} style={{ fontSize: \"0.72rem\", color: \"#b45309\" }}>\n {fill(labels.directoryView.unresolvedTemplate, { count: g.externalLink.unresolvedCount })}\n </span>\n )}\n </div>\n )}\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\" }} data-testid={`group-member-count-${g.id}`}>\n {fill(labels.groupsView.memberCountTemplate, { count: memberCount })}\n </span>\n {/* 上流が所有する行では操作を **描かない**。無効化したボタンを出すと、\n 押せる操作と見分けがつかず、押せない理由も伝わらない */}\n {g.readOnly ? (\n <span data-testid={`readonly-group-${g.id}`} style={{ fontSize: \"0.75rem\", color: \"#475569\" }}>\n {labels.common.readOnly}\n </span>\n ) : (\n <div>\n <button\n type=\"button\"\n onClick={(event) => openModal({ kind: \"edit\", group: g }, event)}\n disabled={busy}\n aria-busy={busy}\n aria-label={fill(labels.groupsView.editGroupAria, { group: g.name })}\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.common.edit}\n </button>\n <button\n type=\"button\"\n onClick={() => handleDeleteGroup(g.id)}\n disabled={busy}\n aria-busy={busy}\n aria-label={fill(labels.groupsView.deleteGroupAria, { group: g.name })}\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 {labels.common.delete}\n </button>\n </div>\n )}\n </div>\n </div>\n )\n })}\n </div>\n {filteredGroups.length === 0 && (\n <div data-testid=\"groups-empty\" style={{ padding: \"2rem\", textAlign: \"center\", color: \"#475569\", backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n {data.groups.length === 0 ? labels.groupsView.empty : labels.groupsView.emptyFiltered}\n </div>\n )}\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 }}>\n <form\n ref={createGroupRef}\n onSubmit={handleCreateGroup}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"create-group-title\"\n aria-describedby=\"create-group-error\"\n data-testid=\"group-create-modal\"\n style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 id=\"create-group-title\" style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>\n {labels.groupsView.addGroup}\n </h3>\n <ActionError result={actionResult} id=\"create-group-error\" />\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={() => setModal(null)} data-testid=\"cancel-create-group\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n {labels.common.cancel}\n </button>\n <button type=\"submit\" data-testid=\"submit-create-group\" disabled={busy} aria-busy={busy} style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#4338ca\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n {labels.common.create}\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 }}>\n <form\n ref={editGroupRef}\n onSubmit={handleUpdateGroup}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"edit-group-title\"\n aria-describedby=\"edit-group-error\"\n data-testid=\"group-edit-modal\"\n style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 id=\"edit-group-title\" style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>\n {labels.groupsView.editGroup}\n </h3>\n <ActionError result={actionResult} id=\"edit-group-error\" />\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) => setModal({ kind: \"edit\", group: { ...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) => setModal({ kind: \"edit\", group: { ...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={() => setModal(null)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n {labels.common.cancel}\n </button>\n <button type=\"submit\" data-testid=\"submit-edit-group\" disabled={busy} aria-busy={busy} style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#4338ca\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n {labels.common.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 { submit: submitThen, result: actionResult } = useSubmitThen(fetcher)\n const busy = fetcher.state !== \"idle\"\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [selectedGroupId, setSelectedGroupId] = useState<number>(data.groups[0]?.id ?? 0)\n\n // 再検証で対象が消えることがある。消えた ID を握り続けると空の面になる\n const currentGroup = data.groups.find((g) => g.id === selectedGroupId) ?? data.groups[0]\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 submitThen({ intent: \"removeGroupMember\", groupId: String(selectedGroupId), userId: String(userId) })\n } else {\n submitThen({ intent: \"addGroupMember\", groupId: String(selectedGroupId), userId: String(userId) })\n }\n }\n\n return (\n <section data-testid=\"group-members-view-section\" style={{ display: \"grid\", gridTemplateColumns: \"240px 1fr\", gap: \"1.2rem\" }}>\n <h2 style={{ ...heading, gridColumn: \"1 / -1\" }}>{labels.groupMembersView.title}</h2>\n <ActionError result={actionResult} />\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 {g.readOnly ? (\n <span data-testid={`group-upstream-marker-${g.id}`} style={{ marginLeft: \"6px\", fontSize: \"0.7rem\", color: \"#7c3aed\" }}>\n {labels.common.managedUpstream}\n </span>\n ) : null}\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\" }}>{fill(labels.groupMembersView.membersOfTemplate, { group: currentGroup.name })}</h3>\n <span style={{ fontSize: \"0.8rem\", color: \"#64748b\" }}>\n {labels.groupMembersView.groupKeyLabel}: {currentGroup.groupKey}\n </span>\n {currentGroup.readOnly ? (\n <div data-testid=\"group-members-upstream-notice\" role=\"note\" style={{ marginTop: \"0.5rem\", padding: \"0.5rem 0.7rem\", borderRadius: \"6px\", backgroundColor: \"#f5f3ff\", border: \"1px solid #ddd6fe\", color: \"#5b21b6\", fontSize: \"0.8rem\" }}>\n {labels.groupMembersView.upstreamNotice}\n </div>\n ) : null}\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 {currentGroup.readOnly ? (\n isMember ? (\n <span data-testid={`member-readonly-${u.id}`} style={{ fontSize: \"0.75rem\", color: \"#475569\" }}>\n {labels.common.managedUpstream}\n </span>\n ) : null\n ) : (\n <button\n type=\"button\"\n onClick={() => handleToggleMember(u.id, isMember)}\n disabled={busy}\n aria-busy={busy}\n aria-label={fill(isMember ? labels.groupMembersView.actionRemoveAria : labels.groupMembersView.actionAddAria, { user: u.displayName })}\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 )}\n </div>\n )\n })}\n </div>\n </>\n ) : (\n <div style={{ color: \"#475569\", textAlign: \"center\", padding: \"2rem\" }}>{labels.groupMembersView.selectPrompt}</div>\n )}\n </div>\n </section>\n )\n}\n\n/** Badge colours per allowlist kind. 許可種別ごとのバッジ配色。 */\nfunction allowlistBadgeStyle(type: AuthAllowlistEntry[\"type\"]): { backgroundColor: string; color: string } {\n if (type === \"email\") return { backgroundColor: \"#e0f2fe\", color: \"#0369a1\" }\n if (type === \"domain\") return { backgroundColor: \"#fef3c7\", color: \"#b45309\" }\n return { backgroundColor: \"#ede9fe\", color: \"#6d28d9\" }\n}\n\n/** Badge text per allowlist kind. 許可種別ごとのバッジ文言。 */\nfunction allowlistTypeLabel(type: AuthAllowlistEntry[\"type\"], labels: UserAdminLabels): string {\n if (type === \"email\") return labels.allowlistView.emailTypeShort\n if (type === \"domain\") return labels.allowlistView.domainTypeShort\n return labels.allowlistView.groupTypeShort\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 { submit: submitThen, result: actionResult } = useSubmitThen(fetcher)\n const busy = fetcher.state !== \"idle\"\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 const sent = { pattern, description }\n submitThen({ intent: \"addAllowlistEntry\", pattern: sent.pattern, type, description: sent.description }, () => {\n setPattern((current) => (current === sent.pattern ? \"\" : current))\n setDescription((current) => (current === sent.description ? \"\" : current))\n })\n }\n\n const handleRemoveEntry = (id: number) => {\n submitThen({ intent: \"removeAllowlistEntry\", entryId: String(id) })\n }\n\n /** グループ許可は別の表に住むため、別の intent で消す。 */\n const handleRemoveGroupEntry = (id: number) => {\n submitThen({ intent: \"removeAllowlistGroup\", entryId: String(id) })\n }\n\n return (\n <section data-testid=\"allowlist-view-section\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1.2rem\" }}>\n <h2 style={heading}>{labels.allowlistView.title}</h2>\n <ActionError result={actionResult} />\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\" disabled={busy} aria-busy={busy} 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\" }}>{labels.common.id}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.allowlistView.target}</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.common.actions}</th>\n </tr>\n </thead>\n <tbody>\n {data.allowlist.map((item) => (\n <tr key={`${item.type}-${item.id}`} style={{ borderBottom: \"1px solid #f1f5f9\" }} data-testid={`allowlist-row-${item.type}-${item.id}`} data-allowlist-type={item.type}>\n <td style={{ padding: \"10px 14px\", color: \"#475569\" }}>{fill(labels.common.idTemplate, { id: item.id })}</td>\n <td style={{ padding: \"10px 14px\", fontWeight: 600, color: \"#0f172a\" }}>\n {/* パターン許可とグループ許可では正準値が違う。グループはアドレスではなく\n グループそのものが対象なので、名前とキーを見せる */}\n {item.type === \"group\" ? (\n <span data-testid={`allowlist-group-${item.type}-${item.id}`}>\n {item.groupName}\n <span style={{ marginLeft: \"6px\", fontFamily: \"monospace\", fontSize: \"0.78rem\", color: \"#64748b\" }}>{item.groupKey}</span>\n </span>\n ) : (\n <span style={{ fontFamily: \"monospace\" }}>{item.pattern}</span>\n )}\n </td>\n <td style={{ padding: \"10px 14px\" }}>\n <span style={{ fontSize: \"0.75rem\", padding: \"2px 8px\", borderRadius: \"10px\", ...allowlistBadgeStyle(item.type), fontWeight: 600 }}>{allowlistTypeLabel(item.type, labels)}</span>\n </td>\n <td style={{ padding: \"10px 14px\", color: \"#475569\" }}>\n {item.description || labels.common.none}\n {/* 配備全体が所有する行は、どのテナントの管理者でも編集できない */}\n {item.owner.kind === \"deployment\" && (\n <span data-testid={`allowlist-owner-${item.type}-${item.id}`} style={{ marginLeft: \"8px\", fontSize: \"0.72rem\", color: \"#475569\" }}>\n {labels.allowlistView.deploymentOwner}\n </span>\n )}\n </td>\n <td style={{ padding: \"10px 14px\", textAlign: \"right\" }}>\n {/* 変更不能な供給元 (環境変数など) は削除ボタンを出さない。無効化ではなく\n 非描画にするのは、押せないボタンが押せる操作と見分けられないため */}\n {item.readOnly ? (\n <span data-testid={`readonly-allowlist-${item.type}-${item.id}`} style={{ fontSize: \"0.75rem\", color: \"#475569\" }}>\n {labels.common.readOnly}\n </span>\n ) : (\n <button\n type=\"button\"\n onClick={() => {\n // これでしかサインインできない人はログインできなくなる。取り消せない\n if (!window.confirm(labels.allowlistView.confirmDelete)) return\n if (item.type === \"group\") handleRemoveGroupEntry(item.id)\n else handleRemoveEntry(item.id)\n }}\n disabled={busy}\n aria-busy={busy}\n aria-label={fill(labels.allowlistView.deleteAria, { target: item.type === \"group\" ? item.groupName : item.pattern })}\n data-testid={`remove-allowlist-btn-${item.type}-${item.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #fee2e2\", backgroundColor: \"#fff5f5\", color: \"#b91c1c\", fontSize: \"0.78rem\", cursor: \"pointer\" }}>\n {labels.common.delete}\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: \"#475569\" }}>\n {labels.allowlistView.empty}\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n </section>\n )\n}\n\n/**\n * View for the upstream directory: what is linked, how fresh it is, and the controls that change it.\n * 上流ディレクトリのビュー。何が連携され、どれだけ新しく、それを変える操作を提供する。\n *\n * このタブが無いと、連携の作成・解除・一時停止・即時同期・グループ単位のサインイン許可という\n * 5 つの操作にどこからも到達できない。実装されているのに押せる場所が無い状態は、実装が無いのと\n * 変わらないうえ、「許可を消せるが作れない」という片肺の運用を強いる。\n */\nexport function DirectoryView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const { submit: submitThen, result: actionResult } = useSubmitThen(fetcher)\n const labels = data.labels ?? defaultUserAdminLabels\n const L = labels.directoryView\n\n const linkable = data.groups.filter((g) => !g.externalLink)\n const [groupId, setGroupId] = useState(\"\")\n const [externalKey, setExternalKey] = useState(\"\")\n const [membershipMode, setMembershipMode] = useState<AuthDirectoryMembershipMode>(\"direct\")\n\n const linked = data.groups.filter((g) => g.externalLink)\n const allowedGroupIds = new Set(data.allowlist.filter((e) => e.type === \"group\").map((e) => e.groupId))\n const allowlistEntryOf = (id: number) => data.allowlist.find((e) => e.type === \"group\" && e.groupId === id)\n const busy = fetcher.state !== \"idle\"\n // 「要求しました」を出す対象。押しても何も変わらないと、何度も押される\n const [queuedGroupId, setQueuedGroupId] = useState<number | null>(null)\n // 供給元が判らないまま欄を出すと、送信は必ずサーバー側の内部文言で失敗する\n const canLink = data.directoryStatus.isEnabled && data.directoryStatus.provider !== \"\"\n\n /** 連携を作る。上流キーは運用者が知っている値 (グループアドレス) で受け取る。 */\n const handleLink = (e: React.FormEvent) => {\n e.preventDefault()\n if (!(groupId && externalKey)) return\n // 供給元は配備の設定から来る。ここに特定の値を書くと、別の上流を使う配備で作った\n // リンクが同期側で恒久的に拒否される\n const sentKey = externalKey\n submitThen({ intent: \"linkDirectoryGroup\", groupId, provider: data.directoryStatus.provider, externalKey: sentKey, membershipMode }, () => {\n setExternalKey((current) => (current === sentKey ? \"\" : current))\n // 連携済みになった行は候補から消える。選択を残すと、表示は空欄なのに前の\n // グループへ送信される\n setGroupId(\"\")\n })\n }\n\n return (\n <section data-testid=\"directory-view-section\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1rem\" }}>\n <h2 style={heading}>{L.title}</h2>\n <ActionError result={actionResult} />\n\n {!canLink && (\n <p data-testid=\"directory-not-wired\" style={{ margin: 0, padding: \"0.7rem 1rem\", borderRadius: \"6px\", border: \"1px solid #e2e8f0\", backgroundColor: \"#f8fafc\", color: \"#475569\", fontSize: \"0.85rem\" }}>\n {L.notWiredNotice}\n </p>\n )}\n\n {canLink && linkable.length === 0 && (\n <p data-testid=\"directory-nothing-to-link\" style={{ margin: 0, padding: \"0.7rem 1rem\", borderRadius: \"6px\", border: \"1px solid #e2e8f0\", backgroundColor: \"#f8fafc\", color: \"#475569\", fontSize: \"0.85rem\" }}>\n {L.nothingToLink}\n </p>\n )}\n\n {canLink && linkable.length > 0 && (\n <form onSubmit={handleLink} data-testid=\"directory-link-form\" style={{ display: \"flex\", gap: \"0.75rem\", flexWrap: \"wrap\", alignItems: \"flex-end\", backgroundColor: \"#ffffff\", padding: \"1rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.25rem\" }}>\n <label htmlFor=\"directory-group-select-input\" style={fieldLabel}>\n {L.selectGroup}\n </label>\n <select id=\"directory-group-select-input\" data-testid=\"directory-group-select\" value={groupId} onChange={(e) => setGroupId(e.target.value)} style={{ padding: \"0.5rem\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\" }}>\n <option value=\"\">{L.selectGroup}</option>\n {linkable.map((g) => (\n <option key={g.id} value={String(g.id)}>\n {fill(L.groupOptionTemplate, { name: g.name, key: g.groupKey })}\n </option>\n ))}\n </select>\n </div>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.25rem\", flex: 1, minWidth: \"220px\" }}>\n <label htmlFor=\"directory-external-key-input\" style={fieldLabel}>\n {L.externalKey}\n </label>\n <input\n id=\"directory-external-key-input\"\n type=\"email\"\n required\n data-testid=\"directory-external-key\"\n placeholder={L.externalKeyPlaceholder}\n value={externalKey}\n onChange={(e) => setExternalKey(e.target.value)}\n style={{ padding: \"0.5rem\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\" }}\n />\n </div>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.25rem\" }}>\n <label htmlFor=\"directory-membership-mode-input\" style={fieldLabel}>\n {L.membershipMode}\n </label>\n <select\n id=\"directory-membership-mode-input\"\n data-testid=\"directory-membership-mode\"\n value={membershipMode}\n onChange={(e) => setMembershipMode(e.target.value as AuthDirectoryMembershipMode)}\n style={{ padding: \"0.5rem\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\" }}>\n <option value=\"direct\">{L.directMode}</option>\n <option value=\"transitive\">{L.transitiveMode}</option>\n </select>\n </div>\n <button\n type=\"submit\"\n data-testid=\"directory-link-submit\"\n disabled={!(groupId && externalKey) || busy}\n aria-busy={busy}\n style={{ padding: \"0.5rem 1rem\", borderRadius: \"6px\", border: \"none\", backgroundColor: groupId && externalKey && !busy ? \"#2563eb\" : \"#94a3b8\", color: \"#fff\", fontWeight: 600 }}>\n {L.link}\n </button>\n </form>\n )}\n\n {linked.length === 0 ? (\n <div data-testid=\"directory-empty\" style={{ padding: \"2rem\", textAlign: \"center\", color: \"#64748b\", backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n {L.empty}\n </div>\n ) : (\n <table data-testid=\"directory-table\" style={{ width: \"100%\", borderCollapse: \"collapse\", backgroundColor: \"#ffffff\", borderRadius: \"8px\", overflow: \"hidden\", border: \"1px solid #e2e8f0\" }}>\n <thead>\n <tr style={{ backgroundColor: \"#f8fafc\", textAlign: \"left\" }}>\n <th style={cell}>{labels.groupsView.name}</th>\n <th style={cell}>{L.externalKey}</th>\n <th style={cell}>{L.membershipMode}</th>\n <th style={cell}>{L.lastSynced}</th>\n <th style={cell}>{labels.common.status}</th>\n <th style={cell}>{labels.common.actions}</th>\n </tr>\n </thead>\n <tbody>\n {linked.map((g) => {\n const link = g.externalLink as NonNullable<AuthGroup[\"externalLink\"]>\n const allowed = allowedGroupIds.has(g.id)\n return (\n <tr key={g.id} data-testid={`directory-row-${g.id}`} data-sync-enabled={String(link.isSyncEnabled)} style={{ borderTop: \"1px solid #e2e8f0\" }}>\n <td style={cell}>\n {g.name}\n <div style={{ fontSize: \"0.75rem\", color: \"#64748b\" }}>{g.groupKey}</div>\n </td>\n <td style={cell} data-testid={`directory-external-${g.id}`}>\n {link.externalKey}\n </td>\n <td style={cell}>{link.membershipMode === \"direct\" ? L.directMode : L.transitiveMode}</td>\n <td style={cell} data-testid={`directory-synced-${g.id}`}>\n {link.syncedAt ? formatDateTime(link.syncedAt, labels) : L.neverSynced}\n {link.unresolvedCount > 0 ? (\n <div style={{ fontSize: \"0.75rem\", color: \"#b45309\" }} data-testid={`directory-unresolved-${g.id}`}>\n {fill(L.unresolvedTemplate, { count: link.unresolvedCount })}\n </div>\n ) : null}\n </td>\n <td style={cell}>\n <span\n data-testid={`directory-status-${g.id}`}\n style={{ fontSize: \"0.75rem\", padding: \"2px 8px\", borderRadius: \"10px\", backgroundColor: link.isSyncEnabled ? \"#dcfce7\" : \"#fee2e2\", color: link.isSyncEnabled ? \"#166534\" : \"#b91c1c\", fontWeight: 600 }}>\n {link.isSyncEnabled ? L.syncEnabled : L.syncPaused}\n </span>\n {link.syncError ? (\n <div style={{ fontSize: \"0.75rem\", color: \"#b91c1c\", marginTop: \"0.25rem\" }} data-testid={`directory-error-${g.id}`}>\n {link.syncError}\n </div>\n ) : null}\n </td>\n {/* td 自体を flex にすると表セルの役割が失われ、列と見出しの対応が切れる */}\n <td style={cell}>\n <div style={{ display: \"flex\", gap: \"0.35rem\", flexWrap: \"wrap\" }}>\n <button\n type=\"button\"\n data-testid={`directory-sync-now-${g.id}`}\n disabled={busy}\n aria-busy={busy}\n aria-label={fill(queuedGroupId === g.id ? L.syncQueuedAria : L.syncNowAria, { group: g.name })}\n onClick={() => submitThen({ intent: \"requestDirectorySync\", groupId: String(g.id) }, () => setQueuedGroupId(g.id))}\n style={smallButton}>\n {queuedGroupId === g.id ? L.syncQueued : L.syncNow}\n </button>\n <button\n type=\"button\"\n data-testid={`directory-toggle-sync-${g.id}`}\n disabled={busy}\n aria-busy={busy}\n aria-label={fill(link.isSyncEnabled ? L.pauseAria : L.resumeAria, { group: g.name })}\n onClick={() => {\n setQueuedGroupId(null)\n submitThen({ intent: \"setDirectorySyncEnabled\", groupId: String(g.id), isSyncEnabled: String(!link.isSyncEnabled) })\n }}\n style={smallButton}>\n {link.isSyncEnabled ? L.pause : L.resume}\n </button>\n <button\n type=\"button\"\n data-testid={`directory-allow-${g.id}`}\n disabled={busy}\n aria-busy={busy}\n aria-label={fill(allowed ? L.disallowGroupAria : L.allowGroupAria, { group: g.name })}\n onClick={() => {\n const entry = allowlistEntryOf(g.id)\n if (allowed && entry) {\n // 取り消しはサインインを止める。追加は元に戻すだけなので確認しない\n if (!window.confirm(L.confirmDisallow)) return\n submitThen({ intent: \"removeAllowlistGroup\", entryId: String(entry.id) })\n } else {\n submitThen({ intent: \"addAllowlistGroup\", groupId: String(g.id) })\n }\n }}\n style={smallButton}>\n {allowed ? L.disallowGroup : L.allowGroup}\n </button>\n <button\n type=\"button\"\n data-testid={`directory-unlink-${g.id}`}\n disabled={busy}\n aria-busy={busy}\n aria-label={fill(L.unlinkAria, { group: g.name })}\n onClick={() => {\n // 台帳とグループ単位の許可が失われる。取り消せないので必ず確認する\n if (!window.confirm(L.confirmUnlink)) return\n submitThen({ intent: \"unlinkDirectoryGroup\", groupId: String(g.id) })\n }}\n style={{ ...smallButton, color: \"#b91c1c\", borderColor: \"#fecaca\" }}>\n {L.unlink}\n </button>\n </div>\n </td>\n </tr>\n )\n })}\n </tbody>\n </table>\n )}\n </section>\n )\n}\n\n/** フォーム項目の見出し。プレースホルダは入力すると消えるため名前にならない。 */\nconst fieldLabel: React.CSSProperties = { fontSize: \"0.78rem\", color: \"#475569\", fontWeight: 600 }\n/** 各ビューの見出し。段階を飛ばさないよう h2 に固定する。 */\nconst heading: React.CSSProperties = { margin: 0, fontSize: \"1.05rem\", fontWeight: 700, color: \"#0f172a\" }\n/** 表セルの共通スタイル。 */\nconst cell: React.CSSProperties = { padding: \"0.6rem 0.75rem\", fontSize: \"0.85rem\", verticalAlign: \"top\" }\n/** 行内の小さな操作ボタン。 */\nconst smallButton: React.CSSProperties = { padding: \"0.25rem 0.6rem\", fontSize: \"0.75rem\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#fff\", cursor: \"pointer\" }\n\nconst SEGMENT_VIEWS: Record<string, () => React.ReactElement> = {\n users: UsersView,\n groups: GroupsView,\n group_members: GroupMembersView,\n allowlist: AllowlistView,\n directory: DirectoryView,\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 // 素のオブジェクト参照だと `segment=\"constructor\"` が真値を返し、画面が描けなくなる\n const View = Object.hasOwn(SEGMENT_VIEWS, segment) ? SEGMENT_VIEWS[segment] : UsersView\n\n return (\n <UserAdminShell renderHeader={renderHeader}>\n <View />\n </UserAdminShell>\n )\n}\n"],"mappings":"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","names":["admin_exports","__export","AllowlistView","AuthUserAdminAppView","DirectoryView","GroupMembersView","GroupsView","UserAdminShell","UsersView","createInMemoryUserAdminStore","createUserAdminApp","defaultUserAdminLabels","jaUserAdminLabels","resolveUserAdminLabels","__toCommonJS","defaultUserAdminLabels","jaUserAdminLabels","resolveUserAdminLabels","over","d","import_react_router","createInMemoryUserAdminStore","initial","users","groups","groupMembers","allowlist","data","newUser","u","id","idx","isActive","newGroup","g","m","groupId","userId","existing","newMember","newEntry","a","description","group","isForbidden","error","requireAllowlistType","formData","raw","SEGMENTS","segmentFromPathname","pathname","parts","last","INTENT_ACCESS","requireId","field","n","requireText","value","requireBoolean","requireMembershipMode","optionalText","createUserAdminApp","config","labels","resolveUserAdminLabels","loader","request","segment","directoryStatus","myPermissions","e","action","intent","access","isDenied","denied","dispatch","rawActive","type","import_react_router","fill","template","values","text","key","value","formatDateTime","iso","labels","normalized","import_jsx_runtime","GroupPill","label","summary","DirectorySyncBanner","status","labels","groups","L","warnings","fill","at","formatDateTime","described","id","group","g","reason","w","UserAdminShell","children","renderHeader","data","segment","defaultUserAdminLabels","myPermissions","resourceLabels","labelFor","resourceKey","tabs","hasLinkedGroup","permEntries","getSummary","perm","unavailableSummary","annotation","resKey","tab","isActive","import_react","import_react_router","import_jsx_runtime","ActionError","raw","id","result","useSubmitThen","fetcher","pending","discarded","setDiscarded","onSuccess","payload","useModalBehavior","isOpen","onClose","openerRef","ref","closeRef","panel","opener","focusables","onKeyDown","event","items","first","last","active","outside","UsersView","data","submitThen","resetResult","actionResult","labels","defaultUserAdminLabels","searchQuery","setSearchQuery","modal","setModal","editingUser","isCreateOpen","openModal","next","newDisplayName","setNewDisplayName","newEmail","setNewEmail","createUserRef","editUserRef","busy","filteredUsers","u","handleToggleActive","user","handleCreateUser","e","sentName","sentEmail","current","handleUpdateUser","heading","fill","GroupsView","editingGroup","newGroupKey","setNewGroupKey","newName","setNewName","newDescription","setNewDescription","createGroupRef","editGroupRef","filteredGroups","handleCreateGroup","sent","handleUpdateGroup","handleDeleteGroup","groupId","memberCount","m","GroupMembersView","selectedGroupId","setSelectedGroupId","currentGroup","g","currentMemberUserIds","handleToggleMember","userId","isMember","isSelected","allowlistBadgeStyle","type","allowlistTypeLabel","AllowlistView","pattern","setPattern","setType","description","setDescription","handleAddEntry","handleRemoveEntry","handleRemoveGroupEntry","item","DirectoryView","L","linkable","setGroupId","externalKey","setExternalKey","membershipMode","setMembershipMode","linked","allowedGroupIds","allowlistEntryOf","queuedGroupId","setQueuedGroupId","canLink","handleLink","sentKey","fieldLabel","cell","link","allowed","formatDateTime","smallButton","entry","SEGMENT_VIEWS","AuthUserAdminAppView","renderHeader","segment","View","UserAdminShell"]}