@alepha/ui 0.15.2 → 0.15.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/dist/admin/AdminAudits-BU-p1g7A.js +3 -0
  2. package/dist/admin/{AdminAudits-C0DPYw0W.js → AdminAudits-Oh7iAfQa.js} +2 -2
  3. package/dist/admin/AdminAudits-Oh7iAfQa.js.map +1 -0
  4. package/dist/admin/{AdminUserCreate-DiXi1EWB.js → AdminUserCreate-BVIm4JdN.js} +2 -2
  5. package/dist/admin/AdminUserCreate-BVIm4JdN.js.map +1 -0
  6. package/dist/admin/{AdminUserCreate-Chr-7hLk.js → AdminUserCreate-C1aInRDk.js} +1 -1
  7. package/dist/admin/{AdminUserLayout-D9bqGt6T.js → AdminUserLayout-BnfBC1gD.js} +2 -2
  8. package/dist/admin/{AdminUserLayout-D9bqGt6T.js.map → AdminUserLayout-BnfBC1gD.js.map} +1 -1
  9. package/dist/admin/{AdminUserLayout-CfeQHH6e.js → AdminUserLayout-gb-nbggz.js} +1 -1
  10. package/dist/admin/{AdminUserSettings-BnzRAcqV.js → AdminUserSettings-DZ9iWhJW.js} +2 -2
  11. package/dist/admin/AdminUserSettings-DZ9iWhJW.js.map +1 -0
  12. package/dist/admin/AdminUserSettings-Dg-wTRzN.js +3 -0
  13. package/dist/admin/{AdminUsers-CYkcUWCg.js → AdminUsers-D6Y5K8Am.js} +2 -2
  14. package/dist/admin/AdminUsers-D6Y5K8Am.js.map +1 -0
  15. package/dist/admin/AdminUsers-RCaxccEW.js +3 -0
  16. package/dist/admin/index.d.ts +37 -33
  17. package/dist/admin/index.d.ts.map +1 -1
  18. package/dist/admin/index.js +20 -13
  19. package/dist/admin/index.js.map +1 -1
  20. package/dist/auth/{Login-BAFVcX_J.js → Login-BBqTosqZ.js} +2 -2
  21. package/dist/auth/Login-BBqTosqZ.js.map +1 -0
  22. package/dist/auth/Login-CoU63mMR.js +4 -0
  23. package/dist/auth/Profile-Bxj8Nwom.js +150 -0
  24. package/dist/auth/Profile-Bxj8Nwom.js.map +1 -0
  25. package/dist/auth/Register-BV_oa_AK.js +4 -0
  26. package/dist/auth/{Register-CZRXEcWy.js → Register-Ce675Crg.js} +4 -4
  27. package/dist/auth/Register-Ce675Crg.js.map +1 -0
  28. package/dist/auth/ResetPassword-D5wC8GAA.js +3 -0
  29. package/dist/auth/{ResetPassword-DTYNsBIj.js → ResetPassword-DWdt7c40.js} +2 -2
  30. package/dist/auth/{ResetPassword-DTYNsBIj.js.map → ResetPassword-DWdt7c40.js.map} +1 -1
  31. package/dist/auth/{VerifyEmail-DolENWGn.js → VerifyEmail-CI4JwByV.js} +2 -2
  32. package/dist/auth/{VerifyEmail-DolENWGn.js.map → VerifyEmail-CI4JwByV.js.map} +1 -1
  33. package/dist/auth/VerifyEmail-DAfqVm5s.js +3 -0
  34. package/dist/auth/index.d.ts +6 -1
  35. package/dist/auth/index.d.ts.map +1 -1
  36. package/dist/auth/index.js +47 -13
  37. package/dist/auth/index.js.map +1 -1
  38. package/dist/core/index.d.ts +111 -29
  39. package/dist/core/index.d.ts.map +1 -1
  40. package/dist/core/index.js +324 -251
  41. package/dist/core/index.js.map +1 -1
  42. package/dist/demo/{DemoLogin-mtkN6340.js → DemoLogin-S-b15cmE.js} +2 -2
  43. package/dist/demo/DemoLogin-S-b15cmE.js.map +1 -0
  44. package/dist/demo/{DemoRegister-C0MW7anp.js → DemoRegister-B29MdAaZ.js} +3 -3
  45. package/dist/demo/DemoRegister-B29MdAaZ.js.map +1 -0
  46. package/dist/demo/index.js +2 -2
  47. package/package.json +3 -2
  48. package/src/admin/AdminRouter.ts +2 -2
  49. package/src/admin/components/audits/AdminAudits.tsx +1 -1
  50. package/src/admin/components/users/AdminUserCreate.tsx +1 -1
  51. package/src/admin/components/users/AdminUserLayout.tsx +1 -1
  52. package/src/admin/components/users/AdminUserSettings.tsx +1 -1
  53. package/src/admin/components/users/AdminUsers.tsx +1 -1
  54. package/src/admin/index.ts +11 -1
  55. package/src/auth/AuthRouter.ts +12 -0
  56. package/src/auth/components/Login.tsx +1 -1
  57. package/src/auth/components/Profile.tsx +157 -0
  58. package/src/auth/components/Register.tsx +2 -2
  59. package/src/auth/components/buttons/UserButton.tsx +34 -2
  60. package/src/auth/index.ts +11 -1
  61. package/src/core/UiRouter.ts +15 -0
  62. package/src/core/atoms/alephaThemeListAtom.ts +3 -1
  63. package/src/core/components/buttons/ActionButton.tsx +2 -2
  64. package/src/core/components/layout/AdminShell.tsx +46 -18
  65. package/src/core/components/layout/AppBar.tsx +235 -18
  66. package/src/core/components/layout/Omnibar.tsx +2 -2
  67. package/src/core/components/layout/Sidebar.tsx +1 -5
  68. package/src/core/index.ts +13 -27
  69. package/dist/admin/AdminAudits-BlGGKLof.js +0 -3
  70. package/dist/admin/AdminAudits-C0DPYw0W.js.map +0 -1
  71. package/dist/admin/AdminUserCreate-DiXi1EWB.js.map +0 -1
  72. package/dist/admin/AdminUserSettings-BnzRAcqV.js.map +0 -1
  73. package/dist/admin/AdminUserSettings-CXs-jtRv.js +0 -3
  74. package/dist/admin/AdminUsers-CYkcUWCg.js.map +0 -1
  75. package/dist/admin/AdminUsers-DdFXzrEn.js +0 -3
  76. package/dist/auth/Login-BAFVcX_J.js.map +0 -1
  77. package/dist/auth/Login-C5PUsp8I.js +0 -4
  78. package/dist/auth/Register-CZRXEcWy.js.map +0 -1
  79. package/dist/auth/Register-DMTs5ep_.js +0 -4
  80. package/dist/auth/ResetPassword-D-mhMtmx.js +0 -3
  81. package/dist/auth/VerifyEmail-BsrCmncc.js +0 -3
  82. package/dist/demo/DemoLogin-mtkN6340.js.map +0 -1
  83. package/dist/demo/DemoRegister-C0MW7anp.js.map +0 -1
  84. package/src/core/RootRouter.ts +0 -9
