99import { useState } from 'react' ;
1010import { useAuth } from '@object-ui/auth' ;
1111import { Button , Input , Label } from '@object-ui/components' ;
12+ import { useObjectTranslation } from '@object-ui/i18n' ;
1213import { toCanvas } from 'qrcode' ;
1314
1415/**
@@ -38,19 +39,21 @@ export function RemediationOverlay() {
3839
3940function SignOutLink ( ) {
4041 const { signOut } = useAuth ( ) ;
42+ const { t } = useObjectTranslation ( ) ;
4143 return (
4244 < button
4345 type = "button"
4446 onClick = { ( ) => { void signOut ( ) ; } }
4547 className = "mt-4 w-full text-center text-xs text-muted-foreground hover:text-foreground hover:underline"
4648 >
47- Sign out instead
49+ { t ( 'auth.remediation.signOut' ) }
4850 </ button >
4951 ) ;
5052}
5153
5254function ExpiredPasswordForm ( { message } : { message : string } ) {
5355 const { changePassword, setRemediationRequired } = useAuth ( ) ;
56+ const { t } = useObjectTranslation ( ) ;
5457 const [ current , setCurrent ] = useState ( '' ) ;
5558 const [ next , setNext ] = useState ( '' ) ;
5659 const [ confirm , setConfirm ] = useState ( '' ) ;
@@ -60,44 +63,46 @@ function ExpiredPasswordForm({ message }: { message: string }) {
6063 const submit = async ( e : React . FormEvent ) => {
6164 e . preventDefault ( ) ;
6265 setError ( null ) ;
63- if ( next !== confirm ) { setError ( 'New passwords do not match.' ) ; return ; }
66+ if ( next !== confirm ) { setError ( t ( 'auth.remediation.password.mismatch' ) ) ; return ; }
6467 setBusy ( true ) ;
6568 try {
6669 await changePassword ( current , next ) ;
6770 setRemediationRequired ( null ) ;
6871 window . location . reload ( ) ;
6972 } catch ( err ) {
70- setError ( err instanceof Error ? err . message : 'Could not change password.' ) ;
73+ setError ( err instanceof Error ? err . message : t ( 'auth.remediation. password.failed' ) ) ;
7174 setBusy ( false ) ;
7275 }
7376 } ;
7477
7578 return (
7679 < form onSubmit = { submit } className = "space-y-4" >
7780 < div >
78- < h2 className = "text-lg font-semibold" > Your password has expired</ h2 >
81+ < h2 className = "text-lg font-semibold" > { t ( 'auth.remediation.password.title' ) } </ h2 >
82+ { /* `message` is server-authored (localized upstream or not at all); only
83+ the empty-message fallback is ours to translate. */ }
7984 < p className = "mt-1 text-sm text-muted-foreground" >
80- { message || 'Please set a new password to continue.' }
85+ { message || t ( 'auth.remediation. password.fallbackMessage' ) }
8186 </ p >
8287 </ div >
8388 < div className = "space-y-2" >
84- < Label htmlFor = "rem-cur" > Current password</ Label >
89+ < Label htmlFor = "rem-cur" > { t ( 'auth.remediation. password.current' ) } </ Label >
8590 < Input id = "rem-cur" type = "password" autoComplete = "current-password" value = { current }
8691 onChange = { ( e ) => setCurrent ( e . target . value ) } required />
8792 </ div >
8893 < div className = "space-y-2" >
89- < Label htmlFor = "rem-new" > New password</ Label >
94+ < Label htmlFor = "rem-new" > { t ( 'auth.remediation. password.next' ) } </ Label >
9095 < Input id = "rem-new" type = "password" autoComplete = "new-password" value = { next }
9196 onChange = { ( e ) => setNext ( e . target . value ) } required />
9297 </ div >
9398 < div className = "space-y-2" >
94- < Label htmlFor = "rem-conf" > Confirm new password</ Label >
99+ < Label htmlFor = "rem-conf" > { t ( 'auth.remediation. password.confirm' ) } </ Label >
95100 < Input id = "rem-conf" type = "password" autoComplete = "new-password" value = { confirm }
96101 onChange = { ( e ) => setConfirm ( e . target . value ) } required />
97102 </ div >
98103 { error ? < p className = "text-sm text-destructive" > { error } </ p > : null }
99104 < Button type = "submit" className = "w-full" disabled = { busy } >
100- { busy ? 'Updating…' : 'Change password & continue' }
105+ { busy ? t ( 'auth.remediation.password.submitting' ) : t ( 'auth.remediation. password.submit' ) }
101106 </ Button >
102107 < SignOutLink />
103108 </ form >
@@ -119,6 +124,7 @@ function TotpQr({ uri }: { uri: string }) {
119124
120125function MfaEnrollForm ( { message } : { message : string } ) {
121126 const { enrollTotp, verifyTotp, setRemediationRequired } = useAuth ( ) ;
127+ const { t } = useObjectTranslation ( ) ;
122128 const [ step , setStep ] = useState < 'password' | 'verify' > ( 'password' ) ;
123129 const [ password , setPassword ] = useState ( '' ) ;
124130 const [ code , setCode ] = useState ( '' ) ;
@@ -136,7 +142,7 @@ function MfaEnrollForm({ message }: { message: string }) {
136142 setBackupCodes ( codes ?? [ ] ) ;
137143 setStep ( 'verify' ) ;
138144 } catch ( err ) {
139- setError ( err instanceof Error ? err . message : 'Could not start enrollment.' ) ;
145+ setError ( err instanceof Error ? err . message : t ( 'auth.remediation.mfa.enrollFailed' ) ) ;
140146 } finally {
141147 setBusy ( false ) ;
142148 }
@@ -150,7 +156,7 @@ function MfaEnrollForm({ message }: { message: string }) {
150156 setRemediationRequired ( null ) ;
151157 window . location . reload ( ) ;
152158 } catch ( err ) {
153- setError ( err instanceof Error ? err . message : 'Invalid code. Try again.' ) ;
159+ setError ( err instanceof Error ? err . message : t ( 'auth.remediation.mfa.invalidCode' ) ) ;
154160 setBusy ( false ) ;
155161 }
156162 } ;
@@ -159,19 +165,19 @@ function MfaEnrollForm({ message }: { message: string }) {
159165 return (
160166 < form onSubmit = { start } className = "space-y-4" >
161167 < div >
162- < h2 className = "text-lg font-semibold" > Set up two-factor authentication </ h2 >
168+ < h2 className = "text-lg font-semibold" > { t ( 'auth.remediation.mfa.title' ) } </ h2 >
163169 < p className = "mt-1 text-sm text-muted-foreground" >
164- { message || 'Your organization requires an authenticator app to continue.' }
170+ { message || t ( 'auth.remediation.mfa.fallbackMessage' ) }
165171 </ p >
166172 </ div >
167173 < div className = "space-y-2" >
168- < Label htmlFor = "rem-pw" > Confirm your password </ Label >
174+ < Label htmlFor = "rem-pw" > { t ( 'auth.remediation.mfa.confirmPassword' ) } </ Label >
169175 < Input id = "rem-pw" type = "password" autoComplete = "current-password" value = { password }
170176 onChange = { ( e ) => setPassword ( e . target . value ) } required />
171177 </ div >
172178 { error ? < p className = "text-sm text-destructive" > { error } </ p > : null }
173179 < Button type = "submit" className = "w-full" disabled = { busy } >
174- { busy ? 'Preparing…' : 'Continue' }
180+ { busy ? t ( 'auth.remediation.mfa.preparing' ) : t ( 'auth.remediation.mfa.continue' ) }
175181 </ Button >
176182 < SignOutLink />
177183 </ form >
@@ -181,29 +187,29 @@ function MfaEnrollForm({ message }: { message: string }) {
181187 return (
182188 < form onSubmit = { verify } className = "space-y-4" >
183189 < div >
184- < h2 className = "text-lg font-semibold" > Scan with your authenticator </ h2 >
190+ < h2 className = "text-lg font-semibold" > { t ( 'auth.remediation.mfa.scanTitle' ) } </ h2 >
185191 < p className = "mt-1 text-sm text-muted-foreground" >
186- Scan this QR code with Google Authenticator, 1Password, Authy, etc., then enter the 6-digit code.
192+ { t ( 'auth.remediation.mfa.scanBody' ) }
187193 </ p >
188194 </ div >
189195 { totpUri ? < TotpQr uri = { totpUri } /> : null }
190196 { backupCodes . length > 0 ? (
191197 < details className = "rounded-md border bg-muted/40 p-3 text-xs" >
192- < summary className = "cursor-pointer font-medium" > Save your backup codes </ summary >
193- < p className = "mt-1 text-muted-foreground" > Store these somewhere safe — each can be used once if you lose your device. </ p >
198+ < summary className = "cursor-pointer font-medium" > { t ( 'auth.remediation.mfa.backupTitle' ) } </ summary >
199+ < p className = "mt-1 text-muted-foreground" > { t ( 'auth.remediation.mfa.backupBody' ) } </ p >
194200 < div className = "mt-2 grid grid-cols-2 gap-1 font-mono" >
195201 { backupCodes . map ( ( c ) => < span key = { c } > { c } </ span > ) }
196202 </ div >
197203 </ details >
198204 ) : null }
199205 < div className = "space-y-2" >
200- < Label htmlFor = "rem-code" > 6-digit code </ Label >
206+ < Label htmlFor = "rem-code" > { t ( 'auth.remediation.mfa.codeLabel' ) } </ Label >
201207 < Input id = "rem-code" inputMode = "numeric" autoComplete = "one-time-code" maxLength = { 8 }
202208 placeholder = "123456" value = { code } onChange = { ( e ) => setCode ( e . target . value ) } required />
203209 </ div >
204210 { error ? < p className = "text-sm text-destructive" > { error } </ p > : null }
205211 < Button type = "submit" className = "w-full" disabled = { busy } >
206- { busy ? 'Verifying…' : 'Verify & continue' }
212+ { busy ? t ( 'auth.remediation.mfa.verifying' ) : t ( 'auth.remediation.mfa.verify' ) }
207213 </ Button >
208214 < SignOutLink />
209215 </ form >
0 commit comments