@alepha/ui 0.18.2 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/{AdminApiKeys-BJhIwfD6.js → AdminApiKeys-Bt1PjO6o.js} +3 -4
- package/dist/admin/{AdminApiKeys-BJhIwfD6.js.map → AdminApiKeys-Bt1PjO6o.js.map} +1 -1
- package/dist/admin/{AdminAudits-DzD_4cDt.js → AdminAudits-C7c1CN4c.js} +3 -4
- package/dist/admin/{AdminAudits-DzD_4cDt.js.map → AdminAudits-C7c1CN4c.js.map} +1 -1
- package/dist/admin/{AdminDashboard-C92tIc6x.js → AdminDashboard-C3RXpTp6.js} +3 -4
- package/dist/admin/{AdminDashboard-C92tIc6x.js.map → AdminDashboard-C3RXpTp6.js.map} +1 -1
- package/dist/admin/{AdminFiles-DLpfhBkf.js → AdminFiles-31ivR6Wq.js} +3 -4
- package/dist/admin/{AdminFiles-DLpfhBkf.js.map → AdminFiles-31ivR6Wq.js.map} +1 -1
- package/dist/admin/{AdminJobDashboard-KIOkeMgE.js → AdminJobDashboard-BABLe7hL.js} +73 -25
- package/dist/admin/AdminJobDashboard-BABLe7hL.js.map +1 -0
- package/dist/admin/{AdminJobExecutions-D0Yo_PU0.js → AdminJobExecutions-D-G8RIlr.js} +3 -4
- package/dist/admin/{AdminJobExecutions-D0Yo_PU0.js.map → AdminJobExecutions-D-G8RIlr.js.map} +1 -1
- package/dist/admin/{AdminJobRegistry-PFajqaGK.js → AdminJobRegistry-oIS3K9NX.js} +3 -4
- package/dist/admin/{AdminJobRegistry-PFajqaGK.js.map → AdminJobRegistry-oIS3K9NX.js.map} +1 -1
- package/dist/admin/{AdminLayout-B1DXZHDn.js → AdminLayout-BmZ9mtXh.js} +8 -25
- package/dist/admin/AdminLayout-BmZ9mtXh.js.map +1 -0
- package/dist/admin/AdminNotifications-DHdzksww.js +541 -0
- package/dist/admin/AdminNotifications-DHdzksww.js.map +1 -0
- package/dist/admin/{AdminParameters-BspPeqp_.js → AdminParameters-CyZQSXnN.js} +118 -112
- package/dist/admin/AdminParameters-CyZQSXnN.js.map +1 -0
- package/dist/admin/{AdminSessions-BnH5CZQl.js → AdminSessions--xwELDSO.js} +3 -4
- package/dist/admin/{AdminSessions-BnH5CZQl.js.map → AdminSessions--xwELDSO.js.map} +1 -1
- package/dist/admin/{AdminUserLayout-DUbC6-BI.js → AdminUserLayout-DvBTG5gd.js} +82 -115
- package/dist/admin/AdminUserLayout-DvBTG5gd.js.map +1 -0
- package/dist/admin/{AdminUserProfile-DuTUnjdG.js → AdminUserProfile-CzsPBl6Z.js} +7 -6
- package/dist/admin/AdminUserProfile-CzsPBl6Z.js.map +1 -0
- package/dist/admin/{AdminUserSessions-DvZdAGpL.js → AdminUserSessions-C-aUnhVN.js} +3 -4
- package/dist/admin/{AdminUserSessions-DvZdAGpL.js.map → AdminUserSessions-C-aUnhVN.js.map} +1 -1
- package/dist/admin/{AdminUsers-CR9z0g_5.js → AdminUsers-BYwei5sj.js} +4 -4
- package/dist/admin/AdminUsers-BYwei5sj.js.map +1 -0
- package/dist/admin/{AuthLayout-DsUfp9RG.js → AuthLayout-CkPGLJku.js} +3 -4
- package/dist/admin/{AuthLayout-DsUfp9RG.js.map → AuthLayout-CkPGLJku.js.map} +1 -1
- package/dist/{demo/IconGoogle-CSQLPYwX.js → admin/IconGoogle-8Nkx6yax.js} +2 -4
- package/dist/admin/{IconGoogle-Ch1m3Uzl.js.map → IconGoogle-8Nkx6yax.js.map} +1 -1
- package/dist/admin/Login-DSBqNsZc.js +274 -0
- package/dist/admin/Login-DSBqNsZc.js.map +1 -0
- package/dist/admin/{Profile-B2EcIDB9.js → Profile-CDRjJo0P.js} +31 -29
- package/dist/admin/Profile-CDRjJo0P.js.map +1 -0
- package/dist/admin/{Register-Z3fxRbUF.js → Register-4QGFOnfh.js} +201 -146
- package/dist/admin/Register-4QGFOnfh.js.map +1 -0
- package/dist/admin/{ResetPassword-_Y1qTTKh.js → ResetPassword-Gxc9L_mY.js} +9 -10
- package/dist/admin/ResetPassword-Gxc9L_mY.js.map +1 -0
- package/dist/admin/{VerifyEmail-Bg22bwcC.js → VerifyEmail-D7G5NnaN.js} +25 -11
- package/dist/admin/VerifyEmail-D7G5NnaN.js.map +1 -0
- package/dist/admin/adminUserAtom-DCi4wf-v.js +11 -0
- package/dist/admin/adminUserAtom-DCi4wf-v.js.map +1 -0
- package/dist/admin/{core-BVO_TQxb.js → core-D1AbU50V.js} +662 -570
- package/dist/admin/core-D1AbU50V.js.map +1 -0
- package/dist/admin/index.d.ts +141 -53
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +67 -49
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/rolldown-runtime-CiIaOW0V.js +13 -0
- package/dist/{demo/AuthLayout-DN-ClJQk.js → auth/AuthLayout-CfRKcTqP.js} +3 -4
- package/dist/auth/{AuthLayout-C161NeF6.js.map → AuthLayout-CfRKcTqP.js.map} +1 -1
- package/dist/{admin/IconGoogle-Ch1m3Uzl.js → auth/IconGoogle-8Nkx6yax.js} +2 -4
- package/dist/auth/{IconGoogle-Ch1m3Uzl.js.map → IconGoogle-8Nkx6yax.js.map} +1 -1
- package/dist/auth/Login-DJyweoPS.js +274 -0
- package/dist/auth/Login-DJyweoPS.js.map +1 -0
- package/dist/auth/{Profile-BMpXJ0oi.js → Profile-Cy93pNTw.js} +31 -29
- package/dist/auth/Profile-Cy93pNTw.js.map +1 -0
- package/dist/auth/{Register-2gx8qll-.js → Register-CSqzzitW.js} +201 -146
- package/dist/auth/Register-CSqzzitW.js.map +1 -0
- package/dist/{demo/ResetPassword-CAPj8MO3.js → auth/ResetPassword-B61QPlQi.js} +9 -10
- package/dist/auth/ResetPassword-B61QPlQi.js.map +1 -0
- package/dist/{demo/VerifyEmail-DFmdCdYs.js → auth/VerifyEmail-CqBJ11id.js} +25 -11
- package/dist/auth/VerifyEmail-CqBJ11id.js.map +1 -0
- package/dist/auth/{core-DyfeVr5c.js → core-C6D3pazL.js} +403 -343
- package/dist/auth/core-C6D3pazL.js.map +1 -0
- package/dist/auth/index.d.ts +93 -54
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +30 -31
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/rolldown-runtime-CiIaOW0V.js +13 -0
- package/dist/core/index.d.ts +123 -62
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +878 -776
- package/dist/core/index.js.map +1 -1
- package/dist/{auth/AuthLayout-C161NeF6.js → demo/AuthLayout-Dq5tSLSc.js} +3 -4
- package/dist/demo/{AuthLayout-DN-ClJQk.js.map → AuthLayout-Dq5tSLSc.js.map} +1 -1
- package/dist/demo/DemoButton-_Ws2w-J0.js +181 -0
- package/dist/demo/DemoButton-_Ws2w-J0.js.map +1 -0
- package/dist/demo/DemoControlSelect-ChP4ZOpQ.js +304 -0
- package/dist/demo/DemoControlSelect-ChP4ZOpQ.js.map +1 -0
- package/dist/demo/DemoDataTable-Hwf_UUni.js +361 -0
- package/dist/demo/DemoDataTable-Hwf_UUni.js.map +1 -0
- package/dist/demo/{DemoDialog-DW8QEvD1.js → DemoDialog-B01OMVRd.js} +3 -4
- package/dist/demo/{DemoDialog-DW8QEvD1.js.map → DemoDialog-B01OMVRd.js.map} +1 -1
- package/dist/demo/{DemoFlex-CAhLUanT.js → DemoFlex-870PEl0V.js} +4 -5
- package/dist/demo/{DemoFlex-CAhLUanT.js.map → DemoFlex-870PEl0V.js.map} +1 -1
- package/dist/demo/{DemoHeading-yIFmNjHB.js → DemoHeading-C1YR27fz.js} +4 -5
- package/dist/demo/{DemoHeading-yIFmNjHB.js.map → DemoHeading-C1YR27fz.js.map} +1 -1
- package/dist/demo/{DemoHome-BSGuBHus.js → DemoHome-DRbL2eGf.js} +4 -5
- package/dist/demo/{DemoHome-BSGuBHus.js.map → DemoHome-DRbL2eGf.js.map} +1 -1
- package/dist/demo/{DemoJsonViewer-DsA2IpgV.js → DemoJsonViewer-DoABiqBW.js} +4 -5
- package/dist/demo/{DemoJsonViewer-DsA2IpgV.js.map → DemoJsonViewer-DoABiqBW.js.map} +1 -1
- package/dist/demo/{DemoLayout-Cy6xjn6P.js → DemoLayout-CN_PDCX2.js} +16 -8
- package/dist/demo/DemoLayout-CN_PDCX2.js.map +1 -0
- package/dist/demo/{DemoLogin-vqxgTu4P.js → DemoLogin-B5x-ug3Q.js} +51 -35
- package/dist/demo/DemoLogin-B5x-ug3Q.js.map +1 -0
- package/dist/demo/{DemoRegister-YHPvPg77.js → DemoRegister-Q6sg2xuV.js} +51 -53
- package/dist/demo/DemoRegister-Q6sg2xuV.js.map +1 -0
- package/dist/demo/{DemoResetPassword-mOW18Zlm.js → DemoResetPassword-DrqZfmEw.js} +14 -19
- package/dist/demo/DemoResetPassword-DrqZfmEw.js.map +1 -0
- package/dist/demo/{DemoSidebar-od7aLjP_.js → DemoSidebar-CfKS6w1o.js} +4 -5
- package/dist/demo/{DemoSidebar-od7aLjP_.js.map → DemoSidebar-CfKS6w1o.js.map} +1 -1
- package/dist/demo/{DemoText-DU3JeRS0.js → DemoText-pT6Gi5b5.js} +4 -5
- package/dist/demo/{DemoText-DU3JeRS0.js.map → DemoText-pT6Gi5b5.js.map} +1 -1
- package/dist/demo/{DemoToast-CUJEiPRa.js → DemoToast-I13NBzQQ.js} +3 -4
- package/dist/demo/{DemoToast-CUJEiPRa.js.map → DemoToast-I13NBzQQ.js.map} +1 -1
- package/dist/demo/{DemoTypeForm-C1dNkahD.js → DemoTypeForm-BqzcrtvN.js} +9 -6
- package/dist/demo/DemoTypeForm-BqzcrtvN.js.map +1 -0
- package/dist/demo/DemoVerifyEmail-HwD8xfQw.js +33 -0
- package/dist/demo/DemoVerifyEmail-HwD8xfQw.js.map +1 -0
- package/dist/{auth/IconGoogle-Ch1m3Uzl.js → demo/IconGoogle-CwQy4G9y.js} +2 -4
- package/dist/demo/{IconGoogle-CSQLPYwX.js.map → IconGoogle-CwQy4G9y.js.map} +1 -1
- package/dist/demo/Login-CqG1iJbn.js +274 -0
- package/dist/demo/Login-CqG1iJbn.js.map +1 -0
- package/dist/demo/{Profile-BE_Y3co2.js → Profile-C0ojJCaG.js} +31 -29
- package/dist/demo/Profile-C0ojJCaG.js.map +1 -0
- package/dist/demo/{Register-fXHmBpr3.js → Register-KKZwr_lL.js} +201 -146
- package/dist/demo/Register-KKZwr_lL.js.map +1 -0
- package/dist/{auth/ResetPassword-DBxt9hKk.js → demo/ResetPassword-DMrLFEtr.js} +9 -10
- package/dist/demo/ResetPassword-DMrLFEtr.js.map +1 -0
- package/dist/demo/{Showcase-BtEU0pY9.js → Showcase-D49Wud2v.js} +65 -68
- package/dist/demo/Showcase-D49Wud2v.js.map +1 -0
- package/dist/{auth/VerifyEmail-Z80Ubajk.js → demo/VerifyEmail-BFCAFz6T.js} +25 -11
- package/dist/demo/VerifyEmail-BFCAFz6T.js.map +1 -0
- package/dist/demo/{auth-Djd7SKiw.js → auth-D9qTZzCa.js} +18 -35
- package/dist/demo/{auth-Djd7SKiw.js.map → auth-D9qTZzCa.js.map} +1 -1
- package/dist/demo/{core-B7LNjM78.js → core-DRtQklr3.js} +752 -647
- package/dist/demo/core-DRtQklr3.js.map +1 -0
- package/dist/demo/index.d.ts +1 -0
- package/dist/demo/index.d.ts.map +1 -1
- package/dist/demo/index.js +25 -22
- package/dist/demo/index.js.map +1 -1
- package/dist/demo/rolldown-runtime-CiIaOW0V.js +13 -0
- package/package.json +19 -19
- package/src/admin/AdminRouter.tsx +42 -2
- package/src/admin/atoms/adminUserAtom.ts +7 -0
- package/src/admin/components/AdminLayout.tsx +2 -14
- package/src/admin/components/jobs/AdminJobDashboard.tsx +51 -20
- package/src/admin/components/notifications/AdminNotifications.tsx +519 -0
- package/src/admin/components/parameters/ParameterDetails.tsx +12 -270
- package/src/admin/components/parameters/ParameterDetailsConfigForm.tsx +238 -0
- package/src/admin/components/parameters/ParameterDetailsLoading.tsx +24 -0
- package/src/admin/components/parameters/ParameterHistory.tsx +10 -11
- package/src/admin/components/parameters/ParameterTree.tsx +28 -184
- package/src/admin/components/parameters/ParameterTreeNode.tsx +151 -0
- package/src/admin/components/shared/AdminResourceHeader.tsx +2 -25
- package/src/admin/components/shared/AdminResourceHeaderMenuItem.tsx +37 -0
- package/src/admin/components/shared/AdminResourceTabs.tsx +2 -26
- package/src/admin/components/shared/AdminResourceTabsItem.tsx +36 -0
- package/src/admin/components/users/AdminUserLayout.tsx +84 -127
- package/src/admin/components/users/AdminUserProfile.tsx +5 -2
- package/src/admin/components/users/AdminUsers.tsx +1 -1
- package/src/auth/components/Login.tsx +188 -121
- package/src/auth/components/Profile.tsx +1 -22
- package/src/auth/components/ProfileField.tsx +39 -0
- package/src/auth/components/Register.tsx +215 -158
- package/src/auth/components/ResetPassword.tsx +7 -11
- package/src/auth/components/VerifyEmail.tsx +35 -10
- package/src/auth/components/buttons/UserButton.tsx +19 -21
- package/src/auth/index.ts +1 -0
- package/src/core/components/Flex.tsx +34 -0
- package/src/core/components/buttons/ActionButton.tsx +105 -78
- package/src/core/components/data/DetailDrawer.tsx +102 -96
- package/src/core/components/data/DetailList.tsx +2 -1
- package/src/core/components/dialogs/PromptDialog.tsx +1 -1
- package/src/core/components/layout/Breadcrumb.tsx +4 -7
- package/src/core/components/layout/DashboardShell.tsx +18 -4
- package/src/core/components/layout/Sidebar.tsx +16 -241
- package/src/core/components/layout/SidebarCollapsedItem.tsx +91 -0
- package/src/core/components/layout/SidebarItem.tsx +146 -0
- package/src/core/components/layout/index.ts +3 -1
- package/src/core/form/components/Control.tsx +31 -29
- package/src/core/form/components/ControlArray.tsx +13 -39
- package/src/core/form/components/ControlDate.tsx +10 -21
- package/src/core/form/components/ControlNumber.tsx +4 -33
- package/src/core/form/components/ControlQueryBuilder.tsx +12 -175
- package/src/core/form/components/ControlQueryBuilderHelp.tsx +165 -0
- package/src/core/form/components/ControlSelect.browser.spec.tsx +343 -0
- package/src/core/form/components/ControlSelect.tsx +294 -92
- package/src/core/form/components/TypeForm.browser.spec.tsx +3 -3
- package/src/core/form/components/TypeForm.tsx +5 -2
- package/src/core/form/index.ts +8 -1
- package/src/core/form/utils/parseInput.ts +7 -3
- package/src/core/index.ts +3 -1
- package/src/core/json/components/JsonViewer.tsx +103 -319
- package/src/core/json/components/JsonViewerCopyButton.tsx +46 -0
- package/src/core/json/components/JsonViewerRowNode.tsx +120 -0
- package/src/core/json/components/JsonViewerShared.ts +76 -0
- package/src/core/services/DialogService.tsx +2 -2
- package/src/core/styles.css +13 -2
- package/src/core/table/components/ColumnPicker.tsx +3 -3
- package/src/core/table/components/DataTable.tsx +88 -29
- package/src/core/table/components/DataTableFilters.tsx +6 -11
- package/src/core/table/components/DataTablePagination.tsx +9 -3
- package/src/core/table/components/DataTableToolbar.tsx +7 -3
- package/src/core/table/components/FilterPicker.tsx +3 -3
- package/src/core/table/interfaces/types.ts +29 -0
- package/src/core/utils/icons.tsx +2 -2
- package/src/demo/DemoRouter.ts +8 -1
- package/src/demo/components/DemoLayout.tsx +12 -2
- package/src/demo/components/auth/DemoLogin.tsx +35 -28
- package/src/demo/components/auth/DemoRegister.tsx +35 -49
- package/src/demo/components/auth/DemoResetPassword.tsx +5 -9
- package/src/demo/components/auth/DemoVerifyEmail.tsx +7 -6
- package/src/demo/components/core/DemoButton.tsx +123 -103
- package/src/demo/components/core/DemoControlSelect.tsx +325 -0
- package/src/demo/components/core/DemoDataTable.tsx +255 -237
- package/src/demo/components/core/DemoTypeForm.tsx +7 -2
- package/src/demo/components/shared/MacWindow.tsx +5 -11
- package/src/demo/components/shared/Showcase.tsx +28 -42
- package/dist/admin/AdminJobDashboard-KIOkeMgE.js.map +0 -1
- package/dist/admin/AdminLayout-B1DXZHDn.js.map +0 -1
- package/dist/admin/AdminParameters-BspPeqp_.js.map +0 -1
- package/dist/admin/AdminUserLayout-DUbC6-BI.js.map +0 -1
- package/dist/admin/AdminUserProfile-DuTUnjdG.js.map +0 -1
- package/dist/admin/AdminUsers-CR9z0g_5.js.map +0 -1
- package/dist/admin/Login-DHbYJKwg.js +0 -219
- package/dist/admin/Login-DHbYJKwg.js.map +0 -1
- package/dist/admin/Profile-B2EcIDB9.js.map +0 -1
- package/dist/admin/Register-Z3fxRbUF.js.map +0 -1
- package/dist/admin/ResetPassword-_Y1qTTKh.js.map +0 -1
- package/dist/admin/VerifyEmail-Bg22bwcC.js.map +0 -1
- package/dist/admin/core-BVO_TQxb.js.map +0 -1
- package/dist/admin/rolldown-runtime-CjeV3_4I.js +0 -18
- package/dist/auth/Login-C7jIqf00.js +0 -219
- package/dist/auth/Login-C7jIqf00.js.map +0 -1
- package/dist/auth/Profile-BMpXJ0oi.js.map +0 -1
- package/dist/auth/Register-2gx8qll-.js.map +0 -1
- package/dist/auth/ResetPassword-DBxt9hKk.js.map +0 -1
- package/dist/auth/VerifyEmail-Z80Ubajk.js.map +0 -1
- package/dist/auth/core-DyfeVr5c.js.map +0 -1
- package/dist/auth/rolldown-runtime-CjeV3_4I.js +0 -18
- package/dist/demo/DemoButton-CGUyR9eM.js +0 -178
- package/dist/demo/DemoButton-CGUyR9eM.js.map +0 -1
- package/dist/demo/DemoDataTable-QFG-xXSx.js +0 -358
- package/dist/demo/DemoDataTable-QFG-xXSx.js.map +0 -1
- package/dist/demo/DemoLayout-Cy6xjn6P.js.map +0 -1
- package/dist/demo/DemoLogin-vqxgTu4P.js.map +0 -1
- package/dist/demo/DemoRegister-YHPvPg77.js.map +0 -1
- package/dist/demo/DemoResetPassword-mOW18Zlm.js.map +0 -1
- package/dist/demo/DemoTypeForm-C1dNkahD.js.map +0 -1
- package/dist/demo/DemoVerifyEmail-D9EcXZ38.js +0 -30
- package/dist/demo/DemoVerifyEmail-D9EcXZ38.js.map +0 -1
- package/dist/demo/Login-CoYf_P_F.js +0 -219
- package/dist/demo/Login-CoYf_P_F.js.map +0 -1
- package/dist/demo/Profile-BE_Y3co2.js.map +0 -1
- package/dist/demo/Register-fXHmBpr3.js.map +0 -1
- package/dist/demo/ResetPassword-CAPj8MO3.js.map +0 -1
- package/dist/demo/Showcase-BtEU0pY9.js.map +0 -1
- package/dist/demo/VerifyEmail-DFmdCdYs.js.map +0 -1
- package/dist/demo/core-B7LNjM78.js.map +0 -1
- package/dist/demo/rolldown-runtime-CjeV3_4I.js +0 -18
- package/src/demo/styles.css +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AdminJobDashboard-KIOkeMgE.js","names":["Flex","Text"],"sources":["../../src/admin/components/jobs/AdminJobDashboard.tsx"],"sourcesContent":["import { ActionButton, Flex, StatCards, Text, useToast } from \"@alepha/ui\";\nimport { AreaChart, BarChart, DonutChart } from \"@mantine/charts\";\nimport { Paper, SimpleGrid, Table } from \"@mantine/core\";\nimport {\n IconAlertTriangle,\n IconCircleCheck,\n IconPlayerPlay,\n IconRefresh,\n IconTerminal2,\n} from \"@tabler/icons-react\";\nimport type {\n AdminJobController,\n JobActivityPoint,\n JobFailure,\n JobQueueDepth,\n JobStats,\n} from \"alepha/api/jobs\";\nimport { useClient } from \"alepha/react\";\nimport { useI18n } from \"alepha/react/i18n\";\nimport { useCallback, useEffect, useState } from \"react\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst formatDuration = (\n start: Date | string,\n end?: Date | string | null,\n): string => {\n const startTime = new Date(start).getTime();\n const endTime = end ? new Date(end).getTime() : Date.now();\n const duration = endTime - startTime;\n\n if (duration < 1000) return `${duration}ms`;\n if (duration < 60000) return `${(duration / 1000).toFixed(1)}s`;\n if (duration < 3600000)\n return `${Math.floor(duration / 60000)}m ${Math.floor((duration % 60000) / 1000)}s`;\n return `${Math.floor(duration / 3600000)}h ${Math.floor((duration % 3600000) / 60000)}m`;\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface RecentExecution {\n id: string;\n jobName: string;\n status: string;\n startedAt?: string;\n completedAt?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst AdminJobDashboard = () => {\n const client = useClient<AdminJobController>();\n const { l } = useI18n();\n const toast = useToast();\n\n const [stats, setStats] = useState<JobStats | null>(null);\n const [recent, setRecent] = useState<RecentExecution[]>([]);\n const [failures, setFailures] = useState<JobFailure[]>([]);\n const [activity, setActivity] = useState<JobActivityPoint[]>([]);\n const [queueDepth, setQueueDepth] = useState<JobQueueDepth[]>([]);\n\n const loadData = useCallback(async () => {\n try {\n const [statsData, recentData, failureData, activityData, queueData] =\n await Promise.all([\n client.getJobStats(),\n client.findJobExecutions({ query: { sort: \"-createdAt\", size: 10 } }),\n client.getJobTopFailures(),\n client.getJobActivity({ query: { days: 14 } }),\n client.getJobQueueDepth(),\n ]);\n setStats(statsData);\n setRecent(recentData.content as RecentExecution[]);\n setFailures(failureData);\n setActivity(activityData);\n setQueueDepth(queueData);\n } catch {\n toast.danger(\"Failed to load dashboard data\");\n }\n }, [client, toast]);\n\n useEffect(() => {\n loadData();\n }, [loadData]);\n\n // Prepare chart data\n const activityChartData = activity.map((point) => ({\n date: new Date(point.date).toLocaleDateString(\"en-US\", {\n month: \"short\",\n day: \"numeric\",\n }),\n completed: point.completed,\n failed: point.failed,\n }));\n\n const queueChartData = queueDepth\n .filter(\n (q) => q.pending + q.running + q.scheduled + q.retrying + q.dead > 0,\n )\n .map((q) => ({\n job:\n q.jobName.length > 20\n ? `...${q.jobName.slice(q.jobName.length - 18)}`\n : q.jobName,\n pending: q.pending,\n running: q.running,\n scheduled: q.scheduled,\n retrying: q.retrying,\n dead: q.dead,\n }));\n\n const statusDonutData = stats\n ? [\n { name: \"Running\", value: stats.running, color: \"blue\" },\n { name: \"Pending\", value: stats.pending, color: \"gray\" },\n { name: \"Scheduled\", value: stats.scheduled, color: \"violet\" },\n { name: \"Retrying\", value: stats.retrying, color: \"yellow\" },\n { name: \"Dead\", value: stats.dead, color: \"red\" },\n ].filter((d) => d.value > 0)\n : [];\n\n return (\n <Flex flex={1} direction=\"column\" gap=\"md\" p=\"md\">\n <Flex justify=\"space-between\" align=\"center\">\n <Text size=\"lg\" fw={600}>\n Jobs Dashboard\n </Text>\n <ActionButton\n tooltip=\"Refresh\"\n variant=\"light\"\n size=\"sm\"\n icon={IconRefresh}\n onClick={loadData}\n />\n </Flex>\n\n {/* Stats Cards */}\n {stats && (\n <StatCards\n items={[\n {\n label: \"Registered\",\n value: stats.registered,\n icon: IconTerminal2,\n },\n {\n label: \"Running\",\n value: stats.running,\n icon: IconPlayerPlay,\n },\n {\n label: \"Completed 24h\",\n value: stats.completed24h,\n icon: IconCircleCheck,\n },\n {\n label: \"Failed 24h\",\n value: stats.failed24h,\n icon: IconAlertTriangle,\n },\n ]}\n />\n )}\n\n {/* Charts Row */}\n <SimpleGrid cols={{ base: 1, md: 2 }} spacing=\"md\">\n {/* Activity Timeline */}\n <Paper p=\"md\" radius=\"md\" withBorder>\n <Text size=\"sm\" fw={600} mb=\"sm\">\n Activity (14d)\n </Text>\n {activityChartData.length > 0 ? (\n <AreaChart\n h={220}\n data={activityChartData}\n dataKey=\"date\"\n series={[\n { name: \"completed\", label: \"Completed\", color: \"teal.6\" },\n { name: \"failed\", label: \"Failed\", color: \"red.6\" },\n ]}\n curveType=\"monotone\"\n withGradient\n withTooltip\n withDots={false}\n />\n ) : (\n <Flex h={220} align=\"center\" justify=\"center\">\n <Text size=\"sm\" c=\"dimmed\">\n No activity data\n </Text>\n </Flex>\n )}\n </Paper>\n\n {/* Current Status Donut */}\n <Paper p=\"md\" radius=\"md\" withBorder>\n <Text size=\"sm\" fw={600} mb=\"sm\">\n Active Executions\n </Text>\n {statusDonutData.length > 0 ? (\n <DonutChart\n h={220}\n data={statusDonutData}\n withTooltip\n tooltipDataSource=\"segment\"\n chartLabel={String(\n statusDonutData.reduce((sum, d) => sum + d.value, 0),\n )}\n />\n ) : (\n <Flex h={220} align=\"center\" justify=\"center\">\n <Text size=\"sm\" c=\"dimmed\">\n No active executions\n </Text>\n </Flex>\n )}\n </Paper>\n </SimpleGrid>\n\n {/* Queue Depth Chart */}\n {queueChartData.length > 0 && (\n <Paper p=\"md\" radius=\"md\" withBorder>\n <Text size=\"sm\" fw={600} mb=\"sm\">\n Queue Depth by Job\n </Text>\n <BarChart\n h={200}\n data={queueChartData}\n dataKey=\"job\"\n type=\"stacked\"\n series={[\n { name: \"running\", label: \"Running\", color: \"blue.6\" },\n { name: \"pending\", label: \"Pending\", color: \"gray.5\" },\n { name: \"scheduled\", label: \"Scheduled\", color: \"violet.5\" },\n { name: \"retrying\", label: \"Retrying\", color: \"yellow.5\" },\n { name: \"dead\", label: \"Dead\", color: \"red.6\" },\n ]}\n withTooltip\n withLegend\n />\n </Paper>\n )}\n\n {/* Recent Activity + Top Failures side by side */}\n <SimpleGrid cols={{ base: 1, md: 2 }} spacing=\"md\">\n {/* Recent Activity */}\n <Paper p=\"md\" radius=\"md\" withBorder>\n <Text size=\"sm\" fw={600} mb=\"sm\">\n Recent Executions\n </Text>\n <Table highlightOnHover>\n <Table.Thead>\n <Table.Tr>\n <Table.Th>Job</Table.Th>\n <Table.Th>Status</Table.Th>\n <Table.Th>Duration</Table.Th>\n </Table.Tr>\n </Table.Thead>\n <Table.Tbody>\n {recent.map((exec) => (\n <Table.Tr key={exec.id}>\n <Table.Td>\n <Text size=\"xs\" fw={500} ff=\"monospace\" lineClamp={1}>\n {exec.jobName}\n </Text>\n </Table.Td>\n <Table.Td>\n <Text size=\"xs\" ff=\"monospace\">\n {exec.status}\n </Text>\n </Table.Td>\n <Table.Td>\n <Text size=\"xs\" c=\"dimmed\" ff=\"monospace\">\n {exec.startedAt &&\n (exec.completedAt || exec.status === \"running\")\n ? formatDuration(exec.startedAt, exec.completedAt)\n : \"\\u2014\"}\n </Text>\n </Table.Td>\n </Table.Tr>\n ))}\n {recent.length === 0 && (\n <Table.Tr>\n <Table.Td colSpan={3}>\n <Text size=\"sm\" c=\"dimmed\" ta=\"center\">\n No recent executions\n </Text>\n </Table.Td>\n </Table.Tr>\n )}\n </Table.Tbody>\n </Table>\n </Paper>\n\n {/* Top Failures (7d) */}\n <Paper p=\"md\" radius=\"md\" withBorder>\n <Text size=\"sm\" fw={600} mb=\"sm\">\n Top Failures (7d)\n </Text>\n {failures.length > 0 ? (\n <Table>\n <Table.Thead>\n <Table.Tr>\n <Table.Th>Job</Table.Th>\n <Table.Th>Count</Table.Th>\n <Table.Th>Last Error</Table.Th>\n </Table.Tr>\n </Table.Thead>\n <Table.Tbody>\n {failures.map((f) => (\n <Table.Tr key={f.jobName}>\n <Table.Td>\n <Text size=\"xs\" fw={500} ff=\"monospace\" lineClamp={1}>\n {f.jobName}\n </Text>\n </Table.Td>\n <Table.Td>\n <Text size=\"xs\" fw={600} ff=\"monospace\">\n {f.failures}\n </Text>\n </Table.Td>\n <Table.Td>\n <Text\n size=\"xs\"\n c=\"dimmed\"\n lineClamp={1}\n style={{ maxWidth: 200 }}\n >\n {f.lastError ?? \"\\u2014\"}\n </Text>\n </Table.Td>\n </Table.Tr>\n ))}\n </Table.Tbody>\n </Table>\n ) : (\n <Flex h={100} align=\"center\" justify=\"center\">\n <Text size=\"sm\" c=\"dimmed\">\n No failures in the last 7 days\n </Text>\n </Flex>\n )}\n </Paper>\n </SimpleGrid>\n </Flex>\n );\n};\n\nexport default AdminJobDashboard;\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AdminLayout-B1DXZHDn.js","names":["Text","SidebarCollapseButton"],"sources":["../../src/admin/components/AdminLayout.tsx"],"sourcesContent":["import {\n ActionButton,\n AlephaMantineProvider,\n DashboardShell,\n type DashboardShellProps,\n SidebarCollapseButton,\n Text,\n ui,\n} from \"@alepha/ui\";\nimport { Flex, Image } from \"@mantine/core\";\nimport { IconArrowLeft } from \"@tabler/icons-react\";\n\nexport interface AdminLayoutProps {\n adminShellProps?: DashboardShellProps;\n}\n\nconst AdminLayout = (props: AdminLayoutProps) => {\n return (\n <AlephaMantineProvider\n mantine={{\n theme: {\n components: {\n Button: {\n defaultProps: {\n fw: 400,\n },\n },\n },\n },\n }}\n >\n <DashboardShell\n layout={\"alt\"}\n navbarHeader={(props) => (\n <Flex gap={\"md\"} flex={1} px={\"lg\"} align={\"center\"}>\n <ActionButton\n href={\"/\"}\n variant={\"default\"}\n bd={0}\n icon={IconArrowLeft}\n />\n {!props.collapsed && (\n <>\n <Image pt={4} src={\"/favicon.svg\"} h={36} w={36} />\n <Flex direction={\"column\"}>\n <Text bold>Blog</Text>\n <Text small muted mt={-4}>\n Admin Panel\n </Text>\n </Flex>\n </>\n )}\n </Flex>\n )}\n footerHeight={48}\n navbarFooter={\n <Flex flex={1} px={\"lg\"} align={\"center\"}>\n <SidebarCollapseButton\n c={\"gray\"}\n size={\"xs\"}\n iconSize={ui.sizes.icon.sm}\n p={8}\n bd={0}\n />\n </Flex>\n }\n sidebarProps={{\n autoPopulateMenu: {\n startsWith: \"/admin\",\n },\n }}\n {...props.adminShellProps}\n />\n </AlephaMantineProvider>\n );\n};\n\nexport default AdminLayout;\n"],"mappings":";;;;;;AAgBA,MAAM,eAAe,UAA4B;AAC/C,QACE,oBAAC;EACC,SAAS,EACP,OAAO,EACL,YAAY,EACV,QAAQ,EACN,cAAc,EACZ,IAAI,KACL,EACF,EACF,EACF,EACF;YAED,oBAAC;GACC,QAAQ;GACR,eAAe,UACb,qBAAC;IAAK,KAAK;IAAM,MAAM;IAAG,IAAI;IAAM,OAAO;eACzC,oBAAC;KACC,MAAM;KACN,SAAS;KACT,IAAI;KACJ,MAAM;MACN,EACD,CAAC,MAAM,aACN,4CACE,oBAAC;KAAM,IAAI;KAAG,KAAK;KAAgB,GAAG;KAAI,GAAG;MAAM,EACnD,qBAAC;KAAK,WAAW;gBACf,oBAACA;MAAK;gBAAK;OAAW,EACtB,oBAACA;MAAK;MAAM;MAAM,IAAI;gBAAI;OAEnB;MACF,IACN;KAEA;GAET,cAAc;GACd,cACE,oBAAC;IAAK,MAAM;IAAG,IAAI;IAAM,OAAO;cAC9B,oBAACC;KACC,GAAG;KACH,MAAM;KACN,UAAU,GAAG,MAAM,KAAK;KACxB,GAAG;KACH,IAAI;MACJ;KACG;GAET,cAAc,EACZ,kBAAkB,EAChB,YAAY,UACb,EACF;GACD,GAAI,MAAM;IACV;GACoB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AdminParameters-BspPeqp_.js","names":["Flex","Text","Flex","Text","Flex","Text","Flex","Text","Flex","Text"],"sources":["../../src/admin/components/parameters/types.ts","../../src/admin/components/parameters/ParameterDetails.tsx","../../src/admin/components/parameters/ParameterEmptyState.tsx","../../src/admin/components/parameters/ParameterHistory.tsx","../../src/admin/components/parameters/ParameterTree.tsx","../../src/admin/components/parameters/AdminParameters.tsx"],"sourcesContent":["import type { ParameterResponse } from \"alepha/api/parameters\";\n\nexport interface ParameterValue {\n current?: ParameterResponse;\n next?: ParameterResponse;\n /**\n * Default value from the registered $parameter primitive.\n */\n defaultValue?: unknown;\n /**\n * Current in-memory value (may be default if never saved).\n */\n currentValue?: unknown;\n /**\n * TypeBox/JSON schema for the parameter (as JSON from API).\n */\n schema?: Record<string, unknown>;\n}\n\nexport const getStatusColor = (status: string) => {\n switch (status) {\n case \"current\":\n return \"green\";\n case \"next\":\n return \"blue\";\n case \"future\":\n return \"cyan\";\n case \"expired\":\n return \"gray\";\n default:\n return \"gray\";\n }\n};\n\nexport const formatJson = (obj: unknown): string => {\n try {\n return JSON.stringify(obj, null, 2);\n } catch {\n return String(obj);\n }\n};\n","import { ActionButton, Flex, Text, TypeForm } from \"@alepha/ui\";\nimport { Card, Code, Loader } from \"@mantine/core\";\nimport { IconClock } from \"@tabler/icons-react\";\nimport { jsonSchemaToTypeBox, type TObject, t } from \"alepha\";\nimport { useForm } from \"alepha/react/form\";\nimport { useI18n } from \"alepha/react/i18n\";\nimport { useMemo } from \"react\";\nimport { formatJson, type ParameterValue } from \"./types.ts\";\n\nexport interface ParameterDetailsProps {\n selectedConfig: string | null;\n configValue: ParameterValue | null;\n loading: boolean;\n saving: boolean;\n onSave: (values: Record<string, unknown>) => Promise<void>;\n}\n\n/**\n * Loading state.\n */\nconst LoadingState = () => (\n <Flex\n flex={1}\n h=\"100%\"\n p=\"md\"\n style={{\n overflow: \"hidden\",\n minWidth: 0,\n display: \"flex\",\n }}\n >\n <Flex flex={1} justify=\"center\" align=\"center\" h=\"100%\">\n <Loader size=\"sm\" />\n </Flex>\n </Flex>\n);\n\ninterface ConfigFormProps {\n selectedConfig: string;\n configValue: ParameterValue | null;\n saving: boolean;\n onSave: (values: Record<string, unknown>) => Promise<void>;\n}\n\n/**\n * The actual form component - only rendered when a config is selected.\n */\nconst ConfigForm = ({\n selectedConfig,\n configValue,\n saving,\n onSave,\n}: ConfigFormProps) => {\n const { l } = useI18n();\n\n // Get the current value to display (from saved version or default)\n const currentContent = useMemo(() => {\n if (configValue?.current?.content) {\n return configValue.current.content;\n }\n if (configValue?.currentValue !== undefined) {\n return configValue.currentValue;\n }\n return null;\n }, [configValue]);\n\n // Convert JSON Schema from API to TypeBox schema\n const schemaForForm = useMemo(() => {\n if (!configValue?.schema) {\n return t.object({});\n }\n try {\n return jsonSchemaToTypeBox(configValue.schema) as TObject;\n } catch {\n return t.object({});\n }\n }, [configValue?.schema]);\n\n const form = useForm(\n {\n schema: schemaForForm,\n initialValues: (currentContent ?? {}) as Record<string, unknown>,\n handler: async (values) => {\n await onSave(values as Record<string, unknown>);\n },\n },\n [selectedConfig, schemaForForm, currentContent],\n );\n\n // Check if we have a valid schema with properties\n const hasValidSchema = useMemo(() => {\n const schema = configValue?.schema;\n return (\n schema &&\n typeof schema === \"object\" &&\n \"properties\" in schema &&\n Object.keys(schema.properties as object).length > 0\n );\n }, [configValue?.schema]);\n\n // Count the number of fields to determine column layout\n const fieldCount = useMemo(() => {\n const schema = configValue?.schema;\n if (\n schema &&\n typeof schema === \"object\" &&\n \"properties\" in schema &&\n schema.properties\n ) {\n return Object.keys(schema.properties as object).length;\n }\n return 0;\n }, [configValue?.schema]);\n\n // Determine optimal column count based on field count\n const columns = useMemo(() => {\n if (fieldCount <= 2) return 1;\n if (fieldCount <= 6) return 2;\n return 3;\n }, [fieldCount]);\n\n return (\n <Flex\n flex={1}\n h=\"100%\"\n style={{\n overflow: \"hidden\",\n minWidth: 0,\n display: \"flex\",\n }}\n >\n <Flex direction=\"column\" h=\"100%\" w=\"100%\" style={{ minHeight: 0 }}>\n {/* Content */}\n <Flex\n flex={1}\n p=\"md\"\n className=\"overflow-auto\"\n style={{ minHeight: 0 }}\n >\n {currentContent !== null ? (\n <Flex direction=\"column\" gap=\"lg\">\n {/* Form or JSON view */}\n <Flex>\n {hasValidSchema ? (\n <TypeForm\n form={form}\n columns={columns}\n skipSubmitButton\n fill={false}\n />\n ) : (\n <Flex>\n <Text size=\"xs\" c=\"dimmed\" mb={4}>\n Current Value\n </Text>\n <Code block style={{ whiteSpace: \"pre-wrap\" }}>\n {formatJson(currentContent)}\n </Code>\n </Flex>\n )}\n </Flex>\n\n {/* Metadata */}\n {configValue?.current?.changeDescription && (\n <Flex>\n <Text size=\"xs\" c=\"dimmed\" mb={4}>\n Change Description\n </Text>\n <Text size=\"sm\">{configValue.current.changeDescription}</Text>\n </Flex>\n )}\n\n {configValue?.current && (\n <Flex gap=\"xl\">\n <Flex>\n <Text size=\"xs\" c=\"dimmed\" mb={2}>\n Updated\n </Text>\n <Text size=\"sm\">\n {l(configValue.current.updatedAt, { date: \"fromNow\" })}\n </Text>\n </Flex>\n {configValue.current.creatorName && (\n <Flex>\n <Text size=\"xs\" c=\"dimmed\" mb={2}>\n Updated By\n </Text>\n <Text size=\"sm\">{configValue.current.creatorName}</Text>\n </Flex>\n )}\n </Flex>\n )}\n\n {!configValue?.current &&\n configValue?.currentValue !== undefined && (\n <Text size=\"xs\" c=\"dimmed\">\n This configuration is using its default value. No versions\n have been saved to the database yet.\n </Text>\n )}\n\n {/* Scheduled update preview */}\n {configValue?.next && (\n <Card withBorder p=\"sm\" bg=\"var(--mantine-color-blue-light)\">\n <Flex direction=\"column\" gap=\"xs\">\n <Flex gap=\"xs\">\n <IconClock\n size={14}\n color=\"var(--mantine-color-blue-6)\"\n />\n <Text size=\"xs\" fw={500} c=\"blue\">\n Scheduled Update (v{configValue.next.version})\n </Text>\n </Flex>\n <Text size=\"xs\" c=\"dimmed\">\n Activates{\" \"}\n {l(configValue.next.activationDate, {\n date: \"fromNow\",\n })}\n </Text>\n <Code block style={{ whiteSpace: \"pre-wrap\" }} fz=\"xs\">\n {formatJson(configValue.next.content)}\n </Code>\n </Flex>\n </Card>\n )}\n </Flex>\n ) : (\n <Flex justify=\"center\" align=\"center\" h={200}>\n <Text c=\"dimmed\" size=\"sm\">\n No current value\n </Text>\n </Flex>\n )}\n </Flex>\n\n {/* Footer with actions */}\n {hasValidSchema && currentContent !== null && (\n <Flex\n p=\"md\"\n style={{\n flexShrink: 0,\n borderTop: \"1px solid var(--mantine-color-default-border)\",\n }}\n >\n <Flex justify=\"flex-end\" gap=\"sm\">\n <ActionButton\n variant=\"subtle\"\n onClick={() => form.reset({} as any)}\n disabled={saving}\n >\n Reset\n </ActionButton>\n <ActionButton intent=\"primary\" form={form} loading={saving}>\n Save Changes\n </ActionButton>\n </Flex>\n </Flex>\n )}\n </Flex>\n </Flex>\n );\n};\n\n/**\n * Parameter details panel.\n * Shows loading state or the config form.\n * Note: Empty state is handled by parent (AdminParameters).\n */\nconst ParameterDetails = ({\n selectedConfig,\n configValue,\n loading,\n saving,\n onSave,\n}: ParameterDetailsProps) => {\n // Loading state\n if (loading) {\n return <LoadingState />;\n }\n\n // Config form (selectedConfig is guaranteed to be non-null by parent)\n return (\n <ConfigForm\n selectedConfig={selectedConfig!}\n configValue={configValue}\n saving={saving}\n onSave={onSave}\n />\n );\n};\n\nexport default ParameterDetails;\n","import { Flex, Text } from \"@alepha/ui\";\nimport { IconArrowLeft } from \"@tabler/icons-react\";\n\n/**\n * Empty state displayed when no parameter is selected.\n * Invites user to select a parameter from the tree.\n */\nconst ParameterEmptyState = () => {\n return (\n <Flex flex={1} p={\"xl\"} align=\"center\">\n <Flex direction=\"column\" align=\"center\" gap=\"md\">\n <IconArrowLeft size={32} color=\"var(--mantine-color-dimmed)\" />\n <Flex direction=\"column\" align=\"center\" gap={4}>\n <Text fw={500} c=\"dimmed\">\n No Parameter Selected\n </Text>\n <Text size=\"xs\" c=\"dimmed\" ta=\"center\" maw={240}>\n Choose a parameter from the tree to view and edit its configuration\n </Text>\n </Flex>\n </Flex>\n </Flex>\n );\n};\n\nexport default ParameterEmptyState;\n","import { ActionButton, Flex, Text } from \"@alepha/ui\";\nimport { Badge, Loader, ScrollArea, Timeline } from \"@mantine/core\";\nimport { IconHistory } from \"@tabler/icons-react\";\nimport type { ParameterResponse } from \"alepha/api/parameters\";\nimport { useI18n } from \"alepha/react/i18n\";\nimport { getStatusColor } from \"./types.ts\";\n\nexport interface ParameterHistoryProps {\n selectedConfig: string | null;\n history: ParameterResponse[];\n loading: boolean;\n onRollback: (version: number) => void;\n}\n\nconst ParameterHistory = ({\n history,\n loading,\n onRollback,\n}: ParameterHistoryProps) => {\n const { l } = useI18n();\n\n const renderContent = () => {\n if (loading) {\n return (\n <Flex flex={1} justify=\"center\" align=\"center\">\n <Loader size=\"sm\" />\n </Flex>\n );\n }\n\n if (history.length === 0) {\n return (\n <Flex flex={1} justify=\"center\" align=\"center\">\n <Text c=\"dimmed\" size=\"xs\">\n Empty\n </Text>\n </Flex>\n );\n }\n\n return (\n <ScrollArea flex={1} offsetScrollbars>\n <Timeline\n active={history.findIndex((h) => h.status === \"current\")}\n bulletSize={24}\n lineWidth={2}\n >\n {history.map((version) => (\n <Timeline.Item\n key={version.id}\n bullet={\n <Text size=\"xs\" fw={500}>\n {version.version}\n </Text>\n }\n title={\n <Flex gap=\"xs\">\n <Text size=\"xs\" fw={500}>\n Version {version.version}\n </Text>\n <Badge\n size=\"xs\"\n variant=\"light\"\n color={getStatusColor(version.status)}\n >\n {version.status}\n </Badge>\n </Flex>\n }\n >\n <Flex direction=\"column\" gap={4} mt={4}>\n <Text size=\"xs\" c=\"dimmed\">\n {l(version.createdAt, { date: \"fromNow\" })}\n </Text>\n {version.changeDescription && (\n <Text size=\"xs\" lineClamp={2}>\n {version.changeDescription}\n </Text>\n )}\n {version.creatorName && (\n <Text size=\"xs\" c=\"dimmed\">\n by {version.creatorName}\n </Text>\n )}\n {version.migrationLog && (\n <Badge size=\"xs\" variant=\"outline\" color=\"orange\">\n Schema Changed\n </Badge>\n )}\n {version.status === \"expired\" && (\n <ActionButton\n size=\"compact-xs\"\n variant=\"subtle\"\n onClick={() => onRollback(version.version)}\n >\n Rollback to this version\n </ActionButton>\n )}\n </Flex>\n </Timeline.Item>\n ))}\n </Timeline>\n </ScrollArea>\n );\n };\n\n return (\n <Flex\n w={220}\n h=\"100%\"\n p=\"xs\"\n style={{\n flexShrink: 0,\n overflow: \"hidden\",\n display: \"flex\",\n flexDirection: \"column\",\n borderLeft: \"1px solid var(--mantine-color-default-border)\",\n }}\n >\n <Flex direction=\"column\" gap=\"xs\" h=\"100%\" style={{ minHeight: 0 }}>\n <Flex gap=\"xs\">\n <IconHistory size={16} color=\"var(--mantine-color-dimmed)\" />\n <Text size=\"sm\" fw={500}>\n History\n </Text>\n </Flex>\n {renderContent()}\n </Flex>\n </Flex>\n );\n};\n\nexport default ParameterHistory;\n","import { ActionButton, Flex, Text } from \"@alepha/ui\";\nimport { Collapse, ScrollArea, TextInput, UnstyledButton } from \"@mantine/core\";\nimport {\n IconChevronDown,\n IconChevronRight,\n IconFolder,\n IconFolderOpen,\n IconRefresh,\n IconSearch,\n IconSettings,\n} from \"@tabler/icons-react\";\nimport type { ParameterTreeNode } from \"alepha/api/parameters\";\nimport { memo, useCallback, useMemo, useState } from \"react\";\n\nexport interface ParameterTreeProps {\n treeData: ParameterTreeNode[];\n selectedConfig: string | null;\n onSelect: (name: string) => void;\n onRefresh: () => void;\n}\n\n/**\n * Filters tree nodes by search query.\n */\nconst filterTree = (\n nodes: ParameterTreeNode[],\n query: string,\n): ParameterTreeNode[] => {\n if (!query.trim()) return nodes;\n\n const lowerQuery = query.toLowerCase();\n\n return nodes\n .map((node) => {\n const filteredChildren = filterTree(node.children, query);\n const nameMatches = node.name.toLowerCase().includes(lowerQuery);\n const pathMatches = node.path.toLowerCase().includes(lowerQuery);\n\n if (nameMatches || pathMatches || filteredChildren.length > 0) {\n return {\n ...node,\n children: filteredChildren,\n };\n }\n\n return null;\n })\n .filter((node): node is ParameterTreeNode => node !== null);\n};\n\ninterface TreeNodeProps {\n node: ParameterTreeNode;\n level: number;\n selectedConfig: string | null;\n onSelect: (name: string) => void;\n expandedNodes: Set<string>;\n onToggle: (path: string) => void;\n}\n\n/**\n * Memoized tree node to prevent unnecessary re-renders.\n */\nconst TreeNode = memo(\n ({\n node,\n level,\n selectedConfig,\n onSelect,\n expandedNodes,\n onToggle,\n }: TreeNodeProps) => {\n const [isHovered, setIsHovered] = useState(false);\n const hasChildren = node.children.length > 0;\n const isExpanded = expandedNodes.has(node.path);\n const isSelected = selectedConfig === node.path;\n const isLeaf = !hasChildren;\n\n const handleClick = useCallback(() => {\n if (hasChildren) {\n onToggle(node.path);\n } else {\n onSelect(node.path);\n }\n }, [hasChildren, node.path, onToggle, onSelect]);\n\n const handleMouseEnter = useCallback(() => setIsHovered(true), []);\n const handleMouseLeave = useCallback(() => setIsHovered(false), []);\n\n return (\n <Flex>\n <UnstyledButton\n onClick={handleClick}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n w=\"100%\"\n style={{ display: \"block\" }}\n >\n <Flex\n gap={6}\n wrap=\"nowrap\"\n p=\"4px 8px\"\n pl={8 + level * 16}\n style={{\n borderRadius: \"var(--mantine-radius-sm)\",\n backgroundColor:\n isSelected || isHovered\n ? \"var(--mantine-color-default-hover)\"\n : undefined,\n }}\n >\n {hasChildren ? (\n <>\n <Flex\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: 16,\n }}\n >\n {isExpanded ? (\n <IconChevronDown\n size={14}\n color=\"var(--mantine-color-dimmed)\"\n />\n ) : (\n <IconChevronRight\n size={14}\n color=\"var(--mantine-color-dimmed)\"\n />\n )}\n </Flex>\n {isExpanded ? (\n <IconFolderOpen\n size={16}\n color=\"var(--mantine-color-dimmed)\"\n style={{ flexShrink: 0 }}\n />\n ) : (\n <IconFolder\n size={16}\n color=\"var(--mantine-color-dimmed)\"\n style={{ flexShrink: 0 }}\n />\n )}\n </>\n ) : (\n <>\n <Flex w={16} />\n <IconSettings\n size={16}\n color={\n isSelected\n ? \"var(--mantine-color-blue-6)\"\n : \"var(--mantine-color-dimmed)\"\n }\n style={{ flexShrink: 0 }}\n />\n </>\n )}\n <Text\n size=\"sm\"\n fw={isSelected ? 600 : 400}\n c={isSelected ? undefined : isLeaf ? undefined : \"dimmed\"}\n style={{\n whiteSpace: \"nowrap\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n }}\n >\n {node.name}\n </Text>\n </Flex>\n </UnstyledButton>\n\n {hasChildren && (\n <Collapse in={isExpanded}>\n {node.children.map((child: ParameterTreeNode) => (\n <TreeNode\n key={child.path}\n node={child}\n level={level + 1}\n selectedConfig={selectedConfig}\n onSelect={onSelect}\n expandedNodes={expandedNodes}\n onToggle={onToggle}\n />\n ))}\n </Collapse>\n )}\n </Flex>\n );\n },\n);\n\nTreeNode.displayName = \"TreeNode\";\n\n/**\n * Collects all folder paths to expand by default.\n */\nconst collectAllFolderPaths = (nodes: ParameterTreeNode[]): Set<string> => {\n const paths = new Set<string>();\n\n const traverse = (nodeList: ParameterTreeNode[]) => {\n for (const node of nodeList) {\n if (node.children.length > 0) {\n paths.add(node.path);\n traverse(node.children);\n }\n }\n };\n\n traverse(nodes);\n return paths;\n};\n\nconst ParameterTree = ({\n treeData,\n selectedConfig,\n onSelect,\n onRefresh,\n}: ParameterTreeProps) => {\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [expandedNodes, setExpandedNodes] = useState<Set<string>>(() =>\n collectAllFolderPaths(treeData),\n );\n\n // Filter tree by search query\n const filteredTreeData = useMemo(\n () => filterTree(treeData, searchQuery),\n [treeData, searchQuery],\n );\n\n const handleToggle = useCallback((path: string) => {\n setExpandedNodes((prev) => {\n const next = new Set(prev);\n if (next.has(path)) {\n next.delete(path);\n } else {\n next.add(path);\n }\n return next;\n });\n }, []);\n\n const handleSearchChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n setSearchQuery(e.currentTarget.value);\n },\n [],\n );\n\n return (\n <Flex\n w={280}\n h=\"100%\"\n p=\"sm\"\n style={{\n flexShrink: 0,\n display: \"flex\",\n flexDirection: \"column\",\n borderRight: \"1px solid var(--mantine-color-default-border)\",\n }}\n >\n <Flex direction=\"column\" gap=\"sm\" h=\"100%\" style={{ minHeight: 0 }}>\n <Flex justify=\"space-between\" gap=\"xs\">\n <Text size=\"sm\" fw={600}>\n Parameters\n </Text>\n <ActionButton\n variant=\"subtle\"\n size=\"compact-xs\"\n onClick={onRefresh}\n tooltip=\"Refresh\"\n >\n <IconRefresh size={14} />\n </ActionButton>\n </Flex>\n\n <TextInput\n placeholder=\"Search...\"\n size=\"xs\"\n leftSection={<IconSearch size={14} />}\n value={searchQuery}\n onChange={handleSearchChange}\n />\n\n <ScrollArea flex={1} offsetScrollbars style={{ minHeight: 0 }}>\n {filteredTreeData.length === 0 ? (\n <Text size=\"xs\" c=\"dimmed\" ta=\"center\" py=\"md\">\n {searchQuery ? \"No matching parameters\" : \"No parameters\"}\n </Text>\n ) : (\n <Flex direction=\"column\" gap={0} style={{ gap: 1 }}>\n {filteredTreeData.map((node) => (\n <TreeNode\n key={node.path}\n node={node}\n level={0}\n selectedConfig={selectedConfig}\n onSelect={onSelect}\n expandedNodes={expandedNodes}\n onToggle={handleToggle}\n />\n ))}\n </Flex>\n )}\n </ScrollArea>\n </Flex>\n </Flex>\n );\n};\n\nexport default ParameterTree;\n","import { Flex, Text, useToast } from \"@alepha/ui\";\nimport { Card } from \"@mantine/core\";\nimport { IconSettings } from \"@tabler/icons-react\";\nimport type {\n AdminParameterController,\n ParameterResponse,\n ParameterTreeNode,\n} from \"alepha/api/parameters\";\nimport { useClient } from \"alepha/react\";\nimport { useCallback, useEffect, useState } from \"react\";\nimport ParameterDetails from \"./ParameterDetails.tsx\";\nimport ParameterEmptyState from \"./ParameterEmptyState.tsx\";\nimport ParameterHistory from \"./ParameterHistory.tsx\";\nimport ParameterTree from \"./ParameterTree.tsx\";\nimport type { ParameterValue } from \"./types.ts\";\n\nexport interface AdminParametersProps {\n treeData: ParameterTreeNode[];\n}\n\nconst AdminParameters = ({\n treeData: initialTreeData,\n}: AdminParametersProps) => {\n const client = useClient<AdminParameterController>();\n const toast = useToast();\n\n // State\n const [treeData, setTreeData] =\n useState<ParameterTreeNode[]>(initialTreeData);\n const [selectedConfig, setSelectedConfig] = useState<string | null>(null);\n const [configValue, setConfigValue] = useState<ParameterValue | null>(null);\n const [history, setHistory] = useState<ParameterResponse[]>([]);\n const [loadingConfig, setLoadingConfig] = useState(false);\n const [loadingHistory, setLoadingHistory] = useState(false);\n const [saving, setSaving] = useState(false);\n\n // Refresh tree data\n const handleRefresh = useCallback(async () => {\n try {\n const tree = await client.getParameterTree({});\n setTreeData(tree as ParameterTreeNode[]);\n } catch (error) {\n toast.danger({\n title: \"Failed to refresh parameters\",\n message: error instanceof Error ? error.message : \"Unknown error\",\n });\n }\n }, [client, toast]);\n\n // Load config value and history when selection changes\n const loadConfigDetails = useCallback(\n async (name: string) => {\n setLoadingConfig(true);\n setLoadingHistory(true);\n\n try {\n const [currentResponse, historyResponse] = await Promise.all([\n client.getCurrent({ params: { name } }),\n client.getHistory({ params: { name } }),\n ]);\n setConfigValue(currentResponse);\n setHistory(historyResponse.versions);\n } catch (error) {\n toast.danger({\n title: \"Failed to load configuration\",\n message: error instanceof Error ? error.message : \"Unknown error\",\n });\n setConfigValue(null);\n setHistory([]);\n } finally {\n setLoadingConfig(false);\n setLoadingHistory(false);\n }\n },\n [client, toast],\n );\n\n // Handle save\n const handleSave = useCallback(\n async (values: Record<string, unknown>) => {\n if (!selectedConfig || !configValue) return;\n\n setSaving(true);\n try {\n await client.createVersion({\n params: { name: selectedConfig },\n body: {\n content: values,\n schemaHash: \"\", // Schema hash is computed server-side when empty\n changeDescription: \"Updated via admin UI\",\n },\n });\n\n toast.success({\n title: \"Configuration saved\",\n message: `${selectedConfig} has been updated`,\n });\n\n // Reload details\n await loadConfigDetails(selectedConfig);\n } catch (error) {\n toast.danger({\n title: \"Failed to save configuration\",\n message: error instanceof Error ? error.message : \"Unknown error\",\n });\n throw error;\n } finally {\n setSaving(false);\n }\n },\n [client, selectedConfig, configValue, loadConfigDetails, toast],\n );\n\n // Load details when selection changes\n useEffect(() => {\n if (selectedConfig) {\n loadConfigDetails(selectedConfig);\n } else {\n setConfigValue(null);\n setHistory([]);\n }\n }, [selectedConfig, loadConfigDetails]);\n\n // Handle rollback\n const handleRollback = useCallback(\n async (version: number) => {\n if (!selectedConfig) return;\n\n try {\n await client.rollback({\n params: { name: selectedConfig },\n body: { targetVersion: version },\n });\n\n toast.success({\n title: \"Rollback successful\",\n message: `${selectedConfig} rolled back to version ${version}`,\n });\n\n // Reload details\n await loadConfigDetails(selectedConfig);\n } catch (error) {\n toast.danger({\n title: \"Rollback failed\",\n message: error instanceof Error ? error.message : \"Unknown error\",\n });\n }\n },\n [client, selectedConfig, loadConfigDetails, toast],\n );\n\n // Empty state when no configs exist\n if (treeData.length === 0) {\n return (\n <Flex flex={1} justify=\"center\" align=\"center\">\n <Flex direction=\"column\" align=\"center\" gap=\"xs\">\n <IconSettings\n size={48}\n stroke={1.5}\n color=\"var(--mantine-color-dimmed)\"\n />\n <Text c=\"dimmed\">No Parameters Found</Text>\n <Text size=\"xs\" c=\"dimmed\" ta=\"center\" maw={400}>\n Define parameters using the $parameter primitive to manage dynamic\n application settings. Parameters will appear here once created.\n </Text>\n </Flex>\n </Flex>\n );\n }\n\n return (\n <Flex flex={1} p=\"md\">\n <Card\n withBorder\n p={0}\n w={\"100%\"}\n style={{\n flexDirection: \"row\",\n }}\n >\n <ParameterTree\n treeData={treeData}\n selectedConfig={selectedConfig}\n onSelect={setSelectedConfig}\n onRefresh={handleRefresh}\n />\n\n {selectedConfig ? (\n <>\n <ParameterDetails\n selectedConfig={selectedConfig}\n configValue={configValue}\n loading={loadingConfig}\n saving={saving}\n onSave={handleSave}\n />\n\n <ParameterHistory\n selectedConfig={selectedConfig}\n history={history}\n loading={loadingHistory}\n onRollback={handleRollback}\n />\n </>\n ) : (\n <ParameterEmptyState />\n )}\n </Card>\n </Flex>\n );\n};\n\nexport default AdminParameters;\n"],"mappings":";;;;;;;;;;;AAmBA,MAAa,kBAAkB,WAAmB;AAChD,SAAQ,QAAR;EACE,KAAK,UACH,QAAO;EACT,KAAK,OACH,QAAO;EACT,KAAK,SACH,QAAO;EACT,KAAK,UACH,QAAO;EACT,QACE,QAAO;;;AAIb,MAAa,cAAc,QAAyB;AAClD,KAAI;AACF,SAAO,KAAK,UAAU,KAAK,MAAM,EAAE;SAC7B;AACN,SAAO,OAAO,IAAI;;;;;;;;;AClBtB,MAAM,qBACJ,oBAACA;CACC,MAAM;CACN,GAAE;CACF,GAAE;CACF,OAAO;EACL,UAAU;EACV,UAAU;EACV,SAAS;EACV;WAED,oBAACA;EAAK,MAAM;EAAG,SAAQ;EAAS,OAAM;EAAS,GAAE;YAC/C,oBAAC,UAAO,MAAK,OAAO;GACf;EACF;;;;AAaT,MAAM,cAAc,EAClB,gBACA,aACA,QACA,aACqB;CACrB,MAAM,EAAE,MAAM,SAAS;CAGvB,MAAM,iBAAiB,cAAc;AACnC,MAAI,aAAa,SAAS,QACxB,QAAO,YAAY,QAAQ;AAE7B,MAAI,aAAa,iBAAiB,OAChC,QAAO,YAAY;AAErB,SAAO;IACN,CAAC,YAAY,CAAC;CAGjB,MAAM,gBAAgB,cAAc;AAClC,MAAI,CAAC,aAAa,OAChB,QAAO,EAAE,OAAO,EAAE,CAAC;AAErB,MAAI;AACF,UAAO,oBAAoB,YAAY,OAAO;UACxC;AACN,UAAO,EAAE,OAAO,EAAE,CAAC;;IAEpB,CAAC,aAAa,OAAO,CAAC;CAEzB,MAAM,OAAO,QACX;EACE,QAAQ;EACR,eAAgB,kBAAkB,EAAE;EACpC,SAAS,OAAO,WAAW;AACzB,SAAM,OAAO,OAAkC;;EAElD,EACD;EAAC;EAAgB;EAAe;EAAe,CAChD;CAGD,MAAM,iBAAiB,cAAc;EACnC,MAAM,SAAS,aAAa;AAC5B,SACE,UACA,OAAO,WAAW,YAClB,gBAAgB,UAChB,OAAO,KAAK,OAAO,WAAqB,CAAC,SAAS;IAEnD,CAAC,aAAa,OAAO,CAAC;CAGzB,MAAM,aAAa,cAAc;EAC/B,MAAM,SAAS,aAAa;AAC5B,MACE,UACA,OAAO,WAAW,YAClB,gBAAgB,UAChB,OAAO,WAEP,QAAO,OAAO,KAAK,OAAO,WAAqB,CAAC;AAElD,SAAO;IACN,CAAC,aAAa,OAAO,CAAC;CAGzB,MAAM,UAAU,cAAc;AAC5B,MAAI,cAAc,EAAG,QAAO;AAC5B,MAAI,cAAc,EAAG,QAAO;AAC5B,SAAO;IACN,CAAC,WAAW,CAAC;AAEhB,QACE,oBAACA;EACC,MAAM;EACN,GAAE;EACF,OAAO;GACL,UAAU;GACV,UAAU;GACV,SAAS;GACV;YAED,qBAACA;GAAK,WAAU;GAAS,GAAE;GAAO,GAAE;GAAO,OAAO,EAAE,WAAW,GAAG;cAEhE,oBAACA;IACC,MAAM;IACN,GAAE;IACF,WAAU;IACV,OAAO,EAAE,WAAW,GAAG;cAEtB,mBAAmB,OAClB,qBAACA;KAAK,WAAU;KAAS,KAAI;;MAE3B,oBAACA,oBACE,iBACC,oBAAC;OACO;OACG;OACT;OACA,MAAM;QACN,GAEF,qBAACA,qBACC,oBAACC;OAAK,MAAK;OAAK,GAAE;OAAS,IAAI;iBAAG;QAE3B,EACP,oBAAC;OAAK;OAAM,OAAO,EAAE,YAAY,YAAY;iBAC1C,WAAW,eAAe;QACtB,IACF,GAEJ;MAGN,aAAa,SAAS,qBACrB,qBAACD,qBACC,oBAACC;OAAK,MAAK;OAAK,GAAE;OAAS,IAAI;iBAAG;QAE3B,EACP,oBAACA;OAAK,MAAK;iBAAM,YAAY,QAAQ;QAAyB,IACzD;MAGR,aAAa,WACZ,qBAACD;OAAK,KAAI;kBACR,qBAACA,qBACC,oBAACC;QAAK,MAAK;QAAK,GAAE;QAAS,IAAI;kBAAG;SAE3B,EACP,oBAACA;QAAK,MAAK;kBACR,EAAE,YAAY,QAAQ,WAAW,EAAE,MAAM,WAAW,CAAC;SACjD,IACF,EACN,YAAY,QAAQ,eACnB,qBAACD,qBACC,oBAACC;QAAK,MAAK;QAAK,GAAE;QAAS,IAAI;kBAAG;SAE3B,EACP,oBAACA;QAAK,MAAK;kBAAM,YAAY,QAAQ;SAAmB,IACnD;QAEJ;MAGR,CAAC,aAAa,WACb,aAAa,iBAAiB,UAC5B,oBAACA;OAAK,MAAK;OAAK,GAAE;iBAAS;QAGpB;MAIV,aAAa,QACZ,oBAAC;OAAK;OAAW,GAAE;OAAK,IAAG;iBACzB,qBAACD;QAAK,WAAU;QAAS,KAAI;;SAC3B,qBAACA;UAAK,KAAI;qBACR,oBAAC;WACC,MAAM;WACN,OAAM;YACN,EACF,qBAACC;WAAK,MAAK;WAAK,IAAI;WAAK,GAAE;;YAAO;YACZ,YAAY,KAAK;YAAQ;;YACxC;WACF;SACP,qBAACA;UAAK,MAAK;UAAK,GAAE;;WAAS;WACf;WACT,EAAE,YAAY,KAAK,gBAAgB,EAClC,MAAM,WACP,CAAC;;WACG;SACP,oBAAC;UAAK;UAAM,OAAO,EAAE,YAAY,YAAY;UAAE,IAAG;oBAC/C,WAAW,YAAY,KAAK,QAAQ;WAChC;;SACF;QACF;;MAEJ,GAEP,oBAACD;KAAK,SAAQ;KAAS,OAAM;KAAS,GAAG;eACvC,oBAACC;MAAK,GAAE;MAAS,MAAK;gBAAK;OAEpB;MACF;KAEJ,EAGN,kBAAkB,mBAAmB,QACpC,oBAACD;IACC,GAAE;IACF,OAAO;KACL,YAAY;KACZ,WAAW;KACZ;cAED,qBAACA;KAAK,SAAQ;KAAW,KAAI;gBAC3B,oBAAC;MACC,SAAQ;MACR,eAAe,KAAK,MAAM,EAAE,CAAQ;MACpC,UAAU;gBACX;OAEc,EACf,oBAAC;MAAa,QAAO;MAAgB;MAAM,SAAS;gBAAQ;OAE7C;MACV;KACF;IAEJ;GACF;;;;;;;AASX,MAAM,oBAAoB,EACxB,gBACA,aACA,SACA,QACA,aAC2B;AAE3B,KAAI,QACF,QAAO,oBAAC,iBAAe;AAIzB,QACE,oBAAC;EACiB;EACH;EACL;EACA;GACR;;;;;;;;;ACzRN,MAAM,4BAA4B;AAChC,QACE,oBAACE;EAAK,MAAM;EAAG,GAAG;EAAM,OAAM;YAC5B,qBAACA;GAAK,WAAU;GAAS,OAAM;GAAS,KAAI;cAC1C,oBAAC;IAAc,MAAM;IAAI,OAAM;KAAgC,EAC/D,qBAACA;IAAK,WAAU;IAAS,OAAM;IAAS,KAAK;eAC3C,oBAACC;KAAK,IAAI;KAAK,GAAE;eAAS;MAEnB,EACP,oBAACA;KAAK,MAAK;KAAK,GAAE;KAAS,IAAG;KAAS,KAAK;eAAK;MAE1C;KACF;IACF;GACF;;;;;ACPX,MAAM,oBAAoB,EACxB,SACA,SACA,iBAC2B;CAC3B,MAAM,EAAE,MAAM,SAAS;CAEvB,MAAM,sBAAsB;AAC1B,MAAI,QACF,QACE,oBAACC;GAAK,MAAM;GAAG,SAAQ;GAAS,OAAM;aACpC,oBAAC,UAAO,MAAK,OAAO;IACf;AAIX,MAAI,QAAQ,WAAW,EACrB,QACE,oBAACA;GAAK,MAAM;GAAG,SAAQ;GAAS,OAAM;aACpC,oBAACC;IAAK,GAAE;IAAS,MAAK;cAAK;KAEpB;IACF;AAIX,SACE,oBAAC;GAAW,MAAM;GAAG;aACnB,oBAAC;IACC,QAAQ,QAAQ,WAAW,MAAM,EAAE,WAAW,UAAU;IACxD,YAAY;IACZ,WAAW;cAEV,QAAQ,KAAK,YACZ,oBAAC,SAAS;KAER,QACE,oBAACA;MAAK,MAAK;MAAK,IAAI;gBACjB,QAAQ;OACJ;KAET,OACE,qBAACD;MAAK,KAAI;iBACR,qBAACC;OAAK,MAAK;OAAK,IAAI;kBAAK,YACd,QAAQ;QACZ,EACP,oBAAC;OACC,MAAK;OACL,SAAQ;OACR,OAAO,eAAe,QAAQ,OAAO;iBAEpC,QAAQ;QACH;OACH;eAGT,qBAACD;MAAK,WAAU;MAAS,KAAK;MAAG,IAAI;;OACnC,oBAACC;QAAK,MAAK;QAAK,GAAE;kBACf,EAAE,QAAQ,WAAW,EAAE,MAAM,WAAW,CAAC;SACrC;OACN,QAAQ,qBACP,oBAACA;QAAK,MAAK;QAAK,WAAW;kBACxB,QAAQ;SACJ;OAER,QAAQ,eACP,qBAACA;QAAK,MAAK;QAAK,GAAE;mBAAS,OACrB,QAAQ;SACP;OAER,QAAQ,gBACP,oBAAC;QAAM,MAAK;QAAK,SAAQ;QAAU,OAAM;kBAAS;SAE1C;OAET,QAAQ,WAAW,aAClB,oBAAC;QACC,MAAK;QACL,SAAQ;QACR,eAAe,WAAW,QAAQ,QAAQ;kBAC3C;SAEc;;OAEZ;OAjDF,QAAQ,GAkDC,CAChB;KACO;IACA;;AAIjB,QACE,oBAACD;EACC,GAAG;EACH,GAAE;EACF,GAAE;EACF,OAAO;GACL,YAAY;GACZ,UAAU;GACV,SAAS;GACT,eAAe;GACf,YAAY;GACb;YAED,qBAACA;GAAK,WAAU;GAAS,KAAI;GAAK,GAAE;GAAO,OAAO,EAAE,WAAW,GAAG;cAChE,qBAACA;IAAK,KAAI;eACR,oBAAC;KAAY,MAAM;KAAI,OAAM;MAAgC,EAC7D,oBAACC;KAAK,MAAK;KAAK,IAAI;eAAK;MAElB;KACF,EACN,eAAe;IACX;GACF;;;;;;;;ACxGX,MAAM,cACJ,OACA,UACwB;AACxB,KAAI,CAAC,MAAM,MAAM,CAAE,QAAO;CAE1B,MAAM,aAAa,MAAM,aAAa;AAEtC,QAAO,MACJ,KAAK,SAAS;EACb,MAAM,mBAAmB,WAAW,KAAK,UAAU,MAAM;EACzD,MAAM,cAAc,KAAK,KAAK,aAAa,CAAC,SAAS,WAAW;EAChE,MAAM,cAAc,KAAK,KAAK,aAAa,CAAC,SAAS,WAAW;AAEhE,MAAI,eAAe,eAAe,iBAAiB,SAAS,EAC1D,QAAO;GACL,GAAG;GACH,UAAU;GACX;AAGH,SAAO;GACP,CACD,QAAQ,SAAoC,SAAS,KAAK;;;;;AAe/D,MAAM,WAAW,MACd,EACC,MACA,OACA,gBACA,UACA,eACA,eACmB;CACnB,MAAM,CAAC,WAAW,gBAAgB,SAAS,MAAM;CACjD,MAAM,cAAc,KAAK,SAAS,SAAS;CAC3C,MAAM,aAAa,cAAc,IAAI,KAAK,KAAK;CAC/C,MAAM,aAAa,mBAAmB,KAAK;CAC3C,MAAM,SAAS,CAAC;AAahB,QACE,qBAACC,qBACC,oBAAC;EACC,SAdc,kBAAkB;AACpC,OAAI,YACF,UAAS,KAAK,KAAK;OAEnB,UAAS,KAAK,KAAK;KAEpB;GAAC;GAAa,KAAK;GAAM;GAAU;GAAS,CAAC;EAS1C,cAPmB,kBAAkB,aAAa,KAAK,EAAE,EAAE,CAAC;EAQ5D,cAPmB,kBAAkB,aAAa,MAAM,EAAE,EAAE,CAAC;EAQ7D,GAAE;EACF,OAAO,EAAE,SAAS,SAAS;YAE3B,qBAACA;GACC,KAAK;GACL,MAAK;GACL,GAAE;GACF,IAAI,IAAI,QAAQ;GAChB,OAAO;IACL,cAAc;IACd,iBACE,cAAc,YACV,uCACA;IACP;cAEA,cACC,4CACE,oBAACA;IACC,OAAO;KACL,SAAS;KACT,YAAY;KACZ,gBAAgB;KAChB,OAAO;KACR;cAEA,aACC,oBAAC;KACC,MAAM;KACN,OAAM;MACN,GAEF,oBAAC;KACC,MAAM;KACN,OAAM;MACN;KAEC,EACN,aACC,oBAAC;IACC,MAAM;IACN,OAAM;IACN,OAAO,EAAE,YAAY,GAAG;KACxB,GAEF,oBAAC;IACC,MAAM;IACN,OAAM;IACN,OAAO,EAAE,YAAY,GAAG;KACxB,IAEH,GAEH,4CACE,oBAACA,UAAK,GAAG,KAAM,EACf,oBAAC;IACC,MAAM;IACN,OACE,aACI,gCACA;IAEN,OAAO,EAAE,YAAY,GAAG;KACxB,IACD,EAEL,oBAACC;IACC,MAAK;IACL,IAAI,aAAa,MAAM;IACvB,GAAG,aAAa,SAAY,SAAS,SAAY;IACjD,OAAO;KACL,YAAY;KACZ,UAAU;KACV,cAAc;KACf;cAEA,KAAK;KACD;IACF;GACQ,EAEhB,eACC,oBAAC;EAAS,IAAI;YACX,KAAK,SAAS,KAAK,UAClB,oBAAC;GAEC,MAAM;GACN,OAAO,QAAQ;GACC;GACN;GACK;GACL;KANL,MAAM,KAOX,CACF;GACO,IAER;EAGZ;AAED,SAAS,cAAc;;;;AAKvB,MAAM,yBAAyB,UAA4C;CACzE,MAAM,wBAAQ,IAAI,KAAa;CAE/B,MAAM,YAAY,aAAkC;AAClD,OAAK,MAAM,QAAQ,SACjB,KAAI,KAAK,SAAS,SAAS,GAAG;AAC5B,SAAM,IAAI,KAAK,KAAK;AACpB,YAAS,KAAK,SAAS;;;AAK7B,UAAS,MAAM;AACf,QAAO;;AAGT,MAAM,iBAAiB,EACrB,UACA,gBACA,UACA,gBACwB;CACxB,MAAM,CAAC,aAAa,kBAAkB,SAAS,GAAG;CAClD,MAAM,CAAC,eAAe,oBAAoB,eACxC,sBAAsB,SAAS,CAChC;CAGD,MAAM,mBAAmB,cACjB,WAAW,UAAU,YAAY,EACvC,CAAC,UAAU,YAAY,CACxB;CAED,MAAM,eAAe,aAAa,SAAiB;AACjD,oBAAkB,SAAS;GACzB,MAAM,OAAO,IAAI,IAAI,KAAK;AAC1B,OAAI,KAAK,IAAI,KAAK,CAChB,MAAK,OAAO,KAAK;OAEjB,MAAK,IAAI,KAAK;AAEhB,UAAO;IACP;IACD,EAAE,CAAC;CAEN,MAAM,qBAAqB,aACxB,MAA2C;AAC1C,iBAAe,EAAE,cAAc,MAAM;IAEvC,EAAE,CACH;AAED,QACE,oBAACD;EACC,GAAG;EACH,GAAE;EACF,GAAE;EACF,OAAO;GACL,YAAY;GACZ,SAAS;GACT,eAAe;GACf,aAAa;GACd;YAED,qBAACA;GAAK,WAAU;GAAS,KAAI;GAAK,GAAE;GAAO,OAAO,EAAE,WAAW,GAAG;;IAChE,qBAACA;KAAK,SAAQ;KAAgB,KAAI;gBAChC,oBAACC;MAAK,MAAK;MAAK,IAAI;gBAAK;OAElB,EACP,oBAAC;MACC,SAAQ;MACR,MAAK;MACL,SAAS;MACT,SAAQ;gBAER,oBAAC,eAAY,MAAM,KAAM;OACZ;MACV;IAEP,oBAAC;KACC,aAAY;KACZ,MAAK;KACL,aAAa,oBAAC,cAAW,MAAM,KAAM;KACrC,OAAO;KACP,UAAU;MACV;IAEF,oBAAC;KAAW,MAAM;KAAG;KAAiB,OAAO,EAAE,WAAW,GAAG;eAC1D,iBAAiB,WAAW,IAC3B,oBAACA;MAAK,MAAK;MAAK,GAAE;MAAS,IAAG;MAAS,IAAG;gBACvC,cAAc,2BAA2B;OACrC,GAEP,oBAACD;MAAK,WAAU;MAAS,KAAK;MAAG,OAAO,EAAE,KAAK,GAAG;gBAC/C,iBAAiB,KAAK,SACrB,oBAAC;OAEO;OACN,OAAO;OACS;OACN;OACK;OACf,UAAU;SANL,KAAK,KAOV,CACF;OACG;MAEE;;IACR;GACF;;;;;ACjSX,MAAM,mBAAmB,EACvB,UAAU,sBACgB;CAC1B,MAAM,SAAS,WAAqC;CACpD,MAAM,QAAQ,UAAU;CAGxB,MAAM,CAAC,UAAU,eACf,SAA8B,gBAAgB;CAChD,MAAM,CAAC,gBAAgB,qBAAqB,SAAwB,KAAK;CACzE,MAAM,CAAC,aAAa,kBAAkB,SAAgC,KAAK;CAC3E,MAAM,CAAC,SAAS,cAAc,SAA8B,EAAE,CAAC;CAC/D,MAAM,CAAC,eAAe,oBAAoB,SAAS,MAAM;CACzD,MAAM,CAAC,gBAAgB,qBAAqB,SAAS,MAAM;CAC3D,MAAM,CAAC,QAAQ,aAAa,SAAS,MAAM;CAG3C,MAAM,gBAAgB,YAAY,YAAY;AAC5C,MAAI;AAEF,eADa,MAAM,OAAO,iBAAiB,EAAE,CAAC,CACN;WACjC,OAAO;AACd,SAAM,OAAO;IACX,OAAO;IACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU;IACnD,CAAC;;IAEH,CAAC,QAAQ,MAAM,CAAC;CAGnB,MAAM,oBAAoB,YACxB,OAAO,SAAiB;AACtB,mBAAiB,KAAK;AACtB,oBAAkB,KAAK;AAEvB,MAAI;GACF,MAAM,CAAC,iBAAiB,mBAAmB,MAAM,QAAQ,IAAI,CAC3D,OAAO,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,EACvC,OAAO,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CACxC,CAAC;AACF,kBAAe,gBAAgB;AAC/B,cAAW,gBAAgB,SAAS;WAC7B,OAAO;AACd,SAAM,OAAO;IACX,OAAO;IACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU;IACnD,CAAC;AACF,kBAAe,KAAK;AACpB,cAAW,EAAE,CAAC;YACN;AACR,oBAAiB,MAAM;AACvB,qBAAkB,MAAM;;IAG5B,CAAC,QAAQ,MAAM,CAChB;CAGD,MAAM,aAAa,YACjB,OAAO,WAAoC;AACzC,MAAI,CAAC,kBAAkB,CAAC,YAAa;AAErC,YAAU,KAAK;AACf,MAAI;AACF,SAAM,OAAO,cAAc;IACzB,QAAQ,EAAE,MAAM,gBAAgB;IAChC,MAAM;KACJ,SAAS;KACT,YAAY;KACZ,mBAAmB;KACpB;IACF,CAAC;AAEF,SAAM,QAAQ;IACZ,OAAO;IACP,SAAS,GAAG,eAAe;IAC5B,CAAC;AAGF,SAAM,kBAAkB,eAAe;WAChC,OAAO;AACd,SAAM,OAAO;IACX,OAAO;IACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU;IACnD,CAAC;AACF,SAAM;YACE;AACR,aAAU,MAAM;;IAGpB;EAAC;EAAQ;EAAgB;EAAa;EAAmB;EAAM,CAChE;AAGD,iBAAgB;AACd,MAAI,eACF,mBAAkB,eAAe;OAC5B;AACL,kBAAe,KAAK;AACpB,cAAW,EAAE,CAAC;;IAEf,CAAC,gBAAgB,kBAAkB,CAAC;CAGvC,MAAM,iBAAiB,YACrB,OAAO,YAAoB;AACzB,MAAI,CAAC,eAAgB;AAErB,MAAI;AACF,SAAM,OAAO,SAAS;IACpB,QAAQ,EAAE,MAAM,gBAAgB;IAChC,MAAM,EAAE,eAAe,SAAS;IACjC,CAAC;AAEF,SAAM,QAAQ;IACZ,OAAO;IACP,SAAS,GAAG,eAAe,0BAA0B;IACtD,CAAC;AAGF,SAAM,kBAAkB,eAAe;WAChC,OAAO;AACd,SAAM,OAAO;IACX,OAAO;IACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU;IACnD,CAAC;;IAGN;EAAC;EAAQ;EAAgB;EAAmB;EAAM,CACnD;AAGD,KAAI,SAAS,WAAW,EACtB,QACE,oBAACE;EAAK,MAAM;EAAG,SAAQ;EAAS,OAAM;YACpC,qBAACA;GAAK,WAAU;GAAS,OAAM;GAAS,KAAI;;IAC1C,oBAAC;KACC,MAAM;KACN,QAAQ;KACR,OAAM;MACN;IACF,oBAACC;KAAK,GAAE;eAAS;MAA0B;IAC3C,oBAACA;KAAK,MAAK;KAAK,GAAE;KAAS,IAAG;KAAS,KAAK;eAAK;MAG1C;;IACF;GACF;AAIX,QACE,oBAACD;EAAK,MAAM;EAAG,GAAE;YACf,qBAAC;GACC;GACA,GAAG;GACH,GAAG;GACH,OAAO,EACL,eAAe,OAChB;cAED,oBAAC;IACW;IACM;IAChB,UAAU;IACV,WAAW;KACX,EAED,iBACC,4CACE,oBAAC;IACiB;IACH;IACb,SAAS;IACD;IACR,QAAQ;KACR,EAEF,oBAAC;IACiB;IACP;IACT,SAAS;IACT,YAAY;KACZ,IACD,GAEH,oBAAC,wBAAsB;IAEpB;GACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AdminUserLayout-DUbC6-BI.js","names":["Flex","Text"],"sources":["../../src/admin/components/shared/AdminResourceHeader.tsx","../../src/admin/components/shared/AdminResourceTabs.tsx","../../src/admin/components/users/AdminUserLayout.tsx"],"sourcesContent":["import { ActionButton } from \"@alepha/ui\";\nimport {\n ActionIcon,\n Avatar,\n Badge,\n Button,\n Flex,\n Menu,\n Text,\n Tooltip,\n} from \"@mantine/core\";\nimport {\n IconChevronDown,\n IconChevronLeft,\n IconExternalLink,\n} from \"@tabler/icons-react\";\nimport { useRouter } from \"alepha/react/router\";\nimport type { ComponentType, ReactNode } from \"react\";\n\nexport interface AdminResourceAction {\n label: string;\n icon?: ComponentType<{ size?: number }>;\n onClick?: () => void;\n href?: string;\n color?: string;\n disabled?: boolean;\n loading?: boolean;\n variant?: \"filled\" | \"light\" | \"outline\" | \"subtle\";\n}\n\nexport interface AdminResourceHeaderProps {\n /**\n * Back navigation URL\n */\n backHref?: string;\n\n /**\n * Back navigation label\n */\n backLabel?: string;\n\n /**\n * Avatar content (letter, image URL, or custom node)\n */\n avatar?: string | ReactNode;\n\n /**\n * Avatar color\n */\n avatarColor?: string;\n\n /**\n * Resource title (e.g., user name)\n */\n title: string;\n\n /**\n * Secondary text (e.g., email)\n */\n subtitle?: string;\n\n /**\n * Tertiary identifier to copy (e.g., user ID)\n */\n identifier?: string;\n\n /**\n * Label for the identifier tooltip\n */\n identifierLabel?: string;\n\n /**\n * Status badge\n */\n status?: {\n label: string;\n color: \"green\" | \"red\" | \"yellow\" | \"blue\" | \"gray\";\n };\n\n /**\n * Additional badges (e.g., roles)\n */\n badges?: Array<{\n label: string;\n color?: string;\n variant?: \"filled\" | \"light\" | \"outline\" | \"dot\";\n }>;\n\n /**\n * Primary action button\n */\n primaryAction?: AdminResourceAction;\n\n /**\n * Menu actions (shown in dropdown)\n */\n menuActions?: AdminResourceAction[];\n\n /**\n * External link URL\n */\n externalUrl?: string;\n\n /**\n * Loading state\n */\n loading?: boolean;\n}\n\nconst ActionMenuItem = (props: { action: AdminResourceAction }) => {\n const { action } = props;\n const router = useRouter();\n\n const menuItemProps: Record<string, unknown> = {};\n if (action.href) {\n Object.assign(menuItemProps, router.anchor(action.href));\n } else if (action.onClick) {\n menuItemProps.onClick = action.onClick;\n }\n\n return (\n <Menu.Item\n leftSection={action.icon ? <action.icon size={16} /> : undefined}\n color={action.color}\n disabled={action.disabled}\n {...menuItemProps}\n >\n {action.label}\n </Menu.Item>\n );\n};\n\nconst AdminResourceHeader = (props: AdminResourceHeaderProps) => {\n const {\n backHref,\n backLabel = \"Back\",\n avatar,\n avatarColor = \"blue\",\n title,\n subtitle,\n identifier,\n identifierLabel = \"ID\",\n status,\n badges = [],\n primaryAction,\n menuActions = [],\n externalUrl,\n } = props;\n\n const renderAvatar = () => {\n if (typeof avatar === \"string\") {\n if (avatar.startsWith(\"http\") || avatar.startsWith(\"/\")) {\n return (\n <Avatar src={avatar} size={56} radius=\"md\" color={avatarColor} />\n );\n }\n return (\n <Avatar size={56} radius=\"md\" color={avatarColor}>\n {avatar}\n </Avatar>\n );\n }\n if (avatar) {\n return avatar;\n }\n return (\n <Avatar size={56} radius=\"md\" color={avatarColor}>\n {title.charAt(0).toUpperCase()}\n </Avatar>\n );\n };\n\n return (\n <Flex direction=\"column\" gap=\"xs\">\n {/* Breadcrumb / Back navigation */}\n {backHref && (\n <Flex>\n <ActionButton\n variant=\"subtle\"\n size=\"xs\"\n href={backHref}\n leftSection={<IconChevronLeft size={14} />}\n c=\"dimmed\"\n >\n {backLabel}\n </ActionButton>\n </Flex>\n )}\n\n {/* Main header */}\n <Flex justify=\"space-between\" align=\"flex-start\" wrap=\"nowrap\">\n {/* Left: Avatar + Info */}\n <Flex gap=\"md\" wrap=\"nowrap\">\n {renderAvatar()}\n\n <Flex\n direction=\"column\"\n gap={2}\n justify=\"center\"\n style={{ minHeight: 56 }}\n >\n {/* Title row */}\n <Flex gap=\"xs\" align=\"center\">\n <Text size=\"md\" fw={600} lh={1.2}>\n {title}\n </Text>\n {status && (\n <Badge\n size=\"xs\"\n variant=\"light\"\n color={status.color}\n tt=\"lowercase\"\n >\n {status.label}\n </Badge>\n )}\n </Flex>\n\n {/* Subtitle */}\n {subtitle && (\n <Text size=\"xs\" c=\"dimmed\">\n {subtitle}\n </Text>\n )}\n </Flex>\n </Flex>\n\n {/* Right: Actions */}\n <Flex gap=\"xs\">\n {externalUrl && (\n <Tooltip label=\"Open in new tab\" openDelay={500}>\n <ActionIcon\n variant=\"subtle\"\n color=\"gray\"\n component=\"a\"\n href={externalUrl}\n target=\"_blank\"\n >\n <IconExternalLink size={18} />\n </ActionIcon>\n </Tooltip>\n )}\n\n {primaryAction && (\n <ActionButton\n variant={primaryAction.variant ?? \"light\"}\n color={primaryAction.color}\n onClick={primaryAction.onClick}\n href={primaryAction.href}\n loading={primaryAction.loading}\n disabled={primaryAction.disabled}\n leftSection={\n primaryAction.icon ? (\n <primaryAction.icon size={16} />\n ) : undefined\n }\n >\n {primaryAction.label}\n </ActionButton>\n )}\n\n {menuActions.length > 0 && (\n <Menu position=\"bottom-end\" shadow=\"md\" width={220}>\n <Menu.Target>\n <Button\n variant=\"default\"\n rightSection={<IconChevronDown size={16} />}\n >\n Actions\n </Button>\n </Menu.Target>\n <Menu.Dropdown>\n {menuActions.map((action, index) => (\n <ActionMenuItem key={index} action={action} />\n ))}\n </Menu.Dropdown>\n </Menu>\n )}\n </Flex>\n </Flex>\n </Flex>\n );\n};\n\nexport default AdminResourceHeader;\n","import { Tabs } from \"@mantine/core\";\nimport { useActive, useRouter } from \"alepha/react/router\";\nimport type { ComponentType, ReactNode } from \"react\";\n\nexport interface AdminResourceTab {\n /**\n * Tab key/value\n */\n value: string;\n\n /**\n * Tab label\n */\n label: string;\n\n /**\n * Tab icon\n */\n icon?: ComponentType<{ size?: number }>;\n\n /**\n * Navigation href\n */\n href: string;\n\n /**\n * Whether tab is disabled\n */\n disabled?: boolean;\n\n /**\n * Badge count to show\n */\n count?: number;\n}\n\nexport interface AdminResourceTabsProps {\n /**\n * Array of tab configurations\n */\n tabs: AdminResourceTab[];\n\n /**\n * Currently active tab value\n */\n activeTab?: string;\n\n /**\n * Content to render below tabs\n */\n children?: ReactNode;\n}\n\nconst TabItem = (props: { tab: AdminResourceTab }) => {\n const { tab } = props;\n const router = useRouter();\n const { isActive, isPending } = useActive({ href: tab.href });\n const anchorProps = router.anchor(tab.href);\n\n return (\n <Tabs.Tab\n value={tab.value}\n component=\"a\"\n leftSection={tab.icon ? <tab.icon size={16} /> : undefined}\n disabled={tab.disabled}\n data-active={isActive || undefined}\n style={{\n opacity: isPending ? 0.6 : 1,\n }}\n {...anchorProps}\n >\n {tab.label}\n {tab.count !== undefined && tab.count > 0 && ` (${tab.count})`}\n </Tabs.Tab>\n );\n};\n\nconst AdminResourceTabs = (props: AdminResourceTabsProps) => {\n const { tabs, activeTab, children } = props;\n\n return (\n <Tabs value={activeTab} variant=\"default\">\n <Tabs.List>\n {tabs.map((tab) => (\n <TabItem key={tab.value} tab={tab} />\n ))}\n </Tabs.List>\n\n {children}\n </Tabs>\n );\n};\n\nexport default AdminResourceTabs;\n","import { Flex, Text, useDialog, useToast } from \"@alepha/ui\";\nimport { Loader } from \"@mantine/core\";\nimport { IconBan, IconShieldCheck, IconTrash } from \"@tabler/icons-react\";\nimport { t } from \"alepha\";\nimport type { AdminUserController, UserEntity } from \"alepha/api/users\";\nimport { useClient } from \"alepha/react\";\nimport { NestedView, useRouter, useRouterState } from \"alepha/react/router\";\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useState,\n} from \"react\";\nimport type { AdminRouter } from \"../../AdminRouter.ts\";\nimport AdminResourceHeader from \"../shared/AdminResourceHeader.tsx\";\nimport AdminResourceTabs from \"../shared/AdminResourceTabs.tsx\";\n\nexport interface AdminUserLayoutProps {\n userRealmName?: string;\n}\n\ninterface UserContextValue {\n user: UserEntity;\n reload: () => void;\n}\n\nconst UserContext = createContext<UserContextValue | null>(null);\n\nexport const useUser = () => {\n const ctx = useContext(UserContext);\n if (!ctx) throw new Error(\"useUser must be used within AdminUserLayout\");\n return ctx;\n};\n\nconst updateUserSchema = t.object({\n email: t.optional(t.email()),\n phoneNumber: t.optional(t.e164()),\n firstName: t.optional(t.string()),\n lastName: t.optional(t.string()),\n roles: t.optional(t.array(t.string())),\n enabled: t.optional(t.boolean()),\n});\n\nconst displayName = (u: UserEntity) =>\n u.firstName || u.lastName\n ? `${u.firstName ?? \"\"} ${u.lastName ?? \"\"}`.trim()\n : u.username || u.email || \"User\";\n\nconst AdminUserLayout = (props: AdminUserLayoutProps) => {\n const router = useRouter<AdminRouter>();\n const state = useRouterState();\n const client = useClient<AdminUserController>();\n const dialog = useDialog();\n const toast = useToast();\n const userId = state.params.userId as string;\n\n const [user, setUser] = useState<UserEntity | null>(null);\n const [loading, setLoading] = useState(true);\n\n const realmQuery = { userRealmName: props.userRealmName };\n\n const loadUser = useCallback(async () => {\n setLoading(true);\n try {\n const data = await client.getUser({\n params: { id: userId },\n query: realmQuery,\n });\n setUser(data);\n } finally {\n setLoading(false);\n }\n }, [userId, client]);\n\n useEffect(() => {\n loadUser();\n }, [loadUser]);\n\n const handleToggleEnabled = async () => {\n if (!user) return;\n const action = user.enabled ? \"disable\" : \"enable\";\n const confirmed = await dialog.confirm({\n title: `${user.enabled ? \"Disable\" : \"Enable\"} User`,\n message: `Are you sure you want to ${action} ${displayName(user)}?`,\n });\n if (confirmed) {\n const updated = await client.updateUser({\n params: { id: user.id },\n query: realmQuery,\n body: { enabled: !user.enabled },\n });\n setUser(updated);\n toast.success({ title: `User ${action}d` });\n }\n };\n\n const handleDelete = async () => {\n if (!user) return;\n const confirmed = await dialog.confirm({\n title: \"Delete User\",\n message: `Are you sure you want to delete ${displayName(user)}? This cannot be undone.`,\n confirmColor: \"red\",\n confirmLabel: \"Delete\",\n });\n if (confirmed) {\n await client.deleteUser({\n params: { id: user.id },\n query: realmQuery,\n });\n toast.success({ title: \"User deleted\" });\n router.push(\"adminUsers\");\n }\n };\n\n if (loading) {\n return (\n <Flex flex={1} justify=\"center\" align=\"center\">\n <Loader />\n </Flex>\n );\n }\n\n if (!user) {\n return (\n <Flex flex={1} justify=\"center\" align=\"center\">\n <Text c=\"dimmed\">User not found</Text>\n </Flex>\n );\n }\n\n return (\n <UserContext.Provider value={{ user, reload: loadUser }}>\n <Flex flex={1} direction=\"column\" gap=\"lg\" p=\"md\">\n <AdminResourceHeader\n backHref={router.path(\"adminUsers\")}\n backLabel=\"Users\"\n title={displayName(user)}\n subtitle={user.email || user.username}\n status={{\n label: user.enabled ? \"Active\" : \"Disabled\",\n color: user.enabled ? \"green\" : \"gray\",\n }}\n menuActions={[\n {\n label: user.enabled ? \"Disable\" : \"Enable\",\n icon: user.enabled ? IconBan : IconShieldCheck,\n onClick: handleToggleEnabled,\n },\n {\n label: \"Delete\",\n icon: IconTrash,\n color: \"red\",\n onClick: handleDelete,\n },\n ]}\n />\n\n <AdminResourceTabs\n tabs={[\n {\n value: \"profile\",\n label: \"Profile\",\n href: router.path(\"adminUserProfile\", {\n params: { userId },\n }),\n },\n {\n value: \"sessions\",\n label: \"Sessions\",\n href: router.path(\"adminUserSessions\", {\n params: { userId },\n }),\n },\n ]}\n />\n\n <NestedView />\n </Flex>\n </UserContext.Provider>\n );\n};\n\nexport default AdminUserLayout;\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AdminUserProfile-DuTUnjdG.js","names":["Flex"],"sources":["../../src/admin/components/users/AdminUserProfile.tsx"],"sourcesContent":["import { DetailList, Flex } from \"@alepha/ui\";\nimport { Badge } from \"@mantine/core\";\nimport { useI18n } from \"alepha/react/i18n\";\nimport { useUser } from \"./AdminUserLayout.tsx\";\n\nconst AdminUserProfile = () => {\n const { user } = useUser();\n const { l } = useI18n();\n\n return (\n <DetailList\n items={[\n { label: \"ID\", value: user.id, copyable: user.id },\n { label: \"Username\", value: user.username },\n { label: \"Email\", value: user.email },\n {\n label: \"Email Verified\",\n value: user.emailVerified ? \"Yes\" : \"No\",\n },\n { label: \"Phone\", value: user.phoneNumber },\n { label: \"First Name\", value: user.firstName },\n { label: \"Last Name\", value: user.lastName },\n { label: \"Realm\", value: user.realm },\n {\n label: \"Roles\",\n value:\n user.roles.length > 0 ? (\n <Flex gap={4}>\n {user.roles.map((role) => (\n <Badge key={role} size=\"xs\" variant=\"default\">\n {role}\n </Badge>\n ))}\n </Flex>\n ) : null,\n },\n {\n label: \"Created\",\n value: String(l(user.createdAt, { date: \"lll\" })),\n },\n {\n label: \"Updated\",\n value: String(l(user.updatedAt, { date: \"lll\" })),\n },\n ]}\n />\n );\n};\n\nexport default AdminUserProfile;\n"],"mappings":";;;;;;;AAKA,MAAM,yBAAyB;CAC7B,MAAM,EAAE,SAAS,SAAS;CAC1B,MAAM,EAAE,MAAM,SAAS;AAEvB,QACE,oBAAC,cACC,OAAO;EACL;GAAE,OAAO;GAAM,OAAO,KAAK;GAAI,UAAU,KAAK;GAAI;EAClD;GAAE,OAAO;GAAY,OAAO,KAAK;GAAU;EAC3C;GAAE,OAAO;GAAS,OAAO,KAAK;GAAO;EACrC;GACE,OAAO;GACP,OAAO,KAAK,gBAAgB,QAAQ;GACrC;EACD;GAAE,OAAO;GAAS,OAAO,KAAK;GAAa;EAC3C;GAAE,OAAO;GAAc,OAAO,KAAK;GAAW;EAC9C;GAAE,OAAO;GAAa,OAAO,KAAK;GAAU;EAC5C;GAAE,OAAO;GAAS,OAAO,KAAK;GAAO;EACrC;GACE,OAAO;GACP,OACE,KAAK,MAAM,SAAS,IAClB,oBAACA;IAAK,KAAK;cACR,KAAK,MAAM,KAAK,SACf,oBAAC;KAAiB,MAAK;KAAK,SAAQ;eACjC;OADS,KAEJ,CACR;KACG,GACL;GACP;EACD;GACE,OAAO;GACP,OAAO,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,OAAO,CAAC,CAAC;GAClD;EACD;GACE,OAAO;GACP,OAAO,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,OAAO,CAAC,CAAC;GAClD;EACF,GACD"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AdminUsers-CR9z0g_5.js","names":["Flex","Text"],"sources":["../../src/admin/components/users/AdminUsers.tsx"],"sourcesContent":["import {\n ActionButton,\n DataTable,\n Flex,\n Text,\n useDialog,\n useToast,\n} from \"@alepha/ui\";\nimport { Avatar, Badge } from \"@mantine/core\";\nimport {\n IconEye,\n IconTrash,\n IconUserOff,\n IconUserPlus,\n} from \"@tabler/icons-react\";\nimport { type Page, t } from \"alepha\";\nimport type { AdminUserController, UserEntity } from \"alepha/api/users\";\nimport { useClient } from \"alepha/react\";\nimport { useI18n } from \"alepha/react/i18n\";\nimport { useRouter } from \"alepha/react/router\";\nimport { useState } from \"react\";\nimport type { AdminRouter } from \"../../AdminRouter.tsx\";\n\nexport interface AdminUsersProps {\n userRealmName?: string;\n}\n\nconst filters = t.object({\n query: t.optional(t.text()),\n enabled: t.optional(t.boolean()),\n emailVerified: t.optional(t.boolean()),\n});\n\nconst AdminUsers = (props: AdminUsersProps) => {\n const client = useClient<AdminUserController>();\n const router = useRouter<AdminRouter>();\n const { l } = useI18n();\n const dialog = useDialog();\n const toast = useToast();\n const [refreshKey, setRefreshKey] = useState(0);\n\n const refresh = () => setRefreshKey((k) => k + 1);\n\n const handleToggleEnabled = async (user: UserEntity) => {\n const enabled = !user.enabled;\n const confirmed = await dialog.confirm({\n title: enabled ? \"Enable user\" : \"Disable user\",\n message: enabled\n ? `Enable ${user.email || user.username || \"this user\"}?`\n : `Disable ${user.email || user.username || \"this user\"}? They will no longer be able to sign in.`,\n });\n if (!confirmed) return;\n await client.updateUser({\n params: { id: user.id },\n query: { userRealmName: props.userRealmName },\n body: { enabled },\n });\n toast.success({\n message: enabled ? \"User enabled\" : \"User disabled\",\n });\n refresh();\n };\n\n const handleDelete = async (user: UserEntity) => {\n const confirmed = await dialog.confirm({\n title: \"Delete user\",\n message: `Permanently delete ${user.email || user.username || \"this user\"}? This action cannot be undone.`,\n });\n if (!confirmed) return;\n await client.deleteUser({\n params: { id: user.id },\n query: { userRealmName: props.userRealmName },\n });\n toast.success({ message: \"User deleted\" });\n refresh();\n };\n\n const handleBulkDisable = async (\n items: UserEntity[],\n clearSelection: () => void,\n ) => {\n const enabledUsers = items.filter((u) => u.enabled);\n if (enabledUsers.length === 0) {\n toast.danger({ message: \"No active users in selection\" });\n return;\n }\n const confirmed = await dialog.confirm({\n title: \"Disable users\",\n message: `Disable ${enabledUsers.length} user(s)? They will no longer be able to sign in.`,\n });\n if (!confirmed) return;\n for (const user of enabledUsers) {\n await client.updateUser({\n params: { id: user.id },\n query: { userRealmName: props.userRealmName },\n body: { enabled: false },\n });\n }\n toast.success({\n message: `${enabledUsers.length} user(s) disabled`,\n });\n clearSelection();\n refresh();\n };\n\n return (\n <Flex p={\"md\"} flex={1} direction=\"column\">\n <DataTable<UserEntity, typeof filters>\n withCheckbox\n withExport\n checkboxActions={[\n {\n intent: \"danger\",\n label: \"Disable selected\",\n icon: <IconUserOff />,\n onClick: (ctx) =>\n handleBulkDisable(ctx.selectedItems, ctx.clearSelection),\n },\n ]}\n key={refreshKey}\n submitOnInit\n defaultSize={10}\n defaultFilters={[\"query\"]}\n tableProps={{\n horizontalSpacing: \"xs\",\n verticalSpacing: \"xs\",\n }}\n onFilterChange={(_key, _value, form) => form.submit()}\n filters={filters}\n items={async (filters) => {\n const response = await client.findUsers({\n query: {\n ...filters,\n userRealmName: props.userRealmName,\n },\n });\n return response as Page<UserEntity>;\n }}\n columns={{\n user: {\n label: \"User\",\n value: (item) => {\n const name =\n `${item.firstName || \"\"} ${item.lastName || \"\"}`.trim() ||\n item.username ||\n \"Anonymous\";\n\n return (\n <ActionButton\n variant={\"transparent\"}\n href={`/admin/users/${item.id}`}\n >\n <Flex gap={\"xs\"} centerY>\n <Avatar size={\"sm\"} name={name} />\n <Flex col>\n <Text size={\"sm\"} bold>\n {name}\n </Text>\n <Text size=\"xs\" muted>\n {item.email || \"\\u2014\"}\n </Text>\n </Flex>\n </Flex>\n </ActionButton>\n );\n },\n },\n username: {\n label: \"Username\",\n defaultHidden: true,\n value: (item) => (\n <Text size=\"sm\" ff=\"monospace\">\n {item.username || \"\\u2014\"}\n </Text>\n ),\n },\n roles: {\n label: \"Roles\",\n value: (item) =>\n item.roles.length > 0 ? (\n <Flex gap={4}>\n {item.roles.map((role: string) => (\n <Badge key={role} size=\"xs\" variant=\"default\">\n {role}\n </Badge>\n ))}\n </Flex>\n ) : (\n <Text size=\"xs\" muted>\n No roles\n </Text>\n ),\n },\n enabled: {\n label: \"Status\",\n value: (item) => (\n <Badge\n size=\"sm\"\n variant=\"light\"\n color={item.enabled ? \"green\" : \"red\"}\n >\n {item.enabled ? \"Active\" : \"Disabled\"}\n </Badge>\n ),\n },\n emailVerified: {\n label: \"Email\",\n value: (item) => (\n <Badge\n size=\"sm\"\n variant=\"light\"\n color={item.emailVerified ? \"green\" : \"gray\"}\n >\n {item.emailVerified ? \"Verified\" : \"Unverified\"}\n </Badge>\n ),\n },\n createdAt: {\n label: \"Joined\",\n sortable: true,\n sortKey: \"createdAt\",\n value: (item) => (\n <Text size=\"xs\" muted>\n {l(item.createdAt, { date: \"fromNow\" })}\n </Text>\n ),\n },\n }}\n rowActions={(item) => [\n {\n label: \"View profile\",\n icon: IconEye,\n onClick: () =>\n router.push(\"adminUserProfile\", {\n params: { userId: item.id },\n }),\n },\n {\n label: item.enabled ? \"Disable user\" : \"Enable user\",\n icon: item.enabled ? IconUserOff : IconUserPlus,\n color: item.enabled ? \"red\" : \"green\",\n onClick: () => handleToggleEnabled(item),\n },\n {\n label: \"Delete user\",\n icon: IconTrash,\n color: \"red\",\n onClick: () => handleDelete(item),\n },\n ]}\n />\n </Flex>\n );\n};\n\nexport default AdminUsers;\n"],"mappings":"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"}
|
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
import { t as __exportAll } from "./rolldown-runtime-CjeV3_4I.js";
|
|
2
|
-
import { _ as ActionButton, a as Control, n as capitalize } from "./core-BVO_TQxb.js";
|
|
3
|
-
import { n as IconGithub, t as IconGoogle } from "./IconGoogle-Ch1m3Uzl.js";
|
|
4
|
-
import { AlephaError, t } from "alepha";
|
|
5
|
-
import { FormValidationError, useForm } from "alepha/react/form";
|
|
6
|
-
import { useI18n } from "alepha/react/i18n";
|
|
7
|
-
import { Card, Flex, Image, Text, Title } from "@mantine/core";
|
|
8
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
-
import { useMemo } from "react";
|
|
10
|
-
import { IconLock, IconUser } from "@tabler/icons-react";
|
|
11
|
-
import { useRouter } from "alepha/react/router";
|
|
12
|
-
import { useAuth } from "alepha/react/auth";
|
|
13
|
-
import { HttpError } from "alepha/server";
|
|
14
|
-
|
|
15
|
-
//#region ../../src/auth/components/Login.tsx
|
|
16
|
-
var Login_exports = /* @__PURE__ */ __exportAll({ default: () => Login });
|
|
17
|
-
const Login = (props) => {
|
|
18
|
-
const auth = useAuth();
|
|
19
|
-
const router = useRouter();
|
|
20
|
-
const { tr } = useI18n();
|
|
21
|
-
const redirect = router.query.r || "/";
|
|
22
|
-
const credentialsProvider = props.realmConfig.authenticationMethods.find((it) => it.type === "CREDENTIALS");
|
|
23
|
-
const settings = props.realmConfig.settings;
|
|
24
|
-
const loginMethods = useMemo(() => {
|
|
25
|
-
const methods = [];
|
|
26
|
-
if (settings.usernameEnabled !== false) methods.push("username");
|
|
27
|
-
if (settings.emailEnabled !== false) methods.push("email");
|
|
28
|
-
if (settings.phoneEnabled === true) methods.push("phone");
|
|
29
|
-
return methods;
|
|
30
|
-
}, [settings]);
|
|
31
|
-
const identifierTitle = useMemo(() => {
|
|
32
|
-
if (loginMethods.length === 0) return tr("loginUsername");
|
|
33
|
-
if (loginMethods.length === 1) {
|
|
34
|
-
if (loginMethods[0] === "username") return tr("loginUsername");
|
|
35
|
-
if (loginMethods[0] === "email") return tr("loginEmail");
|
|
36
|
-
if (loginMethods[0] === "phone") return tr("loginPhone");
|
|
37
|
-
}
|
|
38
|
-
const labels = loginMethods.map((m) => {
|
|
39
|
-
if (m === "username") return tr("loginUsername").toLowerCase();
|
|
40
|
-
if (m === "email") return tr("loginEmail").toLowerCase();
|
|
41
|
-
if (m === "phone") return tr("loginPhone").toLowerCase();
|
|
42
|
-
return m;
|
|
43
|
-
});
|
|
44
|
-
return capitalize(`${labels.slice(0, -1).join(", ")} or ${labels[labels.length - 1]}`);
|
|
45
|
-
}, [loginMethods, tr]);
|
|
46
|
-
const form = useForm({
|
|
47
|
-
schema: t.object({
|
|
48
|
-
identifier: t.string({ minLength: 1 }),
|
|
49
|
-
password: t.string({ minLength: settings.passwordPolicy?.minLength || 6 })
|
|
50
|
-
}),
|
|
51
|
-
handler: async (data) => {
|
|
52
|
-
if (!credentialsProvider) throw new AlephaError("Credentials provider not configured");
|
|
53
|
-
try {
|
|
54
|
-
await auth.login(credentialsProvider.name, {
|
|
55
|
-
username: data.identifier,
|
|
56
|
-
password: data.password,
|
|
57
|
-
realm: props.realmConfig.realmName
|
|
58
|
-
});
|
|
59
|
-
await router.push(router.query.r || "/");
|
|
60
|
-
} catch (error) {
|
|
61
|
-
if (error instanceof HttpError && error.error === "InvalidCredentialsError") throw new FormValidationError({
|
|
62
|
-
message: "Invalid identifier or password",
|
|
63
|
-
path: "/password"
|
|
64
|
-
});
|
|
65
|
-
throw error;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
const getAutoCompleteType = () => {
|
|
70
|
-
if (loginMethods.includes("email")) return "email";
|
|
71
|
-
if (loginMethods.includes("username")) return "username";
|
|
72
|
-
if (loginMethods.includes("phone")) return "tel";
|
|
73
|
-
return "username";
|
|
74
|
-
};
|
|
75
|
-
const externalLoginMethods = props.realmConfig.authenticationMethods.filter((method) => method.type !== "CREDENTIALS");
|
|
76
|
-
const showOrDivider = credentialsProvider && externalLoginMethods.length > 0;
|
|
77
|
-
return /* @__PURE__ */ jsx(Flex, {
|
|
78
|
-
flex: 1,
|
|
79
|
-
justify: "center",
|
|
80
|
-
align: "center",
|
|
81
|
-
children: /* @__PURE__ */ jsxs(Flex, {
|
|
82
|
-
direction: "column",
|
|
83
|
-
gap: "sm",
|
|
84
|
-
w: 360,
|
|
85
|
-
children: [/* @__PURE__ */ jsx(Card, {
|
|
86
|
-
withBorder: true,
|
|
87
|
-
p: "lg",
|
|
88
|
-
bg: "var(--alepha-elevated)",
|
|
89
|
-
children: /* @__PURE__ */ jsxs(Flex, {
|
|
90
|
-
direction: "column",
|
|
91
|
-
gap: "md",
|
|
92
|
-
children: [
|
|
93
|
-
(settings.logoUrl || settings.displayName || settings.description) && /* @__PURE__ */ jsxs(Flex, {
|
|
94
|
-
direction: "column",
|
|
95
|
-
gap: "xs",
|
|
96
|
-
align: "center",
|
|
97
|
-
mb: "xs",
|
|
98
|
-
children: [
|
|
99
|
-
settings.logoUrl && /* @__PURE__ */ jsx(Image, {
|
|
100
|
-
src: settings.logoUrl,
|
|
101
|
-
alt: settings.displayName || props.realmConfig.realmName,
|
|
102
|
-
h: 48,
|
|
103
|
-
w: "auto",
|
|
104
|
-
fit: "contain"
|
|
105
|
-
}),
|
|
106
|
-
settings.displayName && /* @__PURE__ */ jsx(Title, {
|
|
107
|
-
order: 4,
|
|
108
|
-
ta: "center",
|
|
109
|
-
children: settings.displayName
|
|
110
|
-
}),
|
|
111
|
-
settings.description && /* @__PURE__ */ jsx(Text, {
|
|
112
|
-
size: "sm",
|
|
113
|
-
c: "dimmed",
|
|
114
|
-
ta: "center",
|
|
115
|
-
children: settings.description
|
|
116
|
-
})
|
|
117
|
-
]
|
|
118
|
-
}),
|
|
119
|
-
credentialsProvider && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("form", {
|
|
120
|
-
...form.props,
|
|
121
|
-
children: /* @__PURE__ */ jsxs(Flex, {
|
|
122
|
-
direction: "column",
|
|
123
|
-
flex: 1,
|
|
124
|
-
gap: "md",
|
|
125
|
-
children: [
|
|
126
|
-
/* @__PURE__ */ jsx(Control, {
|
|
127
|
-
title: identifierTitle,
|
|
128
|
-
input: form.input.identifier,
|
|
129
|
-
icon: IconUser,
|
|
130
|
-
text: { autoComplete: getAutoCompleteType() }
|
|
131
|
-
}),
|
|
132
|
-
/* @__PURE__ */ jsx(Control, {
|
|
133
|
-
title: tr("loginPassword"),
|
|
134
|
-
input: form.input.password,
|
|
135
|
-
icon: IconLock,
|
|
136
|
-
password: { autoComplete: "current-password" }
|
|
137
|
-
}),
|
|
138
|
-
/* @__PURE__ */ jsx(ActionButton, {
|
|
139
|
-
variant: "filled",
|
|
140
|
-
form,
|
|
141
|
-
children: tr("loginSignIn")
|
|
142
|
-
})
|
|
143
|
-
]
|
|
144
|
-
})
|
|
145
|
-
}), settings.resetPasswordAllowed && /* @__PURE__ */ jsx(Text, {
|
|
146
|
-
size: "sm",
|
|
147
|
-
ta: "center",
|
|
148
|
-
children: /* @__PURE__ */ jsx(ActionButton, {
|
|
149
|
-
href: router.path("resetPassword", { query: { realm: props.realmConfig.realmName } }),
|
|
150
|
-
anchorProps: { inherit: true },
|
|
151
|
-
children: tr("loginForgotPassword")
|
|
152
|
-
})
|
|
153
|
-
})] }),
|
|
154
|
-
showOrDivider && /* @__PURE__ */ jsxs(Flex, {
|
|
155
|
-
align: "center",
|
|
156
|
-
justify: "center",
|
|
157
|
-
gap: "md",
|
|
158
|
-
children: [
|
|
159
|
-
/* @__PURE__ */ jsx(Flex, {
|
|
160
|
-
flex: 1,
|
|
161
|
-
h: "1px",
|
|
162
|
-
bg: "var(--alepha-border)"
|
|
163
|
-
}),
|
|
164
|
-
/* @__PURE__ */ jsx(Text, {
|
|
165
|
-
size: "xs",
|
|
166
|
-
c: "dimmed",
|
|
167
|
-
children: tr("loginOr")
|
|
168
|
-
}),
|
|
169
|
-
/* @__PURE__ */ jsx(Flex, {
|
|
170
|
-
flex: 1,
|
|
171
|
-
h: "1px",
|
|
172
|
-
bg: "var(--alepha-border)"
|
|
173
|
-
})
|
|
174
|
-
]
|
|
175
|
-
}),
|
|
176
|
-
externalLoginMethods.length > 0 && /* @__PURE__ */ jsx(Flex, {
|
|
177
|
-
direction: "column",
|
|
178
|
-
gap: "sm",
|
|
179
|
-
children: externalLoginMethods.map((method) => /* @__PURE__ */ jsx(ActionButton, {
|
|
180
|
-
variant: "default",
|
|
181
|
-
leftSection: leftSection(method.name.toLowerCase()),
|
|
182
|
-
onClick: () => auth.login(method.name, {
|
|
183
|
-
redirect,
|
|
184
|
-
realm: props.realmConfig.realmName
|
|
185
|
-
}),
|
|
186
|
-
children: tr("loginContinueWith", { args: [capitalize(method.name)] })
|
|
187
|
-
}, method.type))
|
|
188
|
-
}),
|
|
189
|
-
settings.registrationAllowed && /* @__PURE__ */ jsxs(Text, {
|
|
190
|
-
size: "sm",
|
|
191
|
-
ta: "center",
|
|
192
|
-
children: [
|
|
193
|
-
tr("loginNoAccount"),
|
|
194
|
-
" ",
|
|
195
|
-
/* @__PURE__ */ jsx(ActionButton, {
|
|
196
|
-
href: router.path("register", { query: { realm: props.realmConfig.realmName } }),
|
|
197
|
-
anchorProps: { inherit: true },
|
|
198
|
-
children: tr("loginSignUp")
|
|
199
|
-
})
|
|
200
|
-
]
|
|
201
|
-
})
|
|
202
|
-
]
|
|
203
|
-
})
|
|
204
|
-
}), /* @__PURE__ */ jsx(ActionButton, {
|
|
205
|
-
variant: "subtle",
|
|
206
|
-
href: "/",
|
|
207
|
-
children: tr("loginCancel")
|
|
208
|
-
})]
|
|
209
|
-
})
|
|
210
|
-
});
|
|
211
|
-
};
|
|
212
|
-
const leftSection = (name) => {
|
|
213
|
-
if (name === "google") return /* @__PURE__ */ jsx(IconGoogle, {});
|
|
214
|
-
if (name === "github") return /* @__PURE__ */ jsx(IconGithub, {});
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
//#endregion
|
|
218
|
-
export { Login_exports as t };
|
|
219
|
-
//# sourceMappingURL=Login-DHbYJKwg.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Login-DHbYJKwg.js","names":[],"sources":["../../src/auth/components/Login.tsx"],"sourcesContent":["import { ActionButton, Control, capitalize } from \"@alepha/ui\";\nimport { Card, Flex, Image, Text, Title } from \"@mantine/core\";\nimport { IconLock, IconUser } from \"@tabler/icons-react\";\nimport { AlephaError, t } from \"alepha\";\nimport type { RealmConfig } from \"alepha/api/users\";\nimport { useAuth } from \"alepha/react/auth\";\nimport { FormValidationError, useForm } from \"alepha/react/form\";\nimport { useI18n } from \"alepha/react/i18n\";\nimport { useRouter } from \"alepha/react/router\";\nimport { HttpError } from \"alepha/server\";\nimport { useMemo } from \"react\";\nimport type { AuthI18n } from \"../AuthI18n.ts\";\nimport type { AuthRouter } from \"../AuthRouter.ts\";\nimport IconGithub from \"./icons/IconGithub.tsx\";\nimport IconGoogle from \"./icons/IconGoogle.tsx\";\n\nexport interface LoginProps {\n realmConfig: RealmConfig;\n}\n\nconst Login = (props: LoginProps) => {\n const auth = useAuth();\n const router = useRouter<AuthRouter>();\n const { tr } = useI18n<AuthI18n, \"en\">();\n const redirect = router.query.r || \"/\";\n\n const credentialsProvider = props.realmConfig.authenticationMethods.find(\n (it) => it.type === \"CREDENTIALS\",\n );\n\n const settings = props.realmConfig.settings;\n\n // Determine what login methods are available\n const loginMethods = useMemo(() => {\n const methods = [];\n if (settings.usernameEnabled !== false) methods.push(\"username\");\n if (settings.emailEnabled !== false) methods.push(\"email\");\n if (settings.phoneEnabled === true) methods.push(\"phone\");\n return methods;\n }, [settings]);\n\n // Create identifier title based on enabled methods\n const identifierTitle = useMemo(() => {\n if (loginMethods.length === 0) return tr(\"loginUsername\");\n if (loginMethods.length === 1) {\n if (loginMethods[0] === \"username\") return tr(\"loginUsername\");\n if (loginMethods[0] === \"email\") return tr(\"loginEmail\");\n if (loginMethods[0] === \"phone\") return tr(\"loginPhone\");\n }\n const labels = loginMethods.map((m) => {\n if (m === \"username\") return tr(\"loginUsername\").toLowerCase();\n if (m === \"email\") return tr(\"loginEmail\").toLowerCase();\n if (m === \"phone\") return tr(\"loginPhone\").toLowerCase();\n return m;\n });\n return capitalize(\n `${labels.slice(0, -1).join(\", \")} or ${labels[labels.length - 1]}`,\n );\n }, [loginMethods, tr]);\n\n const form = useForm({\n schema: t.object({\n identifier: t.string({\n minLength: 1,\n }),\n password: t.string({\n minLength: settings.passwordPolicy?.minLength || 6,\n }),\n }),\n handler: async (data) => {\n if (!credentialsProvider) {\n throw new AlephaError(\"Credentials provider not configured\");\n }\n\n try {\n await auth.login(credentialsProvider.name, {\n username: data.identifier,\n password: data.password,\n realm: props.realmConfig.realmName,\n });\n await router.push(router.query.r || \"/\");\n } catch (error) {\n if (\n error instanceof HttpError &&\n error.error === \"InvalidCredentialsError\"\n ) {\n throw new FormValidationError({\n message: \"Invalid identifier or password\",\n path: \"/password\",\n });\n }\n throw error;\n }\n },\n });\n\n const getAutoCompleteType = () => {\n if (loginMethods.includes(\"email\")) {\n return \"email\";\n }\n if (loginMethods.includes(\"username\")) {\n return \"username\";\n }\n if (loginMethods.includes(\"phone\")) {\n return \"tel\";\n }\n return \"username\";\n };\n\n const externalLoginMethods = props.realmConfig.authenticationMethods.filter(\n (method) => method.type !== \"CREDENTIALS\",\n );\n\n const showOrDivider = credentialsProvider && externalLoginMethods.length > 0;\n\n return (\n <Flex flex={1} justify={\"center\"} align={\"center\"}>\n <Flex direction=\"column\" gap={\"sm\"} w={360}>\n <Card withBorder p={\"lg\"} bg={\"var(--alepha-elevated)\"}>\n <Flex direction=\"column\" gap={\"md\"}>\n {/* Realm branding */}\n {(settings.logoUrl ||\n settings.displayName ||\n settings.description) && (\n <Flex direction=\"column\" gap={\"xs\"} align=\"center\" mb=\"xs\">\n {settings.logoUrl && (\n <Image\n src={settings.logoUrl}\n alt={settings.displayName || props.realmConfig.realmName}\n h={48}\n w=\"auto\"\n fit=\"contain\"\n />\n )}\n {settings.displayName && (\n <Title order={4} ta=\"center\">\n {settings.displayName}\n </Title>\n )}\n {settings.description && (\n <Text size=\"sm\" c=\"dimmed\" ta=\"center\">\n {settings.description}\n </Text>\n )}\n </Flex>\n )}\n\n {/* Credentials login form */}\n {credentialsProvider && (\n <>\n <form {...form.props}>\n <Flex direction=\"column\" flex={1} gap={\"md\"}>\n <Control\n title={identifierTitle}\n input={form.input.identifier}\n icon={IconUser}\n text={{\n autoComplete: getAutoCompleteType(),\n }}\n />\n <Control\n title={tr(\"loginPassword\")}\n input={form.input.password}\n icon={IconLock}\n password={{\n autoComplete: \"current-password\",\n }}\n />\n <ActionButton variant={\"filled\"} form={form}>\n {tr(\"loginSignIn\")}\n </ActionButton>\n </Flex>\n </form>\n {settings.resetPasswordAllowed && (\n <Text size=\"sm\" ta=\"center\">\n <ActionButton\n href={router.path(\"resetPassword\", {\n query: { realm: props.realmConfig.realmName },\n })}\n anchorProps={{ inherit: true }}\n >\n {tr(\"loginForgotPassword\")}\n </ActionButton>\n </Text>\n )}\n </>\n )}\n\n {/* OR divider - only when both credentials AND external methods exist */}\n {showOrDivider && (\n <Flex align=\"center\" justify=\"center\" gap={\"md\"}>\n <Flex flex={1} h={\"1px\"} bg={\"var(--alepha-border)\"} />\n <Text size=\"xs\" c={\"dimmed\"}>\n {tr(\"loginOr\")}\n </Text>\n <Flex flex={1} h={\"1px\"} bg={\"var(--alepha-border)\"} />\n </Flex>\n )}\n\n {/* External login methods */}\n {externalLoginMethods.length > 0 && (\n <Flex direction=\"column\" gap={\"sm\"}>\n {externalLoginMethods.map((method) => (\n <ActionButton\n variant={\"default\"}\n key={method.type}\n leftSection={leftSection(method.name.toLowerCase())}\n onClick={() =>\n auth.login(method.name, {\n redirect,\n realm: props.realmConfig.realmName,\n })\n }\n >\n {tr(\"loginContinueWith\", {\n args: [capitalize(method.name)],\n })}\n </ActionButton>\n ))}\n </Flex>\n )}\n\n {/* Registration link */}\n {settings.registrationAllowed && (\n <Text size=\"sm\" ta=\"center\">\n {tr(\"loginNoAccount\")}{\" \"}\n <ActionButton\n href={router.path(\"register\", {\n query: { realm: props.realmConfig.realmName },\n })}\n anchorProps={{ inherit: true }}\n >\n {tr(\"loginSignUp\")}\n </ActionButton>\n </Text>\n )}\n </Flex>\n </Card>\n <ActionButton variant={\"subtle\"} href={\"/\"}>\n {tr(\"loginCancel\")}\n </ActionButton>\n </Flex>\n </Flex>\n );\n};\n\nexport default Login;\n\nconst leftSection = (name: string) => {\n if (name === \"google\") {\n return <IconGoogle />;\n }\n\n if (name === \"github\") {\n return <IconGithub />;\n }\n};\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Profile-B2EcIDB9.js","names":[],"sources":["../../src/auth/components/Profile.tsx"],"sourcesContent":["import { ActionButton } from \"@alepha/ui\";\nimport { Avatar, Badge, Card, Flex, Text, Title } from \"@mantine/core\";\nimport {\n IconAt,\n IconCalendar,\n IconId,\n IconShield,\n IconUser,\n} from \"@tabler/icons-react\";\nimport { useAuth } from \"alepha/react/auth\";\nimport { useRouter } from \"alepha/react/router\";\nimport type { AuthRouter } from \"../AuthRouter.ts\";\n\nconst Profile = () => {\n const auth = useAuth();\n const router = useRouter<AuthRouter>();\n\n if (!auth.user) {\n return null;\n }\n\n const { id, name, email, username, picture, roles, organizations } =\n auth.user;\n\n const displayName = name || username || email || \"User\";\n\n return (\n <Flex flex={1} justify=\"center\" align=\"center\">\n <Flex direction=\"column\" gap=\"md\" w={400}>\n <Card withBorder p=\"xl\" bg=\"var(--alepha-elevated)\">\n <Flex direction=\"column\" gap=\"lg\">\n {/* Avatar and name */}\n <Flex direction=\"column\" align=\"center\" gap=\"md\">\n <Avatar\n src={picture ? `/api/files/${picture}` : undefined}\n size={96}\n radius=\"xl\"\n color=\"blue\"\n >\n {!picture && <IconUser size={48} />}\n </Avatar>\n <Flex direction=\"column\" gap={4} align=\"center\">\n <Title order={3}>{displayName}</Title>\n {email && username && (\n <Text size=\"sm\" c=\"dimmed\">\n @{username}\n </Text>\n )}\n </Flex>\n </Flex>\n\n {/* User details */}\n <Flex direction=\"column\" gap=\"sm\">\n {email && (\n <ProfileField icon={<IconAt size={18} />} label=\"Email\">\n {email}\n </ProfileField>\n )}\n\n {username && (\n <ProfileField icon={<IconUser size={18} />} label=\"Username\">\n {username}\n </ProfileField>\n )}\n\n {id && (\n <ProfileField icon={<IconId size={18} />} label=\"User ID\">\n <Text size=\"xs\" c=\"dimmed\" ff=\"monospace\">\n {id}\n </Text>\n </ProfileField>\n )}\n\n {/* Roles */}\n {roles && roles.length > 0 && (\n <ProfileField icon={<IconShield size={18} />} label=\"Roles\">\n <Flex gap=\"xs\">\n {roles.map((role) => (\n <Badge key={role} size=\"sm\" variant=\"light\">\n {role}\n </Badge>\n ))}\n </Flex>\n </ProfileField>\n )}\n\n {/* Organizations */}\n {organizations && organizations.length > 0 && (\n <ProfileField\n icon={<IconCalendar size={18} />}\n label=\"Organizations\"\n >\n <Flex gap=\"xs\">\n {organizations.map((org) => (\n <Badge key={org} size=\"sm\" variant=\"outline\">\n {org}\n </Badge>\n ))}\n </Flex>\n </ProfileField>\n )}\n </Flex>\n\n {/* Actions */}\n <Flex direction=\"column\" gap=\"sm\" mt=\"md\">\n <ActionButton\n variant=\"light\"\n color=\"red\"\n onClick={() => auth.logout()}\n fullWidth\n >\n Sign out\n </ActionButton>\n </Flex>\n </Flex>\n </Card>\n\n <ActionButton variant=\"subtle\" href=\"/\">\n Back to home\n </ActionButton>\n </Flex>\n </Flex>\n );\n};\n\ninterface ProfileFieldProps {\n icon: React.ReactNode;\n label: string;\n children: React.ReactNode;\n}\n\nconst ProfileField = ({ icon, label, children }: ProfileFieldProps) => {\n return (\n <Flex gap=\"sm\" align=\"flex-start\">\n <Flex c=\"dimmed\" mt={2}>\n {icon}\n </Flex>\n <Flex direction=\"column\" gap={2} flex={1}>\n <Text size=\"xs\" c=\"dimmed\" tt=\"uppercase\" fw={500}>\n {label}\n </Text>\n <Text size=\"sm\">{children}</Text>\n </Flex>\n </Flex>\n );\n};\n\nexport default Profile;\n"],"mappings":";;;;;;;;AAaA,MAAM,gBAAgB;CACpB,MAAM,OAAO,SAAS;AACP,YAAuB;AAEtC,KAAI,CAAC,KAAK,KACR,QAAO;CAGT,MAAM,EAAE,IAAI,MAAM,OAAO,UAAU,SAAS,OAAO,kBACjD,KAAK;CAEP,MAAM,cAAc,QAAQ,YAAY,SAAS;AAEjD,QACE,oBAAC;EAAK,MAAM;EAAG,SAAQ;EAAS,OAAM;YACpC,qBAAC;GAAK,WAAU;GAAS,KAAI;GAAK,GAAG;cACnC,oBAAC;IAAK;IAAW,GAAE;IAAK,IAAG;cACzB,qBAAC;KAAK,WAAU;KAAS,KAAI;;MAE3B,qBAAC;OAAK,WAAU;OAAS,OAAM;OAAS,KAAI;kBAC1C,oBAAC;QACC,KAAK,UAAU,cAAc,YAAY;QACzC,MAAM;QACN,QAAO;QACP,OAAM;kBAEL,CAAC,WAAW,oBAAC,YAAS,MAAM,KAAM;SAC5B,EACT,qBAAC;QAAK,WAAU;QAAS,KAAK;QAAG,OAAM;mBACrC,oBAAC;SAAM,OAAO;mBAAI;UAAoB,EACrC,SAAS,YACR,qBAAC;SAAK,MAAK;SAAK,GAAE;oBAAS,KACvB;UACG;SAEJ;QACF;MAGP,qBAAC;OAAK,WAAU;OAAS,KAAI;;QAC1B,SACC,oBAAC;SAAa,MAAM,oBAAC,UAAO,MAAM,KAAM;SAAE,OAAM;mBAC7C;UACY;QAGhB,YACC,oBAAC;SAAa,MAAM,oBAAC,YAAS,MAAM,KAAM;SAAE,OAAM;mBAC/C;UACY;QAGhB,MACC,oBAAC;SAAa,MAAM,oBAAC,UAAO,MAAM,KAAM;SAAE,OAAM;mBAC9C,oBAAC;UAAK,MAAK;UAAK,GAAE;UAAS,IAAG;oBAC3B;WACI;UACM;QAIhB,SAAS,MAAM,SAAS,KACvB,oBAAC;SAAa,MAAM,oBAAC,cAAW,MAAM,KAAM;SAAE,OAAM;mBAClD,oBAAC;UAAK,KAAI;oBACP,MAAM,KAAK,SACV,oBAAC;WAAiB,MAAK;WAAK,SAAQ;qBACjC;aADS,KAEJ,CACR;WACG;UACM;QAIhB,iBAAiB,cAAc,SAAS,KACvC,oBAAC;SACC,MAAM,oBAAC,gBAAa,MAAM,KAAM;SAChC,OAAM;mBAEN,oBAAC;UAAK,KAAI;oBACP,cAAc,KAAK,QAClB,oBAAC;WAAgB,MAAK;WAAK,SAAQ;qBAChC;aADS,IAEJ,CACR;WACG;UACM;;QAEZ;MAGP,oBAAC;OAAK,WAAU;OAAS,KAAI;OAAK,IAAG;iBACnC,oBAAC;QACC,SAAQ;QACR,OAAM;QACN,eAAe,KAAK,QAAQ;QAC5B;kBACD;SAEc;QACV;;MACF;KACF,EAEP,oBAAC;IAAa,SAAQ;IAAS,MAAK;cAAI;KAEzB;IACV;GACF;;AAUX,MAAM,gBAAgB,EAAE,MAAM,OAAO,eAAkC;AACrE,QACE,qBAAC;EAAK,KAAI;EAAK,OAAM;aACnB,oBAAC;GAAK,GAAE;GAAS,IAAI;aAClB;IACI,EACP,qBAAC;GAAK,WAAU;GAAS,KAAK;GAAG,MAAM;cACrC,oBAAC;IAAK,MAAK;IAAK,GAAE;IAAS,IAAG;IAAY,IAAI;cAC3C;KACI,EACP,oBAAC;IAAK,MAAK;IAAM;KAAgB;IAC5B;GACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Register-Z3fxRbUF.js","names":[],"sources":["../../src/auth/components/Register.tsx"],"sourcesContent":["import { ActionButton, Control, capitalize } from \"@alepha/ui\";\nimport { Alert, Card, Flex, Image, PinInput, Text, Title } from \"@mantine/core\";\nimport {\n IconAlertCircle,\n IconLock,\n IconMail,\n IconPhone,\n IconUser,\n} from \"@tabler/icons-react\";\nimport { TypeBoxError, t } from \"alepha\";\nimport type {\n RealmConfig,\n RegistrationIntentResponse,\n UserController,\n} from \"alepha/api/users\";\nimport { useClient } from \"alepha/react\";\nimport { useAuth } from \"alepha/react/auth\";\nimport { useForm } from \"alepha/react/form\";\nimport { useI18n } from \"alepha/react/i18n\";\nimport { useRouter } from \"alepha/react/router\";\nimport { useMemo, useState } from \"react\";\nimport type { AuthI18n } from \"../AuthI18n.ts\";\nimport type { AuthRouter } from \"../AuthRouter.ts\";\nimport IconGithub from \"./icons/IconGithub.tsx\";\nimport IconGoogle from \"./icons/IconGoogle.tsx\";\n\nexport interface RegisterProps {\n realmConfig: RealmConfig;\n}\n\ntype RegistrationPhase = \"form\" | \"verification\";\n\ninterface RegistrationState {\n phase: RegistrationPhase;\n intent?: RegistrationIntentResponse;\n credentials?: {\n identifier: string;\n password: string;\n };\n}\n\nconst Register = (props: RegisterProps) => {\n const auth = useAuth();\n const userCtrl = useClient<UserController>();\n const router = useRouter<AuthRouter>();\n const { tr } = useI18n<AuthI18n, \"en\">();\n const redirect = router.query.r || \"/\";\n\n const [registrationState, setRegistrationState] = useState<RegistrationState>(\n {\n phase: \"form\",\n },\n );\n const [emailCode, setEmailCode] = useState(\"\");\n const [phoneCode, setPhoneCode] = useState(\"\");\n const [verificationError, setVerificationError] = useState<string | null>(\n null,\n );\n const [isSubmitting, setIsSubmitting] = useState(false);\n\n const credentialsProvider = props.realmConfig.authenticationMethods.find(\n (it) => it.type === \"CREDENTIALS\",\n );\n\n const settings = props.realmConfig.settings || {};\n const isRegistrationAllowed = settings.registrationAllowed !== false;\n\n const registerSchema = useMemo(() => {\n const registerSchema = t.object({\n username: t.optional(\n t.text({\n trim: true,\n pattern: settings.usernameRegExp,\n }),\n ),\n email: t.optional(t.email()),\n phoneNumber: t.optional(t.e164()),\n password: t.string({ minLength: 8 }),\n confirmPassword: t.string({ minLength: 8 }),\n });\n\n const required = registerSchema.required as string[];\n\n if (settings.usernameRequired) required.push(\"username\");\n if (settings.emailRequired) required.push(\"email\");\n if (settings.phoneRequired) required.push(\"phoneNumber\");\n\n return registerSchema;\n }, []);\n\n const form = useForm({\n schema: registerSchema,\n handler: async (data) => {\n if (data.password !== data.confirmPassword) {\n throw new TypeBoxError({\n message: \"Passwords do not match\",\n instancePath: \"/confirmPassword\",\n keyword: \"not\",\n schemaPath: \"\",\n params: {},\n });\n }\n\n // Phase 1: Create registration intent\n const intent = await userCtrl.createRegistrationIntent({\n query: { userRealmName: props.realmConfig.realmName },\n body: {\n username: data.username,\n email: data.email,\n phoneNumber: data.phoneNumber,\n password: data.password,\n },\n });\n\n const identifier = data.username ?? data.email ?? data.phoneNumber;\n\n // Check if verification is needed\n if (\n intent.expectEmailVerification ||\n intent.expectPhoneVerification ||\n intent.expectCaptcha\n ) {\n // Move to verification phase\n setRegistrationState({\n phase: \"verification\",\n intent,\n credentials: identifier\n ? { identifier, password: data.password }\n : undefined,\n });\n return;\n }\n\n // No verification needed - complete registration immediately\n await userCtrl.createUserFromIntent({\n body: { intentId: intent.intentId },\n });\n\n // Auto-login after registration\n if (identifier && credentialsProvider) {\n await auth.login(credentialsProvider.name, {\n username: identifier,\n password: data.password,\n realm: props.realmConfig.realmName,\n });\n }\n\n await router.push(router.query.r || \"/\");\n },\n });\n\n const handleVerificationSubmit = async () => {\n if (!registrationState.intent) return;\n\n setIsSubmitting(true);\n setVerificationError(null);\n\n try {\n // Phase 2: Complete registration with verification codes\n await userCtrl.createUserFromIntent({\n body: {\n intentId: registrationState.intent.intentId,\n emailCode: registrationState.intent.expectEmailVerification\n ? emailCode\n : undefined,\n phoneCode: registrationState.intent.expectPhoneVerification\n ? phoneCode\n : undefined,\n },\n });\n\n // Auto-login after registration\n if (registrationState.credentials && credentialsProvider) {\n await auth.login(credentialsProvider.name, {\n username: registrationState.credentials.identifier,\n password: registrationState.credentials.password,\n realm: props.realmConfig.realmName,\n });\n }\n\n await router.push(router.query.r || \"/\");\n } catch (error) {\n setVerificationError(\n error instanceof Error ? error.message : \"Verification failed\",\n );\n } finally {\n setIsSubmitting(false);\n }\n };\n\n const canSubmitVerification = () => {\n if (!registrationState.intent) return false;\n\n if (\n registrationState.intent.expectEmailVerification &&\n emailCode.length !== 6\n ) {\n return false;\n }\n\n if (\n registrationState.intent.expectPhoneVerification &&\n phoneCode.length !== 6\n ) {\n return false;\n }\n\n return true;\n };\n\n // Verification phase UI\n if (registrationState.phase === \"verification\" && registrationState.intent) {\n return (\n <Flex flex={1} justify={\"center\"} align={\"center\"}>\n <Flex direction=\"column\" gap={\"sm\"} w={360}>\n <Card withBorder p={\"lg\"} bg={\"var(--alepha-elevated)\"}>\n <Flex direction=\"column\" gap={\"md\"}>\n <Text size=\"lg\" fw={500} ta=\"center\">\n {tr(\"registerVerifyTitle\") ?? \"Verify your account\"}\n </Text>\n <Text size=\"sm\" c=\"dimmed\" ta=\"center\">\n {tr(\"registerVerifyDescription\") ??\n \"Please enter the verification code(s) sent to you.\"}\n </Text>\n\n {verificationError && (\n <Alert variant=\"light\" color=\"red\" icon={<IconAlertCircle />}>\n <Text size=\"sm\">{verificationError}</Text>\n </Alert>\n )}\n\n {registrationState.intent.expectEmailVerification && (\n <Flex direction=\"column\" gap={\"xs\"}>\n <Text size=\"sm\" fw={500}>\n {tr(\"registerEmailCode\")}\n </Text>\n <Flex justify=\"center\">\n <PinInput\n length={6}\n value={emailCode}\n onChange={setEmailCode}\n type=\"number\"\n oneTimeCode\n aria-label=\"Email verification code\"\n />\n </Flex>\n </Flex>\n )}\n\n {registrationState.intent.expectPhoneVerification && (\n <Flex direction=\"column\" gap={\"xs\"}>\n <Text size=\"sm\" fw={500}>\n {tr(\"registerPhoneCode\")}\n </Text>\n <Flex justify=\"center\">\n <PinInput\n length={6}\n value={phoneCode}\n onChange={setPhoneCode}\n type=\"number\"\n oneTimeCode\n aria-label=\"Phone verification code\"\n />\n </Flex>\n </Flex>\n )}\n\n <ActionButton\n color={\"blue\"}\n onClick={handleVerificationSubmit}\n loading={isSubmitting}\n disabled={!canSubmitVerification()}\n >\n {tr(\"registerVerifySubmit\")}\n </ActionButton>\n\n <ActionButton\n variant=\"subtle\"\n onClick={() =>\n setRegistrationState({ phase: \"form\", intent: undefined })\n }\n >\n {tr(\"registerVerifyBack\") ?? \"Back to registration\"}\n </ActionButton>\n </Flex>\n </Card>\n </Flex>\n </Flex>\n );\n }\n\n // External login methods\n const externalMethods = props.realmConfig.authenticationMethods.filter(\n (method) => method.type !== \"CREDENTIALS\",\n );\n\n const showOrDivider = credentialsProvider && externalMethods.length > 0;\n\n // Registration form phase UI\n return (\n <Flex flex={1} justify={\"center\"} align={\"center\"}>\n <Flex direction=\"column\" gap={\"sm\"} w={360}>\n <Card withBorder p={\"lg\"} bg={\"var(--alepha-elevated)\"}>\n <Flex direction=\"column\" gap={\"md\"}>\n {/* Realm branding */}\n {(settings.logoUrl ||\n settings.displayName ||\n settings.description) && (\n <Flex direction=\"column\" gap={\"xs\"} align=\"center\" mb=\"xs\">\n {settings.logoUrl && (\n <Image\n src={settings.logoUrl}\n alt={settings.displayName || props.realmConfig.realmName}\n h={48}\n w=\"auto\"\n fit=\"contain\"\n />\n )}\n {settings.displayName && (\n <Title order={4} ta=\"center\">\n {settings.displayName}\n </Title>\n )}\n {settings.description && (\n <Text size=\"sm\" c=\"dimmed\" ta=\"center\">\n {settings.description}\n </Text>\n )}\n </Flex>\n )}\n\n {!isRegistrationAllowed ? (\n <>\n <Alert\n variant=\"light\"\n color=\"yellow\"\n icon={<IconAlertCircle />}\n >\n <Text size=\"sm\">{tr(\"registerDisabled\")}</Text>\n </Alert>\n <ActionButton\n href={router.path(\"login\", {\n query: { realm: props.realmConfig.realmName },\n })}\n >\n {tr(\"registerBackToSignIn\")}\n </ActionButton>\n </>\n ) : (\n <>\n {/* Credentials registration form */}\n {credentialsProvider && (\n <form {...form.props}>\n <Flex direction=\"column\" flex={1} gap={\"md\"}>\n {settings.usernameEnabled !== false &&\n form.input.username && (\n <Control\n title={tr(\"registerUsername\")}\n input={form.input.username}\n icon={<IconUser />}\n text={{\n autoComplete: \"username\",\n }}\n />\n )}\n {settings.emailEnabled !== false && form.input.email && (\n <Control\n title={tr(\"registerEmail\")}\n input={form.input.email}\n icon={<IconMail />}\n text={{\n autoComplete: \"email\",\n }}\n />\n )}\n {settings.phoneEnabled === true &&\n form.input.phoneNumber && (\n <Control\n title={tr(\"registerPhone\")}\n input={form.input.phoneNumber}\n icon={<IconPhone />}\n text={{\n autoComplete: \"tel\",\n }}\n />\n )}\n <Control\n title={tr(\"registerPassword\")}\n input={form.input.password}\n icon={<IconLock />}\n password={{\n autoComplete: \"new-password\",\n }}\n />\n <Control\n title={tr(\"registerConfirmPassword\")}\n input={form.input.confirmPassword}\n icon={<IconLock />}\n password={{\n autoComplete: \"new-password\",\n }}\n />\n <ActionButton\n form={form}\n color={\"blue\"}\n variant={\"filled\"}\n >\n {tr(\"registerCreateAccount\")}\n </ActionButton>\n </Flex>\n </form>\n )}\n\n {/* OR divider - only when both credentials AND external methods exist */}\n {showOrDivider && (\n <Flex align=\"center\" justify=\"center\" gap={\"md\"}>\n <Flex flex={1} h={\"1px\"} bg={\"var(--alepha-border)\"} />\n <Text size=\"xs\" c=\"dimmed\">\n {tr(\"registerOr\")}\n </Text>\n <Flex flex={1} h={\"1px\"} bg={\"var(--alepha-border)\"} />\n </Flex>\n )}\n\n {/* External login methods */}\n {externalMethods.length > 0 && (\n <Flex direction=\"column\" gap={\"sm\"}>\n {externalMethods.map((method) => (\n <ActionButton\n variant={\"default\"}\n key={method.type}\n leftSection={leftSection(method.name.toLowerCase())}\n onClick={() =>\n auth.login(method.name, {\n redirect,\n realm: props.realmConfig.realmName,\n })\n }\n >\n {tr(\"registerContinueWith\", {\n args: [capitalize(method.name)],\n })}\n </ActionButton>\n ))}\n </Flex>\n )}\n\n {/* Sign in link */}\n <Text size=\"sm\" ta=\"center\">\n {tr(\"registerHaveAccount\")}{\" \"}\n <ActionButton\n href={router.path(\"login\", {\n query: { realm: props.realmConfig.realmName },\n })}\n anchorProps={{ inherit: true }}\n >\n {tr(\"registerSignIn\")}\n </ActionButton>\n </Text>\n </>\n )}\n </Flex>\n </Card>\n <ActionButton variant={\"subtle\"} href={redirect}>\n {tr(\"registerCancel\")}\n </ActionButton>\n </Flex>\n </Flex>\n );\n};\n\nexport default Register;\n\nconst leftSection = (name: string) => {\n if (name === \"google\") {\n return <IconGoogle />;\n }\n\n if (name === \"github\") {\n return <IconGithub />;\n }\n};\n"],"mappings":"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"}
|