@@ -55,7 +55,7 @@ const Login = (props) => {
55
55
  password: data.password,
56
56
  realm: props.realmConfig.realmName
57
57
  });
58
- await router.go(router.query.r || "/");
58
+ await router.push(router.query.r || "/");
59
59
  } catch (error) {
60
60
  if (error instanceof HttpError && error.error === "InvalidCredentialsError") throw new FormValidationError({
61
61
  message: "Invalid identifier or password",
@@ -325,4 +325,4 @@ var DemoLogin_default = DemoLogin;
325
325
 
326
326
  //#endregion
327
327
  export { DemoLogin_default as default };
328
- //# sourceMappingURL=DemoLogin-mtkN6340.js.map
328
+ //# sourceMappingURL=DemoLogin-S-b15cmE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DemoLogin-S-b15cmE.js","names":["IconGoogle","IconGithub","Showcase","Login"],"sources":["../../src/auth/components/Login.tsx","../../src/demo/components/auth/DemoLogin.tsx"],"sourcesContent":["import { ActionButton, Control, capitalize } from \"@alepha/ui\";\nimport { Card, Flex, Group, Image, Stack, 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 <Stack gap={\"sm\"} w={360}>\n <Card withBorder p={\"lg\"} bg={\"var(--alepha-elevated)\"}>\n <Stack gap={\"md\"}>\n {/* Realm branding */}\n {(settings.logoUrl ||\n settings.displayName ||\n settings.description) && (\n <Stack 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 </Stack>\n )}\n\n {/* Credentials login form */}\n {credentialsProvider && (\n <>\n <form {...form.props}>\n <Stack 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 </Stack>\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 <Group 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 </Group>\n )}\n\n {/* External login methods */}\n {externalLoginMethods.length > 0 && (\n <Stack 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 </Stack>\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 </Stack>\n </Card>\n <ActionButton variant={\"subtle\"} href={\"/\"}>\n {tr(\"loginCancel\")}\n </ActionButton>\n </Stack>\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","import { t } from \"alepha\";\nimport type { RealmConfig } from \"alepha/api/users\";\nimport Login from \"../../../auth/components/Login.tsx\";\nimport Showcase from \"../shared/Showcase.tsx\";\n\nconst showcaseSchema = t.object({\n showCredentials: t.boolean({\n title: \"Credentials\",\n default: true,\n $control: { switch: true },\n }),\n showGoogleOAuth: t.boolean({\n title: \"Google OAuth\",\n default: true,\n $control: { switch: true },\n }),\n showGithubOAuth: t.boolean({\n title: \"GitHub OAuth\",\n default: false,\n $control: { switch: true },\n }),\n usernameEnabled: t.boolean({\n title: \"Username Login\",\n default: true,\n $control: { switch: true },\n }),\n emailEnabled: t.boolean({\n title: \"Email Login\",\n default: true,\n $control: { switch: true },\n }),\n phoneEnabled: t.boolean({\n title: \"Phone Login\",\n default: false,\n $control: { switch: true },\n }),\n registrationAllowed: t.boolean({\n title: \"Show Sign Up\",\n default: true,\n $control: { switch: true },\n }),\n resetPasswordAllowed: t.boolean({\n title: \"Forgot Password\",\n default: true,\n $control: { switch: true },\n }),\n showBranding: t.boolean({\n title: \"Show Branding\",\n default: true,\n $control: { switch: true },\n }),\n});\n\nconst buildRealmConfig = (props: {\n showCredentials: boolean;\n showGoogleOAuth: boolean;\n showGithubOAuth: boolean;\n usernameEnabled: boolean;\n emailEnabled: boolean;\n phoneEnabled: boolean;\n registrationAllowed: boolean;\n resetPasswordAllowed: boolean;\n showBranding: boolean;\n}): RealmConfig => {\n const authMethods: RealmConfig[\"authenticationMethods\"] = [];\n\n if (props.showCredentials) {\n authMethods.push({ name: \"credentials\", type: \"CREDENTIALS\" });\n }\n if (props.showGoogleOAuth) {\n authMethods.push({ name: \"google\", type: \"OAUTH2\" });\n }\n if (props.showGithubOAuth) {\n authMethods.push({ name: \"github\", type: \"OAUTH2\" });\n }\n\n return {\n realmName: \"demo\",\n authenticationMethods: authMethods,\n settings: {\n displayName: props.showBranding ? \"Demo App\" : undefined,\n description: props.showBranding ? \"Sign in to continue\" : undefined,\n logoUrl: undefined,\n registrationAllowed: props.registrationAllowed,\n emailEnabled: props.emailEnabled,\n emailRequired: false,\n usernameEnabled: props.usernameEnabled,\n usernameRegExp: \"^[a-zA-Z0-9_]{3,30}$\",\n usernameRequired: false,\n phoneEnabled: props.phoneEnabled,\n phoneRequired: false,\n verifyEmailRequired: false,\n verifyPhoneRequired: false,\n firstNameLastNameEnabled: false,\n firstNameLastNameRequired: false,\n resetPasswordAllowed: props.resetPasswordAllowed,\n passwordPolicy: {\n minLength: 8,\n requireUppercase: true,\n requireLowercase: true,\n requireNumbers: true,\n requireSpecialCharacters: false,\n },\n },\n };\n};\n\nconst DemoLogin = () => {\n return (\n <Showcase\n title=\"Login\"\n schema={showcaseSchema}\n initialValues={{\n showCredentials: true,\n showGoogleOAuth: true,\n showGithubOAuth: false,\n usernameEnabled: true,\n emailEnabled: true,\n phoneEnabled: false,\n registrationAllowed: true,\n resetPasswordAllowed: true,\n showBranding: true,\n }}\n columns={1}\n >\n {(props) => <Login realmConfig={buildRealmConfig(props)} />}\n </Showcase>\n );\n};\n\nexport default DemoLogin;\n"],"mappings":"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"}
@@ -80,7 +80,7 @@ const Register = (props) => {
80
80
  password: data.password,
81
81
  realm: props.realmConfig.realmName
82
82
  });
83
- await router.go(router.query.r || "/");
83
+ await router.push(router.query.r || "/");
84
84
  }
85
85
  });
