web-mojo 2.2.68 → 2.2.69

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.
Files changed (96) hide show
  1. package/CHANGELOG.md +25 -9
  2. package/dist/admin.cjs.js +1 -1
  3. package/dist/admin.cjs.js.map +1 -1
  4. package/dist/admin.es.js +1 -1
  5. package/dist/admin.es.js.map +1 -1
  6. package/dist/auth.cjs.js +1 -1
  7. package/dist/auth.es.js +1 -1
  8. package/dist/charts.cjs.js +1 -1
  9. package/dist/charts.es.js +1 -1
  10. package/dist/chunks/ChatView-CZ3Key2k.js +2 -0
  11. package/dist/chunks/ChatView-CZ3Key2k.js.map +1 -0
  12. package/dist/chunks/ChatView-Dw-iVmht.js +2 -0
  13. package/dist/chunks/ChatView-Dw-iVmht.js.map +1 -0
  14. package/dist/chunks/{Dialog-DW7PHzUc.js → Dialog-Dhqtd9Yz.js} +2 -2
  15. package/dist/chunks/{Dialog-DW7PHzUc.js.map → Dialog-Dhqtd9Yz.js.map} +1 -1
  16. package/dist/chunks/{Dialog-jfBsXy5X.js → Dialog-t_9l2Mou.js} +2 -2
  17. package/dist/chunks/{Dialog-jfBsXy5X.js.map → Dialog-t_9l2Mou.js.map} +1 -1
  18. package/dist/chunks/Files-6eRT5k3r.js +2 -0
  19. package/dist/chunks/{Files-C-ChBvr5.js.map → Files-6eRT5k3r.js.map} +1 -1
  20. package/dist/chunks/Files-Dh_5PFBn.js +2 -0
  21. package/dist/chunks/{Files-DNbHDy43.js.map → Files-Dh_5PFBn.js.map} +1 -1
  22. package/dist/chunks/{FormView-EoB_ZdIB.js → FormView-B1CXO2t8.js} +2 -2
  23. package/dist/chunks/{FormView-EoB_ZdIB.js.map → FormView-B1CXO2t8.js.map} +1 -1
  24. package/dist/chunks/{FormView-Q_lFA0nr.js → FormView-BRHAIawp.js} +2 -2
  25. package/dist/chunks/{FormView-Q_lFA0nr.js.map → FormView-BRHAIawp.js.map} +1 -1
  26. package/dist/chunks/{MetricsMiniChartWidget-lzq4lSTF.js → MetricsMiniChartWidget-D1w608Jy.js} +2 -2
  27. package/dist/chunks/{MetricsMiniChartWidget-lzq4lSTF.js.map → MetricsMiniChartWidget-D1w608Jy.js.map} +1 -1
  28. package/dist/chunks/{MetricsMiniChartWidget-ukn-NRMR.js → MetricsMiniChartWidget-Dg1e6EQJ.js} +2 -2
  29. package/dist/chunks/{MetricsMiniChartWidget-ukn-NRMR.js.map → MetricsMiniChartWidget-Dg1e6EQJ.js.map} +1 -1
  30. package/dist/chunks/{PDFViewer-iOqYpg-6.js → PDFViewer-CDeV9OBs.js} +2 -2
  31. package/dist/chunks/{PDFViewer-iOqYpg-6.js.map → PDFViewer-CDeV9OBs.js.map} +1 -1
  32. package/dist/chunks/{PDFViewer-sFoyopz3.js → PDFViewer-D_3V8QJe.js} +2 -2
  33. package/dist/chunks/{PDFViewer-sFoyopz3.js.map → PDFViewer-D_3V8QJe.js.map} +1 -1
  34. package/dist/chunks/TableView-CI_7a-kD.js +2 -0
  35. package/dist/chunks/TableView-CI_7a-kD.js.map +1 -0
  36. package/dist/chunks/TableView-CWk5k4LQ.js +2 -0
  37. package/dist/chunks/TableView-CWk5k4LQ.js.map +1 -0
  38. package/dist/chunks/ToastService-C2tTooFn.js +3 -0
  39. package/dist/chunks/ToastService-C2tTooFn.js.map +1 -0
  40. package/dist/chunks/ToastService-nUaGVpSl.js +3 -0
  41. package/dist/chunks/ToastService-nUaGVpSl.js.map +1 -0
  42. package/dist/chunks/{TokenManager-ChNOca0K.js → TokenManager-ien2XzwO.js} +2 -2
  43. package/dist/chunks/{TokenManager-ChNOca0K.js.map → TokenManager-ien2XzwO.js.map} +1 -1
  44. package/dist/chunks/{TokenManager-DKzxBt6g.js → TokenManager-sZgt--C9.js} +2 -2
  45. package/dist/chunks/{TokenManager-DKzxBt6g.js.map → TokenManager-sZgt--C9.js.map} +1 -1
  46. package/dist/chunks/User-BL9M_PWB.js +2 -0
  47. package/dist/chunks/User-BL9M_PWB.js.map +1 -0
  48. package/dist/chunks/{User-BnlvMG5J.js → User-DqHG5Gr1.js} +2 -3
  49. package/dist/chunks/User-DqHG5Gr1.js.map +1 -0
  50. package/dist/chunks/UserProfileView-DnVMHcLH.js +2 -0
  51. package/dist/chunks/UserProfileView-DnVMHcLH.js.map +1 -0
  52. package/dist/chunks/UserProfileView-kupeq2rN.js +2 -0
  53. package/dist/chunks/UserProfileView-kupeq2rN.js.map +1 -0
  54. package/dist/chunks/{WebApp-Bsic6FPo.js → WebApp-Bti0Gqqo.js} +2 -2
  55. package/dist/chunks/{WebApp-Bsic6FPo.js.map → WebApp-Bti0Gqqo.js.map} +1 -1
  56. package/dist/chunks/{WebApp-B0m6JCjO.js → WebApp-CcVF73yg.js} +2 -2
  57. package/dist/chunks/{WebApp-B0m6JCjO.js.map → WebApp-CcVF73yg.js.map} +1 -1
  58. package/dist/chunks/index-Aq9ke4vg.js +2 -0
  59. package/dist/chunks/index-Aq9ke4vg.js.map +1 -0
  60. package/dist/chunks/index-Da9sT-tE.js +2 -0
  61. package/dist/chunks/index-Da9sT-tE.js.map +1 -0
  62. package/dist/chunks/{version-BmVUtM_7.js → version-D8JjsPW0.js} +2 -2
  63. package/dist/chunks/{version-BmVUtM_7.js.map → version-D8JjsPW0.js.map} +1 -1
  64. package/dist/chunks/{version-i7K_82Qy.js → version-XmirKYWA.js} +2 -2
  65. package/dist/chunks/{version-i7K_82Qy.js.map → version-XmirKYWA.js.map} +1 -1
  66. package/dist/css/web-mojo.css +1 -1
  67. package/dist/docit.cjs.js +1 -1
  68. package/dist/docit.cjs.js.map +1 -1
  69. package/dist/docit.es.js +1 -1
  70. package/dist/docit.es.js.map +1 -1
  71. package/dist/index.cjs.js +1 -1
  72. package/dist/index.cjs.js.map +1 -1
  73. package/dist/index.es.js +1 -1
  74. package/dist/index.es.js.map +1 -1
  75. package/dist/lightbox.cjs.js +1 -1
  76. package/dist/lightbox.es.js +1 -1
  77. package/dist/map.cjs.js +1 -1
  78. package/dist/map.es.js +1 -1
  79. package/dist/user-profile.cjs.js +2 -0
  80. package/dist/user-profile.cjs.js.map +1 -0
  81. package/dist/user-profile.es.js +2 -0
  82. package/dist/user-profile.es.js.map +1 -0
  83. package/dist/web-mojo.lite.iife.js +9 -6
  84. package/dist/web-mojo.lite.iife.js.map +1 -1
  85. package/dist/web-mojo.lite.iife.min.js +11 -11
  86. package/dist/web-mojo.lite.iife.min.js.map +1 -1
  87. package/package.json +5 -1
  88. package/dist/chunks/ChatView-Cfe0ZGvr.js +0 -2
  89. package/dist/chunks/ChatView-Cfe0ZGvr.js.map +0 -1
  90. package/dist/chunks/ChatView-DuQVFrCY.js +0 -2
  91. package/dist/chunks/ChatView-DuQVFrCY.js.map +0 -1
  92. package/dist/chunks/Files-C-ChBvr5.js +0 -2
  93. package/dist/chunks/Files-DNbHDy43.js +0 -2
  94. package/dist/chunks/User-BnlvMG5J.js.map +0 -1
  95. package/dist/chunks/User-DSqcOwPL.js +0 -3
  96. package/dist/chunks/User-DSqcOwPL.js.map +0 -1
