File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change 112112 "@doLogout": {
113113 "description": "Abmelden Button Text"
114114 },
115+ "doSave": "Speichern",
116+ "@doSave": {
117+ "description": "Speichern Button Text"
118+ },
119+ "updatePassword": "Passwort ändern",
120+ "@updatePassword": {
121+ "description": "Passwort ändern Button Text"
122+ },
123+ "accountStatusActive": "Aktiv",
124+ "@accountStatusActive": {
125+ "description": "Kontostatus wenn angemeldet"
126+ },
127+ "profilePicture": "Profilbild",
128+ "@profilePicture": {
129+ "description": "Profilbild Abschnittsüberschrift"
130+ },
131+ "profilePictureComingSoon": "Profilbild hochladen – in Kürze.",
132+ "@profilePictureComingSoon": {
133+ "description": "Platzhalter für Profilbild-Upload"
134+ },
135+ "accountSectionProfile": "Profil",
136+ "@accountSectionProfile": {
137+ "description": "Profil-Abschnittsüberschrift"
138+ },
139+ "personalInfoTitle": "Persönliche Angaben",
140+ "@personalInfoTitle": {
141+ "description": "Persönliche Angaben Abschnittsüberschrift"
142+ },
143+ "passwordSectionTitle": "Passwort",
144+ "@passwordSectionTitle": {
145+ "description": "Passwort-Abschnittsüberschrift"
146+ },
115147 "doCancel": "Abbrechen",
116148 "@doCancel": {
117149 "description": "Abbrechen Button Text"
Original file line number Diff line number Diff line change 112112 "@doLogout": {
113113 "description": "Logout button text"
114114 },
115+ "doSave": "Save",
116+ "@doSave": {
117+ "description": "Save button text"
118+ },
119+ "updatePassword": "Update Password",
120+ "@updatePassword": {
121+ "description": "Update password button text"
122+ },
123+ "accountStatusActive": "Active",
124+ "@accountStatusActive": {
125+ "description": "Account status label when signed in"
126+ },
127+ "profilePicture": "Profile picture",
128+ "@profilePicture": {
129+ "description": "Profile picture section heading"
130+ },
131+ "profilePictureComingSoon": "Upload profile picture – coming soon.",
132+ "@profilePictureComingSoon": {
133+ "description": "Placeholder for profile picture upload"
134+ },
135+ "accountSectionProfile": "Profile",
136+ "@accountSectionProfile": {
137+ "description": "Account profile section title"
138+ },
139+ "personalInfoTitle": "Personal information",
140+ "@personalInfoTitle": {
141+ "description": "Personal info section title"
142+ },
143+ "passwordSectionTitle": "Password",
144+ "@passwordSectionTitle": {
145+ "description": "Password section title"
146+ },
115147 "doCancel": "Cancel",
116148 "@doCancel": {
117149 "description": "Cancel button text"
Original file line number Diff line number Diff line change 1+ /* eslint-disable @typescript-eslint/no-empty-object-type */
2+ import type { ExtendKcContext } from 'keycloakify/account'
3+ import type { KcEnvName , ThemeName } from '../kc.gen'
4+
5+ export type KcContextExtension = {
6+ themeName : ThemeName ,
7+ properties : Record < KcEnvName , string > & { } ,
8+ }
9+
10+ export type KcContextExtensionPerPage = { }
11+
12+ export type KcContext = ExtendKcContext < KcContextExtension , KcContextExtensionPerPage >
Original file line number Diff line number Diff line change 1+ import { Suspense } from 'react'
2+ import type { KcContext } from './KcContext'
3+ import { AccountPageLayout } from './components/AccountPageLayout'
4+ import AccountSettings from './pages/AccountSettings'
5+
6+ export default function KcPage ( props : { kcContext : KcContext } ) {
7+ const { kcContext } = props
8+
9+ return (
10+ < Suspense >
11+ { ( ( ) => {
12+ if ( kcContext . pageId === 'account.ftl' ) {
13+ return (
14+ < AccountPageLayout kcContext = { kcContext } >
15+ < AccountSettings
16+ kcContext = { kcContext }
17+ />
18+ </ AccountPageLayout >
19+ )
20+ }
21+
22+ return (
23+ < AccountPageLayout kcContext = { kcContext } >
24+ < div style = { { display : 'flex' , flexDirection : 'column' , gap : '1rem' } } >
25+ < p >
26+ { kcContext . pageId === 'password.ftl'
27+ ? 'Use the link below to update your password.'
28+ : 'Use the link below to return to your account.' }
29+ </ p >
30+ { kcContext . pageId === 'password.ftl' && (
31+ < a
32+ href = { kcContext . url . passwordUrl }
33+ className = "text-[var(--hw-color-primary-600)] underline"
34+ >
35+ Update Password
36+ </ a >
37+ ) }
38+ < a
39+ href = { kcContext . url . accountUrl }
40+ className = "text-[var(--hw-color-primary-600)] underline"
41+ >
42+ Back to Account
43+ </ a >
44+ </ div >
45+ </ AccountPageLayout >
46+ )
47+ } ) ( ) }
48+ </ Suspense >
49+ )
50+ }
Original file line number Diff line number Diff line change 1+ import type { DeepPartial } from 'keycloakify/tools/DeepPartial'
2+ import type { KcContext } from './KcContext'
3+ import { createGetKcContextMock } from 'keycloakify/account/KcContext'
4+ import type { KcContextExtension , KcContextExtensionPerPage } from './KcContext'
5+ import KcPage from './KcPage'
6+ import { themeNames , kcEnvDefaults } from '../kc.gen'
7+
8+ const kcContextExtension : KcContextExtension = {
9+ themeName : themeNames [ 0 ] ,
10+ properties : {
11+ ...kcEnvDefaults
12+ }
13+ }
14+ const kcContextExtensionPerPage : KcContextExtensionPerPage = { }
15+
16+ export const { getKcContextMock } = createGetKcContextMock ( {
17+ kcContextExtension,
18+ kcContextExtensionPerPage,
19+ overrides : { } ,
20+ overridesPerPage : { }
21+ } )
22+
23+ export function createKcPageStory < PageId extends KcContext [ 'pageId' ] > ( params : { pageId : PageId } ) {
24+ const { pageId } = params
25+
26+ function KcPageStory ( props : {
27+ kcContext ?: DeepPartial < Extract < KcContext , { pageId : PageId } > > ,
28+ } ) {
29+ const { kcContext : overrides } = props
30+
31+ const kcContextMock = getKcContextMock ( {
32+ pageId,
33+ overrides
34+ } )
35+
36+ return < KcPage kcContext = { kcContextMock } />
37+ }
38+
39+ return { KcPageStory }
40+ }
Original file line number Diff line number Diff line change 1+ import type { ReactNode } from 'react'
2+ import type { KcContext } from '../KcContext'
3+ import { Branding } from '../../login/components/Branding'
4+ import { ThemeSwitcher } from '../../login/components/ThemeSwitcher'
5+ import { LanguageSwitcher } from '../../login/components/LanguageSwitcher'
6+ import { Footer } from '../../login/components/Footer'
7+ import { hideKeycloakStyles } from '../../login/utils/hideKeycloakStyles'
8+ import { Button } from '@helpwave/hightide'
9+ import { useTranslation } from '../../i18n/useTranslation'
10+
11+ type AccountPageLayoutProps = {
12+ kcContext : KcContext ,
13+ children : ReactNode ,
14+ }
15+
16+ export function AccountPageLayout ( { kcContext, children } : AccountPageLayoutProps ) {
17+ const t = useTranslation ( )
18+
19+ return (
20+ < >
21+ < style > { hideKeycloakStyles } </ style >
22+ < div className = "flex flex-col min-h-screen p-4 relative" >
23+ < div className = "absolute top-4 right-4 flex gap-2 z-[1000] sm:top-2 sm:right-2 sm:gap-1 items-center" >
24+ < ThemeSwitcher />
25+ < LanguageSwitcher />
26+ < Button
27+ type = "button"
28+ color = "negative"
29+ onClick = { ( ) => {
30+ window . location . href = kcContext . url . getLogoutUrl ( )
31+ } }
32+ >
33+ { t ( 'doLogout' ) }
34+ </ Button >
35+ </ div >
36+
37+ < div className = "flex flex-col items-center justify-center flex-1 w-[360px] max-w-[360px] mx-auto py-8 px-4 md:w-full md:max-w-[360px] md:py-6 md:px-4 sm:w-full sm:max-w-full sm:py-4 sm:px-2" >
38+ < Branding />
39+
40+ < div className = "w-full max-w-full mt-8 box-border [&_form]:w-full [&_form]:max-w-full [&_form]:box-border [&_>*]:w-full [&_>*]:max-w-full [&_>*]:box-border [&_input]:w-full [&_input]:max-w-full [&_input]:box-border [&_button]:w-full [&_button]:max-w-full [&_button]:box-border" >
41+ { children }
42+ </ div >
43+ </ div >
44+
45+ < Footer />
46+ </ div >
47+ </ >
48+ )
49+ }
Original file line number Diff line number Diff line change 1+ /* eslint-disable @typescript-eslint/no-unused-vars */
2+ import { i18nBuilder } from 'keycloakify/account'
3+ import type { ThemeName } from '../kc.gen'
4+
5+ const { useI18n, ofTypeI18n } = i18nBuilder . withThemeName < ThemeName > ( ) . build ( )
6+
7+ type I18n = typeof ofTypeI18n
8+
9+ export { useI18n , type I18n }
Original file line number Diff line number Diff line change 1+ import type { Meta , StoryObj } from '@storybook/react-vite'
2+ import { createKcPageStory } from '../KcPageStory'
3+
4+ const { KcPageStory } = createKcPageStory ( { pageId : 'account.ftl' } )
5+
6+ const meta = {
7+ title : 'account/account.ftl' ,
8+ component : KcPageStory
9+ } satisfies Meta < typeof KcPageStory >
10+
11+ export default meta
12+
13+ type Story = StoryObj < typeof meta >
14+
15+ export const Default : Story = {
16+ render : ( ) => < KcPageStory />
17+ }
18+
19+ export const WithUser : Story = {
20+ render : ( ) => (
21+ < KcPageStory
22+ kcContext = { {
23+ account : {
24+ username : 'jane.doe' ,
25+ email : 'jane.doe@example.com' ,
26+ firstName : 'Jane' ,
27+ lastName : 'Doe'
28+ } ,
29+ realm : {
30+ registrationEmailAsUsername : false ,
31+ editUsernameAllowed : true
32+ }
33+ } }
34+ />
35+ )
36+ }
37+
38+ export const WithMessage : Story = {
39+ render : ( ) => (
40+ < KcPageStory
41+ kcContext = { {
42+ account : {
43+ username : 'user' ,
44+ email : 'user@example.com' ,
45+ firstName : 'Test' ,
46+ lastName : 'User'
47+ } ,
48+ message : {
49+ type : 'success' ,
50+ summary : 'Your account has been updated.'
51+ }
52+ } }
53+ />
54+ )
55+ }
56+
57+ export const UsernameReadOnly : Story = {
58+ render : ( ) => (
59+ < KcPageStory
60+ kcContext = { {
61+ account : {
62+ username : 'fixed.username' ,
63+ email : 'user@example.com' ,
64+ firstName : 'Test' ,
65+ lastName : 'User'
66+ } ,
67+ realm : {
68+ registrationEmailAsUsername : false ,
69+ editUsernameAllowed : false
70+ }
71+ } }
72+ />
73+ )
74+ }
You can’t perform that action at this time.
0 commit comments