86
86
  const handleVerificationSubmit = async () => {
@@ -98,7 +98,7 @@ const Register = (props) => {
98
98
  password: registrationState.credentials.password,
99
99
  realm: props.realmConfig.realmName
100
100
  });
101
- await router.go(router.query.r || "/");
101
+ await router.push(router.query.r || "/");
102
102
  } catch (error) {
103
103
  setVerificationError(error instanceof Error ? error.message : "Verification failed");
104
104
  } finally {
@@ -489,4 +489,4 @@ var DemoRegister_default = DemoRegister;
489
489
 
490
490
  //#endregion
491
491
  export { DemoRegister_default as default };
492
- //# sourceMappingURL=DemoRegister-C0MW7anp.js.map
492
+ //# sourceMappingURL=DemoRegister-B29MdAaZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DemoRegister-B29MdAaZ.js","names":["IconGoogle","IconGithub","Showcase","Register"],"sources":["../../src/auth/components/Register.tsx","../../src/demo/components/auth/DemoRegister.tsx"],"sourcesContent":["import { ActionButton, Control, capitalize } from \"@alepha/ui\";\nimport {\n Alert,\n Card,\n Flex,\n Group,\n Image,\n PinInput,\n Stack,\n Text,\n Title,\n} 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 <Stack gap={\"sm\"} w={360}>\n <Card withBorder p={\"lg\"} bg={\"var(--alepha-elevated)\"}>\n <Stack 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 <Stack 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 </Stack>\n )}\n\n {registrationState.intent.expectPhoneVerification && (\n <Stack 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 </Stack>\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 </Stack>\n </Card>\n </Stack>\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 <Stack gap={\"sm\"} w={360}>\n <Card withBorder p={\"lg\"} bg={\"var(--alepha-elevated)\"}>\n <Stack gap={\"md\"}>\n {/* Realm branding */}\n {(settings.logoUrl ||\n settings.displayName ||\n settings.description) && (\n <Stack 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 </Stack>\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 <Stack 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 </Stack>\n </form>\n )}\n\n {/* OR divider - only when both credentials AND external methods exist */}\n {showOrDivider && (\n <Group 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 </Group>\n )}\n\n {/* External login methods */}\n {externalMethods.length > 0 && (\n <Stack 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 </Stack>\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 </Stack>\n </Card>\n <ActionButton variant={\"subtle\"} href={redirect}>\n {tr(\"registerCancel\")}\n </ActionButton>\n </Stack>\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","import { t } from \"alepha\";\nimport type { RealmConfig } from \"alepha/api/users\";\nimport Register from \"../../../auth/components/Register.tsx\";\nimport Showcase from \"../shared/Showcase.tsx\";\n\nconst showcaseSchema = t.object({\n showCredentials: t.boolean({\n title: \"Credentials\",\n default: true,\n $control: { switch: true },\n }),\n showGoogleOAuth: t.boolean({\n title: \"Google OAuth\",\n default: true,\n $control: { switch: true },\n }),\n showGithubOAuth: t.boolean({\n title: \"GitHub OAuth\",\n default: false,\n $control: { switch: true },\n }),\n usernameEnabled: t.boolean({\n title: \"Username Field\",\n default: true,\n $control: { switch: true },\n }),\n usernameRequired: t.boolean({\n title: \"Username Required\",\n default: false,\n $control: { switch: true },\n }),\n emailEnabled: t.boolean({\n title: \"Email Field\",\n default: true,\n $control: { switch: true },\n }),\n emailRequired: t.boolean({\n title: \"Email Required\",\n default: true,\n $control: { switch: true },\n }),\n phoneEnabled: t.boolean({\n title: \"Phone Field\",\n default: false,\n $control: { switch: true },\n }),\n phoneRequired: t.boolean({\n title: \"Phone Required\",\n default: false,\n $control: { switch: true },\n }),\n registrationAllowed: t.boolean({\n title: \"Registration Allowed\",\n default: true,\n $control: { switch: true },\n }),\n showBranding: t.boolean({\n title: \"Show Branding\",\n default: true,\n $control: { switch: true },\n }),\n});\n\nconst buildRealmConfig = (props: {\n showCredentials: boolean;\n showGoogleOAuth: boolean;\n showGithubOAuth: boolean;\n usernameEnabled: boolean;\n usernameRequired: boolean;\n emailEnabled: boolean;\n emailRequired: boolean;\n phoneEnabled: boolean;\n phoneRequired: boolean;\n registrationAllowed: boolean;\n showBranding: boolean;\n}): RealmConfig => {\n const authMethods: RealmConfig[\"authenticationMethods\"] = [];\n\n if (props.showCredentials) {\n authMethods.push({ name: \"credentials\", type: \"CREDENTIALS\" });\n }\n if (props.showGoogleOAuth) {\n authMethods.push({ name: \"google\", type: \"OAUTH2\" });\n }\n if (props.showGithubOAuth) {\n authMethods.push({ name: \"github\", type: \"OAUTH2\" });\n }\n\n return {\n realmName: \"demo\",\n authenticationMethods: authMethods,\n settings: {\n displayName: props.showBranding ? \"Demo App\" : undefined,\n description: props.showBranding ? \"Create your account\" : undefined,\n logoUrl: undefined,\n registrationAllowed: props.registrationAllowed,\n emailEnabled: props.emailEnabled,\n emailRequired: props.emailRequired,\n usernameEnabled: props.usernameEnabled,\n usernameRequired: props.usernameRequired,\n usernameRegExp: \"^[a-zA-Z0-9_]{3,30}$\",\n phoneEnabled: props.phoneEnabled,\n phoneRequired: props.phoneRequired,\n verifyEmailRequired: false,\n verifyPhoneRequired: false,\n firstNameLastNameEnabled: false,\n firstNameLastNameRequired: false,\n resetPasswordAllowed: true,\n passwordPolicy: {\n minLength: 8,\n requireUppercase: true,\n requireLowercase: true,\n requireNumbers: true,\n requireSpecialCharacters: false,\n },\n },\n };\n};\n\nconst DemoRegister = () => {\n return (\n <Showcase\n title=\"Register\"\n schema={showcaseSchema}\n initialValues={{\n showCredentials: true,\n showGoogleOAuth: true,\n showGithubOAuth: false,\n usernameEnabled: true,\n usernameRequired: false,\n emailEnabled: true,\n emailRequired: true,\n phoneEnabled: false,\n phoneRequired: false,\n registrationAllowed: true,\n showBranding: true,\n }}\n columns={1}\n >\n {(props) => <Register realmConfig={buildRealmConfig(props)} />}\n </Showcase>\n );\n};\n\nexport default DemoRegister;\n"],"mappings":"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"}
@@ -82,13 +82,13 @@ var DemoRouter = class {
82
82
  icon: IconLogin,
83
83
  path: "/login",
84
84
  label: "Login",
85
- lazy: () => import("./DemoLogin-mtkN6340.js")
85
+ lazy: () => import("./DemoLogin-S-b15cmE.js")
86
86
  });