@@ -1,3 +1,2 @@
1
- "use strict";const e=require("./Collection-CmjTsmrP.js");class Group extends e.Model{constructor(e={}){super(e,{endpoint:"/api/group"})}}class GroupList extends e.Collection{constructor(e={}){super({ModelClass:Group,endpoint:"/api/group",size:10,...e})}}const t={org:"Organization",division:"Division",department:"Department",team:"Team",merchant:"Merchant",partner:"Partner",client:"Client",iso:"ISO",sales:"Sales",reseller:"Reseller",location:"Location",region:"Region",route:"Route",project:"Project",inventory:"Inventory",test:"Testing",misc:"Miscellaneous",qa:"Quality Assurance"},a=Object.entries(t).map(([e,t])=>({value:e,label:t})),s={create:{title:"Create Group",fields:[{name:"name",type:"text",label:"Group Name",required:!0,placeholder:"Enter group name"},{name:"kind",type:"select",label:"Group Kind",required:!0,options:a},{type:"collection",name:"parent",label:"Parent Group",Collection:GroupList,labelField:"name",valueField:"id",maxItems:10,placeholder:"Search groups...",emptyFetch:!1,debounceMs:300}]},edit:{title:"Edit Group",fields:[{name:"name",type:"text",label:"Group Name",required:!0,placeholder:"Enter group name"},{name:"kind",type:"select",label:"Group Kind",required:!0,options:a},{type:"collection",name:"parent",label:"Parent Group",Collection:GroupList,labelField:"name",valueField:"id",maxItems:10,placeholder:"Search groups...",emptyFetch:!1,debounceMs:300},{name:"metadata.domain",type:"text",label:"Default Domain",placeholder:"Enter Domain"},{name:"metadata.portal",type:"text",label:"Default Portal",placeholder:"Enter Portal URL"},{name:"is_active",type:"switch",label:"Is Active",cols:4}]},detailed:{title:"Group Details",fields:[{type:"header",text:"Profile Information",level:4,class:"text-primary mb-3"},{type:"group",columns:{xs:12,md:4},fields:[{type:"image",name:"avatar",size:"lg",imageSize:{width:200,height:200},placeholder:"Upload your avatar",help:"Square images work best",columns:12},{name:"is_active",type:"switch",label:"Is Active",columns:12}]},{type:"group",columns:{xs:12,md:8},title:"Details",fields:[{name:"name",type:"text",label:"Group Name",required:!0,placeholder:"Enter group name",columns:12},{name:"kind",type:"select",label:"Group Kind",required:!0,columns:12,options:[{value:"org",label:"Organization"},{value:"team",label:"Team"},{value:"department",label:"Department"},{value:"merchant",label:"Merchant"},{value:"iso",label:"ISO"},{value:"group",label:"Group"}]},{type:"collection",name:"parent",label:"Parent Group",Collection:GroupList,labelField:"name",valueField:"id",maxItems:10,placeholder:"Search groups...",emptyFetch:!1,debounceMs:300,columns:12}]},{type:"group",columns:12,title:"Account Settings",class:"pt-3",fields:[{type:"select",name:"metadata.timezone",label:"Timezone",columns:6,value:"America/Los_Angeles",options:[{value:"America/New_York",text:"Eastern Time"},{value:"America/Chicago",text:"Central Time"},{value:"America/Denver",text:"Mountain Time"},{value:"America/Los_Angeles",text:"Pacific Time"},{value:"UTC",text:"UTC"}]},{type:"select",name:"metadata.eod_hour",label:"End of Day Hour",columns:6,options:[{value:0,text:"Midnight"},{value:1,text:"1 AM"},{value:2,text:"2 AM"},{value:3,text:"3 AM"},{value:4,text:"4 AM"},{value:5,text:"5 AM"},{value:6,text:"6 AM"},{value:7,text:"7 AM"},{value:8,text:"8 AM"},{value:9,text:"9 AM"},{value:10,text:"10 AM"},{value:11,text:"11 AM"},{value:12,text:"12 PM"},{value:13,text:"1 PM"},{value:14,text:"2 PM"},{value:15,text:"3 PM"},{value:16,text:"4 PM"},{value:17,text:"5 PM"},{value:18,text:"6 PM"},{value:19,text:"7 PM"},{value:20,text:"8 PM"},{value:21,text:"9 PM"},{value:22,text:"10 PM"},{value:23,text:"11 PM"}]}]},{type:"text",label:"Email Template (Prefix)",name:"metadata.email_template",columns:12}]}};Group.EDIT_FORM=s.edit,Group.ADD_FORM=s.create,Group.CREATE_FORM=s.create,Group.GroupKindOptions=a,Group.GroupKinds=t;const i=/* @__PURE__ */Object.freeze(/* @__PURE__ */Object.defineProperty({__proto__:null,Group:Group,GroupForms:s,GroupList:GroupList},Symbol.toStringTag,{value:"Module"}));class User extends e.Model{constructor(e={}){super(e,{endpoint:"/api/user"})}hasPermission(e){if(this.get("is_superuser"))return!0;if(Array.isArray(e))return e.some(e=>this.hasPermission(e));const t=e.startsWith("sys."),a=t?e.substring(4):e;return!!this._hasPermission(a)||!(t||!this.member||!this.member.hasPermission(e))}_hasPermission(e){const t=this.get("permissions");return!!t&&1==t[e]}hasPerm(e){return this.hasPermission(e)}}class UserList extends e.Collection{constructor(e={}){super({ModelClass:User,endpoint:"/api/user",...e})}}User.PERMISSIONS=[{name:"manage_users",label:"Manage Users"},{name:"view_users",label:"View Users"},{name:"view_groups",label:"View Groups"},{name:"manage_groups",label:"Manage Groups"},{name:"view_metrics",label:"View System Metrics"},{name:"manage_metrics",label:"Manage System Metrics"},{name:"view_logs",label:"View Logs"},{name:"view_incidents",label:"View Incidents"},{name:"manage_incidents",label:"Manage Incidents"},{name:"view_tickets",label:"View Tickets"},{name:"manage_tickets",label:"Manage Tickets"},{name:"view_admin",label:"View Admin"},{name:"view_jobs",label:"View Jobs"},{name:"manage_jobs",label:"Manage Jobs"},{name:"view_global",label:"View Global"},{name:"manage_notifications",label:"Manage Notifications"},{name:"manage_files",label:"Manage Files"},{name:"force_single_session",label:"Force Single Session"},{name:"file_vault",label:"Access File Vault"},{name:"manage_aws",label:"Manage AWS"},{name:"manage_docit",label:"Manage DocIt"}],User.PERMISSION_FIELDS=[...User.PERMISSIONS.map(e=>({name:`permissions.${e.name}`,type:"switch",label:e.label,columns:4}))];const o={create:{title:"Create User",fields:[{name:"email",type:"text",label:"Email",required:!0},{name:"phone_number",type:"text",label:"Phone number",columns:12},{name:"display_name",type:"text",label:"Display Name"}]},edit:{title:"Edit User",fields:[{name:"email",type:"email",label:"Email",columns:12},{name:"display_name",type:"text",label:"Display Name",columns:12},{name:"phone_number",type:"text",label:"Phone number",columns:12},{type:"collection",name:"org",label:"Organization",Collection:GroupList,labelField:"name",valueField:"id",columns:12}]},permissions:{title:"Edit Permissions",fields:User.PERMISSION_FIELDS}},n={profile:{title:"User Profile",columns:2,fields:[{name:"id",label:"User ID",type:"number",columns:4},{name:"last_login",label:"Last Login",type:"datetime",format:"relative",columns:4},{name:"last_activity",label:"Last Activity",type:"datetime",format:"relative",columns:4},{name:"username",label:"Username",type:"text",format:"lowercase",columns:4},{name:"display_name",label:"Display Name",type:"text",columns:4},{name:"email",label:"Email",type:"email",columns:12},{name:"org.name",label:"Organization",type:"text",columns:6},{name:"phone_number",label:"Phone Number",type:"text",columns:6}]},activity:{title:"User Activity",columns:2,fields:[{name:"last_login",label:"Last Login",type:"datetime",format:"relative",colSize:6},{name:"last_activity",label:"Last Activity",type:"datetime",format:"relative",colSize:6}]},detailed:{title:"Detailed User Information",columns:2,showEmptyValues:!0,emptyValueText:"Not set",fields:[{name:"id",label:"User ID",type:"number",colSize:3},{name:"display_name",label:"Display Name",type:"text",format:'capitalize|default("Unnamed User")',colSize:9},{name:"username",label:"Username",type:"text",format:"lowercase",colSize:6},{name:"email",label:"Email Address",type:"email",colSize:6},{name:"phone_number",label:"Phone Number",type:"phone",format:'phone|default("Not provided")',colSize:6},{name:"is_active",label:"Account Status",type:"boolean",colSize:6},{name:"last_login",label:"Last Login",type:"datetime",format:"relative",colSize:6},{name:"last_activity",label:"Last Activity",type:"datetime",format:"relative",colSize:6},{name:"avatar.url",label:"Avatar",type:"url",colSize:12},{name:"permissions",label:"User Permissions",type:"dataview",dataViewColumns:2,showEmptyValues:!1},{name:"metadata",label:"User Metadata",type:"dataview",dataViewColumns:1},{name:"avatar",label:"Avatar Details",type:"dataview",dataViewColumns:1}]},permissions:{title:"User Permissions",columns:1,fields:[{name:"display_name",label:"User",type:"text",format:"capitalize",columns:12},{name:"permissions",label:"Assigned Permissions",type:"dataview",dataViewColumns:3,showEmptyValues:!1,colSize:12}]},summary:{title:"User Summary",columns:3,fields:[{name:"display_name",label:"Name",type:"text",format:"capitalize|truncate(30)"},{name:"email",label:"Email",type:"email"},{name:"is_active",label:"Status",type:"boolean"},{name:"last_activity",label:"Last Seen",type:"datetime",format:"relative",colSize:12}]}};User.DATA_VIEW=n.detailed,User.EDIT_FORM=o.edit,User.ADD_FORM=o.create;class UserDevice extends e.Model{constructor(e={}){super(e,{endpoint:"/api/user/device"})}static async getByDuid(e){const t=new UserDevice,a=await t.rest.GET("/api/user/device/lookup",{duid:e});return a.success&&a.data&&a.data.data?new UserDevice(a.data.data):null}}class UserDeviceList extends e.Collection{constructor(e={}){super({ModelClass:UserDevice,endpoint:"/api/user/device",...e})}}class UserDeviceLocation extends e.Model{constructor(e={}){super(e,{endpoint:"/api/user/device/location"})}}class UserDeviceLocationList extends e.Collection{constructor(e={}){super({ModelClass:UserDeviceLocation,endpoint:"/api/user/device/location",...e})}}exports.Group=Group,exports.Group$1=i,exports.GroupForms=s,exports.GroupList=GroupList,exports.ToastService=class{constructor(e={}){this.options={containerId:"toast-container",position:"top-end",autohide:!0,defaultDelay:5e3,maxToasts:5,...e},this.toasts=/* @__PURE__ */new Map,this.toastCounter=0,this.init()}init(){this.createContainer()}createContainer(){let e=document.getElementById(this.options.containerId);e||(e=document.createElement("div"),e.id=this.options.containerId,e.className=`toast-container position-fixed ${this.getPositionClasses()}`,e.style.zIndex="1070",e.setAttribute("aria-live","polite"),e.setAttribute("aria-atomic","true"),document.body.appendChild(e)),this.container=e}getPositionClasses(){const e={"top-start":"top-0 start-0 p-3","top-center":"top-0 start-50 translate-middle-x p-3","top-end":"top-0 end-0 p-3","middle-start":"top-50 start-0 translate-middle-y p-3","middle-center":"top-50 start-50 translate-middle p-3","middle-end":"top-50 end-0 translate-middle-y p-3","bottom-start":"bottom-0 start-0 p-3","bottom-center":"bottom-0 start-50 translate-middle-x p-3","bottom-end":"bottom-0 end-0 p-3"};return e[this.options.position]||e["top-end"]}success(e,t={}){return this.show(e,"success",{icon:"bi-check-circle-fill",...t})}error(e,t={}){return this.show(e,"error",{icon:"bi-exclamation-triangle-fill",autohide:!0,...t})}info(e,t={}){return this.show(e,"info",{icon:"bi-info-circle-fill",...t})}warning(e,t={}){return this.show(e,"warning",{icon:"bi-exclamation-triangle-fill",...t})}plain(e,t={}){return this.show(e,"plain",{...t})}show(e,t="info",a={}){this.enforceMaxToasts();const s="toast-"+ ++this.toastCounter,i={title:this.getDefaultTitle(t),icon:this.getDefaultIcon(t),autohide:this.options.autohide,delay:this.options.defaultDelay,dismissible:!0,...a},o=this.createToastElement(s,e,t,i);if(this.container.appendChild(o),"undefined"==typeof bootstrap)throw new Error("Bootstrap is required for ToastService. Make sure Bootstrap 5 is loaded.");const n=new bootstrap.Toast(o,{autohide:i.autohide,delay:i.delay});return this.toasts.set(s,{element:o,bootstrap:n,type:t,message:e}),o.addEventListener("hidden.bs.toast",()=>{this.cleanup(s)}),n.show(),{id:s,hide:()=>{try{n.hide()}catch(e){console.warn("Error hiding toast:",e)}},dispose:()=>this.cleanup(s),updateProgress:a.updateProgress||null}}showView(e,t="info",a={}){this.enforceMaxToasts();const s="toast-"+ ++this.toastCounter,i={title:a.title||this.getDefaultTitle(t),icon:a.icon||this.getDefaultIcon(t),autohide:this.options.autohide,delay:this.options.defaultDelay,dismissible:!0,...a},o=this.createViewToastElement(s,e,t,i);if(this.container.appendChild(o),"undefined"==typeof bootstrap)throw new Error("Bootstrap is required for ToastService. Make sure Bootstrap 5 is loaded.");const n=new bootstrap.Toast(o,{autohide:i.autohide,delay:i.delay});this.toasts.set(s,{element:o,bootstrap:n,type:t,view:e,message:"View toast"}),o.addEventListener("hidden.bs.toast",()=>{this.cleanupView(s)});const l=o.querySelector(".toast-view-body");return l&&e&&e.render(!0,l),n.show(),{id:s,view:e,hide:()=>{try{n.hide()}catch(e){console.warn("Error hiding view toast:",e)}},dispose:()=>this.cleanupView(s),updateProgress:t=>{e&&"function"==typeof e.updateProgress&&e.updateProgress(t)}}}createToastElement(e,t,a,s){const i=document.createElement("div");i.id=e,i.className=`toast toast-service-${a}`,i.setAttribute("role","alert"),i.setAttribute("aria-live","assertive"),i.setAttribute("aria-atomic","true");const o=s.title||s.icon?this.createToastHeader(s,a):"",n=this.createToastBody(t,s.icon&&!s.title);return i.innerHTML=`\n ${o}\n ${n}\n `,i}createViewToastElement(e,t,a,s){const i=document.createElement("div");i.id=e,i.className=`toast toast-service-${a}`,i.setAttribute("role","alert"),i.setAttribute("aria-live","assertive"),i.setAttribute("aria-atomic","true");const o=s.title||s.icon?this.createToastHeader(s,a):"",n=this.createViewToastBody();return i.innerHTML=`\n ${o}\n ${n}\n `,i}createViewToastBody(){return'\n <div class="toast-body p-0">\n <div class="toast-view-body p-3"></div>\n </div>\n '}createToastHeader(e,t){const a=e.icon?`<i class="${e.icon} toast-service-icon me-2"></i>`:"",s=e.title?`<strong class="me-auto">${a}${this.escapeHtml(e.title)}</strong>`:"",i=e.showTime?`<small class="text-muted">${this.getTimeString()}</small>`:"",o=e.dismissible?'<button type="button" class="btn-close toast-service-close" data-bs-dismiss="toast" aria-label="Close"></button>':"";return s||i||o?`\n <div class="toast-header">\n ${s}\n ${i}\n ${o}\n </div>\n `:""}createToastBody(e,t=!1){return`\n <div class="toast-body d-flex align-items-center">\n ${t?`<i class="${this.getDefaultIcon("info")} toast-service-icon me-2"></i>`:""}\n <span>${this.escapeHtml(e)}</span>\n </div>\n `}getDefaultTitle(e){return{success:"Success",error:"Error",warning:"Warning",info:"Information",plain:""}[e]||"Notification"}getDefaultIcon(e){return{success:"bi-check-circle-fill",error:"bi-exclamation-triangle-fill",warning:"bi-exclamation-triangle-fill",info:"bi-info-circle-fill",plain:""}[e]||"bi-info-circle-fill"}enforceMaxToasts(){if(Array.from(this.toasts.values()).length>=this.options.maxToasts){const e=this.toasts.keys().next().value,t=this.toasts.get(e);t&&t.bootstrap.hide()}}cleanup(e){const t=this.toasts.get(e);if(t){try{t.bootstrap.dispose()}catch(a){console.warn("Error disposing toast:",a)}t.element&&t.element.parentNode&&t.element.parentNode.removeChild(t.element),this.toasts.delete(e)}}cleanupView(e){const t=this.toasts.get(e);if(t){if(t.view&&"function"==typeof t.view.dispose)try{t.view.dispose()}catch(a){console.warn("Error disposing view in toast:",a)}try{t.bootstrap.dispose()}catch(a){console.warn("Error disposing toast:",a)}t.element&&t.element.parentNode&&t.element.parentNode.removeChild(t.element),this.toasts.delete(e)}}hideAll(){this.toasts.forEach((e,t)=>{e.bootstrap.hide()})}clearAll(){this.toasts.forEach((e,t)=>{this.cleanup(t)})}getTimeString(){/* @__PURE__ */
2
- return(new Date).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})}escapeHtml(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}dispose(){this.clearAll(),this.container&&this.container.parentNode&&this.container.parentNode.removeChild(this.container)}getStats(){const e={total:this.toasts.size,byType:{}};return this.toasts.forEach(t=>{e.byType[t.type]=(e.byType[t.type]||0)+1}),e}setOptions(e){this.options={...this.options,...e},e.position&&this.container&&(this.container.className=`toast-container position-fixed ${this.getPositionClasses()}`)}},exports.User=User,exports.UserDataView=n,exports.UserDevice=UserDevice,exports.UserDeviceList=UserDeviceList,exports.UserDeviceLocation=UserDeviceLocation,exports.UserDeviceLocationList=UserDeviceLocationList,exports.UserForms=o,exports.UserList=UserList;
3
- //# sourceMappingURL=User-BnlvMG5J.js.map
1
+ "use strict";const e=require("./Collection-CmjTsmrP.js");class Group extends e.Model{constructor(e={}){super(e,{endpoint:"/api/group"})}}class GroupList extends e.Collection{constructor(e={}){super({ModelClass:Group,endpoint:"/api/group",size:10,...e})}}const a={org:"Organization",division:"Division",department:"Department",team:"Team",merchant:"Merchant",partner:"Partner",client:"Client",iso:"ISO",sales:"Sales",reseller:"Reseller",location:"Location",region:"Region",route:"Route",project:"Project",inventory:"Inventory",test:"Testing",misc:"Miscellaneous",qa:"Quality Assurance"},t=Object.entries(a).map(([e,a])=>({value:e,label:a})),l={create:{title:"Create Group",fields:[{name:"name",type:"text",label:"Group Name",required:!0,placeholder:"Enter group name"},{name:"kind",type:"select",label:"Group Kind",required:!0,options:t},{type:"collection",name:"parent",label:"Parent Group",Collection:GroupList,labelField:"name",valueField:"id",maxItems:10,placeholder:"Search groups...",emptyFetch:!1,debounceMs:300}]},edit:{title:"Edit Group",fields:[{name:"name",type:"text",label:"Group Name",required:!0,placeholder:"Enter group name"},{name:"kind",type:"select",label:"Group Kind",required:!0,options:t},{type:"collection",name:"parent",label:"Parent Group",Collection:GroupList,labelField:"name",valueField:"id",maxItems:10,placeholder:"Search groups...",emptyFetch:!1,debounceMs:300},{name:"metadata.domain",type:"text",label:"Default Domain",placeholder:"Enter Domain"},{name:"metadata.portal",type:"text",label:"Default Portal",placeholder:"Enter Portal URL"},{name:"is_active",type:"switch",label:"Is Active",cols:4}]},detailed:{title:"Group Details",fields:[{type:"header",text:"Profile Information",level:4,class:"text-primary mb-3"},{type:"group",columns:{xs:12,md:4},fields:[{type:"image",name:"avatar",size:"lg",imageSize:{width:200,height:200},placeholder:"Upload your avatar",help:"Square images work best",columns:12},{name:"is_active",type:"switch",label:"Is Active",columns:12}]},{type:"group",columns:{xs:12,md:8},title:"Details",fields:[{name:"name",type:"text",label:"Group Name",required:!0,placeholder:"Enter group name",columns:12},{name:"kind",type:"select",label:"Group Kind",required:!0,columns:12,options:[{value:"org",label:"Organization"},{value:"team",label:"Team"},{value:"department",label:"Department"},{value:"merchant",label:"Merchant"},{value:"iso",label:"ISO"},{value:"group",label:"Group"}]},{type:"collection",name:"parent",label:"Parent Group",Collection:GroupList,labelField:"name",valueField:"id",maxItems:10,placeholder:"Search groups...",emptyFetch:!1,debounceMs:300,columns:12}]},{type:"group",columns:12,title:"Account Settings",class:"pt-3",fields:[{type:"select",name:"metadata.timezone",label:"Timezone",columns:6,value:"America/Los_Angeles",options:[{value:"America/New_York",text:"Eastern Time"},{value:"America/Chicago",text:"Central Time"},{value:"America/Denver",text:"Mountain Time"},{value:"America/Los_Angeles",text:"Pacific Time"},{value:"UTC",text:"UTC"}]},{type:"select",name:"metadata.eod_hour",label:"End of Day Hour",columns:6,options:[{value:0,text:"Midnight"},{value:1,text:"1 AM"},{value:2,text:"2 AM"},{value:3,text:"3 AM"},{value:4,text:"4 AM"},{value:5,text:"5 AM"},{value:6,text:"6 AM"},{value:7,text:"7 AM"},{value:8,text:"8 AM"},{value:9,text:"9 AM"},{value:10,text:"10 AM"},{value:11,text:"11 AM"},{value:12,text:"12 PM"},{value:13,text:"1 PM"},{value:14,text:"2 PM"},{value:15,text:"3 PM"},{value:16,text:"4 PM"},{value:17,text:"5 PM"},{value:18,text:"6 PM"},{value:19,text:"7 PM"},{value:20,text:"8 PM"},{value:21,text:"9 PM"},{value:22,text:"10 PM"},{value:23,text:"11 PM"}]}]},{type:"text",label:"Email Template (Prefix)",name:"metadata.email_template",columns:12}]}};Group.EDIT_FORM=l.edit,Group.ADD_FORM=l.create,Group.CREATE_FORM=l.create,Group.GroupKindOptions=t,Group.GroupKinds=a;const i=/* @__PURE__ */Object.freeze(/* @__PURE__ */Object.defineProperty({__proto__:null,Group:Group,GroupForms:l,GroupList:GroupList},Symbol.toStringTag,{value:"Module"}));class User extends e.Model{constructor(e={}){super(e,{endpoint:"/api/user"})}hasPermission(e){if(this.get("is_superuser"))return!0;if(Array.isArray(e))return e.some(e=>this.hasPermission(e));const a=e.startsWith("sys."),t=a?e.substring(4):e;return!!this._hasPermission(t)||!(a||!this.member||!this.member.hasPermission(e))}_hasPermission(e){const a=this.get("permissions");return!!a&&1==a[e]}hasPerm(e){return this.hasPermission(e)}}class UserList extends e.Collection{constructor(e={}){super({ModelClass:User,endpoint:"/api/user",...e})}}User.PERMISSIONS=[{name:"manage_users",label:"Manage Users"},{name:"view_users",label:"View Users"},{name:"view_groups",label:"View Groups"},{name:"manage_groups",label:"Manage Groups"},{name:"view_metrics",label:"View System Metrics"},{name:"manage_metrics",label:"Manage System Metrics"},{name:"view_logs",label:"View Logs"},{name:"view_incidents",label:"View Incidents"},{name:"manage_incidents",label:"Manage Incidents"},{name:"view_tickets",label:"View Tickets"},{name:"manage_tickets",label:"Manage Tickets"},{name:"view_admin",label:"View Admin"},{name:"view_jobs",label:"View Jobs"},{name:"manage_jobs",label:"Manage Jobs"},{name:"view_global",label:"View Global"},{name:"manage_notifications",label:"Manage Notifications"},{name:"manage_files",label:"Manage Files"},{name:"force_single_session",label:"Force Single Session"},{name:"file_vault",label:"Access File Vault"},{name:"manage_aws",label:"Manage AWS"},{name:"manage_docit",label:"Manage DocIt"}],User.PERMISSION_FIELDS=[...User.PERMISSIONS.map(e=>({name:`permissions.${e.name}`,type:"switch",label:e.label,columns:4}))];const s={create:{title:"Create User",fields:[{name:"email",type:"text",label:"Email",required:!0},{name:"phone_number",type:"text",label:"Phone number",columns:12},{name:"display_name",type:"text",label:"Display Name"}]},edit:{title:"Edit User",fields:[{name:"email",type:"email",label:"Email",columns:12},{name:"display_name",type:"text",label:"Display Name",columns:12},{name:"phone_number",type:"text",label:"Phone number",columns:12},{type:"collection",name:"org",label:"Organization",Collection:GroupList,labelField:"name",valueField:"id",columns:12}]},permissions:{title:"Edit Permissions",fields:User.PERMISSION_FIELDS}},n={profile:{title:"User Profile",columns:2,fields:[{name:"id",label:"User ID",type:"number",columns:4},{name:"last_login",label:"Last Login",type:"datetime",format:"relative",columns:4},{name:"last_activity",label:"Last Activity",type:"datetime",format:"relative",columns:4},{name:"username",label:"Username",type:"text",format:"lowercase",columns:4},{name:"display_name",label:"Display Name",type:"text",columns:4},{name:"email",label:"Email",type:"email",columns:12},{name:"org.name",label:"Organization",type:"text",columns:6},{name:"phone_number",label:"Phone Number",type:"text",columns:6}]},activity:{title:"User Activity",columns:2,fields:[{name:"last_login",label:"Last Login",type:"datetime",format:"relative",colSize:6},{name:"last_activity",label:"Last Activity",type:"datetime",format:"relative",colSize:6}]},detailed:{title:"Detailed User Information",columns:2,showEmptyValues:!0,emptyValueText:"Not set",fields:[{name:"id",label:"User ID",type:"number",colSize:3},{name:"display_name",label:"Display Name",type:"text",format:'capitalize|default("Unnamed User")',colSize:9},{name:"username",label:"Username",type:"text",format:"lowercase",colSize:6},{name:"email",label:"Email Address",type:"email",colSize:6},{name:"phone_number",label:"Phone Number",type:"phone",format:'phone|default("Not provided")',colSize:6},{name:"is_active",label:"Account Status",type:"boolean",colSize:6},{name:"last_login",label:"Last Login",type:"datetime",format:"relative",colSize:6},{name:"last_activity",label:"Last Activity",type:"datetime",format:"relative",colSize:6},{name:"avatar.url",label:"Avatar",type:"url",colSize:12},{name:"permissions",label:"User Permissions",type:"dataview",dataViewColumns:2,showEmptyValues:!1},{name:"metadata",label:"User Metadata",type:"dataview",dataViewColumns:1},{name:"avatar",label:"Avatar Details",type:"dataview",dataViewColumns:1}]},permissions:{title:"User Permissions",columns:1,fields:[{name:"display_name",label:"User",type:"text",format:"capitalize",columns:12},{name:"permissions",label:"Assigned Permissions",type:"dataview",dataViewColumns:3,showEmptyValues:!1,colSize:12}]},summary:{title:"User Summary",columns:3,fields:[{name:"display_name",label:"Name",type:"text",format:"capitalize|truncate(30)"},{name:"email",label:"Email",type:"email"},{name:"is_active",label:"Status",type:"boolean"},{name:"last_activity",label:"Last Seen",type:"datetime",format:"relative",colSize:12}]}};User.DATA_VIEW=n.detailed,User.EDIT_FORM=s.edit,User.ADD_FORM=s.create;class UserDevice extends e.Model{constructor(e={}){super(e,{endpoint:"/api/user/device"})}static async getByDuid(e){const a=new UserDevice,t=await a.rest.GET("/api/user/device/lookup",{duid:e});return t.success&&t.data&&t.data.data?new UserDevice(t.data.data):null}}class UserDeviceList extends e.Collection{constructor(e={}){super({ModelClass:UserDevice,endpoint:"/api/user/device",...e})}}class UserDeviceLocation extends e.Model{constructor(e={}){super(e,{endpoint:"/api/user/device/location"})}}class UserDeviceLocationList extends e.Collection{constructor(e={}){super({ModelClass:UserDeviceLocation,endpoint:"/api/user/device/location",...e})}}exports.Group=Group,exports.Group$1=i,exports.GroupForms=l,exports.GroupList=GroupList,exports.User=User,exports.UserDataView=n,exports.UserDevice=UserDevice,exports.UserDeviceList=UserDeviceList,exports.UserDeviceLocation=UserDeviceLocation,exports.UserDeviceLocationList=UserDeviceLocationList,exports.UserForms=s,exports.UserList=UserList;
2
+ //# sourceMappingURL=User-DqHG5Gr1.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"User-DqHG5Gr1.js","sources":["../../src/core/models/Group.js","../../src/core/models/User.js"],"sourcesContent":["\nimport Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\n/**\n * Group Model - Represents an organization, team, or group entity\n *\n * Features:\n * - Hierarchical group support (parent/child relationships)\n * - Member management\n * - Search and filtering capabilities\n * - Role-based permissions within groups\n * - Metadata and settings management\n */\nclass Group extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/group'\n });\n }\n}\n\n/**\n * GroupCollection - Enhanced collection for managing groups with advanced search and filtering\n */\nclass GroupList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: Group,\n endpoint: '/api/group',\n size: 10,\n ...options\n });\n }\n}\n\nconst GroupKinds = {\n 'org': 'Organization',\n 'division': 'Division',\n 'department': 'Department',\n 'team': 'Team',\n 'merchant': 'Merchant',\n 'partner': 'Partner',\n 'client': 'Client',\n 'iso': 'ISO',\n 'sales': 'Sales',\n 'reseller': 'Reseller',\n 'location': 'Location',\n 'region': 'Region',\n 'route': 'Route',\n 'project': 'Project',\n \"inventory\": \"Inventory\",\n 'test': 'Testing',\n 'misc': 'Miscellaneous',\n 'qa': 'Quality Assurance'\n};\n\n// Convert GroupKinds to select options\nconst GroupKindOptions = Object.entries(GroupKinds).map(([key, label]) => ({\n value: key,\n label: label\n}));\n\n/**\n * Form configurations for group management\n */\nconst GroupForms = {\n create: {\n title: 'Create Group',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Group Name',\n required: true,\n placeholder: 'Enter group name'\n },\n {\n name: 'kind',\n type: 'select',\n label: 'Group Kind',\n required: true,\n options: GroupKindOptions\n },\n {\n type: 'collection',\n name: 'parent',\n label: 'Parent Group',\n Collection: GroupList, // Collection class\n labelField: 'name', // Field to display in dropdown\n valueField: 'id', // Field to use as value\n maxItems: 10, // Max items to show in dropdown\n placeholder: 'Search groups...',\n emptyFetch: false,\n debounceMs: 300, // Search debounce delay\n }\n ]\n },\n\n edit: {\n title: 'Edit Group',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Group Name',\n required: true,\n placeholder: 'Enter group name',\n },\n {\n name: 'kind',\n type: 'select',\n label: 'Group Kind',\n required: true,\n options: GroupKindOptions\n },\n {\n type: 'collection',\n name: 'parent',\n label: 'Parent Group',\n Collection: GroupList, // Collection class\n labelField: 'name', // Field to display in dropdown\n valueField: 'id', // Field to use as value\n maxItems: 10, // Max items to show in dropdown\n placeholder: 'Search groups...',\n emptyFetch: false,\n debounceMs: 300, // Search debounce delay\n },\n {\n name: 'metadata.domain',\n type: 'text',\n label: 'Default Domain',\n placeholder: 'Enter Domain',\n },\n {\n name: 'metadata.portal',\n type: 'text',\n label: 'Default Portal',\n placeholder: 'Enter Portal URL',\n },\n {\n name: 'is_active',\n type: 'switch',\n label: 'Is Active',\n cols: 4\n },\n ]\n },\n\n detailed: {\n title: 'Group Details',\n fields: [\n // Profile Header\n {\n type: 'header',\n text: 'Profile Information',\n level: 4,\n class: 'text-primary mb-3'\n },\n\n // Avatar and Basic Info\n {\n type: 'group',\n columns: { xs: 12, md: 4 },\n fields: [\n {\n type: 'image',\n name: 'avatar',\n size: 'lg',\n imageSize: { width: 200, height: 200 },\n placeholder: 'Upload your avatar',\n help: 'Square images work best',\n columns: 12\n },\n {\n name: 'is_active',\n type: 'switch',\n label: 'Is Active',\n columns: 12\n },\n ]\n },\n\n // Profile Details\n {\n type: 'group',\n columns: { xs: 12, md: 8 },\n title: 'Details',\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Group Name',\n required: true,\n placeholder: 'Enter group name',\n columns: 12\n },\n {\n name: 'kind',\n type: 'select',\n label: 'Group Kind',\n required: true,\n columns: 12,\n options: [\n { value: 'org', label: 'Organization' },\n { value: 'team', label: 'Team' },\n { value: 'department', label: 'Department' },\n { value: 'merchant', label: 'Merchant' },\n { value: 'iso', label: 'ISO' },\n { value: 'group', label: 'Group' }\n ]\n },\n {\n type: 'collection',\n name: 'parent',\n label: 'Parent Group',\n Collection: GroupList, // Collection class\n labelField: 'name', // Field to display in dropdown\n valueField: 'id', // Field to use as value\n maxItems: 10, // Max items to show in dropdown\n placeholder: 'Search groups...',\n emptyFetch: false,\n debounceMs: 300, // Search debounce delay\n columns: 12\n }\n ]\n },\n\n // Account Settings\n {\n type: 'group',\n columns: 12,\n title: 'Account Settings',\n class: \"pt-3\",\n fields: [\n {\n type: 'select',\n name: 'metadata.timezone',\n label: 'Timezone',\n columns: 6,\n value: 'America/Los_Angeles',\n options: [\n { value: 'America/New_York', text: 'Eastern Time' },\n { value: 'America/Chicago', text: 'Central Time' },\n { value: 'America/Denver', text: 'Mountain Time' },\n { value: 'America/Los_Angeles', text: 'Pacific Time' },\n { value: 'UTC', text: 'UTC' }\n ]\n },\n {\n type: 'select',\n name: 'metadata.eod_hour',\n label: 'End of Day Hour',\n columns: 6,\n options: [\n { value: 0, text: 'Midnight' },\n { value: 1, text: '1 AM' },\n { value: 2, text: '2 AM' },\n { value: 3, text: '3 AM' },\n { value: 4, text: '4 AM' },\n { value: 5, text: '5 AM' },\n { value: 6, text: '6 AM' },\n { value: 7, text: '7 AM' },\n { value: 8, text: '8 AM' },\n { value: 9, text: '9 AM' },\n { value: 10, text: '10 AM' },\n { value: 11, text: '11 AM' },\n { value: 12, text: '12 PM' },\n { value: 13, text: '1 PM' },\n { value: 14, text: '2 PM' },\n { value: 15, text: '3 PM' },\n { value: 16, text: '4 PM' },\n { value: 17, text: '5 PM' },\n { value: 18, text: '6 PM' },\n { value: 19, text: '7 PM' },\n { value: 20, text: '8 PM' },\n { value: 21, text: '9 PM' },\n { value: 22, text: '10 PM' },\n { value: 23, text: '11 PM' }\n ]\n }\n ]\n },\n {\n type: \"text\",\n label: \"Email Template (Prefix)\",\n name: \"metadata.email_template\",\n columns: 12\n }\n ]\n },\n};\n\nGroup.EDIT_FORM = GroupForms.edit;\nGroup.ADD_FORM = GroupForms.create;\nGroup.CREATE_FORM = GroupForms.create; // Alias for compatibility\nGroup.GroupKindOptions = GroupKindOptions;\nGroup.GroupKinds = GroupKinds;\nexport { Group, GroupList, GroupForms };\n","import Collection from '@core/Collection.js';\nimport Model from '@core/Model.js';\n\nimport { GroupList } from './Group.js';\n\nclass User extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/user'\n });\n }\n\n hasPermission(permission) {\n if (this.get(\"is_superuser\")) return true;\n if (Array.isArray(permission)) {\n return permission.some(p => this.hasPermission(p));\n }\n\n // Check if permission has \"sys.\" prefix\n const isSysPermission = permission.startsWith('sys.');\n const permissionToCheck = isSysPermission ? permission.substring(4) : permission;\n\n if (this._hasPermission(permissionToCheck)) {\n return true;\n }\n\n // Only check member permissions if it's not a system permission\n if (!isSysPermission && this.member && this.member.hasPermission(permission)) {\n return true;\n }\n\n return false;\n }\n\n _hasPermission(permission) {\n const permissions = this.get(\"permissions\");\n if (!permissions) {\n return false;\n }\n return permissions[permission] == true;\n }\n\n hasPerm(p) {\n return this.hasPermission(p);\n }\n}\n\nclass UserList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: User,\n endpoint: '/api/user',\n ...options\n });\n }\n}\n\nUser.PERMISSIONS = [\n { name: \"manage_users\", label: \"Manage Users\" },\n { name: \"view_users\", label: \"View Users\" },\n { name: \"view_groups\", label: \"View Groups\" },\n { name: \"manage_groups\", label: \"Manage Groups\" },\n { name: \"view_metrics\", label: \"View System Metrics\" },\n { name: \"manage_metrics\", label: \"Manage System Metrics\" },\n { name: \"view_logs\", label: \"View Logs\" },\n { name: \"view_incidents\", label: \"View Incidents\" },\n { name: \"manage_incidents\", label: \"Manage Incidents\" },\n { name: \"view_tickets\", label: \"View Tickets\" },\n { name: \"manage_tickets\", label: \"Manage Tickets\" },\n { name: \"view_admin\", label: \"View Admin\" },\n { name: \"view_jobs\", label: \"View Jobs\" },\n { name: \"manage_jobs\", label: \"Manage Jobs\" },\n { name: \"view_global\", label: \"View Global\" },\n { name: \"manage_notifications\", label: \"Manage Notifications\" },\n { name: \"manage_files\", label: \"Manage Files\" },\n { name: \"force_single_session\", label: \"Force Single Session\" },\n { name: \"file_vault\", label: \"Access File Vault\" },\n { name: \"manage_aws\", label: \"Manage AWS\" },\n { name: \"manage_docit\", label: \"Manage DocIt\" }\n];\n\n\nUser.PERMISSION_FIELDS = [\n ...User.PERMISSIONS.map(permission => ({\n name: `permissions.${permission.name}`,\n type: 'switch',\n label: permission.label,\n columns: 4\n }))\n];\n\nconst UserForms = {\n create: {\n title: 'Create User',\n fields: [\n { name: 'email', type: 'text', label: 'Email', required: true },\n { name: 'phone_number', type: 'text', label: 'Phone number', columns: 12 },\n { name: 'display_name', type: 'text', label: 'Display Name' }\n ]\n },\n edit: {\n title: 'Edit User',\n fields: [\n { name: 'email', type: 'email', label: 'Email', columns: 12 },\n { name: 'display_name', type: 'text', label: 'Display Name', columns: 12},\n { name: 'phone_number', type: 'text', label: 'Phone number', columns: 12 },\n { type: 'collection', name: 'org', label: 'Organization', Collection: GroupList, labelField: 'name', valueField: 'id', columns: 12 },\n ]\n },\n permissions: {\n title: 'Edit Permissions',\n fields: User.PERMISSION_FIELDS\n }\n};\n\n\n// DataView configuration for User model\nconst UserDataView = {\n // Basic user profile view\n profile: {\n title: 'User Profile',\n columns: 2,\n fields: [\n {\n name: 'id',\n label: 'User ID',\n type: 'number',\n columns: 4\n },\n {\n name: 'last_login',\n label: 'Last Login',\n type: 'datetime',\n format: 'relative',\n columns: 4\n },\n {\n name: 'last_activity',\n label: 'Last Activity',\n type: 'datetime',\n format: 'relative',\n columns: 4\n },\n {\n name: 'username',\n label: 'Username',\n type: 'text',\n format: 'lowercase',\n columns: 4\n },\n {\n name: 'display_name',\n label: 'Display Name',\n type: 'text',\n columns: 4\n },\n\n {\n name: 'email',\n label: 'Email',\n type: 'email',\n columns: 12\n },\n\n {\n name: 'org.name',\n label: 'Organization',\n type: 'text',\n columns: 6\n },\n {\n name: 'phone_number',\n label: 'Phone Number',\n type: 'text',\n columns: 6\n }\n ]\n },\n\n // Activity tracking view\n activity: {\n title: 'User Activity',\n columns: 2,\n fields: [\n {\n name: 'last_login',\n label: 'Last Login',\n type: 'datetime',\n format: 'relative',\n colSize: 6\n },\n {\n name: 'last_activity',\n label: 'Last Activity',\n type: 'datetime',\n format: 'relative',\n colSize: 6\n }\n ]\n },\n\n // Comprehensive view with all data\n detailed: {\n title: 'Detailed User Information',\n columns: 2,\n showEmptyValues: true,\n emptyValueText: 'Not set',\n fields: [\n // Basic Info Section\n {\n name: 'id',\n label: 'User ID',\n type: 'number',\n colSize: 3\n },\n {\n name: 'display_name',\n label: 'Display Name',\n type: 'text',\n format: 'capitalize|default(\"Unnamed User\")',\n colSize: 9\n },\n {\n name: 'username',\n label: 'Username',\n type: 'text',\n format: 'lowercase',\n colSize: 6\n },\n {\n name: 'email',\n label: 'Email Address',\n type: 'email',\n colSize: 6\n },\n {\n name: 'phone_number',\n label: 'Phone Number',\n type: 'phone',\n format: 'phone|default(\"Not provided\")',\n colSize: 6\n },\n {\n name: 'is_active',\n label: 'Account Status',\n type: 'boolean',\n colSize: 6\n },\n\n // Activity Info\n {\n name: 'last_login',\n label: 'Last Login',\n type: 'datetime',\n format: 'relative',\n colSize: 6\n },\n {\n name: 'last_activity',\n label: 'Last Activity',\n type: 'datetime',\n format: 'relative',\n colSize: 6\n },\n\n // Avatar Info\n {\n name: 'avatar.url',\n label: 'Avatar',\n type: 'url',\n colSize: 12\n },\n\n // Complex Data (will use full width automatically)\n {\n name: 'permissions',\n label: 'User Permissions',\n type: 'dataview',\n dataViewColumns: 2,\n showEmptyValues: false\n },\n {\n name: 'metadata',\n label: 'User Metadata',\n type: 'dataview',\n dataViewColumns: 1\n },\n {\n name: 'avatar',\n label: 'Avatar Details',\n type: 'dataview',\n dataViewColumns: 1\n }\n ]\n },\n\n // Permissions-focused view\n permissions: {\n title: 'User Permissions',\n columns: 1,\n fields: [\n {\n name: 'display_name',\n label: 'User',\n type: 'text',\n format: 'capitalize',\n columns: 12\n },\n {\n name: 'permissions',\n label: 'Assigned Permissions',\n type: 'dataview',\n dataViewColumns: 3,\n showEmptyValues: false,\n colSize: 12\n }\n ]\n },\n\n // Compact summary view\n summary: {\n title: 'User Summary',\n columns: 3,\n fields: [\n {\n name: 'display_name',\n label: 'Name',\n type: 'text',\n format: 'capitalize|truncate(30)'\n },\n {\n name: 'email',\n label: 'Email',\n type: 'email'\n },\n {\n name: 'is_active',\n label: 'Status',\n type: 'boolean'\n },\n {\n name: 'last_activity',\n label: 'Last Seen',\n type: 'datetime',\n format: 'relative',\n colSize: 12\n }\n ]\n }\n};\n\nUser.DATA_VIEW = UserDataView.detailed;\nUser.EDIT_FORM = UserForms.edit;\nUser.ADD_FORM = UserForms.create;\n\n/* =========================\n * UserDevice\n * ========================= */\nclass UserDevice extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/user/device',\n });\n }\n\n static async getByDuid(duid) {\n const model = new UserDevice();\n const resp = await model.rest.GET('/api/user/device/lookup', { duid: duid });\n if (resp.success && resp.data && resp.data.data) {\n // A direct lookup should return a single object\n return new UserDevice(resp.data.data);\n }\n return null;\n }\n}\n\nclass UserDeviceList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: UserDevice,\n endpoint: '/api/user/device',\n ...options,\n });\n }\n}\n\n/* =========================\n * UserDeviceLocation\n * ========================= */\nclass UserDeviceLocation extends Model {\n constructor(data = {}) {\n super(data, {\n endpoint: '/api/user/device/location',\n });\n }\n}\n\nclass UserDeviceLocationList extends Collection {\n constructor(options = {}) {\n super({\n ModelClass: UserDeviceLocation,\n endpoint: '/api/user/device/location',\n ...options,\n });\n }\n}\n\nexport { User, UserList, UserForms, UserDataView, UserDevice, UserDeviceList, UserDeviceLocation, UserDeviceLocationList };\n"],"names":["Group","Model","constructor","data","super","endpoint","GroupList","Collection","options","ModelClass","size","GroupKinds","org","division","department","team","merchant","partner","client","iso","sales","reseller","location","region","route","project","inventory","test","misc","qa","GroupKindOptions","Object","entries","map","key","label","value","GroupForms","create","title","fields","name","type","required","placeholder","labelField","valueField","maxItems","emptyFetch","debounceMs","edit","cols","detailed","text","level","class","columns","xs","md","imageSize","width","height","help","EDIT_FORM","ADD_FORM","CREATE_FORM","User","hasPermission","permission","this","get","Array","isArray","some","p","isSysPermission","startsWith","permissionToCheck","substring","_hasPermission","member","permissions","hasPerm","UserList","PERMISSIONS","PERMISSION_FIELDS","UserForms","UserDataView","profile","format","activity","colSize","showEmptyValues","emptyValueText","dataViewColumns","summary","DATA_VIEW","UserDevice","getByDuid","duid","model","resp","rest","GET","success","UserDeviceList","UserDeviceLocation","UserDeviceLocationList"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{V as e,r as t}from"./Rest-BJ3Mvx1L.js";import i from"./Dialog-t_9l2Mou.js";import{M as s,C as n}from"./Collection-BWKmydl5.js";import{a,b as o,c as r}from"./User-BL9M_PWB.js";import{T as l,a as d,M as c}from"./TableView-CWk5k4LQ.js";import{a as p,L as m}from"./ListView-BLFFK_Ir.js";class ProfileOverviewSection extends e{constructor(e={}){super({className:"profile-overview-section",template:'\n <style>\n .po-section-label { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #adb5bd; margin-bottom: 0.5rem; margin-top: 1.75rem; }\n .po-section-label:first-child { margin-top: 0; }\n .po-field-row { display: flex; align-items: center; padding: 0.6rem 0; border-bottom: 1px solid #f0f0f0; }\n .po-field-row:last-child { border-bottom: none; }\n .po-field-label { width: 130px; font-size: 0.8rem; color: #6c757d; flex-shrink: 0; }\n .po-field-value { flex: 1; font-size: 0.88rem; color: #212529; display: flex; align-items: center; gap: 0.4rem; }\n .po-field-action { color: #6c757d; cursor: pointer; font-size: 0.8rem; margin-left: auto; padding: 0.15rem 0.4rem; border-radius: 4px; background: none; border: none; }\n .po-field-action:hover { background: #f0f0f0; color: #0d6efd; }\n .po-badge-warn { font-size: 0.65rem; padding: 0.15em 0.45em; background: #fff3cd; color: #856404; border-radius: 3px; }\n .po-badge-ok { font-size: 0.65rem; padding: 0.15em 0.45em; background: #d1e7dd; color: #0f5132; border-radius: 3px; }\n .po-badge-muted { font-size: 0.65rem; padding: 0.15em 0.45em; background: #f0f0f0; color: #6c757d; border-radius: 3px; }\n .po-not-set { color: #adb5bd; font-style: italic; font-size: 0.85rem; }\n .po-perm-pill { display: inline-block; font-size: 0.72rem; padding: 0.2em 0.55em; background: #e7f1ff; color: #0d6efd; border-radius: 3px; margin: 0.1rem; }\n .po-perm-more { font-size: 0.72rem; color: #6c757d; cursor: pointer; }\n .po-perm-more:hover { color: #0d6efd; text-decoration: underline; }\n </style>\n\n \x3c!-- Contact --\x3e\n <div class="po-section-label">Contact</div>\n <div class="po-field-row">\n <div class="po-field-label">Email</div>\n <div class="po-field-value">\n {{model.email}}\n {{#model.is_email_verified|bool}}\n <span class="po-badge-ok">Verified</span>\n {{/model.is_email_verified|bool}}\n {{^model.is_email_verified|bool}}\n <span class="po-badge-warn">Unverified</span>\n {{/model.is_email_verified|bool}}\n </div>\n {{^model.is_email_verified|bool}}\n <button type="button" class="po-field-action" data-action="verify-email" title="Send verification email"><i class="bi bi-envelope-check"></i></button>\n {{/model.is_email_verified|bool}}\n <button type="button" class="po-field-action" data-action="update-email" title="Change email"><i class="bi bi-pencil"></i></button>\n </div>\n <div class="po-field-row">\n <div class="po-field-label">Phone</div>\n <div class="po-field-value">\n {{#hasPhone|bool}}\n {{model.phone_number}}\n {{#model.is_phone_verified|bool}}\n <span class="po-badge-ok">Verified</span>\n {{/model.is_phone_verified|bool}}\n {{^model.is_phone_verified|bool}}\n <span class="po-badge-warn">Unverified</span>\n {{/model.is_phone_verified|bool}}\n {{/hasPhone|bool}}\n {{^hasPhone|bool}}\n <span class="po-not-set">Not set</span>\n {{/hasPhone|bool}}\n </div>\n {{^hasPhone|bool}}\n <button type="button" class="po-field-action" data-action="add-phone" title="Add phone number"><i class="bi bi-plus"></i></button>\n {{/hasPhone|bool}}\n {{#hasPhone|bool}}\n {{^model.is_phone_verified|bool}}\n <button type="button" class="po-field-action" data-action="verify-phone" title="Send verification"><i class="bi bi-phone-vibrate"></i></button>\n {{/model.is_phone_verified|bool}}\n <button type="button" class="po-field-action" data-action="update-phone" title="Change phone number"><i class="bi bi-pencil"></i></button>\n <button type="button" class="po-field-action" data-action="remove-phone" title="Remove phone number"><i class="bi bi-x-lg"></i></button>\n {{/hasPhone|bool}}\n </div>\n\n \x3c!-- Account --\x3e\n <div class="po-section-label">Account</div>\n <div class="po-field-row">\n <div class="po-field-label">Username</div>\n <div class="po-field-value">{{model.username}}</div>\n </div>\n <div class="po-field-row">\n <div class="po-field-label">Status</div>\n <div class="po-field-value">\n {{#model.is_active|bool}}<span class="po-badge-ok">Active</span>{{/model.is_active|bool}}\n {{^model.is_active|bool}}<span class="po-badge-warn">Inactive</span>{{/model.is_active|bool}}\n </div>\n </div>\n <div class="po-field-row">\n <div class="po-field-label">Role</div>\n <div class="po-field-value">\n {{roleLabel}}\n {{#model.is_staff|bool}}<span class="po-badge-muted">Staff</span>{{/model.is_staff|bool}}\n </div>\n </div>\n <div class="po-field-row">\n <div class="po-field-label">MFA</div>\n <div class="po-field-value">\n {{#model.requires_mfa|bool}}<span class="po-badge-ok">Required</span>{{/model.requires_mfa|bool}}\n {{^model.requires_mfa|bool}}<span class="po-badge-muted">Not required</span>{{/model.requires_mfa|bool}}\n </div>\n </div>\n <div class="po-field-row">\n <div class="po-field-label">Member Since</div>\n <div class="po-field-value">{{model.date_joined|date}}</div>\n </div>\n <div class="po-field-row">\n <div class="po-field-label">Last Login</div>\n <div class="po-field-value">{{model.last_login|relative}}</div>\n </div>\n\n\n \x3c!-- Danger zone --\x3e\n <div style="margin-top: 2.5rem; padding-top: 1rem; border-top: 1px solid #f0f0f0;">\n <button type="button" class="btn btn-link text-danger p-0" style="font-size: 0.8rem; text-decoration: none;" data-action="deactivate-account">\n <i class="bi bi-exclamation-triangle me-1"></i>Deactivate Account\n </button>\n </div>\n ',...e})}get hasPhone(){return!(!this.model||!this.model.get("phone_number"))}get roleLabel(){return this.model&&this.model.get("is_superuser")?"Superuser":"User"}get permissionPeek(){if(!this.model)return null;const e=this.model.get("permissions");if(!e)return null;const t={};a.PERMISSIONS.forEach(e=>{t[e.name]=e.label});const i=Object.keys(e).filter(t=>!0===e[t]).map(e=>t[e]||e.replace(/_/g," ").replace(/\b\w/g,e=>e.toUpperCase()));return 0===i.length?null:{items:i.slice(0,5),remaining:Math.max(0,i.length-5)}}get hasActiveGroup(){const e=this.getApp();return!(!e?.activeGroup||!this.model?.member)}get activeGroupName(){const e=this.getApp();return e?.activeGroup?.get("name")||e?.activeGroup?.get("display_name")||"Current Group"}get groupPermissionPeek(){if(!this.model?.member)return null;const e=this.model.member.get("permissions");if(!e)return null;const t=Object.keys(e).filter(t=>!0===e[t]).map(e=>e.replace(/_/g," ").replace(/\b\w/g,e=>e.toUpperCase()));return 0===t.length?null:{items:t.slice(0,5),remaining:Math.max(0,t.length-5)}}async onActionDeactivateAccount(){const e=this.getApp();if(!(await i.confirm("Are you sure you want to deactivate your account? A confirmation email will be sent to complete the process. This action cannot be undone.","Deactivate Account")))return!0;const s=await t.POST("/api/account/deactivate");return s.success?e?.toast?.success("A confirmation email has been sent. Follow the link to complete deactivation."):e?.toast?.error(s.message||"Failed to request deactivation"),!0}async onActionVerifyEmail(){const e=this.getApp(),s=this.model.get("email"),n=await t.POST("/api/auth/verify/email/send",{method:"code"});if(!n.success)return e?.toast?.error(n.message||"Failed to send verification code"),!0;const a=await i.prompt(`Enter the 6-digit code sent to <strong>${s}</strong>`,"Verify Email",{placeholder:"000000"});if(!a)return!0;const o=await t.POST("/api/auth/verify/email/confirm",{code:a.trim()});return o.success?(e?.toast?.success("Email verified"),this.model.set("is_email_verified",!0),await this.render()):e?.toast?.error(o.message||"Invalid or expired code"),!0}async onActionVerifyPhone(){const e=this.getApp(),s=this.model.get("phone_number"),n=await t.POST("/api/auth/verify/phone/send");if(!n.success)return e?.toast?.error(n.message||"Failed to send verification code"),!0;const a=await i.prompt(`Enter the 6-digit code sent to <strong>${s}</strong>`,"Verify Phone",{placeholder:"000000"});if(!a)return!0;const o=await t.POST("/api/auth/verify/phone/confirm",{code:a.trim()});return o.success?(e?.toast?.success("Phone verified"),this.model.set("is_phone_verified",!0),await this.render()):e?.toast?.error(o.message||"Invalid or expired code"),!0}async onActionAddPhone(){const e=this.getApp(),s=await i.prompt("Enter your phone number:","Add Phone Number",{placeholder:"(415) 555-0123"});if(!s||!s.trim())return!0;const n=await this.model.save({phone_number:s.trim()});if(200!==n.status)return e?.toast?.error(n.message||"Failed to save phone number"),!0;const a=await t.POST("/api/auth/verify/phone/send");if(!a.success)return e?.toast?.error(a.message||"Failed to send verification code"),await this.render(),!0;const o=await i.prompt(`Enter the 6-digit code sent to <strong>${s.trim()}</strong>`,"Verify Phone",{placeholder:"000000"});if(!o)return await this.render(),!0;const r=await t.POST("/api/auth/verify/phone/confirm",{code:o.trim()});return r.success?(e?.toast?.success("Phone number added and verified"),this.model.set("is_phone_verified",!0),await this.render()):(e?.toast?.error(r.message||"Invalid or expired code"),await this.render()),!0}async onActionRemovePhone(){const e=this.getApp();if(!(await i.confirm("Remove your phone number? You will need to add it again to use phone-based verification.","Remove Phone")))return!0;const t=await this.model.save({phone_number:null});return 200===t.status?(e?.toast?.success("Phone number removed"),this.model.set("is_phone_verified",!1),await this.render()):e?.toast?.error(t.message||"Failed to remove phone number"),!0}async onActionNavigate(e,t){return!this.parent||!this.parent.onActionNavigate||this.parent.onActionNavigate(e,t)}}class ProfilePersonalSection extends e{constructor(e={}){super({className:"profile-personal-section",template:'\n <style>\n .pp-section-label { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #adb5bd; margin-bottom: 0.5rem; margin-top: 1.75rem; }\n .pp-section-label:first-child { margin-top: 0; }\n .pp-field-row { display: flex; align-items: center; padding: 0.6rem 0; border-bottom: 1px solid #f0f0f0; }\n .pp-field-row:last-child { border-bottom: none; }\n .pp-field-label { width: 130px; font-size: 0.8rem; color: #6c757d; flex-shrink: 0; }\n .pp-field-value { flex: 1; font-size: 0.88rem; color: #212529; display: flex; align-items: center; gap: 0.4rem; }\n .pp-field-action { color: #6c757d; cursor: pointer; font-size: 0.8rem; margin-left: auto; padding: 0.15rem 0.4rem; border-radius: 4px; background: none; border: none; }\n .pp-field-action:hover { background: #f0f0f0; color: #0d6efd; }\n .pp-badge-ok { font-size: 0.65rem; padding: 0.15em 0.45em; background: #d1e7dd; color: #0f5132; border-radius: 3px; }\n .pp-badge-warn { font-size: 0.65rem; padding: 0.15em 0.45em; background: #fff3cd; color: #856404; border-radius: 3px; }\n .pp-not-set { color: #adb5bd; font-style: italic; font-size: 0.85rem; }\n </style>\n\n \x3c!-- Name --\x3e\n <div class="pp-section-label">Name</div>\n <div class="pp-field-row">\n <div class="pp-field-label">Display Name</div>\n <div class="pp-field-value">\n {{#model.display_name}}{{model.display_name}}{{/model.display_name}}\n {{^model.display_name}}<span class="pp-not-set">Not set</span>{{/model.display_name}}\n </div>\n <button type="button" class="pp-field-action" data-action="edit-display-name" title="Edit"><i class="bi bi-pencil"></i></button>\n </div>\n <div class="pp-field-row">\n <div class="pp-field-label">First Name</div>\n <div class="pp-field-value">\n {{#model.first_name}}{{model.first_name}}{{/model.first_name}}\n {{^model.first_name}}<span class="pp-not-set">Not set</span>{{/model.first_name}}\n </div>\n <button type="button" class="pp-field-action" data-action="edit-first-name" title="Edit"><i class="bi bi-pencil"></i></button>\n </div>\n <div class="pp-field-row">\n <div class="pp-field-label">Last Name</div>\n <div class="pp-field-value">\n {{#model.last_name}}{{model.last_name}}{{/model.last_name}}\n {{^model.last_name}}<span class="pp-not-set">Not set</span>{{/model.last_name}}\n </div>\n <button type="button" class="pp-field-action" data-action="edit-last-name" title="Edit"><i class="bi bi-pencil"></i></button>\n </div>\n\n \x3c!-- Details --\x3e\n <div class="pp-section-label">Details</div>\n <div class="pp-field-row">\n <div class="pp-field-label">Date of Birth</div>\n <div class="pp-field-value">\n {{#hasDob|bool}}\n {{dobFormatted}}\n {{#model.is_dob_verified|bool}}<span class="pp-badge-ok">Verified</span>{{/model.is_dob_verified|bool}}\n {{^model.is_dob_verified|bool}}<span class="pp-badge-warn">Unverified</span>{{/model.is_dob_verified|bool}}\n {{/hasDob|bool}}\n {{^hasDob|bool}}<span class="pp-not-set">Not set</span>{{/hasDob|bool}}\n </div>\n <button type="button" class="pp-field-action" data-action="edit-dob" title="Edit"><i class="bi bi-pencil"></i></button>\n </div>\n <div class="pp-field-row">\n <div class="pp-field-label">Timezone</div>\n <div class="pp-field-value">{{timezoneDisplay}}</div>\n <button type="button" class="pp-field-action" data-action="edit-timezone" title="Edit"><i class="bi bi-pencil"></i></button>\n </div>\n\n \x3c!-- Address --\x3e\n <div class="pp-section-label">Address</div>\n <div class="pp-field-row">\n <div class="pp-field-label">Address</div>\n <div class="pp-field-value">\n {{#hasAddress|bool}}\n {{addressSummary}}\n {{/hasAddress|bool}}\n {{^hasAddress|bool}}\n <span class="pp-not-set">Not set</span>\n {{/hasAddress|bool}}\n </div>\n <button type="button" class="pp-field-action" data-action="edit-address" title="Edit"><i class="bi bi-pencil"></i></button>\n </div>\n ',...e})}get hasDob(){return!!this.model?.get("dob")}get dobFormatted(){const e=this.model?.get("dob");if(!e)return"";try{const[t,i,s]=e.split("-");return`${i}/${s}/${t}`}catch{return e}}get timezoneDisplay(){return(this.model?.get("metadata")||{}).timezone||"Not set"}get hasAddress(){const e=this.model?.get("metadata")||{};return!!(e.street||e.city||e.state||e.zip||e.country)}get addressSummary(){const e=this.model?.get("metadata")||{};return[e.street,e.city,e.state,e.zip,e.country].filter(Boolean).join(", ")}async onActionEditDisplayName(){const e=await i.prompt("Enter your display name:","Display Name",{defaultValue:this.model.get("display_name")||""});return null!==e&&e.trim()&&(200===(await this.model.save({display_name:e.trim()})).status?(this.getApp()?.toast?.success("Display name updated"),await this.render()):this.getApp()?.toast?.error("Failed to update display name")),!0}async onActionEditFirstName(){const e=await i.prompt("Enter your first name:","First Name",{defaultValue:this.model.get("first_name")||""});return null!==e&&(200===(await this.model.save({first_name:e.trim()})).status?(this.getApp()?.toast?.success("First name updated"),await this.render()):this.getApp()?.toast?.error("Failed to update first name")),!0}async onActionEditLastName(){const e=await i.prompt("Enter your last name:","Last Name",{defaultValue:this.model.get("last_name")||""});return null!==e&&(200===(await this.model.save({last_name:e.trim()})).status?(this.getApp()?.toast?.success("Last name updated"),await this.render()):this.getApp()?.toast?.error("Failed to update last name")),!0}async onActionEditDob(){const e=await i.showForm({title:"Date of Birth",size:"sm",fields:[{name:"dob",type:"date",label:"Date of Birth",cols:12}],data:{dob:this.model.get("dob")||""}});return!e||(200===(await this.model.save({dob:e.dob||null})).status?(this.getApp()?.toast?.success("Date of birth updated"),await this.render()):this.getApp()?.toast?.error("Failed to update date of birth"),!0)}async onActionEditTimezone(){const e=this.model.get("metadata")||{},t=await i.showForm({title:"Change Timezone",fields:[{name:"timezone",type:"select",label:"Timezone",cols:12,options:[{value:"America/New_York",text:"Eastern Time (ET)"},{value:"America/Chicago",text:"Central Time (CT)"},{value:"America/Denver",text:"Mountain Time (MT)"},{value:"America/Los_Angeles",text:"Pacific Time (PT)"},{value:"America/Anchorage",text:"Alaska Time (AKT)"},{value:"Pacific/Honolulu",text:"Hawaii Time (HT)"},{value:"UTC",text:"UTC"},{value:"Europe/London",text:"London (GMT/BST)"},{value:"Europe/Paris",text:"Paris (CET/CEST)"},{value:"Europe/Berlin",text:"Berlin (CET/CEST)"},{value:"Asia/Tokyo",text:"Tokyo (JST)"},{value:"Asia/Shanghai",text:"Shanghai (CST)"},{value:"Australia/Sydney",text:"Sydney (AEST)"}]}],data:{timezone:e.timezone||""},size:"sm"});if(!t)return!0;const s={...e,timezone:t.timezone};return 200===(await this.model.save({metadata:s})).status?(this.getApp()?.toast?.success("Timezone updated"),await this.render()):this.getApp()?.toast?.error("Failed to update timezone"),!0}async onActionEditAddress(){const e=this.model.get("metadata")||{},t=await i.showForm({title:"Edit Address",size:"md",fields:[{name:"street",type:"text",label:"Street",placeholder:"123 Main St",cols:12},{name:"city",type:"text",label:"City",cols:6},{name:"state",type:"text",label:"State / Province",cols:6},{name:"zip",type:"text",label:"Zip / Postal Code",cols:6},{name:"country",type:"text",label:"Country",cols:6}],data:{street:e.street||"",city:e.city||"",state:e.state||"",zip:e.zip||"",country:e.country||""}});if(!t)return!0;const s={...e,street:t.street||"",city:t.city||"",state:t.state||"",zip:t.zip||"",country:t.country||""};return 200===(await this.model.save({metadata:s})).status?(this.getApp()?.toast?.success("Address updated"),await this.render()):this.getApp()?.toast?.error("Failed to update address"),!0}}function u(e){const t=e.replace(/-/g,"+").replace(/_/g,"/"),i=t+"=".repeat((4-t.length%4)%4);return Uint8Array.from(atob(i),e=>e.charCodeAt(0))}function f(e){return btoa(String.fromCharCode(...new Uint8Array(e))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}class Passkey extends s{constructor(e={},t={}){super(e,{endpoint:"/api/account/passkeys",...t})}static suggestName(){const e=navigator.userAgent;let t="Device";/iPad/.test(e)?t="iPad":/iPhone/.test(e)?t="iPhone":/Macintosh|MacIntel/.test(e)?t="Mac":/Android/.test(e)?t="Android":/Windows/.test(e)?t="Windows PC":/Linux/.test(e)&&(t="Linux");let i="";return/Edg\//.test(e)?i="Edge":/Chrome\//.test(e)&&!/Chromium/.test(e)?i="Chrome":/Safari\//.test(e)&&!/Chrome/.test(e)?i="Safari":/Firefox\//.test(e)&&(i="Firefox"),i?`${t} — ${i}`:t}static async register(e){const t=await Passkey.registerBegin();if(!t?.data?.challenge_id||!t?.data?.publicKey)return{success:!1,error:t?.error||"Could not start registration."};const{challenge_id:i,publicKey:s}=t.data;"string"==typeof s.challenge&&(s.challenge=u(s.challenge)),"string"==typeof s.user?.id&&(s.user.id=u(s.user.id)),s.excludeCredentials&&(s.excludeCredentials=s.excludeCredentials.map(e=>({...e,id:"string"==typeof e.id?u(e.id):e.id})));const n=await navigator.credentials.create({publicKey:s});if(!n)return{success:!1,error:"Passkey creation was cancelled."};const a={id:n.id,rawId:f(n.rawId),type:n.type,response:{clientDataJSON:f(n.response.clientDataJSON),attestationObject:f(n.response.attestationObject)}};n.response.getTransports&&(a.transports=n.response.getTransports());const o=await Passkey.registerComplete({challenge_id:i,credential:a,friendly_name:e||"My Passkey"});return o?.data?.id?{success:!0,passkey:o.data}:{success:!1,error:o?.error||"Registration could not be completed."}}static async registerBegin(e={}){try{return await t.POST("/api/account/passkeys/register/begin",{},e.params,{dataOnly:!0})}catch(i){return{success:!1,error:i?.message||"Failed to begin passkey registration"}}}static async registerComplete(e={},i={}){if(!e.challenge_id||!e.credential)return{success:!1,error:"Missing challenge_id or credential data"};try{return await t.POST("/api/account/passkeys/register/complete",e,i.params,{dataOnly:!0})}catch(s){return{success:!1,error:s?.message||"Failed to complete passkey registration"}}}}class PasskeyList extends n{constructor(e={}){super({ModelClass:Passkey,endpoint:"/api/account/passkeys",size:10,...e})}}const g={edit:{fields:[{name:"friendly_name",type:"text",label:"Name",placeholder:"My iPhone",required:!0,columns:12,help:"A friendly name to identify this passkey"},{name:"is_enabled",type:"switch",label:"Enabled",columns:12,help:"Disable to prevent this passkey from being used for authentication"}]}};class PasskeySetupView extends e{constructor(e={}){super({className:"passkey-setup-view",template:'\n <style>\n .pks-body { padding: 2rem 1.75rem 1rem; text-align: center; }\n .pks-icon { width: 56px; height: 56px; background: #e7f1ff; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-size: 1.5rem; color: #0d6efd; margin-bottom: 1rem; }\n .pks-body h5 { font-weight: 700; font-size: 1.05rem; margin-bottom: 0.35rem; }\n .pks-body p { font-size: 0.83rem; color: #6c757d; margin-bottom: 1.25rem; line-height: 1.45; }\n .pks-footer { padding: 0 1.75rem 1.5rem; display: flex; flex-direction: column; gap: 0.4rem; }\n .pks-footer .btn-create { padding: 0.6rem; font-weight: 600; font-size: 0.9rem; border-radius: 8px; }\n .pks-footer .btn-skip { background: none; border: none; color: #6c757d; font-size: 0.82rem; padding: 0.4rem; cursor: pointer; }\n .pks-footer .btn-skip:hover { color: #495057; }\n .pks-dont-show { text-align: center; padding: 0 1.75rem 1.25rem; }\n .pks-dont-show label { font-size: 0.73rem; color: #adb5bd; cursor: pointer; }\n </style>\n\n <div class="pks-body">\n <div class="pks-icon"><i class="bi bi-fingerprint"></i></div>\n <h5>Add a Passkey</h5>\n <p>Sign in faster with Face ID, Touch ID, or your device PIN. No passwords needed.</p>\n </div>\n <div class="pks-footer">\n <button class="btn btn-primary btn-create" data-action="create-passkey"><i class="bi bi-fingerprint me-1"></i>Create Passkey</button>\n <button class="btn-skip" data-action="skip">Not now</button>\n </div>\n <div class="pks-dont-show">\n <label><input type="checkbox" class="form-check-input form-check-input-sm me-1" data-action="dont-ask"> Don\'t ask again</label>\n </div>\n ',...e})}async _askPasskeyName(){const e=Passkey.suggestName();return i.showDialog({title:'<i class="bi bi-fingerprint me-2"></i>Register a Passkey',size:"sm",centered:!0,body:`\n <div style="text-align:center; padding: 0.5rem 0 1rem;">\n <div style="width:72px; height:72px; background:linear-gradient(135deg, #e7f1ff 0%, #d0e2ff 100%); border-radius:50%; display:inline-flex; align-items:center; justify-content:center; font-size:2rem; color:#0d6efd; margin-bottom:1rem;">\n <i class="bi bi-fingerprint"></i>\n </div>\n <p style="font-size:0.85rem; color:#6c757d; margin-bottom:1.25rem; line-height:1.5;">\n Passkeys use your device's biometrics — fingerprint, face, or PIN — instead of a password.\n They're <strong>phishing-resistant</strong> and the private key never leaves your device.\n </p>\n <div class="text-start" style="margin-bottom:0.25rem;">\n <label class="form-label fw-semibold" style="font-size:0.82rem;">Name this passkey</label>\n <input type="text" class="form-control" id="pks-name-input" value="${e}" placeholder="e.g., My MacBook" style="border-radius:8px;">\n <div class="form-text">A label so you can identify this passkey later.</div>\n </div>\n </div>`,buttons:[{text:"Cancel",class:"btn-secondary",dismiss:!0},{text:'<i class="bi bi-fingerprint me-1"></i>Continue',class:"btn-primary",handler:({dialog:t})=>{const i=t.element?.querySelector("#pks-name-input");return i?.value?.trim()||e}}]})}static showSuccess(e){return i.showDialog({title:'<span class="text-success"><i class="bi bi-check-circle-fill me-2"></i>Passkey Registered</span>',size:"sm",centered:!0,body:`\n <div style="text-align:center; padding: 0.5rem 0 0.75rem;">\n <div style="width:72px; height:72px; background:linear-gradient(135deg, #d1e7dd 0%, #badbcc 100%); border-radius:50%; display:inline-flex; align-items:center; justify-content:center; font-size:2rem; color:#198754; margin-bottom:1rem;">\n <i class="bi bi-shield-lock-fill"></i>\n </div>\n <h6 class="fw-bold mb-2">${e||"Your passkey"} is ready</h6>\n <p style="font-size:0.85rem; color:#6c757d; line-height:1.5; margin-bottom:0;">\n Next time you sign in, choose <strong>"Login with Passkey"</strong> — no username or password needed.\n Just your fingerprint, face, or device PIN.\n </p>\n </div>`,buttons:[{text:"Done",class:"btn-success",value:!0}]})}static showError(e){return i.alert({title:"Passkey Error",message:e||"Something went wrong during passkey registration.",type:"error"})}async onActionCreatePasskey(){try{const e=await this._askPasskeyName();if(!e)return!0;const t=await Passkey.register(e);t.success?(localStorage.setItem("passkey_setup_dismissed","1"),await PasskeySetupView.showSuccess(e),this.emit("dismiss")):PasskeySetupView.showError(t.error)}catch(e){if("NotAllowedError"===e.name)return!0;"SecurityError"===e.name?PasskeySetupView.showError("Passkeys are not supported on this domain. Ensure you are using HTTPS."):(console.error("Passkey registration error:",e),PasskeySetupView.showError(e.message||"An unexpected error occurred."))}return!0}async onActionSkip(){return this.emit("dismiss"),!0}async onActionDontAsk(){const e=this.element?.querySelector('.pks-dont-show input[type="checkbox"]');return e&&e.checked?(localStorage.setItem("passkey_setup_dismissed","1"),this.emit("dismiss")):localStorage.removeItem("passkey_setup_dismissed"),!0}}class ProfileSecuritySection extends e{constructor(e={}){super({className:"profile-security-section",template:'\n <style>\n .ps-section-label { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #adb5bd; margin-bottom: 0.5rem; margin-top: 1.75rem; }\n .ps-section-label:first-child { margin-top: 0; }\n .ps-item { display: flex; align-items: center; gap: 0.85rem; padding: 0.85rem 1rem; border: 1px solid #f0f0f0; border-radius: 8px; margin-bottom: 0.5rem; cursor: pointer; transition: border-color 0.15s, background 0.15s; }\n .ps-item:hover { border-color: #dee2e6; background: #fafbfd; }\n .ps-icon { width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 1rem; flex-shrink: 0; }\n .ps-info { flex: 1; min-width: 0; }\n .ps-title { font-weight: 600; font-size: 0.88rem; }\n .ps-desc { font-size: 0.78rem; color: #6c757d; }\n .ps-badge { font-size: 0.72rem; padding: 0.15em 0.5em; border-radius: 3px; flex-shrink: 0; }\n .ps-chevron { color: #ced4da; font-size: 0.8rem; flex-shrink: 0; }\n </style>\n\n <div class="ps-section-label">Authentication</div>\n\n <div class="ps-item" data-action="change-password">\n <div class="ps-icon bg-primary bg-opacity-10 text-primary"><i class="bi bi-lock"></i></div>\n <div class="ps-info">\n <div class="ps-title">Password</div>\n <div class="ps-desc">Change your account password</div>\n </div>\n <span class="ps-badge bg-light text-muted border">Change</span>\n </div>\n\n <div class="ps-item" data-action="manage-passkeys">\n <div class="ps-icon bg-success bg-opacity-10 text-success"><i class="bi bi-fingerprint"></i></div>\n <div class="ps-info">\n <div class="ps-title">Passkeys</div>\n <div class="ps-desc">Passwordless sign-in with biometrics</div>\n </div>\n <i class="bi bi-chevron-right ps-chevron"></i>\n </div>\n\n <div class="ps-item" data-action="manage-totp">\n <div class="ps-icon bg-purple bg-opacity-10" style="background: rgba(111,66,193,0.1); color: #6f42c1;"><i class="bi bi-shield-lock"></i></div>\n <div class="ps-info">\n <div class="ps-title">Authenticator App</div>\n <div class="ps-desc">Two-factor authentication with TOTP codes</div>\n </div>\n {{#model.requires_mfa|bool}}\n <span class="ps-badge bg-success bg-opacity-10 text-success border">Enabled</span>\n {{/model.requires_mfa|bool}}\n {{^model.requires_mfa|bool}}\n <span class="ps-badge bg-light text-muted border">Setup</span>\n {{/model.requires_mfa|bool}}\n </div>\n\n {{#model.requires_mfa|bool}}\n <div class="ps-item" data-action="manage-recovery-codes">\n <div class="ps-icon" style="background: rgba(111,66,193,0.1); color: #6f42c1;"><i class="bi bi-file-earmark-lock"></i></div>\n <div class="ps-info">\n <div class="ps-title">Recovery Codes</div>\n <div class="ps-desc">Backup codes for when you lose your authenticator</div>\n </div>\n <i class="bi bi-chevron-right ps-chevron"></i>\n </div>\n {{/model.requires_mfa|bool}}\n\n <div class="ps-section-label">Sessions</div>\n\n <div class="ps-item" data-action="revoke-all-sessions">\n <div class="ps-icon" style="background: rgba(220,53,69,0.1); color: #dc3545;"><i class="bi bi-box-arrow-right"></i></div>\n <div class="ps-info">\n <div class="ps-title">Revoke All Sessions</div>\n <div class="ps-desc">Sign out of all devices except this one</div>\n </div>\n </div>\n ',...e})}async onActionChangePassword(){const e=this.getApp();return e&&e.changePassword&&await e.changePassword(),!0}async onActionManagePasskeys(){const t=new PasskeyList({params:{user:this.model.id}});try{await t.fetch()}catch(a){}const s=t.models||[],n=new e({template:'\n <style>\n .pk-row { display: flex; align-items: center; gap: 0.75rem; padding: 0.65rem 0.75rem; border: 1px solid #f0f0f0; border-radius: 8px; margin-bottom: 0.4rem; }\n .pk-icon { width: 32px; height: 32px; background: #e7f1ff; border-radius: 6px; display: flex; align-items: center; justify-content: center; color: #0d6efd; font-size: 0.9rem; flex-shrink: 0; }\n .pk-info { flex: 1; min-width: 0; }\n .pk-name { font-weight: 600; font-size: 0.85rem; }\n .pk-meta { font-size: 0.73rem; color: #6c757d; }\n .pk-actions { display: flex; gap: 0.25rem; }\n .pk-actions .btn { padding: 0.2rem 0.4rem; font-size: 0.75rem; }\n .pk-empty { text-align: center; padding: 2rem 1rem; color: #6c757d; }\n .pk-empty i { font-size: 2rem; color: #ced4da; display: block; margin-bottom: 0.5rem; }\n </style>\n {{#passkeys}}\n <div class="pk-row">\n <div class="pk-icon"><i class="bi bi-fingerprint"></i></div>\n <div class="pk-info">\n <div class="pk-name">{{.friendly_name|default:\'Unnamed Passkey\'}}</div>\n <div class="pk-meta">Created {{.created|date}} &middot; Last used {{.last_used|relative|default:\'never\'}} &middot; {{.sign_count}} uses</div>\n </div>\n <div class="pk-actions">\n <button type="button" class="btn btn-outline-secondary" data-action="edit-passkey" data-id="{{.id}}" title="Edit"><i class="bi bi-pencil"></i></button>\n <button type="button" class="btn btn-outline-danger" data-action="delete-passkey" data-id="{{.id}}" title="Delete"><i class="bi bi-trash"></i></button>\n </div>\n </div>\n {{/passkeys}}\n {{^passkeys|bool}}\n <div class="pk-empty">\n <i class="bi bi-fingerprint"></i>\n No passkeys registered yet\n </div>\n {{/passkeys|bool}}\n '});return n.passkeys=s.map(e=>e.toJSON?e.toJSON():e),n.onActionEditPasskey=async(e,t)=>{const n=t.dataset.id,a=s.find(e=>String(e.id)===String(n));return a&&await i.showModelForm({title:"Edit Passkey",model:a,fields:g.edit.fields,size:"sm"}),!0},n.onActionDeletePasskey=async(e,t)=>{const n=t.dataset.id;if(await i.confirm("Delete this passkey? You won't be able to use it to sign in anymore.","Delete Passkey")){const e=s.find(e=>String(e.id)===String(n));e&&(await e.destroy(),this.getApp()?.toast?.success("Passkey deleted"))}return!0},"add"===await i.showDialog({title:"Passkeys",body:n,size:"md",buttons:[{text:"Add Passkey",icon:"bi-plus-lg",class:"btn-primary",value:"add"},{text:"Close",class:"btn-outline-secondary",dismiss:!0}]})&&await this._addPasskey(),!0}async _addPasskey(){const e=Passkey.suggestName(),t=await i.showDialog({title:'<i class="bi bi-fingerprint me-2"></i>Register a Passkey',size:"sm",centered:!0,body:`\n <div style="text-align:center; padding: 0.5rem 0 1rem;">\n <div style="width:72px; height:72px; background:linear-gradient(135deg, #e7f1ff 0%, #d0e2ff 100%); border-radius:50%; display:inline-flex; align-items:center; justify-content:center; font-size:2rem; color:#0d6efd; margin-bottom:1rem;">\n <i class="bi bi-fingerprint"></i>\n </div>\n <p style="font-size:0.85rem; color:#6c757d; margin-bottom:1.25rem; line-height:1.5;">\n Passkeys replace passwords with biometrics — fingerprint, face, or device PIN.\n The private key never leaves your device.\n </p>\n <div class="text-start" style="margin-bottom:0.25rem;">\n <label class="form-label fw-semibold" style="font-size:0.82rem;">Name this passkey</label>\n <input type="text" class="form-control" id="pss-name-input" value="${e}" placeholder="e.g., My MacBook" style="border-radius:8px;">\n <div class="form-text">A label so you can identify this passkey later.</div>\n </div>\n </div>`,buttons:[{text:"Cancel",class:"btn-secondary",dismiss:!0},{text:'<i class="bi bi-fingerprint me-1"></i>Continue',class:"btn-primary",handler:({dialog:t})=>{const i=t.element?.querySelector("#pss-name-input");return i?.value?.trim()||e}}]});if(t)try{const e=await Passkey.register(t);e.success?await PasskeySetupView.showSuccess(t):PasskeySetupView.showError(e.error)}catch(s){if("NotAllowedError"===s.name)return;"SecurityError"===s.name?PasskeySetupView.showError("Passkeys are not supported on this domain. Ensure you are using HTTPS."):(console.error("Passkey registration error:",s),PasskeySetupView.showError(s.message||"An unexpected error occurred."))}}async onActionManageTotp(){const s=this.getApp();if(this.model.get("requires_mfa")){if(!(await i.confirm("Disable authenticator app? You will no longer need a TOTP code to sign in.","Disable Authenticator")))return!0;const e=await t.DELETE("/api/account/totp");return e.success?(s?.toast?.success("Authenticator app disabled"),this.model.set("requires_mfa",!1),await this.render()):s?.toast?.error(e.message||"Failed to disable authenticator"),!0}const n=await t.POST("/api/account/totp/setup",{},{},{dataOnly:!0});if(!n.success||!n.data)return s?.toast?.error(n.message||"Failed to start authenticator setup"),!0;const{secret:a,qr_code:o}=n.data,r=new e({template:'\n <div style="text-align: center; padding: 0.5rem 0;">\n <p style="font-size: 0.85rem; color: #6c757d; margin-bottom: 1rem;">\n Scan this QR code with your authenticator app (Google Authenticator, Authy, 1Password, etc.)\n </p>\n <img src="{{{qrCode}}}" alt="TOTP QR Code" style="width: 200px; height: 200px; margin: 0 auto; display: block; border: 1px solid #e9ecef; border-radius: 8px; padding: 8px;" />\n <div style="margin-top: 1rem;">\n <p style="font-size: 0.75rem; color: #adb5bd; margin-bottom: 0.25rem;">Or enter this key manually:</p>\n <code style="font-size: 0.85rem; letter-spacing: 0.1em; user-select: all; padding: 0.35rem 0.75rem; background: #f8f9fa; border-radius: 4px;">{{secret}}</code>\n </div>\n </div>\n '});if(r.qrCode=o,r.secret=a,"next"!==await i.showDialog({title:"Set Up Authenticator",body:r,size:"sm",buttons:[{text:"Cancel",class:"btn-secondary",dismiss:!0},{text:"Next",class:"btn-primary",value:"next"}]}))return!0;const l=await i.prompt("Enter the 6-digit code from your authenticator app to verify setup:","Verify Authenticator",{placeholder:"000000"});if(!l)return!0;const d=await t.POST("/api/account/totp/confirm",{code:l.trim()});return d.success?(s?.toast?.success("Authenticator app enabled"),this.model.set("requires_mfa",!0),await this.render()):s?.toast?.error(d.message||"Invalid code. Please try setup again."),!0}async onActionManageRecoveryCodes(){const s=this.getApp(),n=await t.GET("/api/account/totp/recovery-codes",{},{dataOnly:!0});if(!n.success||!n.data)return s?.toast?.error(n.message||"Failed to load recovery codes"),!0;const{remaining:a,codes:o}=n.data,r=new e({template:'\n <style>\n .rc-info { font-size: 0.82rem; color: #6c757d; margin-bottom: 1rem; }\n .rc-remaining { font-weight: 600; color: #495057; }\n .rc-list { display: grid; grid-template-columns: 1fr 1fr; gap: 0.3rem; }\n .rc-code { font-family: monospace; font-size: 0.85rem; padding: 0.35rem 0.6rem; background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 4px; text-align: center; }\n </style>\n <div class="rc-info">\n <span class="rc-remaining">{{remaining}}</span> recovery codes remaining\n </div>\n <div class="rc-list">\n {{#codes}}\n <div class="rc-code">{{.}}</div>\n {{/codes}}\n </div>\n '});if(r.remaining=a,r.codes=o||[],"regenerate"===await i.showDialog({title:"Recovery Codes",body:r,size:"sm",buttons:[{text:"Regenerate",icon:"bi-arrow-repeat",class:"btn-warning",value:"regenerate"},{text:"Close",class:"btn-outline-secondary",dismiss:!0}]})){const n=await i.prompt("Enter your current authenticator code to regenerate recovery codes:","Regenerate Recovery Codes",{placeholder:"000000"});if(!n)return!0;const a=await t.POST("/api/account/totp/recovery-codes/regenerate",{code:n.trim()},{},{dataOnly:!0});if(a.success&&a.data?.recovery_codes){const t=a.data.recovery_codes,n=t.join("\n"),o=new e({template:'\n <style>\n .rc-warning { padding: 0.65rem 0.85rem; background: #fff3cd; border: 1px solid #ffecb5; border-radius: 6px; margin-bottom: 1rem; font-size: 0.8rem; color: #664d03; }\n .rc-new-list { display: grid; grid-template-columns: 1fr 1fr; gap: 0.3rem; margin-bottom: 1rem; }\n .rc-new-code { font-family: monospace; font-size: 0.85rem; padding: 0.35rem 0.6rem; background: #d1e7dd; border: 1px solid #badbcc; border-radius: 4px; text-align: center; }\n </style>\n <div class="rc-warning">\n <i class="bi bi-exclamation-triangle me-1"></i>\n <strong>Save these codes now.</strong> They will not be shown again. Old codes are invalidated.\n </div>\n <div class="rc-new-list">\n {{#newCodes}}\n <div class="rc-new-code">{{.}}</div>\n {{/newCodes}}\n </div>\n '});o.newCodes=t,await i.showDialog({title:"New Recovery Codes",body:o,size:"sm",buttons:[{text:"Copy All",icon:"bi-clipboard",class:"btn-primary",handler:async()=>{try{await navigator.clipboard.writeText(n),s?.toast?.success("Recovery codes copied")}catch{s?.toast?.error("Failed to copy codes")}return!1}},{text:"Done",class:"btn-outline-secondary",dismiss:!0}]})}else s?.toast?.error(a.message||"Failed to regenerate recovery codes")}return!0}async onActionRevokeAllSessions(){const e=this.getApp();if(!(await i.confirm("This will sign you out of all other devices and sessions. Your current session will remain active. This cannot be undone.","Revoke All Sessions")))return!0;const s=await i.prompt("Enter your current password to confirm:","Confirm Password",{placeholder:"Current password"});if(!s)return!0;const n=await t.POST("/api/auth/sessions/revoke",{current_password:s.trim()},{},{dataOnly:!0});return n.success&&n.data?(n.data.access_token&&e?.auth?.setTokens?.(n.data),e?.toast?.success("All other sessions have been revoked")):e?.toast?.error(n.message||"Failed to revoke sessions"),!0}async onActionNavigate(e,t){return!this.parent||!this.parent.onActionNavigate||this.parent.onActionNavigate(e,t)}}const b={google:"bi-google",github:"bi-github",microsoft:"bi-microsoft",apple:"bi-apple",facebook:"bi-facebook",twitter:"bi-twitter-x",linkedin:"bi-linkedin"};class ProfileConnectedSection extends e{constructor(e={}){super({className:"profile-connected-section",template:'\n <style>\n .pc-row { display: flex; align-items: center; gap: 0.85rem; padding: 0.85rem 1rem; border: 1px solid #f0f0f0; border-radius: 8px; margin-bottom: 0.5rem; }\n .pc-icon { width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 1rem; flex-shrink: 0; background: #f0f0f0; color: #495057; }\n .pc-info { flex: 1; min-width: 0; }\n .pc-provider { font-weight: 600; font-size: 0.88rem; text-transform: capitalize; }\n .pc-meta { font-size: 0.78rem; color: #6c757d; }\n .pc-actions { flex-shrink: 0; }\n .pc-actions .btn { font-size: 0.75rem; padding: 0.25rem 0.5rem; }\n .pc-empty { text-align: center; padding: 2rem 1rem; color: #6c757d; }\n .pc-empty i { font-size: 2rem; color: #ced4da; display: block; margin-bottom: 0.5rem; }\n </style>\n\n {{#connections}}\n <div class="pc-row">\n <div class="pc-icon"><i class="bi {{.icon}}"></i></div>\n <div class="pc-info">\n <div class="pc-provider">{{.provider}}</div>\n <div class="pc-meta">{{.email}} &middot; Connected {{.created|relative}}</div>\n </div>\n <div class="pc-actions">\n <button type="button" class="btn btn-outline-danger" data-action="unlink" data-id="{{.id}}" title="Unlink"><i class="bi bi-x-lg me-1"></i>Unlink</button>\n </div>\n </div>\n {{/connections}}\n {{^connections|bool}}\n <div class="pc-empty">\n <i class="bi bi-plug"></i>\n No connected accounts\n <div style="font-size: 0.78rem; margin-top: 0.5rem;">\n Connect a social account by signing in with a provider while logged in.\n </div>\n </div>\n {{/connections|bool}}\n ',...e}),this.connections=[]}async onBeforeRender(){try{const e=await t.GET("/api/account/oauth_connection"),i=e?.data?.results||e?.data||[];this.connections=i.map(e=>({...e,icon:b[e.provider]||"bi-link-45deg"}))}catch(e){this.connections=[]}}async onActionUnlink(e,s){const n=s.dataset.id,a=this.connections.find(e=>String(e.id)===String(n)),o=a?.provider||"this account";if(!(await i.confirm(`Unlink ${o}? You won't be able to sign in with this provider anymore.`,"Unlink Account")))return!0;const r=await t.DELETE(`/api/account/oauth_connection/${n}`);return r.success?(this.getApp()?.toast?.success(`${o} account unlinked`),await this.render()):this.getApp()?.toast?.error(r.message||"Failed to unlink account"),!0}}class SessionRow extends d{get deviceIcon(){const e=this.model?.get("user_device")?.device_info?.device||{},t=this.model?.get("user_device")?.device_info?.os||{};return["iPhone","Android"].some(i=>(e.family||"").includes(i)||(t.family||"").includes(i))?"bi-phone":"bi-laptop"}get browserName(){const e=this.model?.get("user_device")?.device_info?.user_agent||{};return e.family?`${e.family} ${e.major||""}`.trim():"Unknown"}get deviceName(){const e=this.model?.get("user_device")?.device_info?.device||{};return`${e.brand||""} ${e.family||""}`.trim()||"Unknown"}get osName(){return(this.model?.get("user_device")?.device_info?.os||{}).family||""}get locationText(){const e=this.model?.get("geolocation")||{},t=[e.city,e.region].filter(Boolean);return t.length?t.join(", "):e.country_name||"—"}get threatFlags(){const e=this.model?.get("geolocation")||{},t=[];return e.is_vpn&&t.push('<span class="badge bg-warning text-dark" style="font-size:0.6rem;">VPN</span>'),e.is_tor&&t.push('<span class="badge bg-danger" style="font-size:0.6rem;">Tor</span>'),e.is_proxy&&t.push('<span class="badge bg-warning text-dark" style="font-size:0.6rem;">Proxy</span>'),t.join(" ")}get hasThreatFlags(){const e=this.model?.get("geolocation")||{};return!!(e.is_vpn||e.is_tor||e.is_proxy)}}class ProfileSessionsSection extends e{constructor(e={}){super({className:"profile-sessions-section",template:'\n <style>\n .pss-primary { font-size: 0.85rem; font-weight: 500; }\n .pss-secondary { font-size: 0.73rem; color: #6c757d; margin-top: 0.15rem; }\n .pss-icon { color: #6c757d; font-size: 1.1rem; vertical-align: middle; margin-right: 0.35rem; }\n </style>\n <div id="sessions-table"></div>\n ',...e})}async onInit(){await super.onInit(),this.tableView=new l({containerId:"sessions-table",collection:new o({size:10}),defaultQuery:{user:this.model.id},searchable:!1,filterable:!1,selectable:!1,actions:null,clickAction:"view",itemClass:SessionRow,columns:[{key:"user_device",label:"Session",template:'\n <div class="pss-primary">\n <i class="bi {{deviceIcon}} pss-icon"></i>{{browserName}} <span class="text-muted fw-normal">on</span> {{deviceName}}\n </div>\n <div class="pss-secondary">\n <i class="bi bi-geo-alt me-1"></i>{{locationText}}\n <span class="text-muted mx-1">&middot;</span>\n {{model.ip_address}}\n {{#hasThreatFlags|bool}} <span class="ms-1">{{{threatFlags}}}</span>{{/hasThreatFlags|bool}}\n </div>'},{key:"last_seen",label:"Last Seen",formatter:"relative"}],onItemView:e=>this._showSessionDetail(e)}),this.addChild(this.tableView)}_showSessionDetail(e){const t=e.toJSON?e.toJSON():e,s=t.user_device||{},n=s.device_info?.device||{},a=s.device_info?.user_agent||{},o=s.device_info?.os||{},r=t.geolocation||{},l=a.family?`${a.family} ${[a.major,a.minor,a.patch].filter(Boolean).join(".")}`:"Unknown",d=`${n.brand||""} ${n.family||""}`.trim()||"Unknown",c=o.family?`${o.family} ${[o.major,o.minor,o.patch].filter(Boolean).join(".")}`:"—",p=[r.city,r.region,r.country_name].filter(Boolean).join(", ")||"—",m=[];r.is_vpn&&m.push('<span class="badge bg-warning text-dark">VPN</span>'),r.is_tor&&m.push('<span class="badge bg-danger">Tor</span>'),r.is_proxy&&m.push('<span class="badge bg-warning text-dark">Proxy</span>'),r.is_datacenter&&m.push('<span class="badge bg-secondary">Datacenter</span>'),r.is_known_attacker&&m.push('<span class="badge bg-danger">Known Attacker</span>');const u=(e,t)=>`\n <div style="display:flex; padding:0.4rem 0; border-bottom:1px solid #f0f0f0;">\n <div style="width:120px; font-size:0.8rem; color:#6c757d; flex-shrink:0;">${e}</div>\n <div style="flex:1; font-size:0.85rem;">${t||"—"}</div>\n </div>`;i.showDialog({title:`<i class="bi bi-clock-history me-2"></i>${l} on ${d}`,size:"sm",centered:!0,body:`\n <div style="font-size:0.85rem;">\n ${u("Browser",l)}\n ${u("Device",d)}\n ${u("OS",c)}\n ${u("IP Address",t.ip_address)}\n ${u("Location",p)}\n ${u("ISP",r.isp||r.asn_org||"—")}\n ${u("ASN",r.asn||"—")}\n ${u("Threat Level",r.threat_level||"—")}\n ${m.length?u("Flags",m.join(" ")):""}\n ${u("First Seen",t.first_seen?new Date(1e3*t.first_seen).toLocaleString():"—")}\n ${u("Last Seen",t.last_seen?new Date(1e3*t.last_seen).toLocaleString():"—")}\n </div>`,buttons:[{text:"Close",class:"btn-outline-secondary",dismiss:!0}]})}}class DeviceRow extends d{get deviceIcon(){const e=this.model?.get("device_info")?.device||{},t=this.model?.get("device_info")?.os||{};return["iPhone","Android"].some(i=>(e.family||"").includes(i)||(t.family||"").includes(i))?"bi-phone":"bi-laptop"}get deviceName(){const e=this.model?.get("device_info")?.device||{};return`${e.brand||""} ${e.family||""}`.trim()||"Unknown Device"}get deviceModel(){return this.model?.get("device_info")?.device?.model||""}get browserName(){const e=this.model?.get("device_info")?.user_agent||{};return e.family?`${e.family} ${e.major||""}`.trim():""}get osName(){const e=this.model?.get("device_info")?.os||{};return e.family?`${e.family} ${e.major||""}`.trim():""}get deviceMeta(){return[this.browserName,this.osName].filter(Boolean).join(" · ")||"—"}}class ProfileDevicesSection extends e{constructor(e={}){super({className:"profile-devices-section",template:'\n <style>\n .pds-primary { font-size: 0.85rem; font-weight: 500; }\n .pds-model { font-weight: 400; color: #6c757d; }\n .pds-secondary { font-size: 0.73rem; color: #6c757d; margin-top: 0.15rem; }\n .pds-icon { color: #6c757d; font-size: 1.1rem; vertical-align: middle; margin-right: 0.35rem; }\n </style>\n <div id="devices-table"></div>\n ',...e})}async onInit(){await super.onInit(),this.tableView=new l({containerId:"devices-table",collection:new r({size:10}),defaultQuery:{user:this.model.id},searchable:!1,filterable:!1,selectable:!1,actions:null,clickAction:"view",itemClass:DeviceRow,columns:[{key:"device_info",label:"Device",template:'\n <div class="pds-primary">\n <i class="bi {{deviceIcon}} pds-icon"></i>{{deviceName}}\n {{#deviceModel}} <span class="pds-model">({{deviceModel}})</span>{{/deviceModel}}\n </div>\n <div class="pds-secondary">\n {{deviceMeta}}\n {{#model.last_ip}} <span class="text-muted mx-1">&middot;</span> {{model.last_ip}}{{/model.last_ip}}\n </div>'},{key:"last_seen",label:"Last Seen",formatter:"relative"}],onItemView:e=>this._showDeviceDetail(e)}),this.addChild(this.tableView)}_showDeviceDetail(e){const t=e.toJSON?e.toJSON():e,s=t.device_info||{},n=s.device||{},a=s.user_agent||{},o=s.os||{},r=`${n.brand||""} ${n.family||""}`.trim()||"Unknown",l=a.family?`${a.family} ${[a.major,a.minor,a.patch].filter(Boolean).join(".")}`:"Unknown",d=o.family?`${o.family} ${[o.major,o.minor,o.patch].filter(Boolean).join(".")}`:"—",c=["iPhone","Android"].some(e=>(n.family||"").includes(e)||(o.family||"").includes(e)),p=(e,t)=>`\n <div style="display:flex; padding:0.4rem 0; border-bottom:1px solid #f0f0f0;">\n <div style="width:120px; font-size:0.8rem; color:#6c757d; flex-shrink:0;">${e}</div>\n <div style="flex:1; font-size:0.85rem;">${t||"—"}</div>\n </div>`;i.showDialog({title:`<i class="bi ${c?"bi-phone":"bi-laptop"} me-2"></i>${r}`,size:"sm",centered:!0,body:`\n <div style="font-size:0.85rem;">\n ${p("Device",`${r} (${n.model||"—"})`)}\n ${p("Browser",l)}\n ${p("OS",d)}\n ${p("Last IP",t.last_ip)}\n ${p("Type",c?"Mobile":"Desktop")}\n ${p("First Seen",t.first_seen?new Date(1e3*t.first_seen).toLocaleString():"—")}\n ${p("Last Seen",t.last_seen?new Date(1e3*t.last_seen).toLocaleString():"—")}\n </div>`,buttons:[{text:"Close",class:"btn-outline-secondary",dismiss:!0}]})}}class SecurityEvent extends s{constructor(e={},t={}){super(e,{endpoint:"/api/account/security-events",...t})}}class SecurityEventList extends n{constructor(e={}){super({ModelClass:SecurityEvent,endpoint:"/api/account/security-events",size:15,...e})}}const h=["invalid_password","totp:login_failed","totp:confirm_failed","passkey:login_failed","sessions:revoke_failed","email_change:invalid","email_change:expired"],v=["login","oauth","email_verify:confirmed","email_verify:confirmed_code","phone_verify:confirmed","phone_change:confirmed","username:changed"];class SecurityEventRow extends d{get kindBadgeClass(){const e=this.model?.get("kind")||"";return h.includes(e)?"bg-danger":v.includes(e)?"bg-success":"bg-secondary"}get kindLabel(){return(this.model?.get("kind")||"").replace(/[_:]/g," ").replace(/\b\w/g,e=>e.toUpperCase())}get kindIcon(){const e=this.model?.get("kind")||"";return h.includes(e)?"bi-exclamation-triangle":v.includes(e)?"bi-check-circle":"bi-info-circle"}}class ProfileSecurityEventsSection extends e{constructor(e={}){super({className:"profile-security-events-section",template:'<div id="security-events-table"></div>',...e})}async onInit(){await super.onInit(),this.tableView=new l({containerId:"security-events-table",collection:new SecurityEventList,defaultQuery:{sort:"-created"},hideActivePillNames:["sort"],itemClass:SecurityEventRow,columns:[{key:"kind",label:"Event",template:'<span class="badge {{kindBadgeClass}}"><i class="bi {{kindIcon}} me-1"></i>{{kindLabel}}</span>'},{key:"summary",label:"Details"},{key:"ip",label:"IP",visibility:"lg"},{key:"created|relative",label:"Time",sortable:!0}],searchable:!0,sortable:!0,filterable:!1,paginated:!0,showAdd:!1,showExport:!1,tableOptions:{striped:!1,hover:!0,size:"sm"},emptyMessage:"No security events"}),this.addChild(this.tableView)}}const y={in_app:"In-App",email:"Email",push:"Push"},w=["in_app","email","push"];class ProfileNotificationsSection extends e{constructor(e={}){super({className:"profile-notifications-section",template:'\n <style>\n .pn-table { width: 100%; border-collapse: collapse; }\n .pn-table th { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #adb5bd; padding: 0.5rem 0.75rem; border-bottom: 2px solid #e9ecef; }\n .pn-table th:first-child { text-align: left; }\n .pn-table th:not(:first-child) { text-align: center; width: 80px; }\n .pn-table td { padding: 0.65rem 0.75rem; border-bottom: 1px solid #f0f0f0; }\n .pn-table td:first-child { font-size: 0.88rem; font-weight: 500; text-transform: capitalize; }\n .pn-table td:not(:first-child) { text-align: center; }\n .pn-table tr:last-child td { border-bottom: none; }\n .pn-empty { text-align: center; padding: 2rem 1rem; color: #6c757d; }\n .pn-empty i { font-size: 2rem; color: #ced4da; display: block; margin-bottom: 0.5rem; }\n </style>\n\n {{#hasPreferences|bool}}\n <table class="pn-table">\n <thead>\n <tr>\n <th>Type</th>\n {{#channels}}\n <th>{{.label}}</th>\n {{/channels}}\n </tr>\n </thead>\n <tbody>\n {{#preferenceRows}}\n <tr>\n <td>{{.kindLabel}}</td>\n {{#.toggles}}\n <td>\n <input type="checkbox" class="form-check-input"\n data-action="toggle-pref"\n data-kind="{{.kind}}"\n data-channel="{{.channel}}"\n {{#.checked}}checked{{/.checked}}>\n </td>\n {{/.toggles}}\n </tr>\n {{/preferenceRows}}\n </tbody>\n </table>\n {{/hasPreferences|bool}}\n {{^hasPreferences|bool}}\n <div class="pn-empty">\n <i class="bi bi-bell"></i>\n No notification preferences configured\n <div style="font-size: 0.78rem; margin-top: 0.5rem;">\n Preferences will appear here once notification types are defined.\n </div>\n </div>\n {{/hasPreferences|bool}}\n ',...e}),this.preferences={}}get channels(){return w.map(e=>({key:e,label:y[e]||e}))}get hasPreferences(){return Object.keys(this.preferences).length>0}get preferenceRows(){return Object.keys(this.preferences).sort().map(e=>({kind:e,kindLabel:e.replace(/[_-]/g," ").replace(/\b\w/g,e=>e.toUpperCase()),toggles:w.map(t=>({kind:e,channel:t,checked:!1!==this.preferences[e]?.[t]}))}))}async onBeforeRender(){try{const e=await t.GET("/api/account/notification/preferences",{},{dataOnly:!0});this.preferences=e?.data?.preferences||e?.data||{}}catch(e){this.preferences={}}}async onActionTogglePref(e,i){const s=i.dataset.kind,n=i.dataset.channel,a=i.checked;this.preferences[s]||(this.preferences[s]={}),this.preferences[s][n]=a;try{const e=await t.POST("/api/account/notification/preferences",{preferences:{[s]:{[n]:a}}});e.success||(this.getApp()?.toast?.error(e.message||"Failed to update preference"),i.checked=!a)}catch(o){this.getApp()?.toast?.error("Failed to update preference"),i.checked=!a}return!0}}class ProfileApiKeysSection extends e{constructor(e={}){super({className:"profile-api-keys-section",template:'\n <style>\n .pak-warning { padding: 0.75rem 1rem; background: #fff3cd; border: 1px solid #ffecb5; border-radius: 8px; margin-bottom: 1.25rem; font-size: 0.82rem; color: #664d03; display: flex; align-items: flex-start; gap: 0.6rem; }\n .pak-warning i { font-size: 1rem; flex-shrink: 0; margin-top: 0.1rem; }\n .pak-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; }\n .pak-header h6 { margin: 0; font-weight: 600; }\n .pak-list { border: 1px solid #e9ecef; border-radius: 8px; overflow: hidden; }\n .pak-item { display: flex; align-items: center; padding: 0.75rem 1rem; border-bottom: 1px solid #f0f0f0; gap: 1rem; }\n .pak-item:last-child { border-bottom: none; }\n .pak-item-icon { color: #6c757d; font-size: 1.1rem; flex-shrink: 0; }\n .pak-item-info { flex: 1; min-width: 0; }\n .pak-item-name { font-weight: 600; font-size: 0.85rem; }\n .pak-item-meta { font-size: 0.75rem; color: #6c757d; display: flex; gap: 1rem; flex-wrap: wrap; margin-top: 0.15rem; }\n .pak-item-actions { flex-shrink: 0; }\n .pak-empty { padding: 2rem; text-align: center; color: #6c757d; font-size: 0.85rem; }\n .pak-result { padding: 1rem; background: #d1e7dd; border: 1px solid #badbcc; border-radius: 8px; margin-bottom: 1rem; }\n .pak-result-label { font-size: 0.75rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #0f5132; margin-bottom: 0.5rem; }\n .pak-token-wrap { display: flex; gap: 0.5rem; align-items: center; }\n .pak-token { flex: 1; font-family: monospace; font-size: 0.78rem; padding: 0.5rem 0.75rem; background: #fff; border: 1px solid #dee2e6; border-radius: 4px; word-break: break-all; max-height: 80px; overflow-y: auto; }\n .pak-token-warning { font-size: 0.75rem; color: #dc3545; margin-top: 0.5rem; font-weight: 600; }\n </style>\n\n <div class="pak-warning">\n <i class="bi bi-exclamation-triangle-fill"></i>\n <div>\n <strong>Treat API keys like passwords.</strong> They carry your full account permissions.\n Store them securely and never expose them in client-side code.\n </div>\n </div>\n\n <div id="pak-new-token" style="display: none;">\n <div class="pak-result">\n <div class="pak-result-label">Your New API Key</div>\n <div class="pak-token-wrap">\n <div class="pak-token" id="pak-token-display"></div>\n <button type="button" class="btn btn-outline-secondary btn-sm" data-action="copy-token" title="Copy">\n <i class="bi bi-clipboard"></i>\n </button>\n </div>\n <div class="pak-token-warning">\n <i class="bi bi-exclamation-circle me-1"></i>This token will not be shown again. Copy it now.\n </div>\n </div>\n </div>\n\n <div class="pak-header">\n <h6>Your API Keys</h6>\n <button type="button" class="btn btn-primary btn-sm" data-action="generate-key">\n <i class="bi bi-plus-lg me-1"></i>Generate Key\n </button>\n </div>\n\n <div id="pak-keys-list"></div>\n ',...e}),this.apiKeys=[],this.generatedToken=null}async onBeforeRender(){await this._loadKeys()}async _loadKeys(){const e=await t.GET("/api/account/api_keys",{},{},{dataOnly:!0});this.apiKeys=e.success&&Array.isArray(e.data)?e.data:[]}onAfterRender(){this._renderKeysList()}_renderKeysList(){const e=this.element?.querySelector("#pak-keys-list");if(!e)return;if(!this.apiKeys.length)return void(e.innerHTML='\n <div class="pak-list">\n <div class="pak-empty">\n <i class="bi bi-key" style="font-size: 1.5rem; display: block; margin-bottom: 0.5rem;"></i>\n No API keys yet. Generate one to get started.\n </div>\n </div>');const t=this.apiKeys.map(e=>{const t=e.name||"API Key",i=e.created?new Date(1e3*e.created).toLocaleDateString():"",s=e.expires?new Date(1e3*e.expires).toLocaleDateString():"Never",n=e.last_used?new Date(1e3*e.last_used).toLocaleDateString():"Never",a=e.allowed_ips?.length?e.allowed_ips.join(", "):"Any";return`\n <div class="pak-item">\n <div class="pak-item-icon"><i class="bi bi-key"></i></div>\n <div class="pak-item-info">\n <div class="pak-item-name">${t} ${!1!==e.is_active?'<span class="badge bg-success">Active</span>':'<span class="badge bg-secondary">Inactive</span>'}</div>\n <div class="pak-item-meta">\n <span><i class="bi bi-code-square me-1"></i>${e.token_prefix?`${e.token_prefix}...`:"••••••••"}</span>\n <span><i class="bi bi-calendar me-1"></i>Created ${i}</span>\n <span><i class="bi bi-clock me-1"></i>Expires ${s}</span>\n <span><i class="bi bi-activity me-1"></i>Last used ${n}</span>\n <span><i class="bi bi-globe me-1"></i>IPs: ${a}</span>\n </div>\n </div>\n <div class="pak-item-actions">\n <button type="button" class="btn btn-outline-danger btn-sm" data-action="delete-key" data-id="${e.id}" title="Delete">\n <i class="bi bi-trash"></i>\n </button>\n </div>\n </div>`}).join("");e.innerHTML=`<div class="pak-list">${t}</div>`}async onActionGenerateKey(){const e=await i.showForm({title:"Generate API Key",icon:"bi-key",fields:[{name:"name",type:"text",label:"Key Name",placeholder:"e.g., CI/CD Pipeline, Mobile App",required:!0,help:"A descriptive name to identify this key."},{name:"allowed_ips",type:"text",label:"Allowed IPs",placeholder:"e.g., 203.0.113.0/24, 10.0.0.1",help:"Optional. Comma-separated IP addresses or CIDR ranges."},{name:"expire_days",type:"select",label:"Expiration",value:"90",options:[{value:"30",label:"30 days"},{value:"60",label:"60 days"},{value:"90",label:"90 days"},{value:"180",label:"180 days"},{value:"360",label:"360 days"}]}]});if(!e)return!0;const s={name:e.name,expire_days:parseInt(e.expire_days||"90",10)},n=(e.allowed_ips||"").trim();n&&(s.allowed_ips=n.split(",").map(e=>e.trim()).filter(Boolean));const a=await t.POST("/api/auth/generate_api_key",s,{},{dataOnly:!0});if(a.success&&a.data?.token){this.generatedToken=a.data.token;const e=this.element.querySelector("#pak-new-token"),t=this.element.querySelector("#pak-token-display");e&&t&&(t.textContent=this.generatedToken,e.style.display="block"),this.getApp()?.toast?.success("API key generated"),await this._loadKeys(),this._renderKeysList()}else this.getApp()?.toast?.error(a.message||"Failed to generate API key");return!0}async onActionDeleteKey(e){const s=e.dataset.id;if(!s)return!0;if(!(await i.confirm("Are you sure you want to delete this API key? Any applications using it will lose access immediately.","Delete API Key")))return!0;const n=await t.DELETE(`/api/account/api_keys/${s}`,{},{},{dataOnly:!0});if(n.success){this.getApp()?.toast?.success("API key deleted");const e=this.element.querySelector("#pak-new-token");e&&(e.style.display="none"),await this._loadKeys(),this._renderKeysList()}else this.getApp()?.toast?.error(n.message||"Failed to delete API key");return!0}async onActionCopyToken(){if(this.generatedToken)try{await navigator.clipboard.writeText(this.generatedToken),this.getApp()?.toast?.success("Token copied to clipboard")}catch{this.getApp()?.toast?.error("Failed to copy token")}return!0}}const k=["#667eea","#f5576c","#38b2ac","#ed8936","#9f7aea","#48bb78","#4299e1","#ed64a6"];class GroupMemberItem extends m{get groupName(){return this.model?.get("group")?.name||"Unknown Group"}get groupKind(){return(this.model?.get("group")?.kind||"").replace(/^\w/,e=>e.toUpperCase())}get initials(){return this.groupName.split(/\s+/).map(e=>e[0]).join("").substring(0,2).toUpperCase()}get avatarColor(){const e=this.groupName;let t=0;for(let i=0;i<e.length;i++)t=e.charCodeAt(i)+((t<<5)-t);return k[Math.abs(t)%k.length]}get roleName(){let e=this.model?.get("metadata")?.role||"";return!e&&this.model?.get("permissions")?.manage_group&&(e="Admin"),e}get hasRole(){return!!this.roleName}get roleBadgeClass(){const e=(this.roleName||"").toLowerCase();return"owner"===e?"bg-primary":"admin"===e?"bg-info":"bg-secondary"}get permissionsList(){const e=this.model?.get("permissions");return e?Object.keys(e).filter(t=>!0===e[t]).map(e=>e.replace(/_/g," ").replace(/\b\w/g,e=>e.toUpperCase())):[]}get hasPermissions(){return this.permissionsList.length>0}}class ProfileGroupsSection extends e{constructor(e={}){super({className:"profile-groups-section",template:'\n <style>\n .pg-row { display: flex; align-items: center; gap: 0.75rem; padding: 0.65rem 0; border-bottom: 1px solid #f0f0f0; }\n .pg-row:last-child { border-bottom: none; }\n .pg-avatar { width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 0.75rem; font-weight: 700; color: #fff; flex-shrink: 0; }\n .pg-info { flex: 1; }\n .pg-name { font-weight: 600; font-size: 0.88rem; }\n .pg-meta { font-size: 0.73rem; color: #6c757d; }\n .pg-role { font-size: 0.7rem; }\n .pg-perms { display: flex; flex-wrap: wrap; gap: 0.25rem; justify-content: flex-end; }\n .pg-perm-tag { display: inline-flex; align-items: center; font-size: 0.68rem; padding: 0.15em 0.45em; background: #f0f4ff; border: 1px solid #d4deff; border-radius: 3px; color: #4a6cf7; }\n </style>\n <div id="groups-list"></div>\n ',...e})}async onInit(){await super.onInit(),this.listView=new p({containerId:"groups-list",collection:new c({size:50}),defaultQuery:{user:this.model.id},itemClass:GroupMemberItem,itemTemplate:'\n <div class="pg-row">\n <div class="pg-avatar" style="background: {{avatarColor}};">{{initials}}</div>\n <div class="pg-info">\n <div class="pg-name">{{groupName}}</div>\n <div class="pg-meta">{{groupKind}}</div>\n </div>\n {{#hasPermissions|bool}}\n <div class="pg-perms">\n {{#permissionsList}}\n <span class="pg-perm-tag">{{.}}</span>\n {{/permissionsList}}\n </div>\n {{/hasPermissions|bool}}\n {{#hasRole|bool}}\n <span class="pg-role badge {{roleBadgeClass}}">{{roleName}}</span>\n {{/hasRole|bool}}\n </div>\n ',emptyMessage:"You are not a member of any groups"}),this.addChild(this.listView)}}class ProfilePermissionsSection extends e{constructor(e={}){super({className:"profile-permissions-section",template:'\n <style>\n .pp-role-bar { display: flex; align-items: center; gap: 0.75rem; padding: 0.65rem 1rem; background: #f0f0ff; border-radius: 8px; margin-bottom: 1.25rem; font-size: 0.85rem; }\n .pp-role-bar i { color: #6f42c1; font-size: 1rem; }\n .pp-role-bar strong { color: #6f42c1; }\n .pp-section-label { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #adb5bd; margin-bottom: 0.5rem; margin-top: 1.75rem; }\n .pp-section-label:first-child { margin-top: 0; }\n .pp-grid { display: flex; flex-wrap: wrap; gap: 0.3rem; margin-bottom: 1.25rem; }\n .pp-tag { display: inline-flex; align-items: center; gap: 0.3rem; font-size: 0.78rem; padding: 0.25em 0.6em; background: #f8f9fc; border: 1px solid #e9ecef; border-radius: 4px; color: #495057; }\n .pp-tag i { font-size: 0.65rem; color: #198754; }\n .pp-tag-group { background: #e7f1ff; border-color: #b6d4fe; }\n .pp-tag-group i { color: #0d6efd; }\n .pp-note { font-size: 0.78rem; color: #adb5bd; margin-top: 1rem; }\n .pp-empty { color: #6c757d; font-style: italic; font-size: 0.85rem; padding: 0.5rem 0; }\n .pp-group-header { display: flex; align-items: center; gap: 0.5rem; }\n .pp-group-name { font-size: 0.78rem; font-weight: 400; color: #6c757d; }\n </style>\n\n {{#model.is_superuser|bool}}\n <div class="pp-role-bar">\n <i class="bi bi-star-fill"></i>\n <span><strong>Superuser</strong> &mdash; full system access</span>\n </div>\n {{/model.is_superuser|bool}}\n\n <div class="pp-section-label">System Permissions</div>\n\n {{#systemPermissions|bool}}\n <div class="pp-grid">\n {{#systemPermissions}}\n <span class="pp-tag"><i class="bi bi-check-circle-fill"></i> {{.}}</span>\n {{/systemPermissions}}\n </div>\n {{/systemPermissions|bool}}\n\n {{^systemPermissions|bool}}\n <div class="pp-empty">No system permissions assigned</div>\n {{/systemPermissions|bool}}\n\n {{#hasActiveGroup|bool}}\n <div class="pp-section-label">\n <div class="pp-group-header">\n <span>Group Permissions</span>\n <span class="pp-group-name">&mdash; {{activeGroupName}}</span>\n </div>\n </div>\n\n {{#groupPermissions|bool}}\n <div class="pp-grid">\n {{#groupPermissions}}\n <span class="pp-tag pp-tag-group"><i class="bi bi-check-circle-fill"></i> {{.}}</span>\n {{/groupPermissions}}\n </div>\n {{/groupPermissions|bool}}\n\n {{^groupPermissions|bool}}\n <div class="pp-empty">No group permissions assigned</div>\n {{/groupPermissions|bool}}\n {{/hasActiveGroup|bool}}\n\n <div class="pp-note">\n <i class="bi bi-info-circle me-1"></i>\n Permissions are managed by your administrator.\n </div>\n ',...e})}get systemPermissions(){if(!this.model)return[];const e=this.model.get("permissions");if(!e)return[];const t={};return a.PERMISSIONS.forEach(e=>{t[e.name]=e.label}),Object.keys(e).filter(t=>!0===e[t]).map(e=>t[e]||e.replace(/_/g," ").replace(/\b\w/g,e=>e.toUpperCase()))}get hasActiveGroup(){const e=this.getApp();return!(!e?.activeGroup||!this.model?.member)}get activeGroupName(){const e=this.getApp();return e?.activeGroup?.get("name")||e?.activeGroup?.get("display_name")||"Current Group"}get groupPermissions(){if(!this.model?.member)return[];const e=this.model.member.get("permissions");return e?Object.keys(e).filter(t=>!0===e[t]).map(e=>e.replace(/_/g," ").replace(/\b\w/g,e=>e.toUpperCase())):[]}}const x={profile:ProfileOverviewSection,personal:ProfilePersonalSection,security:ProfileSecuritySection,connected:ProfileConnectedSection,sessions:ProfileSessionsSection,devices:ProfileDevicesSection,security_events:ProfileSecurityEventsSection,notifications:ProfileNotificationsSection,api_keys:ProfileApiKeysSection,groups:ProfileGroupsSection,permissions:ProfilePermissionsSection};class UserProfileView extends e{constructor(e={}){super({className:"user-profile-view",template:'\n <style>\n .up-layout { display: flex; height: 100%; }\n .up-nav { width: 200px; background: #f8f9fc; border-right: 1px solid #e9ecef; padding: 0.75rem 0; flex-shrink: 0; overflow-y: auto; }\n .up-nav-label { font-size: 0.65rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #adb5bd; padding: 0.75rem 1.25rem 0.25rem; }\n .up-nav a { color: #495057; padding: 0.45rem 1.25rem; font-size: 0.85rem; display: flex; align-items: center; gap: 0.5rem; text-decoration: none; }\n .up-nav a:hover { background: #e9ecef; }\n .up-nav a.active { background: #e7f1ff; color: #0d6efd; font-weight: 600; border-right: 2px solid #0d6efd; }\n .up-nav a i { width: 18px; text-align: center; font-size: 0.9rem; }\n .up-content { flex: 1; overflow-y: auto; padding: 1.5rem 2.5rem; }\n .up-accent { height: 4px; background: linear-gradient(90deg, #1a73e8, #4fc3f7); border-radius: var(--bs-modal-border-radius, 0.5rem) var(--bs-modal-border-radius, 0.5rem) 0 0; }\n .up-header { display: flex; align-items: center; gap: 1rem; padding: 1rem 1.5rem; border-bottom: 1px solid #e9ecef; }\n .up-avatar-wrap { position: relative; flex-shrink: 0; cursor: pointer; }\n .up-avatar-wrap img { width: 56px; height: 56px; border-radius: 50%; object-fit: cover; }\n .up-avatar-initials { width: 56px; height: 56px; border-radius: 50%; background: #e7f1ff; color: #0d6efd; display: flex; align-items: center; justify-content: center; font-size: 1.3rem; font-weight: 700; }\n .up-header-info { flex: 1; min-width: 0; }\n .up-header-name { display: flex; align-items: center; gap: 0.5rem; }\n .up-header-name h5 { margin: 0; font-weight: 700; font-size: 1.05rem; }\n .up-header-badge { font-size: 0.65rem; padding: 0.15em 0.5em; border-radius: 3px; font-weight: 600; }\n .up-header-badge-staff { background: #e7f1ff; color: #0d6efd; }\n .up-header-badge-su { background: #fff3cd; color: #856404; }\n .up-header-sub { font-size: 0.78rem; color: #6c757d; display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }\n .up-header-sub .up-dot { color: #ced4da; }\n .up-header-verified { display: inline-flex; align-items: center; gap: 0.2rem; font-size: 0.72rem; color: #198754; }\n .up-header-verified i { font-size: 0.7rem; }\n .up-close { flex-shrink: 0; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; border: none; background: none; color: #6c757d; font-size: 1.1rem; border-radius: 6px; cursor: pointer; padding: 0; align-self: flex-start; margin-top: -0.15rem; }\n .up-close:hover { background: #f0f0f0; color: #212529; }\n @media (max-width: 576px) {\n .up-nav { display: none; }\n .up-content { padding: 1.25rem; }\n }\n </style>\n <div class="up-layout" style="flex-direction: column; min-height: 480px;">\n <div class="up-accent"></div>\n <div class="up-header">\n <div class="up-avatar-wrap" data-action="change-avatar" title="Change avatar">\n {{{model.avatar|avatar}}}\n </div>\n <div class="up-header-info">\n <div class="up-header-name">\n <h5>{{model.display_name}}</h5>\n {{#model.is_superuser|bool}}<span class="up-header-badge up-header-badge-su">Superuser</span>{{/model.is_superuser|bool}}\n {{^model.is_superuser|bool}}\n {{#model.is_staff|bool}}<span class="up-header-badge up-header-badge-staff">Staff</span>{{/model.is_staff|bool}}\n {{/model.is_superuser|bool}}\n </div>\n <div class="up-header-sub">\n <span>{{model.email}}</span>\n {{#model.is_email_verified|bool}}\n <span class="up-dot">&middot;</span>\n <span class="up-header-verified"><i class="bi bi-patch-check-fill"></i> Email</span>\n {{/model.is_email_verified|bool}}\n {{#model.requires_mfa|bool}}\n <span class="up-dot">&middot;</span>\n <span class="up-header-verified"><i class="bi bi-shield-fill-check"></i> MFA</span>\n {{/model.requires_mfa|bool}}\n </div>\n </div>\n <button type="button" class="up-close" data-action="close-dialog" title="Close"><i class="bi bi-x-lg"></i></button>\n </div>\n <div class="up-layout" style="flex: 1; min-height: 0;">\n <nav class="up-nav">\n <a href="#" class="nav-link active" data-action="navigate" data-section="profile"><i class="bi bi-person"></i> Profile</a>\n <a href="#" class="nav-link" data-action="navigate" data-section="personal"><i class="bi bi-person-vcard"></i> Personal</a>\n <a href="#" class="nav-link" data-action="navigate" data-section="security"><i class="bi bi-shield-lock"></i> Security</a>\n <a href="#" class="nav-link" data-action="navigate" data-section="connected"><i class="bi bi-plug"></i> Connected</a>\n <div class="up-nav-label">Activity</div>\n <a href="#" class="nav-link" data-action="navigate" data-section="sessions"><i class="bi bi-clock-history"></i> Sessions</a>\n <a href="#" class="nav-link" data-action="navigate" data-section="devices"><i class="bi bi-laptop"></i> Devices</a>\n <a href="#" class="nav-link" data-action="navigate" data-section="security_events"><i class="bi bi-shield-exclamation"></i> Security Events</a>\n <div class="up-nav-label">Settings</div>\n <a href="#" class="nav-link" data-action="navigate" data-section="notifications"><i class="bi bi-bell"></i> Notifications</a>\n <a href="#" class="nav-link" data-action="navigate" data-section="api_keys"><i class="bi bi-key"></i> API Keys</a>\n <a href="#" class="nav-link" data-action="navigate" data-section="groups"><i class="bi bi-people"></i> Groups</a>\n <a href="#" class="nav-link" data-action="navigate" data-section="permissions"><i class="bi bi-shield-check"></i> Permissions</a>\n </nav>\n <div class="up-content" id="profile-section"></div>\n </div>\n </div>\n ',...e}),this.activeSection="profile",this.sectionView=null}get hasAvatar(){return!!(this.model&&this.model.get("avatar")&&this.model.get("avatar").url)}async onBeforeRender(){this.model&&await this.model.fetch({params:{graph:"full"}})}async onAfterRender(){this.element?.querySelectorAll(".up-nav a").forEach(e=>{e.classList.toggle("active",e.dataset.section===this.activeSection)})}async onInit(){await super.onInit(),this.sectionView=new ProfileOverviewSection({model:this.model,containerId:"profile-section"}),this.addChild(this.sectionView)}async onActionNavigate(e,t){e.preventDefault();const i=t.dataset.section;if(!i||i===this.activeSection)return!0;const s=x[i];return!s||(this.sectionView&&this.removeChild(this.sectionView),this.sectionView=new s({model:this.model,containerId:"profile-section"}),this.addChild(this.sectionView),await this.sectionView.render(),this.activeSection=i,this.element.querySelectorAll(".up-nav a").forEach(e=>{e.classList.toggle("active",e.dataset.section===i)}),!0)}async onActionChangeAvatar(){const e=await i.updateModelImage({model:this.model,field:"avatar",title:"Change Avatar",upload:!0},{name:"avatar",size:"lg",imageSize:{width:200,height:200},placeholder:"Upload your avatar"});e&&200===e.status&&await this.render()}async onActionCloseDialog(){const e=this.element?.closest(".modal");if(e){const t=bootstrap?.Modal?.getInstance(e);t&&t.hide()}return!0}async onActionUpdateEmail(){const e=this.getApp(),t=e.rest,s=await i.showForm({title:"Change Email Address",size:"sm",submitText:"Send Code",fields:[{name:"new_email_address",type:"text",label:"New Email Address",required:!0,placeholder:"Enter new email address",attributes:{autocomplete:"off",inputmode:"email"},cols:12}]});if(!s)return!0;const n=await t.POST("/api/auth/email/change/request",{email:s.new_email_address,method:"code"});if(!n.success)return e.toast.error(n.message||"Failed to request email change"),!0;const a=await i.prompt(`Enter the 6-digit code sent to <strong>${s.new_email_address}</strong>`,"Confirm Email Change",{placeholder:"000000"});if(!a)return!0;const o=await t.POST("/api/auth/email/change/confirm",{code:a.trim()},{},{dataOnly:!0});return o.success&&o.data?(o.data.access_token&&e.auth?.setTokens?.(o.data),e.toast.success("Email address updated"),await this.model.fetch({params:{graph:"full"}}),await this.render()):e.toast.error(o.message||"Invalid or expired code"),!0}async onActionUpdatePhone(){const e=this.getApp(),t=e.rest;if(!this.model.get("phone_number"))return e.toast.info("Use the profile section to add a phone number"),!0;const s=await i.showForm({title:"Change Phone Number",size:"sm",submitText:"Send Code",fields:[{name:"new_phone",type:"tel",label:"New Phone Number",required:!0,placeholder:"(415) 555-0123",attributes:{autocomplete:"off"},cols:12}]});if(!s)return!0;const n=await t.POST("/api/auth/phone/change/request",{phone_number:s.new_phone},{},{dataOnly:!0});if(!n.success)return e.toast.error(n.message||"Failed to request phone change"),!0;const a=n.data?.session_token,o=await i.prompt(`Enter the 6-digit code sent to <strong>${s.new_phone}</strong>`,"Confirm Phone Change",{placeholder:"000000"});if(!o)return!0;const r=await t.POST("/api/auth/phone/change/confirm",{session_token:a,code:o.trim()});return r.success?(e.toast.success("Phone number updated"),await this.model.fetch({params:{graph:"full"}}),await this.render()):e.toast.error(r.message||"Invalid or expired code"),!0}}export{PasskeySetupView as P,UserProfileView as U,ProfileApiKeysSection as a,ProfileConnectedSection as b,ProfileDevicesSection as c,ProfileGroupsSection as d,ProfileNotificationsSection as e,ProfileOverviewSection as f,ProfilePermissionsSection as g,ProfilePersonalSection as h,ProfileSecurityEventsSection as i,ProfileSecuritySection as j,ProfileSessionsSection as k};
2
+ //# sourceMappingURL=UserProfileView-DnVMHcLH.js.map