@aiquants/auth-react-router 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -1
- package/dist/admin.d.mts +268 -0
- package/dist/admin.d.ts +268 -0
- package/dist/admin.js +2 -0
- package/dist/admin.js.map +1 -0
- package/dist/admin.mjs +2 -0
- package/dist/admin.mjs.map +1 -0
- package/dist/client.d.mts +2 -2
- package/dist/client.d.ts +2 -2
- package/dist/server.d.mts +23 -4
- package/dist/server.d.ts +23 -4
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +1 -1
- package/dist/server.mjs.map +1 -1
- package/dist/styles/auth-react-router.standalone.css +1 -1
- package/package.json +10 -3
- package/src/admin/app.ts +4 -0
- package/src/admin/labels.ts +274 -0
- package/src/admin/server.spec.ts +300 -0
- package/src/admin/server.ts +345 -0
- package/src/admin/ui.tsx +71 -0
- package/src/admin/views.spec.tsx +307 -0
- package/src/admin/views.tsx +696 -0
- package/src/admin.ts +5 -0
- package/src/server/auth-server.ts +1 -0
- package/src/server/google-strategy.spec.ts +56 -0
- package/src/server/google-strategy.ts +10 -1
- package/src/server/ports.ts +22 -4
- package/src/server/session.spec.ts +52 -0
- package/src/server/session.ts +7 -2
package/dist/admin.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var G=Object.defineProperty;var T=Object.getOwnPropertyDescriptor;var F=Object.getOwnPropertyNames;var K=Object.prototype.hasOwnProperty;var $=(i,s)=>{for(var t in s)G(i,t,{get:s[t],enumerable:!0})},W=(i,s,t,d)=>{if(s&&typeof s=="object"||typeof s=="function")for(let c of F(s))!K.call(i,c)&&c!==t&&G(i,c,{get:()=>s[c],enumerable:!(d=T(s,c))||d.enumerable});return i};var q=i=>W(G({},"__esModule",{value:!0}),i);var X={};$(X,{AllowlistView:()=>N,AuthUserAdminAppView:()=>J,GroupMembersView:()=>P,GroupsView:()=>E,UserAdminShell:()=>z,UsersView:()=>M,createInMemoryUserAdminStore:()=>O,createUserAdminApp:()=>Q,defaultUserAdminLabels:()=>x,jaUserAdminLabels:()=>j,resolveUserAdminLabels:()=>R});module.exports=q(X);var x={title:"Users & Groups",heading:"\u{1F464} User administration",tabs:{users:"\u{1F464} Users",groups:"\u{1F465} Groups",groupMembers:"\u{1F517} Members",allowlist:"\u2709\uFE0F Allowlist"},usersView:{title:"Users",searchPlaceholder:"Search by name or email...",addUser:"\uFF0B Add user",editUser:"Edit user",id:"ID",displayName:"Name",email:"Email",status:"Status",active:"Active",inactive:"Inactive",actions:"Actions",edit:"Edit",activate:"Activate",deactivate:"Deactivate",empty:"No matching users",save:"Save",cancel:"Cancel"},groupsView:{title:"Groups",searchPlaceholder:"Search by group name or key...",addGroup:"\uFF0B Add group",editGroup:"Edit group",id:"ID",groupKey:"Group key",name:"Name",description:"Description",memberCount:"Members",status:"Status",actions:"Actions",edit:"Edit",delete:"Delete",confirmDelete:"Delete this group?",noDescription:"No description",groupKeyPlaceholder:"e.g. dev-team",namePlaceholder:"e.g. Development team",empty:"No groups registered",save:"Save",cancel:"Cancel"},groupMembersView:{selectGroup:"Select a group",selectPrompt:"Select a group to manage its members",actionAdd:"Add",actionRemove:"Remove"},allowlistView:{title:"Sign-in allowlist",addEntry:"\uFF0B Add pattern",pattern:"Pattern (e.g. @example.com or user@example.com)",type:"Type",emailType:"Exact email match",domainType:"Domain match",emailTypeShort:"Email",domainTypeShort:"Domain",description:"Description",actions:"Actions",readOnly:"Read-only",delete:"Delete",empty:"No allowlist entries registered",save:"Add",cancel:"Cancel"}},j={title:"\u30E6\u30FC\u30B6\u30FC\u30FB\u30B0\u30EB\u30FC\u30D7\u7BA1\u7406",heading:"\u{1F464} \u30E6\u30FC\u30B6\u30FC\u7BA1\u7406",tabs:{users:"\u{1F464} \u30E6\u30FC\u30B6\u30FC\u4E00\u89A7",groups:"\u{1F465} \u30E6\u30FC\u30B6\u30FC\u30B0\u30EB\u30FC\u30D7",groupMembers:"\u{1F517} \u30B0\u30EB\u30FC\u30D7\u30E1\u30F3\u30D0\u30FC",allowlist:"\u2709\uFE0F \u30A2\u30AF\u30BB\u30B9\u8A31\u53EF\u30EA\u30B9\u30C8"},usersView:{title:"\u30E6\u30FC\u30B6\u30FC\u7BA1\u7406",searchPlaceholder:"\u8868\u793A\u540D\u3084 Email \u3067\u691C\u7D22...",addUser:"\uFF0B \u30E6\u30FC\u30B6\u30FC\u8FFD\u52A0",editUser:"\u30E6\u30FC\u30B6\u30FC\u306E\u7DE8\u96C6",id:"ID",displayName:"\u8868\u793A\u540D",email:"\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9",status:"\u30B9\u30C6\u30FC\u30BF\u30B9",active:"\u6709\u52B9",inactive:"\u7121\u52B9",actions:"\u64CD\u4F5C",edit:"\u7DE8\u96C6",activate:"\u6709\u52B9\u5316",deactivate:"\u7121\u52B9\u5316",empty:"\u8A72\u5F53\u3059\u308B\u30E6\u30FC\u30B6\u30FC\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093",save:"\u4FDD\u5B58",cancel:"\u30AD\u30E3\u30F3\u30BB\u30EB"},groupsView:{title:"\u30E6\u30FC\u30B6\u30FC\u30B0\u30EB\u30FC\u30D7\u7BA1\u7406",searchPlaceholder:"\u30B0\u30EB\u30FC\u30D7\u540D\u3084\u30AD\u30FC\u3067\u691C\u7D22...",addGroup:"\uFF0B \u30B0\u30EB\u30FC\u30D7\u4F5C\u6210",editGroup:"\u30B0\u30EB\u30FC\u30D7\u306E\u7DE8\u96C6",id:"ID",groupKey:"\u30B0\u30EB\u30FC\u30D7\u30AD\u30FC",name:"\u30B0\u30EB\u30FC\u30D7\u540D",description:"\u8AAC\u660E",memberCount:"\u30E1\u30F3\u30D0\u30FC\u6570",status:"\u30B9\u30C6\u30FC\u30BF\u30B9",actions:"\u64CD\u4F5C",edit:"\u7DE8\u96C6",delete:"\u524A\u9664",confirmDelete:"\u3053\u306E\u30B0\u30EB\u30FC\u30D7\u3092\u524A\u9664\u3057\u3066\u3082\u3088\u308D\u3057\u3044\u3067\u3059\u304B\uFF1F",noDescription:"\u8AAC\u660E\u306A\u3057",groupKeyPlaceholder:"\u4F8B: dev-team",namePlaceholder:"\u4F8B: \u958B\u767A\u30C1\u30FC\u30E0",empty:"\u30B0\u30EB\u30FC\u30D7\u304C\u767B\u9332\u3055\u308C\u3066\u3044\u307E\u305B\u3093",save:"\u4FDD\u5B58",cancel:"\u30AD\u30E3\u30F3\u30BB\u30EB"},groupMembersView:{selectGroup:"\u30B0\u30EB\u30FC\u30D7\u9078\u629E",selectPrompt:"\u30B0\u30EB\u30FC\u30D7\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044",actionAdd:"\u8FFD\u52A0",actionRemove:"\u9664\u5916"},allowlistView:{title:"\u30A2\u30AF\u30BB\u30B9\u8A31\u53EF\u30EA\u30B9\u30C8 (Allowlist)",addEntry:"\uFF0B \u8A31\u53EF\u30D1\u30BF\u30FC\u30F3\u8FFD\u52A0",pattern:"\u30D1\u30BF\u30FC\u30F3 (\u4F8B: @example.com \u307E\u305F\u306F user@example.com)",type:"\u7A2E\u5225",emailType:"\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u5B8C\u5168\u4E00\u81F4",domainType:"\u30C9\u30E1\u30A4\u30F3\u4E00\u81F4",emailTypeShort:"Email \u5B8C\u5168\u4E00\u81F4",domainTypeShort:"\u30C9\u30E1\u30A4\u30F3\u4E00\u81F4",description:"\u8AAC\u660E / \u5099\u8003",actions:"\u64CD\u4F5C",readOnly:"\u5909\u66F4\u4E0D\u53EF",delete:"\u524A\u9664",empty:"\u30A2\u30ED\u30EF\u30EA\u30B9\u30C8\u30A8\u30F3\u30C8\u30EA\u304C\u767B\u9332\u3055\u308C\u3066\u3044\u307E\u305B\u3093",save:"\u767B\u9332",cancel:"\u30AD\u30E3\u30F3\u30BB\u30EB"}};function R(i){let s=x;return i?{title:i.title??s.title,heading:i.heading??s.heading,tabs:{...s.tabs,...i.tabs},usersView:{...s.usersView,...i.usersView},groupsView:{...s.groupsView,...i.groupsView},groupMembersView:{...s.groupMembersView,...i.groupMembersView},allowlistView:{...s.allowlistView,...i.allowlistView}}:s}function O(i){let s=i?.users??[{id:1,email:"admin@example.com",displayName:"\u30B7\u30B9\u30C6\u30E0\u7BA1\u7406\u8005",isActive:!0},{id:2,email:"user1@example.com",displayName:"\u958B\u767A \u592A\u90CE",isActive:!0},{id:3,email:"user2@example.com",displayName:"\u55B6\u696D \u82B1\u5B50",isActive:!0}],t=i?.groups??[{id:1,groupKey:"dev-team",name:"\u958B\u767A\u30C1\u30FC\u30E0",description:"\u30B7\u30B9\u30C6\u30E0\u958B\u767A\u62C5\u5F53\u30B0\u30EB\u30FC\u30D7",isActive:!0},{id:2,groupKey:"sales-dept",name:"\u55B6\u696D\u90E8",description:"\u55B6\u696D\u30BD\u30EA\u30E5\u30FC\u30B7\u30E7\u30F3\u62C5\u5F53",isActive:!0}],d=i?.groupMembers??[{id:1,groupId:1,userId:2},{id:2,groupId:2,userId:3}],c=i?.allowlist??[{id:1,pattern:"admin@example.com",type:"email",description:"\u521D\u671F\u7BA1\u7406\u8005"},{id:2,pattern:"@example.com",type:"domain",description:"\u793E\u5185\u30C9\u30E1\u30A4\u30F3"}];return{async listUsers(){return[...s]},async createUser(o){let r={id:Math.max(...s.map(a=>a.id),0)+1,email:o.email,displayName:o.displayName,isActive:!0};return s.push(r),r},async updateUser(o,r){let a=s.findIndex(n=>n.id===o);if(a===-1)throw new Error(`User not found: ${o}`);return s[a]={...s[a],displayName:r.displayName,email:r.email},s[a]},async toggleUserActive(o,r){let a=s.findIndex(n=>n.id===o);if(a===-1)throw new Error(`User not found: ${o}`);return s[a]={...s[a],isActive:r},s[a]},async listGroups(){return[...t]},async createGroup(o){let r={id:Math.max(...t.map(a=>a.id),0)+1,groupKey:o.groupKey,name:o.name,description:o.description,isActive:!0};return t.push(r),r},async updateGroup(o,r){let a=t.findIndex(n=>n.id===o);if(a===-1)throw new Error(`Group not found: ${o}`);return t[a]={...t[a],name:r.name,description:r.description},t[a]},async deleteGroup(o){t=t.filter(r=>r.id!==o),d=d.filter(r=>r.groupId!==o)},async listGroupMembers(){return[...d]},async addGroupMember(o,r){let a=d.find(l=>l.groupId===o&&l.userId===r);if(a)return a;let n={id:Math.max(...d.map(l=>l.id),0)+1,groupId:o,userId:r};return d.push(n),n},async removeGroupMember(o,r){d=d.filter(a=>!(a.groupId===o&&a.userId===r))},async listAllowlist(){return[...c]},async addAllowlistEntry(o){let r={id:Math.max(...c.map(a=>a.id),0)+1,pattern:o.pattern,type:o.type,description:o.description};return c.push(r),r},async removeAllowlistEntry(o){c=c.filter(r=>r.id!==o)}}}var _=new Map([["createUser","create"],["updateUser","update"],["toggleUserActive","update"],["createGroup","create"],["updateGroup","update"],["deleteGroup","delete"],["addGroupMember","create"],["removeGroupMember","delete"],["addAllowlistEntry","create"],["removeAllowlistEntry","delete"]]);function h(i,s){let t=i.get(s),d=Number(t);if(t===null||String(t).trim()===""||!Number.isInteger(d)||d<=0)throw new Error(`Invalid or missing "${s}"`);return d}function w(i,s){let t=String(i.get(s)??"").trim();if(t==="")throw new Error(`Invalid or missing "${s}"`);return t}function I(i,s){let t=String(i.get(s)??"").trim();return t===""?void 0:t}function Q(i){let s=R(i.labels);async function t({request:o}){await i.guards.requireAccess(o,{action:"read",intent:"loader"});let a=new URL(o.url).pathname.split("/").filter(Boolean),n=a[a.length-1]??"users",[l,y,u,v]=await Promise.all([i.store.listUsers(),i.store.listGroups(),i.store.listGroupMembers(),i.store.listAllowlist()]);return{segment:["groups","group_members","allowlist"].includes(n)?n:"users",users:l,groups:y,groupMembers:u,allowlist:v,labels:s}}async function d({request:o}){let r=await o.formData(),a=String(r.get("intent")??""),n=_.get(a);if(!n)return{ok:!1,error:`Unknown intent: ${a}`};await i.guards.requireAccess(o,{action:n,intent:a});try{return await c(a,r)}catch(l){if(l instanceof Response)throw l;return{ok:!1,error:l instanceof Error?l.message:String(l)}}}async function c(o,r){if(o==="createUser")return await i.store.createUser({displayName:w(r,"displayName"),email:w(r,"email")}),{ok:!0};if(o==="updateUser")return await i.store.updateUser(h(r,"userId"),{displayName:w(r,"displayName"),email:w(r,"email")}),{ok:!0};if(o==="toggleUserActive"){let a=String(r.get("isActive")??"");if(a!=="true"&&a!=="false")throw new Error('Invalid or missing "isActive"');return await i.store.toggleUserActive(h(r,"userId"),a==="true"),{ok:!0}}if(o==="createGroup")return await i.store.createGroup({groupKey:w(r,"groupKey"),name:w(r,"name"),description:I(r,"description")}),{ok:!0};if(o==="updateGroup")return await i.store.updateGroup(h(r,"groupId"),{name:w(r,"name"),description:I(r,"description")}),{ok:!0};if(o==="deleteGroup")return await i.store.deleteGroup(h(r,"groupId")),{ok:!0};if(o==="addGroupMember")return await i.store.addGroupMember(h(r,"groupId"),h(r,"userId")),{ok:!0};if(o==="removeGroupMember")return await i.store.removeGroupMember(h(r,"groupId"),h(r,"userId")),{ok:!0};if(o==="addAllowlistEntry"){let a=r.get("type")==="domain"?"domain":"email";return await i.store.addAllowlistEntry({pattern:w(r,"pattern"),type:a,description:I(r,"description")}),{ok:!0}}return o==="removeAllowlistEntry"?(await i.store.removeAllowlistEntry(h(r,"entryId")),{ok:!0}):{ok:!1,error:`Unknown intent: ${o}`}}return{loader:t,action:d}}var C=require("react-router");var f=require("react/jsx-runtime");function z({children:i,renderHeader:s}){let t=(0,C.useLoaderData)(),d=t?.segment??"users",c=t?.labels??x,o=[{key:"users",label:c.tabs.users,href:"/users"},{key:"groups",label:c.tabs.groups,href:"/users/groups"},{key:"group_members",label:c.tabs.groupMembers,href:"/users/group_members"},{key:"allowlist",label:c.tabs.allowlist,href:"/users/allowlist"}],r=(0,f.jsx)("div",{style:{display:"flex",alignItems:"center",gap:"0.5rem"},"data-testid":"user-admin-header-annotation",children:(0,f.jsx)("span",{style:{fontSize:"0.75rem",backgroundColor:"#e0f2fe",color:"#0369a1",padding:"2px 8px",borderRadius:"12px",fontWeight:600},children:c.heading})});return(0,f.jsxs)("div",{"data-testid":"user-admin-shell",style:{width:"100%",minHeight:"100vh",backgroundColor:"#f8fafc",padding:"1.5rem",boxSizing:"border-box"},children:[s?s({title:c.title,annotation:r}):(0,f.jsxs)("div",{style:{marginBottom:"1.5rem",display:"flex",justifyContent:"space-between",alignItems:"center"},children:[(0,f.jsx)("h1",{style:{margin:0,fontSize:"1.5rem",fontWeight:700,color:"#0f172a"},children:c.title}),r]}),(0,f.jsx)("div",{style:{display:"flex",gap:"0.5rem",marginBottom:"1.2rem",borderBottom:"2px solid #e2e8f0",paddingBottom:"0.5rem"},children:o.map(a=>{let n=d===a.key;return(0,f.jsx)(C.Link,{to:a.href,"data-testid":`user-admin-tab-${a.key}`,style:{padding:"0.5rem 1rem",borderRadius:"6px 6px 0 0",fontSize:"0.88rem",fontWeight:n?700:500,color:n?"#0284c7":"#64748b",backgroundColor:n?"#ffffff":"transparent",textDecoration:"none",borderBottom:n?"3px solid #0284c7":"3px solid transparent",transition:"all 0.15s ease"},children:a.label},a.key)})}),(0,f.jsx)("main",{children:i})]})}var g=require("react"),b=require("react-router");var e=require("react/jsx-runtime");function U({fetcher:i}){let s=i.data;return s?.ok!==!1||!s.error?null:(0,e.jsx)("div",{"data-testid":"user-admin-action-error",role:"alert",style:{padding:"0.7rem 1rem",borderRadius:"6px",border:"1px solid #fecaca",backgroundColor:"#fef2f2",color:"#b91c1c",fontSize:"0.85rem"},children:s.error})}function M(){let i=(0,b.useLoaderData)(),s=(0,b.useFetcher)(),t=i.labels??x,[d,c]=(0,g.useState)(""),[o,r]=(0,g.useState)(null),[a,n]=(0,g.useState)(!1),[l,y]=(0,g.useState)(""),[u,v]=(0,g.useState)(""),A=i.users.filter(p=>p.displayName.toLowerCase().includes(d.toLowerCase())||p.email.toLowerCase().includes(d.toLowerCase())),S=p=>{s.submit({intent:"toggleUserActive",userId:String(p.id),isActive:String(!p.isActive)},{method:"post"})},k=p=>{p.preventDefault(),l&&u&&(s.submit({intent:"createUser",displayName:l,email:u},{method:"post"}),y(""),v(""),n(!1))},V=p=>{p.preventDefault(),o&&(s.submit({intent:"updateUser",userId:String(o.id),displayName:o.displayName,email:o.email},{method:"post"}),r(null))};return(0,e.jsxs)("section",{"data-testid":"users-view-section",style:{display:"flex",flexDirection:"column",gap:"1rem"},children:[(0,e.jsx)(U,{fetcher:s}),(0,e.jsxs)("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",backgroundColor:"#ffffff",padding:"1rem",borderRadius:"8px",border:"1px solid #e2e8f0"},children:[(0,e.jsx)("input",{type:"text",placeholder:t.usersView.searchPlaceholder,value:d,onChange:p=>c(p.target.value),"data-testid":"users-search-input",style:{padding:"6px 12px",borderRadius:"6px",border:"1px solid #cbd5e1",fontSize:"0.85rem",width:"260px"}}),(0,e.jsx)("button",{type:"button",onClick:()=>n(!0),"data-testid":"users-add-button",style:{padding:"6px 14px",borderRadius:"6px",backgroundColor:"#0284c7",color:"#ffffff",border:"none",fontSize:"0.85rem",fontWeight:600,cursor:"pointer"},children:t.usersView.addUser})]}),(0,e.jsx)("div",{style:{backgroundColor:"#ffffff",borderRadius:"8px",border:"1px solid #e2e8f0",overflow:"hidden"},children:(0,e.jsxs)("table",{style:{width:"100%",borderCollapse:"collapse",fontSize:"0.85rem",textAlign:"left"},children:[(0,e.jsx)("thead",{children:(0,e.jsxs)("tr",{style:{backgroundColor:"#f8fafc",borderBottom:"1px solid #e2e8f0",color:"#475569"},children:[(0,e.jsx)("th",{style:{padding:"10px 14px"},children:t.usersView.id}),(0,e.jsx)("th",{style:{padding:"10px 14px"},children:t.usersView.displayName}),(0,e.jsx)("th",{style:{padding:"10px 14px"},children:t.usersView.email}),(0,e.jsx)("th",{style:{padding:"10px 14px"},children:t.usersView.status}),(0,e.jsx)("th",{style:{padding:"10px 14px",textAlign:"right"},children:t.usersView.actions})]})}),(0,e.jsxs)("tbody",{children:[A.map(p=>(0,e.jsxs)("tr",{style:{borderBottom:"1px solid #f1f5f9"},"data-testid":`user-row-${p.id}`,children:[(0,e.jsxs)("td",{style:{padding:"10px 14px",color:"#64748b"},children:["#",p.id]}),(0,e.jsx)("td",{style:{padding:"10px 14px",fontWeight:600,color:"#0f172a"},children:p.displayName}),(0,e.jsx)("td",{style:{padding:"10px 14px",color:"#475569"},children:p.email}),(0,e.jsx)("td",{style:{padding:"10px 14px"},children:(0,e.jsx)("span",{style:{fontSize:"0.75rem",padding:"2px 8px",borderRadius:"10px",backgroundColor:p.isActive?"#dcfce7":"#fee2e2",color:p.isActive?"#15803d":"#b91c1c",fontWeight:600},children:p.isActive?t.usersView.active:t.usersView.inactive})}),(0,e.jsxs)("td",{style:{padding:"10px 14px",textAlign:"right"},children:[(0,e.jsx)("button",{type:"button",onClick:()=>r(p),"data-testid":`edit-user-btn-${p.id}`,style:{padding:"3px 8px",borderRadius:"4px",border:"1px solid #cbd5e1",backgroundColor:"#ffffff",fontSize:"0.78rem",cursor:"pointer",marginRight:"6px"},children:t.usersView.edit}),(0,e.jsx)("button",{type:"button",onClick:()=>S(p),"data-testid":`toggle-user-btn-${p.id}`,style:{padding:"3px 8px",borderRadius:"4px",border:"1px solid #cbd5e1",backgroundColor:"#ffffff",fontSize:"0.78rem",cursor:"pointer",color:p.isActive?"#b91c1c":"#15803d"},children:p.isActive?t.usersView.deactivate:t.usersView.activate})]})]},p.id)),A.length===0&&(0,e.jsx)("tr",{children:(0,e.jsx)("td",{colSpan:5,style:{padding:"2rem",textAlign:"center",color:"#94a3b8"},children:t.usersView.empty})})]})]})}),a&&(0,e.jsx)("div",{style:{position:"fixed",inset:0,backgroundColor:"rgba(0,0,0,0.4)",display:"flex",justifyContent:"center",alignItems:"center",zIndex:100},"data-testid":"user-create-modal",children:(0,e.jsxs)("form",{onSubmit:k,style:{backgroundColor:"#ffffff",padding:"1.5rem",borderRadius:"8px",width:"360px",boxShadow:"0 10px 25px rgba(0,0,0,0.15)"},children:[(0,e.jsx)("h3",{style:{margin:"0 0 1rem 0",fontSize:"1.1rem"},children:t.usersView.addUser}),(0,e.jsxs)("div",{style:{marginBottom:"1rem"},children:[(0,e.jsx)("label",{htmlFor:"create-user-display-name-input",style:{display:"block",fontSize:"0.8rem",color:"#475569",marginBottom:"4px"},children:t.usersView.displayName}),(0,e.jsx)("input",{id:"create-user-display-name-input",type:"text",value:l,onChange:p=>y(p.target.value),required:!0,"data-testid":"create-user-display-name",style:{width:"100%",padding:"6px",borderRadius:"4px",border:"1px solid #cbd5e1",boxSizing:"border-box"}})]}),(0,e.jsxs)("div",{style:{marginBottom:"1.2rem"},children:[(0,e.jsx)("label",{htmlFor:"create-user-email-input",style:{display:"block",fontSize:"0.8rem",color:"#475569",marginBottom:"4px"},children:t.usersView.email}),(0,e.jsx)("input",{id:"create-user-email-input",type:"email",value:u,onChange:p=>v(p.target.value),required:!0,"data-testid":"create-user-email",style:{width:"100%",padding:"6px",borderRadius:"4px",border:"1px solid #cbd5e1",boxSizing:"border-box"}})]}),(0,e.jsxs)("div",{style:{display:"flex",justifyContent:"flex-end",gap:"0.5rem"},children:[(0,e.jsx)("button",{type:"button",onClick:()=>n(!1),style:{padding:"6px 12px",borderRadius:"4px",border:"1px solid #cbd5e1",backgroundColor:"#ffffff",cursor:"pointer"},children:"\u30AD\u30E3\u30F3\u30BB\u30EB"}),(0,e.jsx)("button",{type:"submit","data-testid":"submit-create-user",style:{padding:"6px 12px",borderRadius:"4px",backgroundColor:"#0284c7",color:"#ffffff",border:"none",cursor:"pointer"},children:"\u4F5C\u6210"})]})]})}),o&&(0,e.jsx)("div",{style:{position:"fixed",inset:0,backgroundColor:"rgba(0,0,0,0.4)",display:"flex",justifyContent:"center",alignItems:"center",zIndex:100},"data-testid":"user-edit-modal",children:(0,e.jsxs)("form",{onSubmit:V,style:{backgroundColor:"#ffffff",padding:"1.5rem",borderRadius:"8px",width:"360px",boxShadow:"0 10px 25px rgba(0,0,0,0.15)"},children:[(0,e.jsx)("h3",{style:{margin:"0 0 1rem 0",fontSize:"1.1rem"},children:t.usersView.editUser}),(0,e.jsxs)("div",{style:{marginBottom:"1rem"},children:[(0,e.jsx)("label",{htmlFor:"edit-user-display-name-input",style:{display:"block",fontSize:"0.8rem",color:"#475569",marginBottom:"4px"},children:t.usersView.displayName}),(0,e.jsx)("input",{id:"edit-user-display-name-input",type:"text",value:o.displayName,onChange:p=>r({...o,displayName:p.target.value}),required:!0,"data-testid":"edit-user-display-name",style:{width:"100%",padding:"6px",borderRadius:"4px",border:"1px solid #cbd5e1",boxSizing:"border-box"}})]}),(0,e.jsxs)("div",{style:{marginBottom:"1.2rem"},children:[(0,e.jsx)("label",{htmlFor:"edit-user-email-input",style:{display:"block",fontSize:"0.8rem",color:"#475569",marginBottom:"4px"},children:t.usersView.email}),(0,e.jsx)("input",{id:"edit-user-email-input",type:"email",value:o.email,onChange:p=>r({...o,email:p.target.value}),required:!0,"data-testid":"edit-user-email",style:{width:"100%",padding:"6px",borderRadius:"4px",border:"1px solid #cbd5e1",boxSizing:"border-box"}})]}),(0,e.jsxs)("div",{style:{display:"flex",justifyContent:"flex-end",gap:"0.5rem"},children:[(0,e.jsx)("button",{type:"button",onClick:()=>r(null),style:{padding:"6px 12px",borderRadius:"4px",border:"1px solid #cbd5e1",backgroundColor:"#ffffff",cursor:"pointer"},children:t.usersView.cancel}),(0,e.jsx)("button",{type:"submit","data-testid":"submit-edit-user",style:{padding:"6px 12px",borderRadius:"4px",backgroundColor:"#0284c7",color:"#ffffff",border:"none",cursor:"pointer"},children:t.usersView.save})]})]})})]})}function E(){let i=(0,b.useLoaderData)(),s=(0,b.useFetcher)(),t=i.labels??x,[d,c]=(0,g.useState)(""),[o,r]=(0,g.useState)(!1),[a,n]=(0,g.useState)(null),[l,y]=(0,g.useState)(""),[u,v]=(0,g.useState)(""),[A,S]=(0,g.useState)(""),k=i.groups.filter(m=>m.name.toLowerCase().includes(d.toLowerCase())||m.groupKey.toLowerCase().includes(d.toLowerCase())),V=m=>{m.preventDefault(),l&&u&&(s.submit({intent:"createGroup",groupKey:l,name:u,description:A},{method:"post"}),y(""),v(""),S(""),r(!1))},p=m=>{m.preventDefault(),a&&(s.submit({intent:"updateGroup",groupId:String(a.id),name:a.name,description:a.description??""},{method:"post"}),n(null))},L=m=>{window.confirm(t.groupsView.confirmDelete)&&s.submit({intent:"deleteGroup",groupId:String(m)},{method:"post"})};return(0,e.jsxs)("section",{"data-testid":"groups-view-section",style:{display:"flex",flexDirection:"column",gap:"1rem"},children:[(0,e.jsx)(U,{fetcher:s}),(0,e.jsxs)("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",backgroundColor:"#ffffff",padding:"1rem",borderRadius:"8px",border:"1px solid #e2e8f0"},children:[(0,e.jsx)("input",{type:"text",placeholder:t.groupsView.searchPlaceholder,value:d,onChange:m=>c(m.target.value),"data-testid":"groups-search-input",style:{padding:"6px 12px",borderRadius:"6px",border:"1px solid #cbd5e1",fontSize:"0.85rem",width:"260px"}}),(0,e.jsx)("button",{type:"button",onClick:()=>r(!0),"data-testid":"groups-add-button",style:{padding:"6px 14px",borderRadius:"6px",backgroundColor:"#4338ca",color:"#ffffff",border:"none",fontSize:"0.85rem",fontWeight:600,cursor:"pointer"},children:t.groupsView.addGroup})]}),(0,e.jsx)("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(280px, 1fr))",gap:"1rem"},children:k.map(m=>{let B=i.groupMembers.filter(D=>D.groupId===m.id).length;return(0,e.jsxs)("div",{style:{backgroundColor:"#ffffff",borderRadius:"8px",border:"1px solid #e2e8f0",padding:"1rem",display:"flex",flexDirection:"column",justifyContent:"space-between"},"data-testid":`group-card-${m.id}`,children:[(0,e.jsxs)("div",{children:[(0,e.jsxs)("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:"0.5rem"},children:[(0,e.jsx)("h4",{style:{margin:0,fontSize:"1rem",color:"#0f172a"},children:m.name}),(0,e.jsx)("span",{style:{fontSize:"0.72rem",backgroundColor:"#e0e7ff",color:"#4338ca",padding:"2px 6px",borderRadius:"4px",fontFamily:"monospace"},children:m.groupKey})]}),(0,e.jsx)("p",{style:{margin:"0 0 0.8rem 0",fontSize:"0.82rem",color:"#64748b",minHeight:"2.4em"},children:m.description||t.groupsView.noDescription})]}),(0,e.jsxs)("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",borderTop:"1px solid #f1f5f9",paddingTop:"0.6rem"},children:[(0,e.jsxs)("span",{style:{fontSize:"0.78rem",color:"#475569"},children:["\u{1F465} \u30E1\u30F3\u30D0\u30FC: ",(0,e.jsx)("strong",{children:B})," \u4EBA"]}),(0,e.jsxs)("div",{children:[(0,e.jsx)("button",{type:"button",onClick:()=>n(m),"data-testid":`edit-group-btn-${m.id}`,style:{padding:"3px 8px",borderRadius:"4px",border:"1px solid #cbd5e1",backgroundColor:"#ffffff",fontSize:"0.75rem",cursor:"pointer",marginRight:"4px"},children:t.usersView.edit}),(0,e.jsx)("button",{type:"button",onClick:()=>L(m.id),"data-testid":`delete-group-btn-${m.id}`,style:{padding:"3px 8px",borderRadius:"4px",border:"1px solid #fee2e2",backgroundColor:"#fff5f5",color:"#b91c1c",fontSize:"0.75rem",cursor:"pointer"},children:"\u524A\u9664"})]})]})]},m.id)})}),o&&(0,e.jsx)("div",{style:{position:"fixed",inset:0,backgroundColor:"rgba(0,0,0,0.4)",display:"flex",justifyContent:"center",alignItems:"center",zIndex:100},"data-testid":"group-create-modal",children:(0,e.jsxs)("form",{onSubmit:V,style:{backgroundColor:"#ffffff",padding:"1.5rem",borderRadius:"8px",width:"360px",boxShadow:"0 10px 25px rgba(0,0,0,0.15)"},children:[(0,e.jsx)("h3",{style:{margin:"0 0 1rem 0",fontSize:"1.1rem"},children:t.groupsView.addGroup}),(0,e.jsxs)("div",{style:{marginBottom:"1rem"},children:[(0,e.jsx)("label",{htmlFor:"create-group-key-input",style:{display:"block",fontSize:"0.8rem",color:"#475569",marginBottom:"4px"},children:t.groupsView.groupKey}),(0,e.jsx)("input",{id:"create-group-key-input",type:"text",value:l,onChange:m=>y(m.target.value),placeholder:t.groupsView.groupKeyPlaceholder,required:!0,"data-testid":"create-group-key",style:{width:"100%",padding:"6px",borderRadius:"4px",border:"1px solid #cbd5e1",boxSizing:"border-box"}})]}),(0,e.jsxs)("div",{style:{marginBottom:"1rem"},children:[(0,e.jsx)("label",{htmlFor:"create-group-name-input",style:{display:"block",fontSize:"0.8rem",color:"#475569",marginBottom:"4px"},children:t.groupsView.name}),(0,e.jsx)("input",{id:"create-group-name-input",type:"text",value:u,onChange:m=>v(m.target.value),placeholder:t.groupsView.namePlaceholder,required:!0,"data-testid":"create-group-name",style:{width:"100%",padding:"6px",borderRadius:"4px",border:"1px solid #cbd5e1",boxSizing:"border-box"}})]}),(0,e.jsxs)("div",{style:{marginBottom:"1.2rem"},children:[(0,e.jsx)("label",{htmlFor:"create-group-desc-input",style:{display:"block",fontSize:"0.8rem",color:"#475569",marginBottom:"4px"},children:t.groupsView.description}),(0,e.jsx)("textarea",{id:"create-group-desc-input",value:A,onChange:m=>S(m.target.value),"data-testid":"create-group-description",style:{width:"100%",padding:"6px",borderRadius:"4px",border:"1px solid #cbd5e1",boxSizing:"border-box",height:"60px"}})]}),(0,e.jsxs)("div",{style:{display:"flex",justifyContent:"flex-end",gap:"0.5rem"},children:[(0,e.jsx)("button",{type:"button",onClick:()=>r(!1),style:{padding:"6px 12px",borderRadius:"4px",border:"1px solid #cbd5e1",backgroundColor:"#ffffff",cursor:"pointer"},children:"\u30AD\u30E3\u30F3\u30BB\u30EB"}),(0,e.jsx)("button",{type:"submit","data-testid":"submit-create-group",style:{padding:"6px 12px",borderRadius:"4px",backgroundColor:"#4338ca",color:"#ffffff",border:"none",cursor:"pointer"},children:"\u4F5C\u6210"})]})]})}),a&&(0,e.jsx)("div",{style:{position:"fixed",inset:0,backgroundColor:"rgba(0,0,0,0.4)",display:"flex",justifyContent:"center",alignItems:"center",zIndex:100},"data-testid":"group-edit-modal",children:(0,e.jsxs)("form",{onSubmit:p,style:{backgroundColor:"#ffffff",padding:"1.5rem",borderRadius:"8px",width:"360px",boxShadow:"0 10px 25px rgba(0,0,0,0.15)"},children:[(0,e.jsx)("h3",{style:{margin:"0 0 1rem 0",fontSize:"1.1rem"},children:t.groupsView.editGroup}),(0,e.jsxs)("div",{style:{marginBottom:"1rem"},children:[(0,e.jsx)("label",{htmlFor:"edit-group-name-input",style:{display:"block",fontSize:"0.8rem",color:"#475569",marginBottom:"4px"},children:t.groupsView.name}),(0,e.jsx)("input",{id:"edit-group-name-input",type:"text",value:a.name,onChange:m=>n({...a,name:m.target.value}),required:!0,"data-testid":"edit-group-name",style:{width:"100%",padding:"6px",borderRadius:"4px",border:"1px solid #cbd5e1",boxSizing:"border-box"}})]}),(0,e.jsxs)("div",{style:{marginBottom:"1.2rem"},children:[(0,e.jsx)("label",{htmlFor:"edit-group-desc-input",style:{display:"block",fontSize:"0.8rem",color:"#475569",marginBottom:"4px"},children:t.groupsView.description}),(0,e.jsx)("textarea",{id:"edit-group-desc-input",value:a.description??"",onChange:m=>n({...a,description:m.target.value}),"data-testid":"edit-group-description",style:{width:"100%",padding:"6px",borderRadius:"4px",border:"1px solid #cbd5e1",boxSizing:"border-box",height:"60px"}})]}),(0,e.jsxs)("div",{style:{display:"flex",justifyContent:"flex-end",gap:"0.5rem"},children:[(0,e.jsx)("button",{type:"button",onClick:()=>n(null),style:{padding:"6px 12px",borderRadius:"4px",border:"1px solid #cbd5e1",backgroundColor:"#ffffff",cursor:"pointer"},children:t.groupsView.cancel}),(0,e.jsx)("button",{type:"submit","data-testid":"submit-edit-group",style:{padding:"6px 12px",borderRadius:"4px",backgroundColor:"#4338ca",color:"#ffffff",border:"none",cursor:"pointer"},children:t.groupsView.save})]})]})})]})}function P(){let i=(0,b.useLoaderData)(),s=(0,b.useFetcher)(),t=i.labels??x,[d,c]=(0,g.useState)(i.groups[0]?.id??0),o=i.groups.find(n=>n.id===d),r=new Set(i.groupMembers.filter(n=>n.groupId===d).map(n=>n.userId)),a=(n,l)=>{l?s.submit({intent:"removeGroupMember",groupId:String(d),userId:String(n)},{method:"post"}):s.submit({intent:"addGroupMember",groupId:String(d),userId:String(n)},{method:"post"})};return(0,e.jsxs)("section",{"data-testid":"group-members-view-section",style:{display:"grid",gridTemplateColumns:"240px 1fr",gap:"1.2rem"},children:[(0,e.jsx)(U,{fetcher:s}),(0,e.jsxs)("div",{style:{backgroundColor:"#ffffff",padding:"1rem",borderRadius:"8px",border:"1px solid #e2e8f0"},children:[(0,e.jsx)("h4",{style:{margin:"0 0 0.8rem 0",fontSize:"0.9rem",color:"#475569"},children:t.groupMembersView.selectGroup}),(0,e.jsx)("div",{style:{display:"flex",flexDirection:"column",gap:"0.4rem"},children:i.groups.map(n=>{let l=n.id===d;return(0,e.jsx)("button",{type:"button",onClick:()=>c(n.id),"data-testid":`select-group-btn-${n.id}`,style:{textAlign:"left",padding:"8px 10px",borderRadius:"6px",border:l?"1px solid #6366f1":"1px solid #e2e8f0",backgroundColor:l?"#e0e7ff":"#ffffff",color:l?"#3730a3":"#0f172a",cursor:"pointer",fontWeight:l?600:400,fontSize:"0.85rem"},children:n.name},n.id)})})]}),(0,e.jsx)("div",{style:{backgroundColor:"#ffffff",padding:"1.2rem",borderRadius:"8px",border:"1px solid #e2e8f0"},children:o?(0,e.jsxs)(e.Fragment,{children:[(0,e.jsxs)("div",{style:{marginBottom:"1rem",borderBottom:"1px solid #e2e8f0",paddingBottom:"0.8rem"},children:[(0,e.jsxs)("h3",{style:{margin:0,fontSize:"1.1rem",color:"#0f172a"},children:[o.name," \u306E\u6240\u5C5E\u30E1\u30F3\u30D0\u30FC"]}),(0,e.jsxs)("span",{style:{fontSize:"0.8rem",color:"#64748b"},children:["\u30B0\u30EB\u30FC\u30D7\u30AD\u30FC: ",o.groupKey]})]}),(0,e.jsx)("div",{style:{display:"flex",flexDirection:"column",gap:"0.5rem"},children:i.users.map(n=>{let l=r.has(n.id);return(0,e.jsxs)("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",padding:"8px 12px",borderRadius:"6px",border:"1px solid #f1f5f9",backgroundColor:l?"#f0fdf4":"#ffffff"},"data-testid":`member-row-${n.id}`,children:[(0,e.jsxs)("div",{children:[(0,e.jsx)("span",{style:{fontWeight:600,fontSize:"0.88rem",color:"#0f172a",marginRight:"8px"},children:n.displayName}),(0,e.jsxs)("span",{style:{fontSize:"0.78rem",color:"#64748b"},children:["(",n.email,")"]})]}),(0,e.jsx)("button",{type:"button",onClick:()=>a(n.id,l),"data-testid":`toggle-member-btn-${n.id}`,style:{padding:"4px 12px",borderRadius:"4px",border:l?"1px solid #b91c1c":"1px solid #0284c7",backgroundColor:l?"#ffffff":"#0284c7",color:l?"#b91c1c":"#ffffff",fontSize:"0.78rem",fontWeight:600,cursor:"pointer"},children:l?t.groupMembersView.actionRemove:t.groupMembersView.actionAdd})]},n.id)})})]}):(0,e.jsx)("div",{style:{color:"#94a3b8",textAlign:"center",padding:"2rem"},children:t.groupMembersView.selectPrompt})})]})}function N(){let i=(0,b.useLoaderData)(),s=(0,b.useFetcher)(),t=i.labels??x,[d,c]=(0,g.useState)(""),[o,r]=(0,g.useState)("email"),[a,n]=(0,g.useState)(""),l=u=>{u.preventDefault(),d&&(s.submit({intent:"addAllowlistEntry",pattern:d,type:o,description:a},{method:"post"}),c(""),n(""))},y=u=>{s.submit({intent:"removeAllowlistEntry",entryId:String(u)},{method:"post"})};return(0,e.jsxs)("section",{"data-testid":"allowlist-view-section",style:{display:"flex",flexDirection:"column",gap:"1.2rem"},children:[(0,e.jsx)(U,{fetcher:s}),(0,e.jsxs)("form",{onSubmit:l,style:{backgroundColor:"#ffffff",padding:"1.2rem",borderRadius:"8px",border:"1px solid #e2e8f0",display:"flex",flexWrap:"wrap",gap:"0.8rem",alignItems:"flex-end"},children:[(0,e.jsxs)("div",{style:{flex:2,minWidth:"220px"},children:[(0,e.jsx)("label",{htmlFor:"allowlist-pattern-input-field",style:{display:"block",fontSize:"0.78rem",color:"#475569",marginBottom:"4px"},children:t.allowlistView.pattern}),(0,e.jsx)("input",{id:"allowlist-pattern-input-field",type:"text",value:d,onChange:u=>c(u.target.value),required:!0,"data-testid":"allowlist-pattern-input",style:{width:"100%",padding:"6px 10px",borderRadius:"6px",border:"1px solid #cbd5e1",fontSize:"0.85rem",boxSizing:"border-box"}})]}),(0,e.jsxs)("div",{style:{flex:1,minWidth:"140px"},children:[(0,e.jsx)("label",{htmlFor:"allowlist-type-select-field",style:{display:"block",fontSize:"0.78rem",color:"#475569",marginBottom:"4px"},children:t.allowlistView.type}),(0,e.jsxs)("select",{id:"allowlist-type-select-field",value:o,onChange:u=>r(u.target.value),"data-testid":"allowlist-type-select",style:{width:"100%",padding:"6px 10px",borderRadius:"6px",border:"1px solid #cbd5e1",fontSize:"0.85rem",boxSizing:"border-box"},children:[(0,e.jsx)("option",{value:"email",children:t.allowlistView.emailType}),(0,e.jsx)("option",{value:"domain",children:t.allowlistView.domainType})]})]}),(0,e.jsxs)("div",{style:{flex:2,minWidth:"200px"},children:[(0,e.jsx)("label",{htmlFor:"allowlist-desc-input-field",style:{display:"block",fontSize:"0.78rem",color:"#475569",marginBottom:"4px"},children:t.allowlistView.description}),(0,e.jsx)("input",{id:"allowlist-desc-input-field",type:"text",value:a,onChange:u=>n(u.target.value),"data-testid":"allowlist-desc-input",style:{width:"100%",padding:"6px 10px",borderRadius:"6px",border:"1px solid #cbd5e1",fontSize:"0.85rem",boxSizing:"border-box"}})]}),(0,e.jsx)("button",{type:"submit","data-testid":"submit-allowlist-add",style:{padding:"7px 16px",borderRadius:"6px",backgroundColor:"#047857",color:"#ffffff",border:"none",fontSize:"0.85rem",fontWeight:600,cursor:"pointer"},children:t.allowlistView.addEntry})]}),(0,e.jsx)("div",{style:{backgroundColor:"#ffffff",borderRadius:"8px",border:"1px solid #e2e8f0",overflow:"hidden"},children:(0,e.jsxs)("table",{style:{width:"100%",borderCollapse:"collapse",fontSize:"0.85rem",textAlign:"left"},children:[(0,e.jsx)("thead",{children:(0,e.jsxs)("tr",{style:{backgroundColor:"#f8fafc",borderBottom:"1px solid #e2e8f0",color:"#475569"},children:[(0,e.jsx)("th",{style:{padding:"10px 14px"},children:"ID"}),(0,e.jsx)("th",{style:{padding:"10px 14px"},children:t.allowlistView.pattern}),(0,e.jsx)("th",{style:{padding:"10px 14px"},children:t.allowlistView.type}),(0,e.jsx)("th",{style:{padding:"10px 14px"},children:t.allowlistView.description}),(0,e.jsx)("th",{style:{padding:"10px 14px",textAlign:"right"},children:t.allowlistView.actions})]})}),(0,e.jsxs)("tbody",{children:[i.allowlist.map(u=>(0,e.jsxs)("tr",{style:{borderBottom:"1px solid #f1f5f9"},"data-testid":`allowlist-row-${u.id}`,children:[(0,e.jsxs)("td",{style:{padding:"10px 14px",color:"#64748b"},children:["#",u.id]}),(0,e.jsx)("td",{style:{padding:"10px 14px",fontWeight:600,color:"#0f172a",fontFamily:"monospace"},children:u.pattern}),(0,e.jsx)("td",{style:{padding:"10px 14px"},children:(0,e.jsx)("span",{style:{fontSize:"0.75rem",padding:"2px 8px",borderRadius:"10px",backgroundColor:u.type==="email"?"#e0f2fe":"#fef3c7",color:u.type==="email"?"#0369a1":"#b45309",fontWeight:600},children:u.type==="email"?t.allowlistView.emailTypeShort:t.allowlistView.domainTypeShort})}),(0,e.jsx)("td",{style:{padding:"10px 14px",color:"#475569"},children:u.description||"-"}),(0,e.jsx)("td",{style:{padding:"10px 14px",textAlign:"right"},children:u.readOnly?(0,e.jsx)("span",{"data-testid":`readonly-allowlist-${u.id}`,style:{fontSize:"0.75rem",color:"#94a3b8"},children:t.allowlistView.readOnly}):(0,e.jsx)("button",{type:"button",onClick:()=>y(u.id),"data-testid":`remove-allowlist-btn-${u.id}`,style:{padding:"3px 8px",borderRadius:"4px",border:"1px solid #fee2e2",backgroundColor:"#fff5f5",color:"#b91c1c",fontSize:"0.78rem",cursor:"pointer"},children:"\u524A\u9664"})})]},u.id)),i.allowlist.length===0&&(0,e.jsx)("tr",{children:(0,e.jsx)("td",{colSpan:5,style:{padding:"2rem",textAlign:"center",color:"#94a3b8"},children:t.allowlistView.empty})})]})]})})]})}var H={users:M,groups:E,group_members:P,allowlist:N};function J({renderHeader:i}={}){let t=(0,b.useLoaderData)()?.segment??"users",d=H[t]??M;return(0,e.jsx)(z,{renderHeader:i,children:(0,e.jsx)(d,{})})}0&&(module.exports={AllowlistView,AuthUserAdminAppView,GroupMembersView,GroupsView,UserAdminShell,UsersView,createInMemoryUserAdminStore,createUserAdminApp,defaultUserAdminLabels,jaUserAdminLabels,resolveUserAdminLabels});
|
|
2
|
+
//# sourceMappingURL=admin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/admin.ts","../src/admin/labels.ts","../src/admin/server.ts","../src/admin/ui.tsx","../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 heading: string\n tabs: {\n users: string\n groups: string\n groupMembers: string\n allowlist: string\n }\n usersView: {\n title: string\n searchPlaceholder: string\n addUser: string\n editUser: string\n id: string\n displayName: string\n email: string\n status: string\n active: string\n inactive: string\n actions: string\n edit: string\n activate: string\n deactivate: string\n empty: string\n save: string\n cancel: string\n }\n groupsView: {\n title: string\n searchPlaceholder: string\n addGroup: string\n editGroup: string\n id: string\n groupKey: string\n name: string\n description: string\n memberCount: string\n status: string\n actions: string\n edit: string\n delete: string\n confirmDelete: string\n noDescription: string\n groupKeyPlaceholder: string\n namePlaceholder: string\n empty: string\n save: string\n cancel: string\n }\n groupMembersView: {\n selectGroup: string\n selectPrompt: string\n actionAdd: string\n actionRemove: string\n }\n allowlistView: {\n title: string\n addEntry: string\n pattern: string\n type: string\n emailType: string\n domainType: string\n /** 一覧のバッジ表示 (短縮形)。 */\n emailTypeShort: string\n domainTypeShort: string\n description: string\n actions: string\n /** 変更不能な供給元 (環境変数など) 由来の行に出す表示。 */\n readOnly: string\n delete: string\n empty: string\n save: string\n cancel: string\n }\n}\n\n/**\n * Package default labels (English).\n * パッケージ既定ラベル (英語)。\n */\nexport const defaultUserAdminLabels: UserAdminLabels = {\n title: \"Users & Groups\",\n heading: \"👤 User administration\",\n tabs: {\n users: \"👤 Users\",\n groups: \"👥 Groups\",\n groupMembers: \"🔗 Members\",\n allowlist: \"✉️ Allowlist\",\n },\n usersView: {\n title: \"Users\",\n searchPlaceholder: \"Search by name or email...\",\n addUser: \"+ Add user\",\n editUser: \"Edit user\",\n id: \"ID\",\n displayName: \"Name\",\n email: \"Email\",\n status: \"Status\",\n active: \"Active\",\n inactive: \"Inactive\",\n actions: \"Actions\",\n edit: \"Edit\",\n activate: \"Activate\",\n deactivate: \"Deactivate\",\n empty: \"No matching users\",\n save: \"Save\",\n cancel: \"Cancel\",\n },\n groupsView: {\n title: \"Groups\",\n searchPlaceholder: \"Search by group name or key...\",\n addGroup: \"+ Add group\",\n editGroup: \"Edit group\",\n id: \"ID\",\n groupKey: \"Group key\",\n name: \"Name\",\n description: \"Description\",\n memberCount: \"Members\",\n status: \"Status\",\n actions: \"Actions\",\n edit: \"Edit\",\n delete: \"Delete\",\n confirmDelete: \"Delete this group?\",\n noDescription: \"No description\",\n groupKeyPlaceholder: \"e.g. dev-team\",\n namePlaceholder: \"e.g. Development team\",\n empty: \"No groups registered\",\n save: \"Save\",\n cancel: \"Cancel\",\n },\n groupMembersView: {\n selectGroup: \"Select a group\",\n selectPrompt: \"Select a group to manage its members\",\n actionAdd: \"Add\",\n actionRemove: \"Remove\",\n },\n allowlistView: {\n title: \"Sign-in allowlist\",\n addEntry: \"+ Add pattern\",\n pattern: \"Pattern (e.g. @example.com or user@example.com)\",\n type: \"Type\",\n emailType: \"Exact email match\",\n domainType: \"Domain match\",\n emailTypeShort: \"Email\",\n domainTypeShort: \"Domain\",\n description: \"Description\",\n actions: \"Actions\",\n readOnly: \"Read-only\",\n delete: \"Delete\",\n empty: \"No allowlist entries registered\",\n save: \"Add\",\n cancel: \"Cancel\",\n },\n}\n\n/**\n * Standard Japanese labels for user administration (opt-in).\n * ユーザー管理の標準日本語ラベル (アプリ側で明示注入して使う)。\n */\nexport const jaUserAdminLabels: UserAdminLabels = {\n title: \"ユーザー・グループ管理\",\n heading: \"👤 ユーザー管理\",\n tabs: {\n users: \"👤 ユーザー一覧\",\n groups: \"👥 ユーザーグループ\",\n groupMembers: \"🔗 グループメンバー\",\n allowlist: \"✉️ アクセス許可リスト\",\n },\n usersView: {\n title: \"ユーザー管理\",\n searchPlaceholder: \"表示名や Email で検索...\",\n addUser: \"+ ユーザー追加\",\n editUser: \"ユーザーの編集\",\n id: \"ID\",\n displayName: \"表示名\",\n email: \"メールアドレス\",\n status: \"ステータス\",\n active: \"有効\",\n inactive: \"無効\",\n actions: \"操作\",\n edit: \"編集\",\n activate: \"有効化\",\n deactivate: \"無効化\",\n empty: \"該当するユーザーが見つかりません\",\n save: \"保存\",\n cancel: \"キャンセル\",\n },\n groupsView: {\n title: \"ユーザーグループ管理\",\n searchPlaceholder: \"グループ名やキーで検索...\",\n addGroup: \"+ グループ作成\",\n editGroup: \"グループの編集\",\n id: \"ID\",\n groupKey: \"グループキー\",\n name: \"グループ名\",\n description: \"説明\",\n memberCount: \"メンバー数\",\n status: \"ステータス\",\n actions: \"操作\",\n edit: \"編集\",\n delete: \"削除\",\n confirmDelete: \"このグループを削除してもよろしいですか?\",\n noDescription: \"説明なし\",\n groupKeyPlaceholder: \"例: dev-team\",\n namePlaceholder: \"例: 開発チーム\",\n empty: \"グループが登録されていません\",\n save: \"保存\",\n cancel: \"キャンセル\",\n },\n groupMembersView: {\n selectGroup: \"グループ選択\",\n selectPrompt: \"グループを選択してください\",\n actionAdd: \"追加\",\n actionRemove: \"除外\",\n },\n allowlistView: {\n title: \"アクセス許可リスト (Allowlist)\",\n addEntry: \"+ 許可パターン追加\",\n pattern: \"パターン (例: @example.com または user@example.com)\",\n type: \"種別\",\n emailType: \"メールアドレス完全一致\",\n domainType: \"ドメイン一致\",\n emailTypeShort: \"Email 完全一致\",\n domainTypeShort: \"ドメイン一致\",\n description: \"説明 / 備考\",\n actions: \"操作\",\n readOnly: \"変更不可\",\n delete: \"削除\",\n empty: \"アロワリストエントリが登録されていません\",\n save: \"登録\",\n cancel: \"キャンセル\",\n },\n}\n\n/** セクション単位の部分上書き。 */\nexport type PartialUserAdminLabels = {\n title?: string\n heading?: string\n tabs?: Partial<UserAdminLabels[\"tabs\"]>\n usersView?: Partial<UserAdminLabels[\"usersView\"]>\n groupsView?: Partial<UserAdminLabels[\"groupsView\"]>\n groupMembersView?: Partial<UserAdminLabels[\"groupMembersView\"]>\n allowlistView?: Partial<UserAdminLabels[\"allowlistView\"]>\n}\n\n/**\n * Shallow per-section merge over the English defaults.\n * 英語既定に対してセクション単位で浅くマージする処理。\n */\nexport function resolveUserAdminLabels(over?: PartialUserAdminLabels): UserAdminLabels {\n const d = defaultUserAdminLabels\n if (!over) return d\n return {\n title: over.title ?? d.title,\n heading: over.heading ?? d.heading,\n tabs: { ...d.tabs, ...over.tabs },\n usersView: { ...d.usersView, ...over.usersView },\n groupsView: { ...d.groupsView, ...over.groupsView },\n groupMembersView: { ...d.groupMembersView, ...over.groupMembersView },\n allowlistView: { ...d.allowlistView, ...over.allowlistView },\n }\n}\n","import type { AuthAllowlistEntry, AuthGroup, AuthGroupMember, AuthUser } from \"@aiquants/auth-core\"\nimport type { ActionFunctionArgs, LoaderFunctionArgs } from \"react-router\"\nimport { type PartialUserAdminLabels, resolveUserAdminLabels, type UserAdminLabels } from \"./labels\"\n\n/**\n * Server store interface for managing users, groups, memberships, and allowlists.\n * ユーザー・グループ・所属・アロワリストを管理するサーバー層ストアのインターフェース。\n */\nexport type UserAdminStore = {\n listUsers: () => Promise<AuthUser[]>\n createUser: (data: { displayName: string; email: string }) => Promise<AuthUser>\n updateUser: (id: number, data: { displayName: string; email: string }) => Promise<AuthUser>\n toggleUserActive: (id: number, isActive: boolean) => Promise<AuthUser>\n\n listGroups: () => Promise<AuthGroup[]>\n createGroup: (data: { groupKey: string; name: string; description?: string }) => Promise<AuthGroup>\n updateGroup: (id: number, data: { name: string; description?: string }) => Promise<AuthGroup>\n deleteGroup: (id: number) => Promise<void>\n\n listGroupMembers: () => Promise<AuthGroupMember[]>\n addGroupMember: (groupId: number, userId: number) => Promise<AuthGroupMember>\n removeGroupMember: (groupId: number, userId: number) => Promise<void>\n\n listAllowlist: () => Promise<AuthAllowlistEntry[]>\n addAllowlistEntry: (data: { pattern: string; type: \"email\" | \"domain\"; description?: string }) => Promise<AuthAllowlistEntry>\n removeAllowlistEntry: (id: number) => Promise<void>\n}\n\n/**\n * Creates an in-memory UserAdminStore initialized with optional seed data.\n * シードデータで初期化されるメモリ上の UserAdminStore を作成する。\n */\nexport function createInMemoryUserAdminStore(initial?: { users?: AuthUser[]; groups?: AuthGroup[]; groupMembers?: AuthGroupMember[]; allowlist?: AuthAllowlistEntry[] }): UserAdminStore {\n const users: AuthUser[] = initial?.users ?? [\n { id: 1, email: \"admin@example.com\", displayName: \"システム管理者\", isActive: true },\n { id: 2, email: \"user1@example.com\", displayName: \"開発 太郎\", isActive: true },\n { id: 3, email: \"user2@example.com\", displayName: \"営業 花子\", isActive: true },\n ]\n let groups: AuthGroup[] = initial?.groups ?? [\n { id: 1, groupKey: \"dev-team\", name: \"開発チーム\", description: \"システム開発担当グループ\", isActive: true },\n { id: 2, groupKey: \"sales-dept\", name: \"営業部\", description: \"営業ソリューション担当\", isActive: true },\n ]\n let groupMembers: AuthGroupMember[] = initial?.groupMembers ?? [\n { id: 1, groupId: 1, userId: 2 },\n { id: 2, groupId: 2, userId: 3 },\n ]\n let allowlist: AuthAllowlistEntry[] = initial?.allowlist ?? [\n { id: 1, pattern: \"admin@example.com\", type: \"email\", description: \"初期管理者\" },\n { id: 2, pattern: \"@example.com\", type: \"domain\", description: \"社内ドメイン\" },\n ]\n\n return {\n async listUsers() {\n return [...users]\n },\n async createUser(data) {\n const newUser: AuthUser = {\n id: Math.max(...users.map((u) => u.id), 0) + 1,\n email: data.email,\n displayName: data.displayName,\n isActive: true,\n }\n users.push(newUser)\n return newUser\n },\n async updateUser(id, data) {\n const idx = users.findIndex((u) => u.id === id)\n if (idx === -1) throw new Error(`User not found: ${id}`)\n users[idx] = { ...users[idx], displayName: data.displayName, email: data.email }\n return users[idx]\n },\n async toggleUserActive(id, isActive) {\n const idx = users.findIndex((u) => u.id === id)\n if (idx === -1) throw new Error(`User not found: ${id}`)\n users[idx] = { ...users[idx], isActive }\n return users[idx]\n },\n\n async listGroups() {\n return [...groups]\n },\n async createGroup(data) {\n const newGroup: AuthGroup = {\n id: Math.max(...groups.map((g) => g.id), 0) + 1,\n groupKey: data.groupKey,\n name: data.name,\n description: data.description,\n isActive: true,\n }\n groups.push(newGroup)\n return newGroup\n },\n async updateGroup(id, data) {\n const idx = groups.findIndex((g) => g.id === id)\n if (idx === -1) throw new Error(`Group not found: ${id}`)\n groups[idx] = { ...groups[idx], name: data.name, description: data.description }\n return groups[idx]\n },\n async deleteGroup(id) {\n groups = groups.filter((g) => g.id !== id)\n groupMembers = groupMembers.filter((m) => m.groupId !== id)\n },\n\n async listGroupMembers() {\n return [...groupMembers]\n },\n async addGroupMember(groupId, userId) {\n const existing = groupMembers.find((m) => m.groupId === groupId && m.userId === userId)\n if (existing) return existing\n const newMember: AuthGroupMember = {\n id: Math.max(...groupMembers.map((m) => m.id), 0) + 1,\n groupId,\n userId,\n }\n groupMembers.push(newMember)\n return newMember\n },\n async removeGroupMember(groupId, userId) {\n groupMembers = groupMembers.filter((m) => !(m.groupId === groupId && m.userId === userId))\n },\n\n async listAllowlist() {\n return [...allowlist]\n },\n async addAllowlistEntry(data) {\n const newEntry: AuthAllowlistEntry = {\n id: Math.max(...allowlist.map((a) => a.id), 0) + 1,\n pattern: data.pattern,\n type: data.type,\n description: data.description,\n }\n allowlist.push(newEntry)\n return newEntry\n },\n async removeAllowlistEntry(id) {\n allowlist = allowlist.filter((a) => a.id !== id)\n },\n }\n}\n\n/**\n * The CRUD verb an operation performs. Deliberately a plain string union so this package stays\n * independent of any particular authorization library.\n * 操作が行う CRUD 種別。特定の認可ライブラリに依存しないよう素の文字列 union で表現する。\n */\nexport type UserAdminAccess = \"read\" | \"create\" | \"update\" | \"delete\"\n\n/**\n * Access guard the user-admin surface is mounted behind. Deny by throwing\n * (a `Response`/`redirect` or an `Error`); returning normally means \"allow\".\n *\n * ユーザー管理面をマウントする際の必須アクセスガード。拒否は throw で表現する\n * (`Response` / `redirect` / `Error`)。正常 return は「許可」を意味する。\n *\n * 操作ごとの CRUD 種別を受け取るため、呼び出し側は「削除だけ別権限」といった細分化ができる。\n * `intent` は監査ログ用の補助情報(認可判断は `action` で行うこと)。\n */\nexport type UserAdminGuards = {\n /**\n * 許可は「正常 return」、拒否は「throw」で表す。返り値は判定に使わない (`void` 契約)。\n * 述語のつもりで `false` を返す実装を書くと素通りするため、返り値型で明示的に禁じる。\n */\n requireAccess: (request: Request, context: { action: UserAdminAccess; intent: string }) => Promise<void> | void\n}\n\n/**\n * Maps each action intent to the CRUD verb it actually performs.\n * 各 intent が実際に行う操作の CRUD 種別への対応表。\n *\n * 割当の付与・解除は行の追加・削除であるため、認可管理 UI (`/authz`) の\n * `assignUserRole` = create / `removeUserRole` = delete と同じ粒度に揃える。\n */\nconst INTENT_ACCESS = new Map<string, UserAdminAccess>([\n [\"createUser\", \"create\"],\n [\"updateUser\", \"update\"],\n [\"toggleUserActive\", \"update\"],\n [\"createGroup\", \"create\"],\n [\"updateGroup\", \"update\"],\n [\"deleteGroup\", \"delete\"],\n [\"addGroupMember\", \"create\"],\n [\"removeGroupMember\", \"delete\"],\n [\"addAllowlistEntry\", \"create\"],\n [\"removeAllowlistEntry\", \"delete\"],\n])\n\nexport type UserAdminAppConfig = {\n store: UserAdminStore\n /** セクション単位の部分上書き。既定は英語 (`defaultUserAdminLabels`)。 */\n labels?: PartialUserAdminLabels\n /**\n * Required — omitting a guard would expose the whole user directory and its write actions.\n * 必須。省略を許すとユーザー名簿と全更新操作が無防備に露出するため、型で強制する。\n */\n guards: UserAdminGuards\n}\n\n/**\n * Reads a required positive integer id from submitted form data.\n * 送信フォームから必須の正整数 ID を読み取る処理。\n *\n * @throws when the field is missing, non-numeric, or not a positive integer.\n * 欠落・非数値・正整数でない場合は throw。\n */\nfunction requireId(formData: FormData, field: string): number {\n const raw = formData.get(field)\n const n = Number(raw)\n // NaN / 小数 / 0 以下は不正な ID として弾く (Number(null) === 0 の取り違えも防ぐ)\n if (raw === null || String(raw).trim() === \"\" || !Number.isInteger(n) || n <= 0) {\n throw new Error(`Invalid or missing \"${field}\"`)\n }\n return n\n}\n\n/**\n * Reads a required non-empty trimmed string from submitted form data.\n * 送信フォームから必須の非空文字列 (トリム済み) を読み取る処理。\n *\n * @throws when the field is missing or blank. 欠落・空白のみの場合は throw。\n */\nfunction requireText(formData: FormData, field: string): string {\n const value = String(formData.get(field) ?? \"\").trim()\n if (value === \"\") throw new Error(`Invalid or missing \"${field}\"`)\n return value\n}\n\n/** Reads an optional trimmed string; blank becomes `undefined`. 任意文字列の読み取り (空は undefined)。 */\nfunction optionalText(formData: FormData, field: string): string | undefined {\n const value = String(formData.get(field) ?? \"\").trim()\n return value === \"\" ? undefined : value\n}\n\n/**\n * Creates React Router loader & action handler for user administration.\n * ユーザー管理用 React Router の loader および action ハンドラーを作成する。\n *\n * `config.guards` は必須。loader / action の双方が、処理を始める前にガードを通過させる。\n */\nexport function createUserAdminApp(config: UserAdminAppConfig) {\n const labels = resolveUserAdminLabels(config.labels)\n\n async function loader({ request }: LoaderFunctionArgs) {\n // 読取ガード: 拒否は throw されるためここで処理が打ち切られる\n await config.guards.requireAccess(request, { action: \"read\", intent: \"loader\" })\n const url = new URL(request.url)\n const pathSegments = url.pathname.split(\"/\").filter(Boolean)\n const segment = pathSegments[pathSegments.length - 1] ?? \"users\"\n\n const [users, groups, groupMembers, allowlist] = await Promise.all([config.store.listUsers(), config.store.listGroups(), config.store.listGroupMembers(), config.store.listAllowlist()])\n\n return {\n segment: [\"groups\", \"group_members\", \"allowlist\"].includes(segment) ? segment : \"users\",\n users,\n groups,\n groupMembers,\n allowlist,\n labels,\n }\n }\n\n async function action({ request }: ActionFunctionArgs) {\n // intent の判定に FormData が要るため、ここでだけ本文を先に読む。\n // 未知 intent は「何も実行しない」ため、ガード前に読んでも権限判断には影響しない。\n const formData = await request.formData()\n const intent = String(formData.get(\"intent\") ?? \"\")\n\n // 未知 intent はストアへ到達しない (ガードも走らせず 400 相当を返す)。\n // Map 参照のためプロトタイプ継承キー (\"constructor\" 等) を拾わない。\n const access = INTENT_ACCESS.get(intent)\n if (!access) return { ok: false, error: `Unknown intent: ${intent}` }\n\n // 操作ごとの CRUD 種別でガードする (削除は delete 権限が要る)\n await config.guards.requireAccess(request, { action: access, intent })\n\n try {\n return await dispatch(intent, formData)\n } catch (error) {\n // ガード拒否 (Response/redirect) はそのまま伝播させる。ストア由来の業務エラーだけ\n // {ok:false,error} に写して UI へ届ける (ロックアウト理由・重複パターン等)。\n if (error instanceof Response) throw error\n return { ok: false, error: error instanceof Error ? error.message : String(error) }\n }\n }\n\n /** intent ごとの実処理。認可・入力検証を通過した後にのみ呼ばれる。 */\n async function dispatch(intent: string, formData: FormData) {\n if (intent === \"createUser\") {\n await config.store.createUser({ displayName: requireText(formData, \"displayName\"), email: requireText(formData, \"email\") })\n return { ok: true }\n }\n\n if (intent === \"updateUser\") {\n await config.store.updateUser(requireId(formData, \"userId\"), { displayName: requireText(formData, \"displayName\"), email: requireText(formData, \"email\") })\n return { ok: true }\n }\n\n if (intent === \"toggleUserActive\") {\n // 破壊側 (無効化) を既定にしない。明示的な \"true\"/\"false\" 以外は不正入力として弾く。\n const rawActive = String(formData.get(\"isActive\") ?? \"\")\n if (rawActive !== \"true\" && rawActive !== \"false\") throw new Error('Invalid or missing \"isActive\"')\n await config.store.toggleUserActive(requireId(formData, \"userId\"), rawActive === \"true\")\n return { ok: true }\n }\n\n if (intent === \"createGroup\") {\n await config.store.createGroup({ groupKey: requireText(formData, \"groupKey\"), name: requireText(formData, \"name\"), description: optionalText(formData, \"description\") })\n return { ok: true }\n }\n\n if (intent === \"updateGroup\") {\n await config.store.updateGroup(requireId(formData, \"groupId\"), { name: requireText(formData, \"name\"), description: optionalText(formData, \"description\") })\n return { ok: true }\n }\n\n if (intent === \"deleteGroup\") {\n await config.store.deleteGroup(requireId(formData, \"groupId\"))\n return { ok: true }\n }\n\n if (intent === \"addGroupMember\") {\n await config.store.addGroupMember(requireId(formData, \"groupId\"), requireId(formData, \"userId\"))\n return { ok: true }\n }\n\n if (intent === \"removeGroupMember\") {\n await config.store.removeGroupMember(requireId(formData, \"groupId\"), requireId(formData, \"userId\"))\n return { ok: true }\n }\n\n if (intent === \"addAllowlistEntry\") {\n const type: \"email\" | \"domain\" = formData.get(\"type\") === \"domain\" ? \"domain\" : \"email\"\n await config.store.addAllowlistEntry({ pattern: requireText(formData, \"pattern\"), type, description: optionalText(formData, \"description\") })\n return { ok: true }\n }\n\n if (intent === \"removeAllowlistEntry\") {\n await config.store.removeAllowlistEntry(requireId(formData, \"entryId\"))\n return { ok: true }\n }\n\n // INTENT_ACCESS を通過した intent は上のいずれかで処理済み (到達しない安全網)\n return { ok: false, error: `Unknown intent: ${intent}` }\n }\n\n return { loader, action }\n}\n","import type React from \"react\"\nimport { Link, useLoaderData } from \"react-router\"\nimport { defaultUserAdminLabels, type UserAdminLabels } from \"./labels\"\n\nexport type UserAdminShellProps = {\n children?: React.ReactNode\n renderHeader?: (props: { title: string; annotation: React.ReactNode }) => React.ReactNode\n}\n\n/**\n * Shell layout for user administration pages with header annotation delegation.\n * ヘッダー注釈委譲を備えたユーザー管理ページのシェルレイアウト。\n */\nexport function UserAdminShell({ children, renderHeader }: UserAdminShellProps) {\n const data = useLoaderData() as { segment?: string; labels?: UserAdminLabels } | undefined\n const segment = data?.segment ?? \"users\"\n const labels = data?.labels ?? defaultUserAdminLabels\n\n const tabs = [\n { key: \"users\", label: labels.tabs.users, href: \"/users\" },\n { key: \"groups\", label: labels.tabs.groups, href: \"/users/groups\" },\n { key: \"group_members\", label: labels.tabs.groupMembers, href: \"/users/group_members\" },\n { key: \"allowlist\", label: labels.tabs.allowlist, href: \"/users/allowlist\" },\n ]\n\n const annotation = (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: \"0.5rem\" }} data-testid=\"user-admin-header-annotation\">\n <span style={{ fontSize: \"0.75rem\", backgroundColor: \"#e0f2fe\", color: \"#0369a1\", padding: \"2px 8px\", borderRadius: \"12px\", fontWeight: 600 }}>{labels.heading}</span>\n </div>\n )\n\n return (\n <div data-testid=\"user-admin-shell\" style={{ width: \"100%\", minHeight: \"100vh\", backgroundColor: \"#f8fafc\", padding: \"1.5rem\", boxSizing: \"border-box\" }}>\n {renderHeader ? (\n renderHeader({ title: labels.title, annotation })\n ) : (\n <div style={{ marginBottom: \"1.5rem\", display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\" }}>\n <h1 style={{ margin: 0, fontSize: \"1.5rem\", fontWeight: 700, color: \"#0f172a\" }}>{labels.title}</h1>\n {annotation}\n </div>\n )}\n\n <div style={{ display: \"flex\", gap: \"0.5rem\", marginBottom: \"1.2rem\", borderBottom: \"2px solid #e2e8f0\", paddingBottom: \"0.5rem\" }}>\n {tabs.map((tab) => {\n const isActive = segment === tab.key\n return (\n <Link\n key={tab.key}\n to={tab.href}\n data-testid={`user-admin-tab-${tab.key}`}\n style={{\n padding: \"0.5rem 1rem\",\n borderRadius: \"6px 6px 0 0\",\n fontSize: \"0.88rem\",\n fontWeight: isActive ? 700 : 500,\n color: isActive ? \"#0284c7\" : \"#64748b\",\n backgroundColor: isActive ? \"#ffffff\" : \"transparent\",\n textDecoration: \"none\",\n borderBottom: isActive ? \"3px solid #0284c7\" : \"3px solid transparent\",\n transition: \"all 0.15s ease\",\n }}>\n {tab.label}\n </Link>\n )\n })}\n </div>\n\n <main>{children}</main>\n </div>\n )\n}\n","import type { AuthAllowlistEntry, AuthGroup, AuthGroupMember, AuthUser } from \"@aiquants/auth-core\"\nimport type React from \"react\"\nimport { useState } from \"react\"\nimport { useFetcher, useLoaderData } from \"react-router\"\nimport { defaultUserAdminLabels, type UserAdminLabels } from \"./labels\"\nimport { UserAdminShell, type UserAdminShellProps } from \"./ui\"\n\nexport type UserAdminLoaderData = {\n segment: string\n users: AuthUser[]\n groups: AuthGroup[]\n groupMembers: AuthGroupMember[]\n allowlist: AuthAllowlistEntry[]\n labels?: UserAdminLabels\n}\n\n/**\n * View for managing users (list, search, toggle active, modal edit).\n * ユーザー管理ビュー (一覧・検索・アクティブ切替・ダイアログ編集)。\n */\n/**\n * Renders the error returned by the last action submission, if any.\n * 直近の action 送信が返したエラーを表示する部品。\n *\n * ロックアウト理由・重複パターン・入力不正などの文言は action が `{ok:false,error}` で返す。\n * 表示しなければ操作が黙って失敗したように見えるため、各ビューの先頭に必ず置く。\n */\nfunction ActionError({ fetcher }: { fetcher: { data?: unknown } }) {\n const result = fetcher.data as { ok?: boolean; error?: string } | undefined\n if (result?.ok !== false || !result.error) return null\n return (\n <div data-testid=\"user-admin-action-error\" role=\"alert\" style={{ padding: \"0.7rem 1rem\", borderRadius: \"6px\", border: \"1px solid #fecaca\", backgroundColor: \"#fef2f2\", color: \"#b91c1c\", fontSize: \"0.85rem\" }}>\n {result.error}\n </div>\n )\n}\n\nexport function UsersView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [searchQuery, setSearchQuery] = useState(\"\")\n const [editingUser, setEditingUser] = useState<AuthUser | null>(null)\n const [isCreateOpen, setIsCreateOpen] = useState(false)\n\n const [newDisplayName, setNewDisplayName] = useState(\"\")\n const [newEmail, setNewEmail] = useState(\"\")\n\n const filteredUsers = data.users.filter((u) => u.displayName.toLowerCase().includes(searchQuery.toLowerCase()) || u.email.toLowerCase().includes(searchQuery.toLowerCase()))\n\n const handleToggleActive = (user: AuthUser) => {\n fetcher.submit({ intent: \"toggleUserActive\", userId: String(user.id), isActive: String(!user.isActive) }, { method: \"post\" })\n }\n\n const handleCreateUser = (e: React.FormEvent) => {\n e.preventDefault()\n if (!(newDisplayName && newEmail)) return\n fetcher.submit({ intent: \"createUser\", displayName: newDisplayName, email: newEmail }, { method: \"post\" })\n setNewDisplayName(\"\")\n setNewEmail(\"\")\n setIsCreateOpen(false)\n }\n\n const handleUpdateUser = (e: React.FormEvent) => {\n e.preventDefault()\n if (!editingUser) return\n fetcher.submit({ intent: \"updateUser\", userId: String(editingUser.id), displayName: editingUser.displayName, email: editingUser.email }, { method: \"post\" })\n setEditingUser(null)\n }\n\n return (\n <section data-testid=\"users-view-section\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1rem\" }}>\n <ActionError fetcher={fetcher} />\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", backgroundColor: \"#ffffff\", padding: \"1rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n <input\n type=\"text\"\n placeholder={labels.usersView.searchPlaceholder}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n data-testid=\"users-search-input\"\n style={{ padding: \"6px 12px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", width: \"260px\" }}\n />\n <button type=\"button\" onClick={() => setIsCreateOpen(true)} data-testid=\"users-add-button\" style={{ padding: \"6px 14px\", borderRadius: \"6px\", backgroundColor: \"#0284c7\", color: \"#ffffff\", border: \"none\", fontSize: \"0.85rem\", fontWeight: 600, cursor: \"pointer\" }}>\n {labels.usersView.addUser}\n </button>\n </div>\n\n <div style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", overflow: \"hidden\" }}>\n <table style={{ width: \"100%\", borderCollapse: \"collapse\", fontSize: \"0.85rem\", textAlign: \"left\" }}>\n <thead>\n <tr style={{ backgroundColor: \"#f8fafc\", borderBottom: \"1px solid #e2e8f0\", color: \"#475569\" }}>\n <th style={{ padding: \"10px 14px\" }}>{labels.usersView.id}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.usersView.displayName}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.usersView.email}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.usersView.status}</th>\n <th style={{ padding: \"10px 14px\", textAlign: \"right\" }}>{labels.usersView.actions}</th>\n </tr>\n </thead>\n <tbody>\n {filteredUsers.map((u) => (\n <tr key={u.id} style={{ borderBottom: \"1px solid #f1f5f9\" }} data-testid={`user-row-${u.id}`}>\n <td style={{ padding: \"10px 14px\", color: \"#64748b\" }}>#{u.id}</td>\n <td style={{ padding: \"10px 14px\", fontWeight: 600, color: \"#0f172a\" }}>{u.displayName}</td>\n <td style={{ padding: \"10px 14px\", color: \"#475569\" }}>{u.email}</td>\n <td style={{ padding: \"10px 14px\" }}>\n <span style={{ fontSize: \"0.75rem\", padding: \"2px 8px\", borderRadius: \"10px\", backgroundColor: u.isActive ? \"#dcfce7\" : \"#fee2e2\", color: u.isActive ? \"#15803d\" : \"#b91c1c\", fontWeight: 600 }}>\n {u.isActive ? labels.usersView.active : labels.usersView.inactive}\n </span>\n </td>\n <td style={{ padding: \"10px 14px\", textAlign: \"right\" }}>\n <button\n type=\"button\"\n onClick={() => setEditingUser(u)}\n data-testid={`edit-user-btn-${u.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", fontSize: \"0.78rem\", cursor: \"pointer\", marginRight: \"6px\" }}>\n {labels.usersView.edit}\n </button>\n <button\n type=\"button\"\n onClick={() => handleToggleActive(u)}\n data-testid={`toggle-user-btn-${u.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", fontSize: \"0.78rem\", cursor: \"pointer\", color: u.isActive ? \"#b91c1c\" : \"#15803d\" }}>\n {u.isActive ? labels.usersView.deactivate : labels.usersView.activate}\n </button>\n </td>\n </tr>\n ))}\n {filteredUsers.length === 0 && (\n <tr>\n <td colSpan={5} style={{ padding: \"2rem\", textAlign: \"center\", color: \"#94a3b8\" }}>\n {labels.usersView.empty}\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n\n {/* Create Modal */}\n {isCreateOpen && (\n <div style={{ position: \"fixed\", inset: 0, backgroundColor: \"rgba(0,0,0,0.4)\", display: \"flex\", justifyContent: \"center\", alignItems: \"center\", zIndex: 100 }} data-testid=\"user-create-modal\">\n <form onSubmit={handleCreateUser} style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>{labels.usersView.addUser}</h3>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"create-user-display-name-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.usersView.displayName}\n </label>\n <input\n id=\"create-user-display-name-input\"\n type=\"text\"\n value={newDisplayName}\n onChange={(e) => setNewDisplayName(e.target.value)}\n required\n data-testid=\"create-user-display-name\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1.2rem\" }}>\n <label htmlFor=\"create-user-email-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.usersView.email}\n </label>\n <input\n id=\"create-user-email-input\"\n type=\"email\"\n value={newEmail}\n onChange={(e) => setNewEmail(e.target.value)}\n required\n data-testid=\"create-user-email\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: \"0.5rem\" }}>\n <button type=\"button\" onClick={() => setIsCreateOpen(false)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n キャンセル\n </button>\n <button type=\"submit\" data-testid=\"submit-create-user\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#0284c7\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n 作成\n </button>\n </div>\n </form>\n </div>\n )}\n\n {/* Edit Modal */}\n {editingUser && (\n <div style={{ position: \"fixed\", inset: 0, backgroundColor: \"rgba(0,0,0,0.4)\", display: \"flex\", justifyContent: \"center\", alignItems: \"center\", zIndex: 100 }} data-testid=\"user-edit-modal\">\n <form onSubmit={handleUpdateUser} style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>{labels.usersView.editUser}</h3>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"edit-user-display-name-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.usersView.displayName}\n </label>\n <input\n id=\"edit-user-display-name-input\"\n type=\"text\"\n value={editingUser.displayName}\n onChange={(e) => setEditingUser({ ...editingUser, displayName: e.target.value })}\n required\n data-testid=\"edit-user-display-name\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1.2rem\" }}>\n <label htmlFor=\"edit-user-email-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.usersView.email}\n </label>\n <input\n id=\"edit-user-email-input\"\n type=\"email\"\n value={editingUser.email}\n onChange={(e) => setEditingUser({ ...editingUser, email: e.target.value })}\n required\n data-testid=\"edit-user-email\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: \"0.5rem\" }}>\n <button type=\"button\" onClick={() => setEditingUser(null)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n {labels.usersView.cancel}\n </button>\n <button type=\"submit\" data-testid=\"submit-edit-user\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#0284c7\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n {labels.usersView.save}\n </button>\n </div>\n </form>\n </div>\n )}\n </section>\n )\n}\n\n/**\n * View for managing user groups (create, edit, delete).\n * ユーザーグループ管理ビュー (作成・編集・削除)。\n */\nexport function GroupsView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [searchQuery, setSearchQuery] = useState(\"\")\n const [isCreateOpen, setIsCreateOpen] = useState(false)\n const [editingGroup, setEditingGroup] = useState<AuthGroup | null>(null)\n\n const [newGroupKey, setNewGroupKey] = useState(\"\")\n const [newName, setNewName] = useState(\"\")\n const [newDescription, setNewDescription] = useState(\"\")\n\n const filteredGroups = data.groups.filter((g) => g.name.toLowerCase().includes(searchQuery.toLowerCase()) || g.groupKey.toLowerCase().includes(searchQuery.toLowerCase()))\n\n const handleCreateGroup = (e: React.FormEvent) => {\n e.preventDefault()\n if (!(newGroupKey && newName)) return\n fetcher.submit({ intent: \"createGroup\", groupKey: newGroupKey, name: newName, description: newDescription }, { method: \"post\" })\n setNewGroupKey(\"\")\n setNewName(\"\")\n setNewDescription(\"\")\n setIsCreateOpen(false)\n }\n\n const handleUpdateGroup = (e: React.FormEvent) => {\n e.preventDefault()\n if (!editingGroup) return\n fetcher.submit({ intent: \"updateGroup\", groupId: String(editingGroup.id), name: editingGroup.name, description: editingGroup.description ?? \"\" }, { method: \"post\" })\n setEditingGroup(null)\n }\n\n const handleDeleteGroup = (groupId: number) => {\n if (!window.confirm(labels.groupsView.confirmDelete)) return\n fetcher.submit({ intent: \"deleteGroup\", groupId: String(groupId) }, { method: \"post\" })\n }\n\n return (\n <section data-testid=\"groups-view-section\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1rem\" }}>\n <ActionError fetcher={fetcher} />\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", backgroundColor: \"#ffffff\", padding: \"1rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n <input\n type=\"text\"\n placeholder={labels.groupsView.searchPlaceholder}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n data-testid=\"groups-search-input\"\n style={{ padding: \"6px 12px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", width: \"260px\" }}\n />\n <button type=\"button\" onClick={() => setIsCreateOpen(true)} data-testid=\"groups-add-button\" style={{ padding: \"6px 14px\", borderRadius: \"6px\", backgroundColor: \"#4338ca\", color: \"#ffffff\", border: \"none\", fontSize: \"0.85rem\", fontWeight: 600, cursor: \"pointer\" }}>\n {labels.groupsView.addGroup}\n </button>\n </div>\n\n <div style={{ display: \"grid\", gridTemplateColumns: \"repeat(auto-fill, minmax(280px, 1fr))\", gap: \"1rem\" }}>\n {filteredGroups.map((g) => {\n const memberCount = data.groupMembers.filter((m) => m.groupId === g.id).length\n return (\n <div key={g.id} style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", padding: \"1rem\", display: \"flex\", flexDirection: \"column\", justifyContent: \"space-between\" }} data-testid={`group-card-${g.id}`}>\n <div>\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", marginBottom: \"0.5rem\" }}>\n <h4 style={{ margin: 0, fontSize: \"1rem\", color: \"#0f172a\" }}>{g.name}</h4>\n <span style={{ fontSize: \"0.72rem\", backgroundColor: \"#e0e7ff\", color: \"#4338ca\", padding: \"2px 6px\", borderRadius: \"4px\", fontFamily: \"monospace\" }}>{g.groupKey}</span>\n </div>\n <p style={{ margin: \"0 0 0.8rem 0\", fontSize: \"0.82rem\", color: \"#64748b\", minHeight: \"2.4em\" }}>{g.description || labels.groupsView.noDescription}</p>\n </div>\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", borderTop: \"1px solid #f1f5f9\", paddingTop: \"0.6rem\" }}>\n <span style={{ fontSize: \"0.78rem\", color: \"#475569\" }}>\n 👥 メンバー: <strong>{memberCount}</strong> 人\n </span>\n <div>\n <button\n type=\"button\"\n onClick={() => setEditingGroup(g)}\n data-testid={`edit-group-btn-${g.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", fontSize: \"0.75rem\", cursor: \"pointer\", marginRight: \"4px\" }}>\n {labels.usersView.edit}\n </button>\n <button\n type=\"button\"\n onClick={() => handleDeleteGroup(g.id)}\n data-testid={`delete-group-btn-${g.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #fee2e2\", backgroundColor: \"#fff5f5\", color: \"#b91c1c\", fontSize: \"0.75rem\", cursor: \"pointer\" }}>\n 削除\n </button>\n </div>\n </div>\n </div>\n )\n })}\n </div>\n\n {/* Create Group Modal */}\n {isCreateOpen && (\n <div style={{ position: \"fixed\", inset: 0, backgroundColor: \"rgba(0,0,0,0.4)\", display: \"flex\", justifyContent: \"center\", alignItems: \"center\", zIndex: 100 }} data-testid=\"group-create-modal\">\n <form onSubmit={handleCreateGroup} style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>{labels.groupsView.addGroup}</h3>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"create-group-key-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.groupKey}\n </label>\n <input\n id=\"create-group-key-input\"\n type=\"text\"\n value={newGroupKey}\n onChange={(e) => setNewGroupKey(e.target.value)}\n placeholder={labels.groupsView.groupKeyPlaceholder}\n required\n data-testid=\"create-group-key\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"create-group-name-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.name}\n </label>\n <input\n id=\"create-group-name-input\"\n type=\"text\"\n value={newName}\n onChange={(e) => setNewName(e.target.value)}\n placeholder={labels.groupsView.namePlaceholder}\n required\n data-testid=\"create-group-name\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1.2rem\" }}>\n <label htmlFor=\"create-group-desc-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.description}\n </label>\n <textarea\n id=\"create-group-desc-input\"\n value={newDescription}\n onChange={(e) => setNewDescription(e.target.value)}\n data-testid=\"create-group-description\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\", height: \"60px\" }}\n />\n </div>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: \"0.5rem\" }}>\n <button type=\"button\" onClick={() => setIsCreateOpen(false)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n キャンセル\n </button>\n <button type=\"submit\" data-testid=\"submit-create-group\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#4338ca\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n 作成\n </button>\n </div>\n </form>\n </div>\n )}\n\n {/* Edit Group Modal */}\n {editingGroup && (\n <div style={{ position: \"fixed\", inset: 0, backgroundColor: \"rgba(0,0,0,0.4)\", display: \"flex\", justifyContent: \"center\", alignItems: \"center\", zIndex: 100 }} data-testid=\"group-edit-modal\">\n <form onSubmit={handleUpdateGroup} style={{ backgroundColor: \"#ffffff\", padding: \"1.5rem\", borderRadius: \"8px\", width: \"360px\", boxShadow: \"0 10px 25px rgba(0,0,0,0.15)\" }}>\n <h3 style={{ margin: \"0 0 1rem 0\", fontSize: \"1.1rem\" }}>{labels.groupsView.editGroup}</h3>\n <div style={{ marginBottom: \"1rem\" }}>\n <label htmlFor=\"edit-group-name-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.name}\n </label>\n <input\n id=\"edit-group-name-input\"\n type=\"text\"\n value={editingGroup.name}\n onChange={(e) => setEditingGroup({ ...editingGroup, name: e.target.value })}\n required\n data-testid=\"edit-group-name\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ marginBottom: \"1.2rem\" }}>\n <label htmlFor=\"edit-group-desc-input\" style={{ display: \"block\", fontSize: \"0.8rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.groupsView.description}\n </label>\n <textarea\n id=\"edit-group-desc-input\"\n value={editingGroup.description ?? \"\"}\n onChange={(e) => setEditingGroup({ ...editingGroup, description: e.target.value })}\n data-testid=\"edit-group-description\"\n style={{ width: \"100%\", padding: \"6px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", boxSizing: \"border-box\", height: \"60px\" }}\n />\n </div>\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: \"0.5rem\" }}>\n <button type=\"button\" onClick={() => setEditingGroup(null)} style={{ padding: \"6px 12px\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", backgroundColor: \"#ffffff\", cursor: \"pointer\" }}>\n {labels.groupsView.cancel}\n </button>\n <button type=\"submit\" data-testid=\"submit-edit-group\" style={{ padding: \"6px 12px\", borderRadius: \"4px\", backgroundColor: \"#4338ca\", color: \"#ffffff\", border: \"none\", cursor: \"pointer\" }}>\n {labels.groupsView.save}\n </button>\n </div>\n </form>\n </div>\n )}\n </section>\n )\n}\n\n/**\n * View for managing group member assignments.\n * グループメンバー割り当て管理ビュー。\n */\nexport function GroupMembersView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [selectedGroupId, setSelectedGroupId] = useState<number>(data.groups[0]?.id ?? 0)\n\n const currentGroup = data.groups.find((g) => g.id === selectedGroupId)\n const currentMemberUserIds = new Set(data.groupMembers.filter((m) => m.groupId === selectedGroupId).map((m) => m.userId))\n\n const handleToggleMember = (userId: number, isMember: boolean) => {\n if (isMember) {\n fetcher.submit({ intent: \"removeGroupMember\", groupId: String(selectedGroupId), userId: String(userId) }, { method: \"post\" })\n } else {\n fetcher.submit({ intent: \"addGroupMember\", groupId: String(selectedGroupId), userId: String(userId) }, { method: \"post\" })\n }\n }\n\n return (\n <section data-testid=\"group-members-view-section\" style={{ display: \"grid\", gridTemplateColumns: \"240px 1fr\", gap: \"1.2rem\" }}>\n <ActionError fetcher={fetcher} />\n <div style={{ backgroundColor: \"#ffffff\", padding: \"1rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n <h4 style={{ margin: \"0 0 0.8rem 0\", fontSize: \"0.9rem\", color: \"#475569\" }}>{labels.groupMembersView.selectGroup}</h4>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.4rem\" }}>\n {data.groups.map((g) => {\n const isSelected = g.id === selectedGroupId\n return (\n <button\n type=\"button\"\n key={g.id}\n onClick={() => setSelectedGroupId(g.id)}\n data-testid={`select-group-btn-${g.id}`}\n style={{\n textAlign: \"left\",\n padding: \"8px 10px\",\n borderRadius: \"6px\",\n border: isSelected ? \"1px solid #6366f1\" : \"1px solid #e2e8f0\",\n backgroundColor: isSelected ? \"#e0e7ff\" : \"#ffffff\",\n color: isSelected ? \"#3730a3\" : \"#0f172a\",\n cursor: \"pointer\",\n fontWeight: isSelected ? 600 : 400,\n fontSize: \"0.85rem\",\n }}>\n {g.name}\n </button>\n )\n })}\n </div>\n </div>\n\n <div style={{ backgroundColor: \"#ffffff\", padding: \"1.2rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\" }}>\n {currentGroup ? (\n <>\n <div style={{ marginBottom: \"1rem\", borderBottom: \"1px solid #e2e8f0\", paddingBottom: \"0.8rem\" }}>\n <h3 style={{ margin: 0, fontSize: \"1.1rem\", color: \"#0f172a\" }}>{currentGroup.name} の所属メンバー</h3>\n <span style={{ fontSize: \"0.8rem\", color: \"#64748b\" }}>グループキー: {currentGroup.groupKey}</span>\n </div>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.5rem\" }}>\n {data.users.map((u) => {\n const isMember = currentMemberUserIds.has(u.id)\n return (\n <div\n key={u.id}\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n padding: \"8px 12px\",\n borderRadius: \"6px\",\n border: \"1px solid #f1f5f9\",\n backgroundColor: isMember ? \"#f0fdf4\" : \"#ffffff\",\n }}\n data-testid={`member-row-${u.id}`}>\n <div>\n <span style={{ fontWeight: 600, fontSize: \"0.88rem\", color: \"#0f172a\", marginRight: \"8px\" }}>{u.displayName}</span>\n <span style={{ fontSize: \"0.78rem\", color: \"#64748b\" }}>({u.email})</span>\n </div>\n <button\n type=\"button\"\n onClick={() => handleToggleMember(u.id, isMember)}\n data-testid={`toggle-member-btn-${u.id}`}\n style={{\n padding: \"4px 12px\",\n borderRadius: \"4px\",\n border: isMember ? \"1px solid #b91c1c\" : \"1px solid #0284c7\",\n backgroundColor: isMember ? \"#ffffff\" : \"#0284c7\",\n color: isMember ? \"#b91c1c\" : \"#ffffff\",\n fontSize: \"0.78rem\",\n fontWeight: 600,\n cursor: \"pointer\",\n }}>\n {isMember ? labels.groupMembersView.actionRemove : labels.groupMembersView.actionAdd}\n </button>\n </div>\n )\n })}\n </div>\n </>\n ) : (\n <div style={{ color: \"#94a3b8\", textAlign: \"center\", padding: \"2rem\" }}>{labels.groupMembersView.selectPrompt}</div>\n )}\n </div>\n </section>\n )\n}\n\n/**\n * View for managing authentication allowlist entries.\n * アクセス許可リスト (Allowlist) 管理ビュー。\n */\nexport function AllowlistView() {\n const data = useLoaderData() as UserAdminLoaderData\n const fetcher = useFetcher()\n const labels = data.labels ?? defaultUserAdminLabels\n\n const [pattern, setPattern] = useState(\"\")\n const [type, setType] = useState<\"email\" | \"domain\">(\"email\")\n const [description, setDescription] = useState(\"\")\n\n const handleAddEntry = (e: React.FormEvent) => {\n e.preventDefault()\n if (!pattern) return\n fetcher.submit({ intent: \"addAllowlistEntry\", pattern, type, description }, { method: \"post\" })\n setPattern(\"\")\n setDescription(\"\")\n }\n\n const handleRemoveEntry = (id: number) => {\n fetcher.submit({ intent: \"removeAllowlistEntry\", entryId: String(id) }, { method: \"post\" })\n }\n\n return (\n <section data-testid=\"allowlist-view-section\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1.2rem\" }}>\n <ActionError fetcher={fetcher} />\n <form onSubmit={handleAddEntry} style={{ backgroundColor: \"#ffffff\", padding: \"1.2rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", display: \"flex\", flexWrap: \"wrap\", gap: \"0.8rem\", alignItems: \"flex-end\" }}>\n <div style={{ flex: 2, minWidth: \"220px\" }}>\n <label htmlFor=\"allowlist-pattern-input-field\" style={{ display: \"block\", fontSize: \"0.78rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.allowlistView.pattern}\n </label>\n <input\n id=\"allowlist-pattern-input-field\"\n type=\"text\"\n value={pattern}\n onChange={(e) => setPattern(e.target.value)}\n required\n data-testid=\"allowlist-pattern-input\"\n style={{ width: \"100%\", padding: \"6px 10px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", boxSizing: \"border-box\" }}\n />\n </div>\n <div style={{ flex: 1, minWidth: \"140px\" }}>\n <label htmlFor=\"allowlist-type-select-field\" style={{ display: \"block\", fontSize: \"0.78rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.allowlistView.type}\n </label>\n <select\n id=\"allowlist-type-select-field\"\n value={type}\n onChange={(e) => setType(e.target.value as \"email\" | \"domain\")}\n data-testid=\"allowlist-type-select\"\n style={{ width: \"100%\", padding: \"6px 10px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", boxSizing: \"border-box\" }}>\n <option value=\"email\">{labels.allowlistView.emailType}</option>\n <option value=\"domain\">{labels.allowlistView.domainType}</option>\n </select>\n </div>\n <div style={{ flex: 2, minWidth: \"200px\" }}>\n <label htmlFor=\"allowlist-desc-input-field\" style={{ display: \"block\", fontSize: \"0.78rem\", color: \"#475569\", marginBottom: \"4px\" }}>\n {labels.allowlistView.description}\n </label>\n <input\n id=\"allowlist-desc-input-field\"\n type=\"text\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n data-testid=\"allowlist-desc-input\"\n style={{ width: \"100%\", padding: \"6px 10px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.85rem\", boxSizing: \"border-box\" }}\n />\n </div>\n <button type=\"submit\" data-testid=\"submit-allowlist-add\" style={{ padding: \"7px 16px\", borderRadius: \"6px\", backgroundColor: \"#047857\", color: \"#ffffff\", border: \"none\", fontSize: \"0.85rem\", fontWeight: 600, cursor: \"pointer\" }}>\n {labels.allowlistView.addEntry}\n </button>\n </form>\n\n <div style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", overflow: \"hidden\" }}>\n <table style={{ width: \"100%\", borderCollapse: \"collapse\", fontSize: \"0.85rem\", textAlign: \"left\" }}>\n <thead>\n <tr style={{ backgroundColor: \"#f8fafc\", borderBottom: \"1px solid #e2e8f0\", color: \"#475569\" }}>\n <th style={{ padding: \"10px 14px\" }}>ID</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.allowlistView.pattern}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.allowlistView.type}</th>\n <th style={{ padding: \"10px 14px\" }}>{labels.allowlistView.description}</th>\n <th style={{ padding: \"10px 14px\", textAlign: \"right\" }}>{labels.allowlistView.actions}</th>\n </tr>\n </thead>\n <tbody>\n {data.allowlist.map((item) => (\n <tr key={item.id} style={{ borderBottom: \"1px solid #f1f5f9\" }} data-testid={`allowlist-row-${item.id}`}>\n <td style={{ padding: \"10px 14px\", color: \"#64748b\" }}>#{item.id}</td>\n <td style={{ padding: \"10px 14px\", fontWeight: 600, color: \"#0f172a\", fontFamily: \"monospace\" }}>{item.pattern}</td>\n <td style={{ padding: \"10px 14px\" }}>\n <span style={{ fontSize: \"0.75rem\", padding: \"2px 8px\", borderRadius: \"10px\", backgroundColor: item.type === \"email\" ? \"#e0f2fe\" : \"#fef3c7\", color: item.type === \"email\" ? \"#0369a1\" : \"#b45309\", fontWeight: 600 }}>\n {item.type === \"email\" ? labels.allowlistView.emailTypeShort : labels.allowlistView.domainTypeShort}\n </span>\n </td>\n <td style={{ padding: \"10px 14px\", color: \"#475569\" }}>{item.description || \"-\"}</td>\n <td style={{ padding: \"10px 14px\", textAlign: \"right\" }}>\n {/* 変更不能な供給元 (環境変数など) は削除ボタンを出さない */}\n {item.readOnly ? (\n <span data-testid={`readonly-allowlist-${item.id}`} style={{ fontSize: \"0.75rem\", color: \"#94a3b8\" }}>\n {labels.allowlistView.readOnly}\n </span>\n ) : (\n <button\n type=\"button\"\n onClick={() => handleRemoveEntry(item.id)}\n data-testid={`remove-allowlist-btn-${item.id}`}\n style={{ padding: \"3px 8px\", borderRadius: \"4px\", border: \"1px solid #fee2e2\", backgroundColor: \"#fff5f5\", color: \"#b91c1c\", fontSize: \"0.78rem\", cursor: \"pointer\" }}>\n 削除\n </button>\n )}\n </td>\n </tr>\n ))}\n {data.allowlist.length === 0 && (\n <tr>\n <td colSpan={5} style={{ padding: \"2rem\", textAlign: \"center\", color: \"#94a3b8\" }}>\n {labels.allowlistView.empty}\n </td>\n </tr>\n )}\n </tbody>\n </table>\n </div>\n </section>\n )\n}\n\nconst SEGMENT_VIEWS: Record<string, () => React.ReactElement> = {\n users: UsersView,\n groups: GroupsView,\n group_members: GroupMembersView,\n allowlist: AllowlistView,\n}\n\nexport type AuthUserAdminAppViewProps = UserAdminShellProps\n\n/**\n * Top-level application view component for the user administration UI (`/users/*`).\n * ユーザー管理 UI 全体を描画するトップレベルコンポーネント。\n */\nexport function AuthUserAdminAppView({ renderHeader }: AuthUserAdminAppViewProps = {}) {\n const data = useLoaderData() as UserAdminLoaderData | undefined\n const segment = data?.segment ?? \"users\"\n const View = SEGMENT_VIEWS[segment] ?? UsersView\n\n return (\n <UserAdminShell renderHeader={renderHeader}>\n <View />\n </UserAdminShell>\n )\n}\n"],"mappings":"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","names":["admin_exports","__export","AllowlistView","AuthUserAdminAppView","GroupMembersView","GroupsView","UserAdminShell","UsersView","createInMemoryUserAdminStore","createUserAdminApp","defaultUserAdminLabels","jaUserAdminLabels","resolveUserAdminLabels","__toCommonJS","defaultUserAdminLabels","jaUserAdminLabels","resolveUserAdminLabels","over","d","createInMemoryUserAdminStore","initial","users","groups","groupMembers","allowlist","data","newUser","u","id","idx","isActive","newGroup","g","m","groupId","userId","existing","newMember","newEntry","a","INTENT_ACCESS","requireId","formData","field","raw","n","requireText","value","optionalText","createUserAdminApp","config","labels","resolveUserAdminLabels","loader","request","pathSegments","segment","action","intent","access","dispatch","error","rawActive","type","import_react_router","import_jsx_runtime","UserAdminShell","children","renderHeader","data","segment","labels","defaultUserAdminLabels","tabs","annotation","tab","isActive","import_react","import_react_router","import_jsx_runtime","ActionError","fetcher","result","UsersView","data","labels","defaultUserAdminLabels","searchQuery","setSearchQuery","editingUser","setEditingUser","isCreateOpen","setIsCreateOpen","newDisplayName","setNewDisplayName","newEmail","setNewEmail","filteredUsers","u","handleToggleActive","user","handleCreateUser","e","handleUpdateUser","GroupsView","editingGroup","setEditingGroup","newGroupKey","setNewGroupKey","newName","setNewName","newDescription","setNewDescription","filteredGroups","g","handleCreateGroup","handleUpdateGroup","handleDeleteGroup","groupId","memberCount","m","GroupMembersView","selectedGroupId","setSelectedGroupId","currentGroup","currentMemberUserIds","handleToggleMember","userId","isMember","isSelected","AllowlistView","pattern","setPattern","type","setType","description","setDescription","handleAddEntry","handleRemoveEntry","id","item","SEGMENT_VIEWS","AuthUserAdminAppView","renderHeader","segment","View","UserAdminShell"]}
|
package/dist/admin.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var x={title:"Users & Groups",heading:"\u{1F464} User administration",tabs:{users:"\u{1F464} Users",groups:"\u{1F465} Groups",groupMembers:"\u{1F517} Members",allowlist:"\u2709\uFE0F Allowlist"},usersView:{title:"Users",searchPlaceholder:"Search by name or email...",addUser:"\uFF0B Add user",editUser:"Edit user",id:"ID",displayName:"Name",email:"Email",status:"Status",active:"Active",inactive:"Inactive",actions:"Actions",edit:"Edit",activate:"Activate",deactivate:"Deactivate",empty:"No matching users",save:"Save",cancel:"Cancel"},groupsView:{title:"Groups",searchPlaceholder:"Search by group name or key...",addGroup:"\uFF0B Add group",editGroup:"Edit group",id:"ID",groupKey:"Group key",name:"Name",description:"Description",memberCount:"Members",status:"Status",actions:"Actions",edit:"Edit",delete:"Delete",confirmDelete:"Delete this group?",noDescription:"No description",groupKeyPlaceholder:"e.g. dev-team",namePlaceholder:"e.g. Development team",empty:"No groups registered",save:"Save",cancel:"Cancel"},groupMembersView:{selectGroup:"Select a group",selectPrompt:"Select a group to manage its members",actionAdd:"Add",actionRemove:"Remove"},allowlistView:{title:"Sign-in allowlist",addEntry:"\uFF0B Add pattern",pattern:"Pattern (e.g. @example.com or user@example.com)",type:"Type",emailType:"Exact email match",domainType:"Domain match",emailTypeShort:"Email",domainTypeShort:"Domain",description:"Description",actions:"Actions",readOnly:"Read-only",delete:"Delete",empty:"No allowlist entries registered",save:"Add",cancel:"Cancel"}},j={title:"\u30E6\u30FC\u30B6\u30FC\u30FB\u30B0\u30EB\u30FC\u30D7\u7BA1\u7406",heading:"\u{1F464} \u30E6\u30FC\u30B6\u30FC\u7BA1\u7406",tabs:{users:"\u{1F464} \u30E6\u30FC\u30B6\u30FC\u4E00\u89A7",groups:"\u{1F465} \u30E6\u30FC\u30B6\u30FC\u30B0\u30EB\u30FC\u30D7",groupMembers:"\u{1F517} \u30B0\u30EB\u30FC\u30D7\u30E1\u30F3\u30D0\u30FC",allowlist:"\u2709\uFE0F \u30A2\u30AF\u30BB\u30B9\u8A31\u53EF\u30EA\u30B9\u30C8"},usersView:{title:"\u30E6\u30FC\u30B6\u30FC\u7BA1\u7406",searchPlaceholder:"\u8868\u793A\u540D\u3084 Email \u3067\u691C\u7D22...",addUser:"\uFF0B \u30E6\u30FC\u30B6\u30FC\u8FFD\u52A0",editUser:"\u30E6\u30FC\u30B6\u30FC\u306E\u7DE8\u96C6",id:"ID",displayName:"\u8868\u793A\u540D",email:"\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9",status:"\u30B9\u30C6\u30FC\u30BF\u30B9",active:"\u6709\u52B9",inactive:"\u7121\u52B9",actions:"\u64CD\u4F5C",edit:"\u7DE8\u96C6",activate:"\u6709\u52B9\u5316",deactivate:"\u7121\u52B9\u5316",empty:"\u8A72\u5F53\u3059\u308B\u30E6\u30FC\u30B6\u30FC\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093",save:"\u4FDD\u5B58",cancel:"\u30AD\u30E3\u30F3\u30BB\u30EB"},groupsView:{title:"\u30E6\u30FC\u30B6\u30FC\u30B0\u30EB\u30FC\u30D7\u7BA1\u7406",searchPlaceholder:"\u30B0\u30EB\u30FC\u30D7\u540D\u3084\u30AD\u30FC\u3067\u691C\u7D22...",addGroup:"\uFF0B \u30B0\u30EB\u30FC\u30D7\u4F5C\u6210",editGroup:"\u30B0\u30EB\u30FC\u30D7\u306E\u7DE8\u96C6",id:"ID",groupKey:"\u30B0\u30EB\u30FC\u30D7\u30AD\u30FC",name:"\u30B0\u30EB\u30FC\u30D7\u540D",description:"\u8AAC\u660E",memberCount:"\u30E1\u30F3\u30D0\u30FC\u6570",status:"\u30B9\u30C6\u30FC\u30BF\u30B9",actions:"\u64CD\u4F5C",edit:"\u7DE8\u96C6",delete:"\u524A\u9664",confirmDelete:"\u3053\u306E\u30B0\u30EB\u30FC\u30D7\u3092\u524A\u9664\u3057\u3066\u3082\u3088\u308D\u3057\u3044\u3067\u3059\u304B\uFF1F",noDescription:"\u8AAC\u660E\u306A\u3057",groupKeyPlaceholder:"\u4F8B: dev-team",namePlaceholder:"\u4F8B: \u958B\u767A\u30C1\u30FC\u30E0",empty:"\u30B0\u30EB\u30FC\u30D7\u304C\u767B\u9332\u3055\u308C\u3066\u3044\u307E\u305B\u3093",save:"\u4FDD\u5B58",cancel:"\u30AD\u30E3\u30F3\u30BB\u30EB"},groupMembersView:{selectGroup:"\u30B0\u30EB\u30FC\u30D7\u9078\u629E",selectPrompt:"\u30B0\u30EB\u30FC\u30D7\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044",actionAdd:"\u8FFD\u52A0",actionRemove:"\u9664\u5916"},allowlistView:{title:"\u30A2\u30AF\u30BB\u30B9\u8A31\u53EF\u30EA\u30B9\u30C8 (Allowlist)",addEntry:"\uFF0B \u8A31\u53EF\u30D1\u30BF\u30FC\u30F3\u8FFD\u52A0",pattern:"\u30D1\u30BF\u30FC\u30F3 (\u4F8B: @example.com \u307E\u305F\u306F user@example.com)",type:"\u7A2E\u5225",emailType:"\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u5B8C\u5168\u4E00\u81F4",domainType:"\u30C9\u30E1\u30A4\u30F3\u4E00\u81F4",emailTypeShort:"Email \u5B8C\u5168\u4E00\u81F4",domainTypeShort:"\u30C9\u30E1\u30A4\u30F3\u4E00\u81F4",description:"\u8AAC\u660E / \u5099\u8003",actions:"\u64CD\u4F5C",readOnly:"\u5909\u66F4\u4E0D\u53EF",delete:"\u524A\u9664",empty:"\u30A2\u30ED\u30EF\u30EA\u30B9\u30C8\u30A8\u30F3\u30C8\u30EA\u304C\u767B\u9332\u3055\u308C\u3066\u3044\u307E\u305B\u3093",save:"\u767B\u9332",cancel:"\u30AD\u30E3\u30F3\u30BB\u30EB"}};function I(s){let a=x;return s?{title:s.title??a.title,heading:s.heading??a.heading,tabs:{...a.tabs,...s.tabs},usersView:{...a.usersView,...s.usersView},groupsView:{...a.groupsView,...s.groupsView},groupMembersView:{...a.groupMembersView,...s.groupMembersView},allowlistView:{...a.allowlistView,...s.allowlistView}}:a}function Q(s){let a=s?.users??[{id:1,email:"admin@example.com",displayName:"\u30B7\u30B9\u30C6\u30E0\u7BA1\u7406\u8005",isActive:!0},{id:2,email:"user1@example.com",displayName:"\u958B\u767A \u592A\u90CE",isActive:!0},{id:3,email:"user2@example.com",displayName:"\u55B6\u696D \u82B1\u5B50",isActive:!0}],t=s?.groups??[{id:1,groupKey:"dev-team",name:"\u958B\u767A\u30C1\u30FC\u30E0",description:"\u30B7\u30B9\u30C6\u30E0\u958B\u767A\u62C5\u5F53\u30B0\u30EB\u30FC\u30D7",isActive:!0},{id:2,groupKey:"sales-dept",name:"\u55B6\u696D\u90E8",description:"\u55B6\u696D\u30BD\u30EA\u30E5\u30FC\u30B7\u30E7\u30F3\u62C5\u5F53",isActive:!0}],l=s?.groupMembers??[{id:1,groupId:1,userId:2},{id:2,groupId:2,userId:3}],g=s?.allowlist??[{id:1,pattern:"admin@example.com",type:"email",description:"\u521D\u671F\u7BA1\u7406\u8005"},{id:2,pattern:"@example.com",type:"domain",description:"\u793E\u5185\u30C9\u30E1\u30A4\u30F3"}];return{async listUsers(){return[...a]},async createUser(i){let r={id:Math.max(...a.map(o=>o.id),0)+1,email:i.email,displayName:i.displayName,isActive:!0};return a.push(r),r},async updateUser(i,r){let o=a.findIndex(n=>n.id===i);if(o===-1)throw new Error(`User not found: ${i}`);return a[o]={...a[o],displayName:r.displayName,email:r.email},a[o]},async toggleUserActive(i,r){let o=a.findIndex(n=>n.id===i);if(o===-1)throw new Error(`User not found: ${i}`);return a[o]={...a[o],isActive:r},a[o]},async listGroups(){return[...t]},async createGroup(i){let r={id:Math.max(...t.map(o=>o.id),0)+1,groupKey:i.groupKey,name:i.name,description:i.description,isActive:!0};return t.push(r),r},async updateGroup(i,r){let o=t.findIndex(n=>n.id===i);if(o===-1)throw new Error(`Group not found: ${i}`);return t[o]={...t[o],name:r.name,description:r.description},t[o]},async deleteGroup(i){t=t.filter(r=>r.id!==i),l=l.filter(r=>r.groupId!==i)},async listGroupMembers(){return[...l]},async addGroupMember(i,r){let o=l.find(p=>p.groupId===i&&p.userId===r);if(o)return o;let n={id:Math.max(...l.map(p=>p.id),0)+1,groupId:i,userId:r};return l.push(n),n},async removeGroupMember(i,r){l=l.filter(o=>!(o.groupId===i&&o.userId===r))},async listAllowlist(){return[...g]},async addAllowlistEntry(i){let r={id:Math.max(...g.map(o=>o.id),0)+1,pattern:i.pattern,type:i.type,description:i.description};return g.push(r),r},async removeAllowlistEntry(i){g=g.filter(r=>r.id!==i)}}}var B=new Map([["createUser","create"],["updateUser","update"],["toggleUserActive","update"],["createGroup","create"],["updateGroup","update"],["deleteGroup","delete"],["addGroupMember","create"],["removeGroupMember","delete"],["addAllowlistEntry","create"],["removeAllowlistEntry","delete"]]);function y(s,a){let t=s.get(a),l=Number(t);if(t===null||String(t).trim()===""||!Number.isInteger(l)||l<=0)throw new Error(`Invalid or missing "${a}"`);return l}function h(s,a){let t=String(s.get(a)??"").trim();if(t==="")throw new Error(`Invalid or missing "${a}"`);return t}function R(s,a){let t=String(s.get(a)??"").trim();return t===""?void 0:t}function H(s){let a=I(s.labels);async function t({request:i}){await s.guards.requireAccess(i,{action:"read",intent:"loader"});let o=new URL(i.url).pathname.split("/").filter(Boolean),n=o[o.length-1]??"users",[p,f,m,w]=await Promise.all([s.store.listUsers(),s.store.listGroups(),s.store.listGroupMembers(),s.store.listAllowlist()]);return{segment:["groups","group_members","allowlist"].includes(n)?n:"users",users:p,groups:f,groupMembers:m,allowlist:w,labels:a}}async function l({request:i}){let r=await i.formData(),o=String(r.get("intent")??""),n=B.get(o);if(!n)return{ok:!1,error:`Unknown intent: ${o}`};await s.guards.requireAccess(i,{action:n,intent:o});try{return await g(o,r)}catch(p){if(p instanceof Response)throw p;return{ok:!1,error:p instanceof Error?p.message:String(p)}}}async function g(i,r){if(i==="createUser")return await s.store.createUser({displayName:h(r,"displayName"),email:h(r,"email")}),{ok:!0};if(i==="updateUser")return await s.store.updateUser(y(r,"userId"),{displayName:h(r,"displayName"),email:h(r,"email")}),{ok:!0};if(i==="toggleUserActive"){let o=String(r.get("isActive")??"");if(o!=="true"&&o!=="false")throw new Error('Invalid or missing "isActive"');return await s.store.toggleUserActive(y(r,"userId"),o==="true"),{ok:!0}}if(i==="createGroup")return await s.store.createGroup({groupKey:h(r,"groupKey"),name:h(r,"name"),description:R(r,"description")}),{ok:!0};if(i==="updateGroup")return await s.store.updateGroup(y(r,"groupId"),{name:h(r,"name"),description:R(r,"description")}),{ok:!0};if(i==="deleteGroup")return await s.store.deleteGroup(y(r,"groupId")),{ok:!0};if(i==="addGroupMember")return await s.store.addGroupMember(y(r,"groupId"),y(r,"userId")),{ok:!0};if(i==="removeGroupMember")return await s.store.removeGroupMember(y(r,"groupId"),y(r,"userId")),{ok:!0};if(i==="addAllowlistEntry"){let o=r.get("type")==="domain"?"domain":"email";return await s.store.addAllowlistEntry({pattern:h(r,"pattern"),type:o,description:R(r,"description")}),{ok:!0}}return i==="removeAllowlistEntry"?(await s.store.removeAllowlistEntry(y(r,"entryId")),{ok:!0}):{ok:!1,error:`Unknown intent: ${i}`}}return{loader:t,action:l}}import{Link as D,useLoaderData as T}from"react-router";import{jsx as v,jsxs as z}from"react/jsx-runtime";function M({children:s,renderHeader:a}){let t=T(),l=t?.segment??"users",g=t?.labels??x,i=[{key:"users",label:g.tabs.users,href:"/users"},{key:"groups",label:g.tabs.groups,href:"/users/groups"},{key:"group_members",label:g.tabs.groupMembers,href:"/users/group_members"},{key:"allowlist",label:g.tabs.allowlist,href:"/users/allowlist"}],r=v("div",{style:{display:"flex",alignItems:"center",gap:"0.5rem"},"data-testid":"user-admin-header-annotation",children:v("span",{style:{fontSize:"0.75rem",backgroundColor:"#e0f2fe",color:"#0369a1",padding:"2px 8px",borderRadius:"12px",fontWeight:600},children:g.heading})});return z("div",{"data-testid":"user-admin-shell",style:{width:"100%",minHeight:"100vh",backgroundColor:"#f8fafc",padding:"1.5rem",boxSizing:"border-box"},children:[a?a({title:g.title,annotation:r}):z("div",{style:{marginBottom:"1.5rem",display:"flex",justifyContent:"space-between",alignItems:"center"},children:[v("h1",{style:{margin:0,fontSize:"1.5rem",fontWeight:700,color:"#0f172a"},children:g.title}),r]}),v("div",{style:{display:"flex",gap:"0.5rem",marginBottom:"1.2rem",borderBottom:"2px solid #e2e8f0",paddingBottom:"0.5rem"},children:i.map(o=>{let n=l===o.key;return v(D,{to:o.href,"data-testid":`user-admin-tab-${o.key}`,style:{padding:"0.5rem 1rem",borderRadius:"6px 6px 0 0",fontSize:"0.88rem",fontWeight:n?700:500,color:n?"#0284c7":"#64748b",backgroundColor:n?"#ffffff":"transparent",textDecoration:"none",borderBottom:n?"3px solid #0284c7":"3px solid transparent",transition:"all 0.15s ease"},children:o.label},o.key)})}),v("main",{children:s})]})}import{useState as b}from"react";import{useFetcher as U,useLoaderData as S}from"react-router";import{Fragment as q,jsx as e,jsxs as d}from"react/jsx-runtime";function k({fetcher:s}){let a=s.data;return a?.ok!==!1||!a.error?null:e("div",{"data-testid":"user-admin-action-error",role:"alert",style:{padding:"0.7rem 1rem",borderRadius:"6px",border:"1px solid #fecaca",backgroundColor:"#fef2f2",color:"#b91c1c",fontSize:"0.85rem"},children:a.error})}function E(){let s=S(),a=U(),t=s.labels??x,[l,g]=b(""),[i,r]=b(null),[o,n]=b(!1),[p,f]=b(""),[m,w]=b(""),A=s.users.filter(u=>u.displayName.toLowerCase().includes(l.toLowerCase())||u.email.toLowerCase().includes(l.toLowerCase())),C=u=>{a.submit({intent:"toggleUserActive",userId:String(u.id),isActive:String(!u.isActive)},{method:"post"})},V=u=>{u.preventDefault(),p&&m&&(a.submit({intent:"createUser",displayName:p,email:m},{method:"post"}),f(""),w(""),n(!1))},G=u=>{u.preventDefault(),i&&(a.submit({intent:"updateUser",userId:String(i.id),displayName:i.displayName,email:i.email},{method:"post"}),r(null))};return d("section",{"data-testid":"users-view-section",style:{display:"flex",flexDirection:"column",gap:"1rem"},children:[e(k,{fetcher:a}),d("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",backgroundColor:"#ffffff",padding:"1rem",borderRadius:"8px",border:"1px solid #e2e8f0"},children:[e("input",{type:"text",placeholder:t.usersView.searchPlaceholder,value:l,onChange:u=>g(u.target.value),"data-testid":"users-search-input",style:{padding:"6px 12px",borderRadius:"6px",border:"1px solid #cbd5e1",fontSize:"0.85rem",width:"260px"}}),e("button",{type:"button",onClick:()=>n(!0),"data-testid":"users-add-button",style:{padding:"6px 14px",borderRadius:"6px",backgroundColor:"#0284c7",color:"#ffffff",border:"none",fontSize:"0.85rem",fontWeight:600,cursor:"pointer"},children:t.usersView.addUser})]}),e("div",{style:{backgroundColor:"#ffffff",borderRadius:"8px",border:"1px solid #e2e8f0",overflow:"hidden"},children:d("table",{style:{width:"100%",borderCollapse:"collapse",fontSize:"0.85rem",textAlign:"left"},children:[e("thead",{children:d("tr",{style:{backgroundColor:"#f8fafc",borderBottom:"1px solid #e2e8f0",color:"#475569"},children:[e("th",{style:{padding:"10px 14px"},children:t.usersView.id}),e("th",{style:{padding:"10px 14px"},children:t.usersView.displayName}),e("th",{style:{padding:"10px 14px"},children:t.usersView.email}),e("th",{style:{padding:"10px 14px"},children:t.usersView.status}),e("th",{style:{padding:"10px 14px",textAlign:"right"},children:t.usersView.actions})]})}),d("tbody",{children:[A.map(u=>d("tr",{style:{borderBottom:"1px solid #f1f5f9"},"data-testid":`user-row-${u.id}`,children:[d("td",{style:{padding:"10px 14px",color:"#64748b"},children:["#",u.id]}),e("td",{style:{padding:"10px 14px",fontWeight:600,color:"#0f172a"},children:u.displayName}),e("td",{style:{padding:"10px 14px",color:"#475569"},children:u.email}),e("td",{style:{padding:"10px 14px"},children:e("span",{style:{fontSize:"0.75rem",padding:"2px 8px",borderRadius:"10px",backgroundColor:u.isActive?"#dcfce7":"#fee2e2",color:u.isActive?"#15803d":"#b91c1c",fontWeight:600},children:u.isActive?t.usersView.active:t.usersView.inactive})}),d("td",{style:{padding:"10px 14px",textAlign:"right"},children:[e("button",{type:"button",onClick:()=>r(u),"data-testid":`edit-user-btn-${u.id}`,style:{padding:"3px 8px",borderRadius:"4px",border:"1px solid #cbd5e1",backgroundColor:"#ffffff",fontSize:"0.78rem",cursor:"pointer",marginRight:"6px"},children:t.usersView.edit}),e("button",{type:"button",onClick:()=>C(u),"data-testid":`toggle-user-btn-${u.id}`,style:{padding:"3px 8px",borderRadius:"4px",border:"1px solid #cbd5e1",backgroundColor:"#ffffff",fontSize:"0.78rem",cursor:"pointer",color:u.isActive?"#b91c1c":"#15803d"},children:u.isActive?t.usersView.deactivate:t.usersView.activate})]})]},u.id)),A.length===0&&e("tr",{children:e("td",{colSpan:5,style:{padding:"2rem",textAlign:"center",color:"#94a3b8"},children:t.usersView.empty})})]})]})}),o&&e("div",{style:{position:"fixed",inset:0,backgroundColor:"rgba(0,0,0,0.4)",display:"flex",justifyContent:"center",alignItems:"center",zIndex:100},"data-testid":"user-create-modal",children:d("form",{onSubmit:V,style:{backgroundColor:"#ffffff",padding:"1.5rem",borderRadius:"8px",width:"360px",boxShadow:"0 10px 25px rgba(0,0,0,0.15)"},children:[e("h3",{style:{margin:"0 0 1rem 0",fontSize:"1.1rem"},children:t.usersView.addUser}),d("div",{style:{marginBottom:"1rem"},children:[e("label",{htmlFor:"create-user-display-name-input",style:{display:"block",fontSize:"0.8rem",color:"#475569",marginBottom:"4px"},children:t.usersView.displayName}),e("input",{id:"create-user-display-name-input",type:"text",value:p,onChange:u=>f(u.target.value),required:!0,"data-testid":"create-user-display-name",style:{width:"100%",padding:"6px",borderRadius:"4px",border:"1px solid #cbd5e1",boxSizing:"border-box"}})]}),d("div",{style:{marginBottom:"1.2rem"},children:[e("label",{htmlFor:"create-user-email-input",style:{display:"block",fontSize:"0.8rem",color:"#475569",marginBottom:"4px"},children:t.usersView.email}),e("input",{id:"create-user-email-input",type:"email",value:m,onChange:u=>w(u.target.value),required:!0,"data-testid":"create-user-email",style:{width:"100%",padding:"6px",borderRadius:"4px",border:"1px solid #cbd5e1",boxSizing:"border-box"}})]}),d("div",{style:{display:"flex",justifyContent:"flex-end",gap:"0.5rem"},children:[e("button",{type:"button",onClick:()=>n(!1),style:{padding:"6px 12px",borderRadius:"4px",border:"1px solid #cbd5e1",backgroundColor:"#ffffff",cursor:"pointer"},children:"\u30AD\u30E3\u30F3\u30BB\u30EB"}),e("button",{type:"submit","data-testid":"submit-create-user",style:{padding:"6px 12px",borderRadius:"4px",backgroundColor:"#0284c7",color:"#ffffff",border:"none",cursor:"pointer"},children:"\u4F5C\u6210"})]})]})}),i&&e("div",{style:{position:"fixed",inset:0,backgroundColor:"rgba(0,0,0,0.4)",display:"flex",justifyContent:"center",alignItems:"center",zIndex:100},"data-testid":"user-edit-modal",children:d("form",{onSubmit:G,style:{backgroundColor:"#ffffff",padding:"1.5rem",borderRadius:"8px",width:"360px",boxShadow:"0 10px 25px rgba(0,0,0,0.15)"},children:[e("h3",{style:{margin:"0 0 1rem 0",fontSize:"1.1rem"},children:t.usersView.editUser}),d("div",{style:{marginBottom:"1rem"},children:[e("label",{htmlFor:"edit-user-display-name-input",style:{display:"block",fontSize:"0.8rem",color:"#475569",marginBottom:"4px"},children:t.usersView.displayName}),e("input",{id:"edit-user-display-name-input",type:"text",value:i.displayName,onChange:u=>r({...i,displayName:u.target.value}),required:!0,"data-testid":"edit-user-display-name",style:{width:"100%",padding:"6px",borderRadius:"4px",border:"1px solid #cbd5e1",boxSizing:"border-box"}})]}),d("div",{style:{marginBottom:"1.2rem"},children:[e("label",{htmlFor:"edit-user-email-input",style:{display:"block",fontSize:"0.8rem",color:"#475569",marginBottom:"4px"},children:t.usersView.email}),e("input",{id:"edit-user-email-input",type:"email",value:i.email,onChange:u=>r({...i,email:u.target.value}),required:!0,"data-testid":"edit-user-email",style:{width:"100%",padding:"6px",borderRadius:"4px",border:"1px solid #cbd5e1",boxSizing:"border-box"}})]}),d("div",{style:{display:"flex",justifyContent:"flex-end",gap:"0.5rem"},children:[e("button",{type:"button",onClick:()=>r(null),style:{padding:"6px 12px",borderRadius:"4px",border:"1px solid #cbd5e1",backgroundColor:"#ffffff",cursor:"pointer"},children:t.usersView.cancel}),e("button",{type:"submit","data-testid":"submit-edit-user",style:{padding:"6px 12px",borderRadius:"4px",backgroundColor:"#0284c7",color:"#ffffff",border:"none",cursor:"pointer"},children:t.usersView.save})]})]})})]})}function F(){let s=S(),a=U(),t=s.labels??x,[l,g]=b(""),[i,r]=b(!1),[o,n]=b(null),[p,f]=b(""),[m,w]=b(""),[A,C]=b(""),V=s.groups.filter(c=>c.name.toLowerCase().includes(l.toLowerCase())||c.groupKey.toLowerCase().includes(l.toLowerCase())),G=c=>{c.preventDefault(),p&&m&&(a.submit({intent:"createGroup",groupKey:p,name:m,description:A},{method:"post"}),f(""),w(""),C(""),r(!1))},u=c=>{c.preventDefault(),o&&(a.submit({intent:"updateGroup",groupId:String(o.id),name:o.name,description:o.description??""},{method:"post"}),n(null))},P=c=>{window.confirm(t.groupsView.confirmDelete)&&a.submit({intent:"deleteGroup",groupId:String(c)},{method:"post"})};return d("section",{"data-testid":"groups-view-section",style:{display:"flex",flexDirection:"column",gap:"1rem"},children:[e(k,{fetcher:a}),d("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",backgroundColor:"#ffffff",padding:"1rem",borderRadius:"8px",border:"1px solid #e2e8f0"},children:[e("input",{type:"text",placeholder:t.groupsView.searchPlaceholder,value:l,onChange:c=>g(c.target.value),"data-testid":"groups-search-input",style:{padding:"6px 12px",borderRadius:"6px",border:"1px solid #cbd5e1",fontSize:"0.85rem",width:"260px"}}),e("button",{type:"button",onClick:()=>r(!0),"data-testid":"groups-add-button",style:{padding:"6px 14px",borderRadius:"6px",backgroundColor:"#4338ca",color:"#ffffff",border:"none",fontSize:"0.85rem",fontWeight:600,cursor:"pointer"},children:t.groupsView.addGroup})]}),e("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(280px, 1fr))",gap:"1rem"},children:V.map(c=>{let N=s.groupMembers.filter(L=>L.groupId===c.id).length;return d("div",{style:{backgroundColor:"#ffffff",borderRadius:"8px",border:"1px solid #e2e8f0",padding:"1rem",display:"flex",flexDirection:"column",justifyContent:"space-between"},"data-testid":`group-card-${c.id}`,children:[d("div",{children:[d("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:"0.5rem"},children:[e("h4",{style:{margin:0,fontSize:"1rem",color:"#0f172a"},children:c.name}),e("span",{style:{fontSize:"0.72rem",backgroundColor:"#e0e7ff",color:"#4338ca",padding:"2px 6px",borderRadius:"4px",fontFamily:"monospace"},children:c.groupKey})]}),e("p",{style:{margin:"0 0 0.8rem 0",fontSize:"0.82rem",color:"#64748b",minHeight:"2.4em"},children:c.description||t.groupsView.noDescription})]}),d("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",borderTop:"1px solid #f1f5f9",paddingTop:"0.6rem"},children:[d("span",{style:{fontSize:"0.78rem",color:"#475569"},children:["\u{1F465} \u30E1\u30F3\u30D0\u30FC: ",e("strong",{children:N})," \u4EBA"]}),d("div",{children:[e("button",{type:"button",onClick:()=>n(c),"data-testid":`edit-group-btn-${c.id}`,style:{padding:"3px 8px",borderRadius:"4px",border:"1px solid #cbd5e1",backgroundColor:"#ffffff",fontSize:"0.75rem",cursor:"pointer",marginRight:"4px"},children:t.usersView.edit}),e("button",{type:"button",onClick:()=>P(c.id),"data-testid":`delete-group-btn-${c.id}`,style:{padding:"3px 8px",borderRadius:"4px",border:"1px solid #fee2e2",backgroundColor:"#fff5f5",color:"#b91c1c",fontSize:"0.75rem",cursor:"pointer"},children:"\u524A\u9664"})]})]})]},c.id)})}),i&&e("div",{style:{position:"fixed",inset:0,backgroundColor:"rgba(0,0,0,0.4)",display:"flex",justifyContent:"center",alignItems:"center",zIndex:100},"data-testid":"group-create-modal",children:d("form",{onSubmit:G,style:{backgroundColor:"#ffffff",padding:"1.5rem",borderRadius:"8px",width:"360px",boxShadow:"0 10px 25px rgba(0,0,0,0.15)"},children:[e("h3",{style:{margin:"0 0 1rem 0",fontSize:"1.1rem"},children:t.groupsView.addGroup}),d("div",{style:{marginBottom:"1rem"},children:[e("label",{htmlFor:"create-group-key-input",style:{display:"block",fontSize:"0.8rem",color:"#475569",marginBottom:"4px"},children:t.groupsView.groupKey}),e("input",{id:"create-group-key-input",type:"text",value:p,onChange:c=>f(c.target.value),placeholder:t.groupsView.groupKeyPlaceholder,required:!0,"data-testid":"create-group-key",style:{width:"100%",padding:"6px",borderRadius:"4px",border:"1px solid #cbd5e1",boxSizing:"border-box"}})]}),d("div",{style:{marginBottom:"1rem"},children:[e("label",{htmlFor:"create-group-name-input",style:{display:"block",fontSize:"0.8rem",color:"#475569",marginBottom:"4px"},children:t.groupsView.name}),e("input",{id:"create-group-name-input",type:"text",value:m,onChange:c=>w(c.target.value),placeholder:t.groupsView.namePlaceholder,required:!0,"data-testid":"create-group-name",style:{width:"100%",padding:"6px",borderRadius:"4px",border:"1px solid #cbd5e1",boxSizing:"border-box"}})]}),d("div",{style:{marginBottom:"1.2rem"},children:[e("label",{htmlFor:"create-group-desc-input",style:{display:"block",fontSize:"0.8rem",color:"#475569",marginBottom:"4px"},children:t.groupsView.description}),e("textarea",{id:"create-group-desc-input",value:A,onChange:c=>C(c.target.value),"data-testid":"create-group-description",style:{width:"100%",padding:"6px",borderRadius:"4px",border:"1px solid #cbd5e1",boxSizing:"border-box",height:"60px"}})]}),d("div",{style:{display:"flex",justifyContent:"flex-end",gap:"0.5rem"},children:[e("button",{type:"button",onClick:()=>r(!1),style:{padding:"6px 12px",borderRadius:"4px",border:"1px solid #cbd5e1",backgroundColor:"#ffffff",cursor:"pointer"},children:"\u30AD\u30E3\u30F3\u30BB\u30EB"}),e("button",{type:"submit","data-testid":"submit-create-group",style:{padding:"6px 12px",borderRadius:"4px",backgroundColor:"#4338ca",color:"#ffffff",border:"none",cursor:"pointer"},children:"\u4F5C\u6210"})]})]})}),o&&e("div",{style:{position:"fixed",inset:0,backgroundColor:"rgba(0,0,0,0.4)",display:"flex",justifyContent:"center",alignItems:"center",zIndex:100},"data-testid":"group-edit-modal",children:d("form",{onSubmit:u,style:{backgroundColor:"#ffffff",padding:"1.5rem",borderRadius:"8px",width:"360px",boxShadow:"0 10px 25px rgba(0,0,0,0.15)"},children:[e("h3",{style:{margin:"0 0 1rem 0",fontSize:"1.1rem"},children:t.groupsView.editGroup}),d("div",{style:{marginBottom:"1rem"},children:[e("label",{htmlFor:"edit-group-name-input",style:{display:"block",fontSize:"0.8rem",color:"#475569",marginBottom:"4px"},children:t.groupsView.name}),e("input",{id:"edit-group-name-input",type:"text",value:o.name,onChange:c=>n({...o,name:c.target.value}),required:!0,"data-testid":"edit-group-name",style:{width:"100%",padding:"6px",borderRadius:"4px",border:"1px solid #cbd5e1",boxSizing:"border-box"}})]}),d("div",{style:{marginBottom:"1.2rem"},children:[e("label",{htmlFor:"edit-group-desc-input",style:{display:"block",fontSize:"0.8rem",color:"#475569",marginBottom:"4px"},children:t.groupsView.description}),e("textarea",{id:"edit-group-desc-input",value:o.description??"",onChange:c=>n({...o,description:c.target.value}),"data-testid":"edit-group-description",style:{width:"100%",padding:"6px",borderRadius:"4px",border:"1px solid #cbd5e1",boxSizing:"border-box",height:"60px"}})]}),d("div",{style:{display:"flex",justifyContent:"flex-end",gap:"0.5rem"},children:[e("button",{type:"button",onClick:()=>n(null),style:{padding:"6px 12px",borderRadius:"4px",border:"1px solid #cbd5e1",backgroundColor:"#ffffff",cursor:"pointer"},children:t.groupsView.cancel}),e("button",{type:"submit","data-testid":"submit-edit-group",style:{padding:"6px 12px",borderRadius:"4px",backgroundColor:"#4338ca",color:"#ffffff",border:"none",cursor:"pointer"},children:t.groupsView.save})]})]})})]})}function K(){let s=S(),a=U(),t=s.labels??x,[l,g]=b(s.groups[0]?.id??0),i=s.groups.find(n=>n.id===l),r=new Set(s.groupMembers.filter(n=>n.groupId===l).map(n=>n.userId)),o=(n,p)=>{p?a.submit({intent:"removeGroupMember",groupId:String(l),userId:String(n)},{method:"post"}):a.submit({intent:"addGroupMember",groupId:String(l),userId:String(n)},{method:"post"})};return d("section",{"data-testid":"group-members-view-section",style:{display:"grid",gridTemplateColumns:"240px 1fr",gap:"1.2rem"},children:[e(k,{fetcher:a}),d("div",{style:{backgroundColor:"#ffffff",padding:"1rem",borderRadius:"8px",border:"1px solid #e2e8f0"},children:[e("h4",{style:{margin:"0 0 0.8rem 0",fontSize:"0.9rem",color:"#475569"},children:t.groupMembersView.selectGroup}),e("div",{style:{display:"flex",flexDirection:"column",gap:"0.4rem"},children:s.groups.map(n=>{let p=n.id===l;return e("button",{type:"button",onClick:()=>g(n.id),"data-testid":`select-group-btn-${n.id}`,style:{textAlign:"left",padding:"8px 10px",borderRadius:"6px",border:p?"1px solid #6366f1":"1px solid #e2e8f0",backgroundColor:p?"#e0e7ff":"#ffffff",color:p?"#3730a3":"#0f172a",cursor:"pointer",fontWeight:p?600:400,fontSize:"0.85rem"},children:n.name},n.id)})})]}),e("div",{style:{backgroundColor:"#ffffff",padding:"1.2rem",borderRadius:"8px",border:"1px solid #e2e8f0"},children:i?d(q,{children:[d("div",{style:{marginBottom:"1rem",borderBottom:"1px solid #e2e8f0",paddingBottom:"0.8rem"},children:[d("h3",{style:{margin:0,fontSize:"1.1rem",color:"#0f172a"},children:[i.name," \u306E\u6240\u5C5E\u30E1\u30F3\u30D0\u30FC"]}),d("span",{style:{fontSize:"0.8rem",color:"#64748b"},children:["\u30B0\u30EB\u30FC\u30D7\u30AD\u30FC: ",i.groupKey]})]}),e("div",{style:{display:"flex",flexDirection:"column",gap:"0.5rem"},children:s.users.map(n=>{let p=r.has(n.id);return d("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",padding:"8px 12px",borderRadius:"6px",border:"1px solid #f1f5f9",backgroundColor:p?"#f0fdf4":"#ffffff"},"data-testid":`member-row-${n.id}`,children:[d("div",{children:[e("span",{style:{fontWeight:600,fontSize:"0.88rem",color:"#0f172a",marginRight:"8px"},children:n.displayName}),d("span",{style:{fontSize:"0.78rem",color:"#64748b"},children:["(",n.email,")"]})]}),e("button",{type:"button",onClick:()=>o(n.id,p),"data-testid":`toggle-member-btn-${n.id}`,style:{padding:"4px 12px",borderRadius:"4px",border:p?"1px solid #b91c1c":"1px solid #0284c7",backgroundColor:p?"#ffffff":"#0284c7",color:p?"#b91c1c":"#ffffff",fontSize:"0.78rem",fontWeight:600,cursor:"pointer"},children:p?t.groupMembersView.actionRemove:t.groupMembersView.actionAdd})]},n.id)})})]}):e("div",{style:{color:"#94a3b8",textAlign:"center",padding:"2rem"},children:t.groupMembersView.selectPrompt})})]})}function $(){let s=S(),a=U(),t=s.labels??x,[l,g]=b(""),[i,r]=b("email"),[o,n]=b(""),p=m=>{m.preventDefault(),l&&(a.submit({intent:"addAllowlistEntry",pattern:l,type:i,description:o},{method:"post"}),g(""),n(""))},f=m=>{a.submit({intent:"removeAllowlistEntry",entryId:String(m)},{method:"post"})};return d("section",{"data-testid":"allowlist-view-section",style:{display:"flex",flexDirection:"column",gap:"1.2rem"},children:[e(k,{fetcher:a}),d("form",{onSubmit:p,style:{backgroundColor:"#ffffff",padding:"1.2rem",borderRadius:"8px",border:"1px solid #e2e8f0",display:"flex",flexWrap:"wrap",gap:"0.8rem",alignItems:"flex-end"},children:[d("div",{style:{flex:2,minWidth:"220px"},children:[e("label",{htmlFor:"allowlist-pattern-input-field",style:{display:"block",fontSize:"0.78rem",color:"#475569",marginBottom:"4px"},children:t.allowlistView.pattern}),e("input",{id:"allowlist-pattern-input-field",type:"text",value:l,onChange:m=>g(m.target.value),required:!0,"data-testid":"allowlist-pattern-input",style:{width:"100%",padding:"6px 10px",borderRadius:"6px",border:"1px solid #cbd5e1",fontSize:"0.85rem",boxSizing:"border-box"}})]}),d("div",{style:{flex:1,minWidth:"140px"},children:[e("label",{htmlFor:"allowlist-type-select-field",style:{display:"block",fontSize:"0.78rem",color:"#475569",marginBottom:"4px"},children:t.allowlistView.type}),d("select",{id:"allowlist-type-select-field",value:i,onChange:m=>r(m.target.value),"data-testid":"allowlist-type-select",style:{width:"100%",padding:"6px 10px",borderRadius:"6px",border:"1px solid #cbd5e1",fontSize:"0.85rem",boxSizing:"border-box"},children:[e("option",{value:"email",children:t.allowlistView.emailType}),e("option",{value:"domain",children:t.allowlistView.domainType})]})]}),d("div",{style:{flex:2,minWidth:"200px"},children:[e("label",{htmlFor:"allowlist-desc-input-field",style:{display:"block",fontSize:"0.78rem",color:"#475569",marginBottom:"4px"},children:t.allowlistView.description}),e("input",{id:"allowlist-desc-input-field",type:"text",value:o,onChange:m=>n(m.target.value),"data-testid":"allowlist-desc-input",style:{width:"100%",padding:"6px 10px",borderRadius:"6px",border:"1px solid #cbd5e1",fontSize:"0.85rem",boxSizing:"border-box"}})]}),e("button",{type:"submit","data-testid":"submit-allowlist-add",style:{padding:"7px 16px",borderRadius:"6px",backgroundColor:"#047857",color:"#ffffff",border:"none",fontSize:"0.85rem",fontWeight:600,cursor:"pointer"},children:t.allowlistView.addEntry})]}),e("div",{style:{backgroundColor:"#ffffff",borderRadius:"8px",border:"1px solid #e2e8f0",overflow:"hidden"},children:d("table",{style:{width:"100%",borderCollapse:"collapse",fontSize:"0.85rem",textAlign:"left"},children:[e("thead",{children:d("tr",{style:{backgroundColor:"#f8fafc",borderBottom:"1px solid #e2e8f0",color:"#475569"},children:[e("th",{style:{padding:"10px 14px"},children:"ID"}),e("th",{style:{padding:"10px 14px"},children:t.allowlistView.pattern}),e("th",{style:{padding:"10px 14px"},children:t.allowlistView.type}),e("th",{style:{padding:"10px 14px"},children:t.allowlistView.description}),e("th",{style:{padding:"10px 14px",textAlign:"right"},children:t.allowlistView.actions})]})}),d("tbody",{children:[s.allowlist.map(m=>d("tr",{style:{borderBottom:"1px solid #f1f5f9"},"data-testid":`allowlist-row-${m.id}`,children:[d("td",{style:{padding:"10px 14px",color:"#64748b"},children:["#",m.id]}),e("td",{style:{padding:"10px 14px",fontWeight:600,color:"#0f172a",fontFamily:"monospace"},children:m.pattern}),e("td",{style:{padding:"10px 14px"},children:e("span",{style:{fontSize:"0.75rem",padding:"2px 8px",borderRadius:"10px",backgroundColor:m.type==="email"?"#e0f2fe":"#fef3c7",color:m.type==="email"?"#0369a1":"#b45309",fontWeight:600},children:m.type==="email"?t.allowlistView.emailTypeShort:t.allowlistView.domainTypeShort})}),e("td",{style:{padding:"10px 14px",color:"#475569"},children:m.description||"-"}),e("td",{style:{padding:"10px 14px",textAlign:"right"},children:m.readOnly?e("span",{"data-testid":`readonly-allowlist-${m.id}`,style:{fontSize:"0.75rem",color:"#94a3b8"},children:t.allowlistView.readOnly}):e("button",{type:"button",onClick:()=>f(m.id),"data-testid":`remove-allowlist-btn-${m.id}`,style:{padding:"3px 8px",borderRadius:"4px",border:"1px solid #fee2e2",backgroundColor:"#fff5f5",color:"#b91c1c",fontSize:"0.78rem",cursor:"pointer"},children:"\u524A\u9664"})})]},m.id)),s.allowlist.length===0&&e("tr",{children:e("td",{colSpan:5,style:{padding:"2rem",textAlign:"center",color:"#94a3b8"},children:t.allowlistView.empty})})]})]})})]})}var W={users:E,groups:F,group_members:K,allowlist:$};function se({renderHeader:s}={}){let t=S()?.segment??"users",l=W[t]??E;return e(M,{renderHeader:s,children:e(l,{})})}export{$ as AllowlistView,se as AuthUserAdminAppView,K as GroupMembersView,F as GroupsView,M as UserAdminShell,E as UsersView,Q as createInMemoryUserAdminStore,H as createUserAdminApp,x as defaultUserAdminLabels,j as jaUserAdminLabels,I as resolveUserAdminLabels};
|
|
2
|
+
//# sourceMappingURL=admin.mjs.map
|