87
87
  demoRegister = $page({
88
88
  icon: IconUserPlus,
89
89
  path: "/register",
90
90
  label: "Register",
91
- lazy: () => import("./DemoRegister-C0MW7anp.js")
91
+ lazy: () => import("./DemoRegister-B29MdAaZ.js")
92
92
  });
93
93
  demoResetPassword = $page({
94
94
  icon: IconLockQuestion,
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "mantine"
8
8
  ],
9
9
  "author": "Nicolas Foures",
10
- "version": "0.15.2",
10
+ "version": "0.15.3",
11
11
  "type": "module",
12
12
  "engines": {
13
13
  "node": ">=22.0.0"
@@ -30,11 +30,11 @@
30
30
  "@mantine/nprogress": "^8.3.13",
31
31
  "@mantine/spotlight": "^8.3.13",
32
32
  "@tabler/icons-react": "^3.36.1",
33
- "alepha": "0.15.2",
34
33
  "dayjs": "^1.11.19"
35
34
  },
36
35
  "devDependencies": {
37
36
  "@biomejs/biome": "^2.3.13",
37
+ "alepha": "0.15.3",
38
38
  "react": "^19.2.4",
39
39
  "react-dom": "^19.2.4",
40
40
  "typescript": "^5.9.3",
@@ -42,6 +42,7 @@
42
42
  "vitest": "^4.0.18"
43
43
  },
44
44
  "peerDependencies": {
45
+ "alepha": "0.15.3",
45
46
  "react": "*",
46
47
  "react-dom": "*"
47
48
  },
@@ -24,7 +24,7 @@ import { $page, ReactRouter, Redirection } from "alepha/react/router";
24
24
  import { $client } from "alepha/server/links";
25
25
 
26
26
  export class AdminRouter {
27
- protected readonly router = $inject(ReactRouter);
27
+ protected readonly router = $inject(ReactRouter<AdminRouter>);
28
28
  protected readonly authRouter = $inject(AuthRouter);
29
29
  protected readonly auth = $inject(ReactAuth);
30
30
  protected readonly userCtrl = $client<AdminUserController>();
@@ -87,7 +87,7 @@ export class AdminRouter {
87
87
  parent: this.adminLayout,
88
88
  path: "/users/create",
89
89
  label: "Create User",
90
- description: "Create a new user account.",
90
+ description: "Create a new user account",
91
91
  lazy: () => import("./components/users/AdminUserCreate.tsx"),
92
92
  can: () => this.userCtrl.createUser.can(),
93
93
  });
@@ -100,7 +100,7 @@ const AdminAudits = (props: AdminAuditsProps) => {
100
100
  },
101
101
  onClick: () => {
102
102
  if (item.userId) {
103
- router.go("adminUserDetails", {
103
+ router.push("adminUserDetails", {
104
104
  params: { userId: item.userId },
105
105
  });
106
106
  }
@@ -43,7 +43,7 @@ const AdminUserCreate = (props: AdminUserCreateProps) => {
43
43
  },
44
44
  });
45
45
 
46
- await router.go("adminUserDetails", {
46
+ await router.push("adminUserDetails", {
47
47
  params: { userId: user.id },
48
48
  });
49
49
  },
@@ -123,7 +123,7 @@ const AdminUserLayout = (props: AdminUserLayoutProps) => {
123
123
  params: { id: userId },
124
124
  query: { userRealmName: props.userRealmName },
125
125
  });
126
- await router.go("adminUsers");
126
+ await router.push("adminUsers");
127
127
  } finally {
128
128
  setActionLoading(null);
129
129
  }
@@ -60,7 +60,7 @@ const AdminUserSettings = (props: AdminUserSettingsProps) => {
60
60
  params: { id: userId },
61
61
  query: { userRealmName: props.userRealmName },
62
62
  });
