@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.
- package/dist/admin/AdminAudits-BU-p1g7A.js +3 -0
- package/dist/admin/{AdminAudits-C0DPYw0W.js → AdminAudits-Oh7iAfQa.js} +2 -2
- package/dist/admin/AdminAudits-Oh7iAfQa.js.map +1 -0
- package/dist/admin/{AdminUserCreate-DiXi1EWB.js → AdminUserCreate-BVIm4JdN.js} +2 -2
- package/dist/admin/AdminUserCreate-BVIm4JdN.js.map +1 -0
- package/dist/admin/{AdminUserCreate-Chr-7hLk.js → AdminUserCreate-C1aInRDk.js} +1 -1
- package/dist/admin/{AdminUserLayout-D9bqGt6T.js → AdminUserLayout-BnfBC1gD.js} +2 -2
- package/dist/admin/{AdminUserLayout-D9bqGt6T.js.map → AdminUserLayout-BnfBC1gD.js.map} +1 -1
- package/dist/admin/{AdminUserLayout-CfeQHH6e.js → AdminUserLayout-gb-nbggz.js} +1 -1
- package/dist/admin/{AdminUserSettings-BnzRAcqV.js → AdminUserSettings-DZ9iWhJW.js} +2 -2
- package/dist/admin/AdminUserSettings-DZ9iWhJW.js.map +1 -0
- package/dist/admin/AdminUserSettings-Dg-wTRzN.js +3 -0
- package/dist/admin/{AdminUsers-CYkcUWCg.js → AdminUsers-D6Y5K8Am.js} +2 -2
- package/dist/admin/AdminUsers-D6Y5K8Am.js.map +1 -0
- package/dist/admin/AdminUsers-RCaxccEW.js +3 -0
- package/dist/admin/index.d.ts +37 -33
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +20 -13
- package/dist/admin/index.js.map +1 -1
- package/dist/auth/{Login-BAFVcX_J.js → Login-BBqTosqZ.js} +2 -2
- package/dist/auth/Login-BBqTosqZ.js.map +1 -0
- package/dist/auth/Login-CoU63mMR.js +4 -0
- package/dist/auth/Profile-Bxj8Nwom.js +150 -0
- package/dist/auth/Profile-Bxj8Nwom.js.map +1 -0
- package/dist/auth/Register-BV_oa_AK.js +4 -0
- package/dist/auth/{Register-CZRXEcWy.js → Register-Ce675Crg.js} +4 -4
- package/dist/auth/Register-Ce675Crg.js.map +1 -0
- package/dist/auth/ResetPassword-D5wC8GAA.js +3 -0
- package/dist/auth/{ResetPassword-DTYNsBIj.js → ResetPassword-DWdt7c40.js} +2 -2
- package/dist/auth/{ResetPassword-DTYNsBIj.js.map → ResetPassword-DWdt7c40.js.map} +1 -1
- package/dist/auth/{VerifyEmail-DolENWGn.js → VerifyEmail-CI4JwByV.js} +2 -2
- package/dist/auth/{VerifyEmail-DolENWGn.js.map → VerifyEmail-CI4JwByV.js.map} +1 -1
- package/dist/auth/VerifyEmail-DAfqVm5s.js +3 -0
- package/dist/auth/index.d.ts +6 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +47 -13
- package/dist/auth/index.js.map +1 -1
- package/dist/core/index.d.ts +111 -29
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +324 -251
- package/dist/core/index.js.map +1 -1
- package/dist/demo/{DemoLogin-mtkN6340.js → DemoLogin-S-b15cmE.js} +2 -2
- package/dist/demo/DemoLogin-S-b15cmE.js.map +1 -0
- package/dist/demo/{DemoRegister-C0MW7anp.js → DemoRegister-B29MdAaZ.js} +3 -3
- package/dist/demo/DemoRegister-B29MdAaZ.js.map +1 -0
- package/dist/demo/index.js +2 -2
- package/package.json +3 -2
- package/src/admin/AdminRouter.ts +2 -2
- package/src/admin/components/audits/AdminAudits.tsx +1 -1
- package/src/admin/components/users/AdminUserCreate.tsx +1 -1
- package/src/admin/components/users/AdminUserLayout.tsx +1 -1
- package/src/admin/components/users/AdminUserSettings.tsx +1 -1
- package/src/admin/components/users/AdminUsers.tsx +1 -1
- package/src/admin/index.ts +11 -1
- package/src/auth/AuthRouter.ts +12 -0
- package/src/auth/components/Login.tsx +1 -1
- package/src/auth/components/Profile.tsx +157 -0
- package/src/auth/components/Register.tsx +2 -2
- package/src/auth/components/buttons/UserButton.tsx +34 -2
- package/src/auth/index.ts +11 -1
- package/src/core/UiRouter.ts +15 -0
- package/src/core/atoms/alephaThemeListAtom.ts +3 -1
- package/src/core/components/buttons/ActionButton.tsx +2 -2
- package/src/core/components/layout/AdminShell.tsx +46 -18
- package/src/core/components/layout/AppBar.tsx +235 -18
- package/src/core/components/layout/Omnibar.tsx +2 -2
- package/src/core/components/layout/Sidebar.tsx +1 -5
- package/src/core/index.ts +13 -27
- package/dist/admin/AdminAudits-BlGGKLof.js +0 -3
- package/dist/admin/AdminAudits-C0DPYw0W.js.map +0 -1
- package/dist/admin/AdminUserCreate-DiXi1EWB.js.map +0 -1
- package/dist/admin/AdminUserSettings-BnzRAcqV.js.map +0 -1
- package/dist/admin/AdminUserSettings-CXs-jtRv.js +0 -3
- package/dist/admin/AdminUsers-CYkcUWCg.js.map +0 -1
- package/dist/admin/AdminUsers-DdFXzrEn.js +0 -3
- package/dist/auth/Login-BAFVcX_J.js.map +0 -1
- package/dist/auth/Login-C5PUsp8I.js +0 -4
- package/dist/auth/Register-CZRXEcWy.js.map +0 -1
- package/dist/auth/Register-DMTs5ep_.js +0 -4
- package/dist/auth/ResetPassword-D-mhMtmx.js +0 -3
- package/dist/auth/VerifyEmail-BsrCmncc.js +0 -3
- package/dist/demo/DemoLogin-mtkN6340.js.map +0 -1
- package/dist/demo/DemoRegister-C0MW7anp.js.map +0 -1
- 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.
|
|
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-
|
|
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.
|
|
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.
|
|
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-
|
|
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":";;;;;;;;;;;;;;;AAmDA,MAAM,YAAY,UAAyB;CACzC,MAAM,OAAO,SAAS;CACtB,MAAM,WAAW,WAA2B;CAC5C,MAAM,SAAS,WAAuB;CACtC,MAAM,EAAE,OAAO,SAAyB;CACxC,MAAM,WAAW,OAAO,MAAM,KAAK;CAEnC,MAAM,CAAC,mBAAmB,wBAAwB,SAChD,EACE,OAAO,QACR,CACF;CACD,MAAM,CAAC,WAAW,gBAAgB,SAAS,GAAG;CAC9C,MAAM,CAAC,WAAW,gBAAgB,SAAS,GAAG;CAC9C,MAAM,CAAC,mBAAmB,wBAAwB,SAChD,KACD;CACD,MAAM,CAAC,cAAc,mBAAmB,SAAS,MAAM;CAEvD,MAAM,sBAAsB,MAAM,YAAY,sBAAsB,MACjE,OAAO,GAAG,SAAS,cACrB;CAED,MAAM,WAAW,MAAM,YAAY,YAAY,EAAE;CACjD,MAAM,wBAAwB,SAAS,wBAAwB;CAyB/D,MAAM,OAAO,QAAQ;EACnB,QAxBqB,cAAc;GACnC,MAAM,iBAAiB,EAAE,OAAO;IAC9B,UAAU,EAAE,SACV,EAAE,KAAK;KACL,MAAM;KACN,SAAS,SAAS;KACnB,CAAC,CACH;IACD,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC;IAC5B,aAAa,EAAE,SAAS,EAAE,MAAM,CAAC;IACjC,UAAU,EAAE,OAAO,EAAE,WAAW,GAAG,CAAC;IACpC,iBAAiB,EAAE,OAAO,EAAE,WAAW,GAAG,CAAC;IAC5C,CAAC;GAEF,MAAM,WAAW,eAAe;AAEhC,OAAI,SAAS,iBAAkB,UAAS,KAAK,WAAW;AACxD,OAAI,SAAS,cAAe,UAAS,KAAK,QAAQ;AAClD,OAAI,SAAS,cAAe,UAAS,KAAK,cAAc;AAExD,UAAO;KACN,EAAE,CAAC;EAIJ,SAAS,OAAO,SAAS;AACvB,OAAI,KAAK,aAAa,KAAK,gBACzB,OAAM,IAAI,aAAa;IACrB,SAAS;IACT,cAAc;IACd,SAAS;IACT,YAAY;IACZ,QAAQ,EAAE;IACX,CAAC;GAIJ,MAAM,SAAS,MAAM,SAAS,yBAAyB;IACrD,OAAO,EAAE,eAAe,MAAM,YAAY,WAAW;IACrD,MAAM;KACJ,UAAU,KAAK;KACf,OAAO,KAAK;KACZ,aAAa,KAAK;KAClB,UAAU,KAAK;KAChB;IACF,CAAC;GAEF,MAAM,aAAa,KAAK,YAAY,KAAK,SAAS,KAAK;AAGvD,OACE,OAAO,2BACP,OAAO,2BACP,OAAO,eACP;AAEA,yBAAqB;KACnB,OAAO;KACP;KACA,aAAa,aACT;MAAE;MAAY,UAAU,KAAK;MAAU,GACvC;KACL,CAAC;AACF;;AAIF,SAAM,SAAS,qBAAqB,EAClC,MAAM,EAAE,UAAU,OAAO,UAAU,EACpC,CAAC;AAGF,OAAI,cAAc,oBAChB,OAAM,KAAK,MAAM,oBAAoB,MAAM;IACzC,UAAU;IACV,UAAU,KAAK;IACf,OAAO,MAAM,YAAY;IAC1B,CAAC;AAGJ,SAAM,OAAO,KAAK,OAAO,MAAM,KAAK,IAAI;;EAE3C,CAAC;CAEF,MAAM,2BAA2B,YAAY;AAC3C,MAAI,CAAC,kBAAkB,OAAQ;AAE/B,kBAAgB,KAAK;AACrB,uBAAqB,KAAK;AAE1B,MAAI;AAEF,SAAM,SAAS,qBAAqB,EAClC,MAAM;IACJ,UAAU,kBAAkB,OAAO;IACnC,WAAW,kBAAkB,OAAO,0BAChC,YACA;IACJ,WAAW,kBAAkB,OAAO,0BAChC,YACA;IACL,EACF,CAAC;AAGF,OAAI,kBAAkB,eAAe,oBACnC,OAAM,KAAK,MAAM,oBAAoB,MAAM;IACzC,UAAU,kBAAkB,YAAY;IACxC,UAAU,kBAAkB,YAAY;IACxC,OAAO,MAAM,YAAY;IAC1B,CAAC;AAGJ,SAAM,OAAO,KAAK,OAAO,MAAM,KAAK,IAAI;WACjC,OAAO;AACd,wBACE,iBAAiB,QAAQ,MAAM,UAAU,sBAC1C;YACO;AACR,mBAAgB,MAAM;;;CAI1B,MAAM,8BAA8B;AAClC,MAAI,CAAC,kBAAkB,OAAQ,QAAO;AAEtC,MACE,kBAAkB,OAAO,2BACzB,UAAU,WAAW,EAErB,QAAO;AAGT,MACE,kBAAkB,OAAO,2BACzB,UAAU,WAAW,EAErB,QAAO;AAGT,SAAO;;AAIT,KAAI,kBAAkB,UAAU,kBAAkB,kBAAkB,OAClE,QACE,oBAAC;EAAK,MAAM;EAAG,SAAS;EAAU,OAAO;YACvC,oBAAC;GAAM,KAAK;GAAM,GAAG;aACnB,oBAAC;IAAK;IAAW,GAAG;IAAM,IAAI;cAC5B,qBAAC;KAAM,KAAK;;MACV,oBAAC;OAAK,MAAK;OAAK,IAAI;OAAK,IAAG;iBACzB,GAAG,sBAAsB,IAAI;QACzB;MACP,oBAAC;OAAK,MAAK;OAAK,GAAE;OAAS,IAAG;iBAC3B,GAAG,4BAA4B,IAC9B;QACG;MAEN,qBACC,oBAAC;OAAM,SAAQ;OAAQ,OAAM;OAAM,MAAM,oBAAC,oBAAkB;iBAC1D,oBAAC;QAAK,MAAK;kBAAM;SAAyB;QACpC;MAGT,kBAAkB,OAAO,2BACxB,qBAAC;OAAM,KAAK;kBACV,oBAAC;QAAK,MAAK;QAAK,IAAI;kBACjB,GAAG,oBAAoB;SACnB,EACP,oBAAC;QAAK,SAAQ;kBACZ,oBAAC;SACC,QAAQ;SACR,OAAO;SACP,UAAU;SACV,MAAK;SACL;SACA,cAAW;UACX;SACG;QACD;MAGT,kBAAkB,OAAO,2BACxB,qBAAC;OAAM,KAAK;kBACV,oBAAC;QAAK,MAAK;QAAK,IAAI;kBACjB,GAAG,oBAAoB;SACnB,EACP,oBAAC;QAAK,SAAQ;kBACZ,oBAAC;SACC,QAAQ;SACR,OAAO;SACP,UAAU;SACV,MAAK;SACL;SACA,cAAW;UACX;SACG;QACD;MAGV,oBAAC;OACC,OAAO;OACP,SAAS;OACT,SAAS;OACT,UAAU,CAAC,uBAAuB;iBAEjC,GAAG,uBAAuB;QACd;MAEf,oBAAC;OACC,SAAQ;OACR,eACE,qBAAqB;QAAE,OAAO;QAAQ,QAAQ;QAAW,CAAC;iBAG3D,GAAG,qBAAqB,IAAI;QAChB;;MACT;KACH;IACD;GACH;CAKX,MAAM,kBAAkB,MAAM,YAAY,sBAAsB,QAC7D,WAAW,OAAO,SAAS,cAC7B;CAED,MAAM,gBAAgB,uBAAuB,gBAAgB,SAAS;AAGtE,QACE,oBAAC;EAAK,MAAM;EAAG,SAAS;EAAU,OAAO;YACvC,qBAAC;GAAM,KAAK;GAAM,GAAG;cACnB,oBAAC;IAAK;IAAW,GAAG;IAAM,IAAI;cAC5B,qBAAC;KAAM,KAAK;iBAER,SAAS,WACT,SAAS,eACT,SAAS,gBACT,qBAAC;MAAM,KAAK;MAAM,OAAM;MAAS,IAAG;;OACjC,SAAS,WACR,oBAAC;QACC,KAAK,SAAS;QACd,KAAK,SAAS,eAAe,MAAM,YAAY;QAC/C,GAAG;QACH,GAAE;QACF,KAAI;SACJ;OAEH,SAAS,eACR,oBAAC;QAAM,OAAO;QAAG,IAAG;kBACjB,SAAS;SACJ;OAET,SAAS,eACR,oBAAC;QAAK,MAAK;QAAK,GAAE;QAAS,IAAG;kBAC3B,SAAS;SACL;;OAEH,EAGT,CAAC,wBACA,4CACE,oBAAC;MACC,SAAQ;MACR,OAAM;MACN,MAAM,oBAAC,oBAAkB;gBAEzB,oBAAC;OAAK,MAAK;iBAAM,GAAG,mBAAmB;QAAQ;OACzC,EACR,oBAAC;MACC,MAAM,OAAO,KAAK,SAAS,EACzB,OAAO,EAAE,OAAO,MAAM,YAAY,WAAW,EAC9C,CAAC;gBAED,GAAG,uBAAuB;OACd,IACd,GAEH;MAEG,uBACC,oBAAC;OAAK,GAAI,KAAK;iBACb,qBAAC;QAAM,MAAM;QAAG,KAAK;;SAClB,SAAS,oBAAoB,SAC5B,KAAK,MAAM,YACT,oBAAC;UACC,OAAO,GAAG,mBAAmB;UAC7B,OAAO,KAAK,MAAM;UAClB,MAAM,oBAAC,aAAW;UAClB,MAAM,EACJ,cAAc,YACf;WACD;SAEL,SAAS,iBAAiB,SAAS,KAAK,MAAM,SAC7C,oBAAC;UACC,OAAO,GAAG,gBAAgB;UAC1B,OAAO,KAAK,MAAM;UAClB,MAAM,oBAAC,aAAW;UAClB,MAAM,EACJ,cAAc,SACf;WACD;SAEH,SAAS,iBAAiB,QACzB,KAAK,MAAM,eACT,oBAAC;UACC,OAAO,GAAG,gBAAgB;UAC1B,OAAO,KAAK,MAAM;UAClB,MAAM,oBAAC,cAAY;UACnB,MAAM,EACJ,cAAc,OACf;WACD;SAEN,oBAAC;UACC,OAAO,GAAG,mBAAmB;UAC7B,OAAO,KAAK,MAAM;UAClB,MAAM,oBAAC,aAAW;UAClB,UAAU,EACR,cAAc,gBACf;WACD;SACF,oBAAC;UACC,OAAO,GAAG,0BAA0B;UACpC,OAAO,KAAK,MAAM;UAClB,MAAM,oBAAC,aAAW;UAClB,UAAU,EACR,cAAc,gBACf;WACD;SACF,oBAAC;UACO;UACN,OAAO;UACP,SAAS;oBAER,GAAG,wBAAwB;WACf;;SACT;QACH;MAIR,iBACC,qBAAC;OAAM,OAAM;OAAS,SAAQ;OAAS,KAAK;;QAC1C,oBAAC;SAAK,MAAM;SAAG,GAAG;SAAO,IAAI;UAA0B;QACvD,oBAAC;SAAK,MAAK;SAAK,GAAE;mBACf,GAAG,aAAa;UACZ;QACP,oBAAC;SAAK,MAAM;SAAG,GAAG;SAAO,IAAI;UAA0B;;QACjD;MAIT,gBAAgB,SAAS,KACxB,oBAAC;OAAM,KAAK;iBACT,gBAAgB,KAAK,WACpB,oBAAC;QACC,SAAS;QAET,aAAa,YAAY,OAAO,KAAK,aAAa,CAAC;QACnD,eACE,KAAK,MAAM,OAAO,MAAM;SACtB;SACA,OAAO,MAAM,YAAY;SAC1B,CAAC;kBAGH,GAAG,wBAAwB,EAC1B,MAAM,CAAC,WAAW,OAAO,KAAK,CAAC,EAChC,CAAC;UAXG,OAAO,KAYC,CACf;QACI;MAIV,qBAAC;OAAK,MAAK;OAAK,IAAG;;QAChB,GAAG,sBAAsB;QAAE;QAC5B,oBAAC;SACC,MAAM,OAAO,KAAK,SAAS,EACzB,OAAO,EAAE,OAAO,MAAM,YAAY,WAAW,EAC9C,CAAC;SACF,aAAa,EAAE,SAAS,MAAM;mBAE7B,GAAG,iBAAiB;UACR;;QACV;SACN;MAEC;KACH,EACP,oBAAC;IAAa,SAAS;IAAU,MAAM;cACpC,GAAG,iBAAiB;KACR;IACT;GACH;;AAIX,uBAAe;AAEf,MAAM,eAAe,SAAiB;AACpC,KAAI,SAAS,SACX,QAAO,oBAACA,uBAAa;AAGvB,KAAI,SAAS,SACX,QAAO,oBAACC,uBAAa;;;;;ACpezB,MAAM,iBAAiB,EAAE,OAAO;CAC9B,iBAAiB,EAAE,QAAQ;EACzB,OAAO;EACP,SAAS;EACT,UAAU,EAAE,QAAQ,MAAM;EAC3B,CAAC;CACF,iBAAiB,EAAE,QAAQ;EACzB,OAAO;EACP,SAAS;EACT,UAAU,EAAE,QAAQ,MAAM;EAC3B,CAAC;CACF,iBAAiB,EAAE,QAAQ;EACzB,OAAO;EACP,SAAS;EACT,UAAU,EAAE,QAAQ,MAAM;EAC3B,CAAC;CACF,iBAAiB,EAAE,QAAQ;EACzB,OAAO;EACP,SAAS;EACT,UAAU,EAAE,QAAQ,MAAM;EAC3B,CAAC;CACF,kBAAkB,EAAE,QAAQ;EAC1B,OAAO;EACP,SAAS;EACT,UAAU,EAAE,QAAQ,MAAM;EAC3B,CAAC;CACF,cAAc,EAAE,QAAQ;EACtB,OAAO;EACP,SAAS;EACT,UAAU,EAAE,QAAQ,MAAM;EAC3B,CAAC;CACF,eAAe,EAAE,QAAQ;EACvB,OAAO;EACP,SAAS;EACT,UAAU,EAAE,QAAQ,MAAM;EAC3B,CAAC;CACF,cAAc,EAAE,QAAQ;EACtB,OAAO;EACP,SAAS;EACT,UAAU,EAAE,QAAQ,MAAM;EAC3B,CAAC;CACF,eAAe,EAAE,QAAQ;EACvB,OAAO;EACP,SAAS;EACT,UAAU,EAAE,QAAQ,MAAM;EAC3B,CAAC;CACF,qBAAqB,EAAE,QAAQ;EAC7B,OAAO;EACP,SAAS;EACT,UAAU,EAAE,QAAQ,MAAM;EAC3B,CAAC;CACF,cAAc,EAAE,QAAQ;EACtB,OAAO;EACP,SAAS;EACT,UAAU,EAAE,QAAQ,MAAM;EAC3B,CAAC;CACH,CAAC;AAEF,MAAM,oBAAoB,UAYP;CACjB,MAAM,cAAoD,EAAE;AAE5D,KAAI,MAAM,gBACR,aAAY,KAAK;EAAE,MAAM;EAAe,MAAM;EAAe,CAAC;AAEhE,KAAI,MAAM,gBACR,aAAY,KAAK;EAAE,MAAM;EAAU,MAAM;EAAU,CAAC;AAEtD,KAAI,MAAM,gBACR,aAAY,KAAK;EAAE,MAAM;EAAU,MAAM;EAAU,CAAC;AAGtD,QAAO;EACL,WAAW;EACX,uBAAuB;EACvB,UAAU;GACR,aAAa,MAAM,eAAe,aAAa;GAC/C,aAAa,MAAM,eAAe,wBAAwB;GAC1D,SAAS;GACT,qBAAqB,MAAM;GAC3B,cAAc,MAAM;GACpB,eAAe,MAAM;GACrB,iBAAiB,MAAM;GACvB,kBAAkB,MAAM;GACxB,gBAAgB;GAChB,cAAc,MAAM;GACpB,eAAe,MAAM;GACrB,qBAAqB;GACrB,qBAAqB;GACrB,0BAA0B;GAC1B,2BAA2B;GAC3B,sBAAsB;GACtB,gBAAgB;IACd,WAAW;IACX,kBAAkB;IAClB,kBAAkB;IAClB,gBAAgB;IAChB,0BAA0B;IAC3B;GACF;EACF;;AAGH,MAAM,qBAAqB;AACzB,QACE,oBAACC;EACC,OAAM;EACN,QAAQ;EACR,eAAe;GACb,iBAAiB;GACjB,iBAAiB;GACjB,iBAAiB;GACjB,iBAAiB;GACjB,kBAAkB;GAClB,cAAc;GACd,eAAe;GACf,cAAc;GACd,eAAe;GACf,qBAAqB;GACrB,cAAc;GACf;EACD,SAAS;aAEP,UAAU,oBAACC,oBAAS,aAAa,iBAAiB,MAAM,GAAI;GACrD;;AAIf,2BAAe"}
|
package/dist/demo/index.js
CHANGED
|
@@ -82,13 +82,13 @@ var DemoRouter = class {
|
|
|
82
82
|
icon: IconLogin,
|
|
83
83
|
path: "/login",
|
|
84
84
|
label: "Login",
|
|
85
|
-
lazy: () => import("./DemoLogin-
|
|
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-
|
|
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.
|
|
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
|
},
|
package/src/admin/AdminRouter.ts
CHANGED
|
@@ -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
|
});
|
|
@@ -123,7 +123,7 @@ const AdminUserLayout = (props: AdminUserLayoutProps) => {
|
|
|
123
123
|
params: { id: userId },
|
|
124
124
|
query: { userRealmName: props.userRealmName },
|
|
125
125
|
});
|
|
126
|
-
await router.
|
|
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.
|
|
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.
|
|
66
|
+
router.push("adminUserDetails", {
|
|
67
67
|
params: { userId: item.id },
|
|
68
68
|
}),
|
|
69
69
|
};
|
package/src/admin/index.ts
CHANGED
|
@@ -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
|
+
};
|
package/src/auth/AuthRouter.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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 {
|
|
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
|
|
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?:
|
|
554
|
+
routerGoOptions?: RouterPushOptions;
|
|
555
555
|
classNameActive?: string;
|
|
556
556
|
variantActive?: ButtonProps["variant"];
|
|
557
557
|
target?: string;
|