63
- await router.go("adminUsers");
63
+ await router.push("adminUsers");
64
64
  } finally {
65
65
  setDeleteLoading(false);
66
66
  }
@@ -63,7 +63,7 @@ const AdminUsers = (props: AdminUsersProps) => {
63
63
  const baseProps: Record<string, any> = {
64
64
  style: { cursor: "pointer" },
65
65
  onClick: () =>
66
- router.go("adminUserDetails", {
66
+ router.push("adminUserDetails", {
67
67
  params: { userId: item.id },
68
68
  }),
69
69
  };
@@ -1,6 +1,6 @@
1
1
  import { AlephaUI } from "@alepha/ui";
2
2
  import { AlephaUIAuth } from "@alepha/ui/auth";
3
- import { $module } from "alepha";
3
+ import { $context, $module } from "alepha";
4
4
  import { AdminRouter } from "./AdminRouter.ts";
5
5
  import { MainRouter } from "./MainRouter.ts";
6
6
 
@@ -62,3 +62,13 @@ export const AlephaUIAdmin = $module({
62
62
  alepha.with(AdminRouter);
63
63
  },
64
64
  });
65
+
66
+ // ---------------------------------------------------------------------------------------------------------------------
67
+
68
+ /**
69
+ * Register Admin UI components and get the AdminRouter instance.
70
+ */
71
+ export const $uiAdmin = () => {
72
+ const { alepha } = $context();
73
+ return alepha.inject(AdminRouter);
74
+ };
@@ -3,6 +3,7 @@ import {
3
3
  IconLogout2,
4
4
  IconMailCheck,
5
5
  IconPasswordUser,
6
+ IconUser,
6
7
  IconUserPlus,
7
8
  } from "@tabler/icons-react";
8
9
  import { $inject, AlephaError, t } from "alepha";
@@ -34,6 +35,7 @@ export class AuthRouter {
34
35
  this.register,
35
36
  this.resetPassword,
36
37
  this.verifyEmail,
38
+ this.profile,
37
39
  ],
38
40
  });
39
41
 
@@ -115,6 +117,16 @@ export class AuthRouter {
115
117
  },
116
118
  });
117
119
 
120
+ profile = $page({
121
+ name: "userProfile",
122
+ icon: IconUser,
123
+ label: "Profile",
124
+ description: "View your profile",
125
+ path: "/profile",
126
+ can: () => !!this.auth.user,
127
+ lazy: () => import("./components/Profile.tsx"),
128
+ });
129
+
118
130
  protected async loadRealmConfig(realmName?: string) {
119
131
  try {
120
132
  return await this.realmClient.getRealmConfig({
@@ -78,7 +78,7 @@ const Login = (props: LoginProps) => {
78
78
  password: data.password,
79
79
  realm: props.realmConfig.realmName,
80
80
  });
81
- await router.go(router.query.r || "/");
81
+ await router.push(router.query.r || "/");
82
82
  } catch (error) {
83
83
  if (
84
84
  error instanceof HttpError &&
@@ -0,0 +1,157 @@
1
+ import { ActionButton } from "@alepha/ui";
2
+ import {
3
+ Avatar,
4
+ Badge,
5
+ Card,
6
+ Flex,
7
+ Group,
8
+ Stack,
9
+ Text,
10
+ Title,
11
+ } from "@mantine/core";
12
+ import {
13
+ IconAt,
14
+ IconCalendar,
15
+ IconId,
16
+ IconShield,
17
+ IconUser,
18
+ } from "@tabler/icons-react";
19
+ import { useAuth } from "alepha/react/auth";
20
+ import { useRouter } from "alepha/react/router";
21
+ import type { AuthRouter } from "../AuthRouter.ts";
22
+
23
+ const Profile = () => {
24
+ const auth = useAuth();
25
+ const router = useRouter<AuthRouter>();
26
+
27
+ if (!auth.user) {
28
+ return null;
29
+ }
30
+
31
+ const { id, name, email, username, picture, roles, organizations } =
32
+ auth.user;
33
+
34
+ const displayName = name || username || email || "User";
35
+
36
+ return (
37
+ <Flex flex={1} justify="center" align="center">
38
+ <Stack gap="md" w={400}>
39
+ <Card withBorder p="xl" bg="var(--alepha-elevated)">
40
+ <Stack gap="lg">
41
+ {/* Avatar and name */}
42
+ <Flex direction="column" align="center" gap="md">
43
+ <Avatar
44
+ src={picture ? `/api/files/${picture}` : undefined}
45
+ size={96}
46
+ radius="xl"
47
+ color="blue"
48
+ >
49
+ {!picture && <IconUser size={48} />}
50
+ </Avatar>
51
+ <Stack gap={4} align="center">
52
+ <Title order={3}>{displayName}</Title>
53
+ {email && username && (
54
+ <Text size="sm" c="dimmed">
55
+ @{username}
56
+ </Text>
57
+ )}
58
+ </Stack>
59
+ </Flex>
60
+
61
+ {/* User details */}
62
+ <Stack gap="sm">
63
+ {email && (
64
+ <ProfileField icon={<IconAt size={18} />} label="Email">
65
+ {email}
66
+ </ProfileField>
67
+ )}
68
+
69
+ {username && (
70
+ <ProfileField icon={<IconUser size={18} />} label="Username">
71
+ {username}
72
+ </ProfileField>
73
+ )}
74
+
75
+ {id && (
76
+ <ProfileField icon={<IconId size={18} />} label="User ID">
77
+ <Text size="xs" c="dimmed" ff="monospace">
78
+ {id}
79
+ </Text>
80
+ </ProfileField>
81
+ )}
82
+
83
+ {/* Roles */}
84
+ {roles && roles.length > 0 && (
85
+ <ProfileField icon={<IconShield size={18} />} label="Roles">
86
+ <Group gap="xs">
87
+ {roles.map((role) => (
88
+ <Badge key={role} size="sm" variant="light">
89
+ {role}
90
+ </Badge>
91
+ ))}
92
+ </Group>
93
+ </ProfileField>
94
+ )}
95
+
96
+ {/* Organizations */}
97
+ {organizations && organizations.length > 0 && (
98
+ <ProfileField
99
+ icon={<IconCalendar size={18} />}
100
+ label="Organizations"
101
+ >
102
+ <Group gap="xs">
103
+ {organizations.map((org) => (
104
+ <Badge key={org} size="sm" variant="outline">
105
+ {org}
106
+ </Badge>
107
+ ))}
108
+ </Group>
109
+ </ProfileField>
110
+ )}
111
+ </Stack>
112
+
113
+ {/* Actions */}
114
+ <Stack gap="sm" mt="md">
115
+ <ActionButton
116
+ variant="light"
117
+ color="red"
118
+ onClick={() => auth.logout()}
119
+ fullWidth
120
+ >
121
+ Sign out
122
+ </ActionButton>
123
+ </Stack>
124
+ </Stack>
125
+ </Card>
126
+
127
+ <ActionButton variant="subtle" href="/">
128
+ Back to home
129
+ </ActionButton>
130
+ </Stack>
131
+ </Flex>
132
+ );
133
+ };
134
+
135
+ interface ProfileFieldProps {
136
+ icon: React.ReactNode;
137
+ label: string;
138
+ children: React.ReactNode;
139
+ }
140
+
141
+ const ProfileField = ({ icon, label, children }: ProfileFieldProps) => {
142
+ return (
143
+ <Flex gap="sm" align="flex-start">
144
+ <Flex c="dimmed" mt={2}>
145
+ {icon}
146
+ </Flex>
147
+ <Stack gap={2} flex={1}>
148
+ <Text size="xs" c="dimmed" tt="uppercase" fw={500}>
149
+ {label}
150
+ </Text>
151
+ <Text size="sm">{children}</Text>
152
+ </Stack>
153
+ </Flex>
154
+ );
155
+ };
156
+
157
+ export default Profile;
@@ -155,7 +155,7 @@ const Register = (props: RegisterProps) => {
155
155
  });
156
156
  }
157
157
 
158
- await router.go(router.query.r || "/");
158
+ await router.push(router.query.r || "/");
159
159
  },
160
160
  });
161
161
 
@@ -188,7 +188,7 @@ const Register = (props: RegisterProps) => {
188
188
  });
189
189
  }
190
190
 
191
- await router.go(router.query.r || "/");
191
+ await router.push(router.query.r || "/");
192
192
  } catch (error) {
193
193
  setVerificationError(
194
194
  error instanceof Error ? error.message : "Verification failed",
@@ -6,9 +6,16 @@ import {
6
6
  ui,
7
7
  } from "@alepha/ui";
8
8
  import { Avatar } from "@mantine/core";
9
- import { IconLogin2, IconLogout, IconUser } from "@tabler/icons-react";
9
+ import {
10
+ IconLogin2,
11
+ IconLogout,
12
+ IconSettings,
13
+ IconUser,
14
+ } from "@tabler/icons-react";
15
+ import type { AdminUserController } from "alepha/api/users";
16
+ import { useClient, useInject } from "alepha/react";
10
17
  import { useAuth } from "alepha/react/auth";
11
- import { useRouter } from "alepha/react/router";
18
+ import { ReactPageProvider, useRouter } from "alepha/react/router";
12
19
  import type { ReactNode } from "react";
13
20
  import type { AuthRouter } from "../../AuthRouter.ts";
14
21
 
@@ -53,12 +60,19 @@ const UserButton = (props: UserButtonProps) => {
53
60
 
54
61
  buttonProps.variant ??= "subtle";
55
62
 
63
+ const adminUserCtrl = useClient<AdminUserController>();
64
+ const pages = useInject(ReactPageProvider);
65
+
56
66
  const auth = useAuth<{
57
67
  username?: string;
58
68
  email?: string;
59
69
  picture?: string;
60
70
  }>();
61
71
 
72
+ const isConnected = !!auth.user;
73
+ const isAdmin = isConnected && adminUserCtrl.findUsers.can();
74
+ const userPage = pages.getPages().find((it) => it.name === "userProfile");
75
+ const adminPage = pages.getPages().find((it) => it.name === "adminLayout");
62
76
  const authRouter = useRouter<AuthRouter>();
63
77
 
64
78
  if (!auth.user) {
@@ -83,6 +97,24 @@ const UserButton = (props: UserButtonProps) => {
83
97
  });
84
98
  }
85
99
 
100
+ // Add profile page link if available
101
+ if (userPage && isConnected) {
102
+ items.push({
103
+ label: "Profile",
104
+ icon: <IconUser size={ui.sizes.icon.md} />,
105
+ href: authRouter.path("userProfile"),
106
+ });
107
+ }
108
+
109
+ // Add admin page link if available and user is admin
110
+ if (adminPage && isAdmin) {
111
+ items.push({
112
+ label: "Admin",
113
+ icon: <IconSettings size={ui.sizes.icon.md} />,
114
+ href: authRouter.path("adminLayout"),
115
+ });
116
+ }
117
+
86
118
  // Add custom menu items
87
119
  items.push(...menuItems);
88
120
 
package/src/auth/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AlephaUI } from "@alepha/ui";
2
- import { $module } from "alepha";
2
+ import { $context, $module } from "alepha";
3
3
  import { AlephaReactAuth } from "alepha/react/auth";
4
4
  import { AlephaReactI18n } from "alepha/react/i18n";
5
5
  import { AuthI18n } from "./AuthI18n.ts";
@@ -37,3 +37,13 @@ export const AlephaUIAuth = $module({
37
37
  name: "alepha.ui.auth",
38
38
  services: [AlephaUI, AlephaReactAuth, AlephaReactI18n, AuthRouter, AuthI18n],
39
39
  });
40
+
41
+ // ---------------------------------------------------------------------------------------------------------------------
42
+
43
+ /**
44
+ * Register Auth UI components and get the AuthRouter instance.
45
+ */
46
+ export const $uiAuth = () => {
47
+ const { alepha } = $context();
48
+ return alepha.inject(AuthRouter);
49
+ };
@@ -0,0 +1,15 @@
1
+ import { $page } from "alepha/react/router";
2
+ import AlephaMantineProvider from "./components/layout/AlephaMantineProvider.tsx";
3
+
4
+ /**
5
+ * UI Router defining the root page with AlephaMantineProvider.
6
+ *
7
+ * - Use UiRouter when you need Alepha's Mantine-based UI components and theming.
8
+ * - Prefer to use $ui() for convenience. (Custom Factory of UiRouter)
9
+ */
10
+ export class UiRouter {
11
+ public readonly root = $page({
12
+ path: "/",
13
+ component: AlephaMantineProvider,
14
+ });
15
+ }
@@ -1,4 +1,4 @@
1
- import { $atom, t } from "alepha";
1
+ import { $atom, type Static, t } from "alepha";
2
2
  import type { AlephaTheme } from "../interfaces/AlephaTheme.ts";
3
3
  import { defaultTheme } from "./themes/default.ts";
4
4
  import { midnightTheme } from "./themes/midnight.ts";
@@ -8,3 +8,5 @@ export const alephaThemeListAtom = $atom({
8
8
  schema: t.array(t.json<AlephaTheme>()), // TODO: translate to proper schema
9
9
  default: [defaultTheme, midnightTheme],
10
10
  });
11
+
12
+ export type AlephaThemeListAtom = Static<typeof alephaThemeListAtom.schema>;
@@ -18,7 +18,7 @@ import { IconCheck, IconChevronRight } from "@tabler/icons-react";
18
18
  import { type UseActionReturn, useAction } from "alepha/react";
19
19
  import { type FormModel, useFormState } from "alepha/react/form";
20
20
  import {
21
- type RouterGoOptions,
21
+ type RouterPushOptions,
22
22
  type UseActiveOptions,
23
23
  useActive,
24
24
  useRouter,
@@ -551,7 +551,7 @@ const ActionClickButton = (props: ActionClickButtonProps) => {
551
551
  export interface ActionNavigationButtonProps extends ButtonProps {
552
552
  href: string;
553
553
  active?: Partial<UseActiveOptions> | false;
554
- routerGoOptions?: RouterGoOptions;
554
+ routerGoOptions?: RouterPushOptions;
555
555
  classNameActive?: string;
556
556
  variantActive?: ButtonProps["variant"];
557
557
  target?: string;