@@ -48,6 +48,7 @@ import { useAuthorizationContext } from "@/components/providers/AuthorizationPro
4848import { USER_ROLES } from "@/const/auth" ;
4949import { Can } from "@/components/permission/Can" ;
5050import { Tooltip } from "@/components/ui/tooltip" ;
51+ import { getPasswordChecks , getStrengthLevel , validatePassword as validatePasswordUtil } from "@/lib/utils" ;
5152
5253// Default page size for pagination
5354const DEFAULT_PAGE_SIZE = 20 ;
@@ -108,6 +109,13 @@ function TenantList({
108109 // Tracks which skills have completed installation in the current session
109110 const [ installedSkills , setInstalledSkills ] = useState < Set < string > > ( new Set ( ) ) ;
110111
112+ // Password validation state for admin account
113+ const [ adminPasswordValue , setAdminPasswordValue ] = useState ( "" ) ;
114+ const [ adminPasswordError , setAdminPasswordError ] = useState < {
115+ target : "adminPassword" | "confirmAdminPassword" | "" ;
116+ message : string ;
117+ } > ( { target : "" , message : "" } ) ;
118+
111119 // Fetch official skills when install switch is toggled on
112120 useEffect ( ( ) => {
113121 if ( ! installOfficialSkills ) return ;
@@ -148,6 +156,8 @@ function TenantList({
148156 setSelectedSkillIds ( new Set < string > ( ) ) ;
149157 setInstallingSkills ( new Set < string > ( ) ) ;
150158 setInstalledSkills ( new Set < string > ( ) ) ;
159+ setAdminPasswordValue ( "" ) ;
160+ setAdminPasswordError ( { target : "" , message : "" } ) ;
151161 setModalVisible ( true ) ;
152162 } ;
153163
@@ -215,6 +225,54 @@ function TenantList({
215225 setTenantUsers ( [ ] ) ;
216226 } ;
217227
228+ // Handle admin password input change
229+ const handleAdminPasswordChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
230+ const value = e . target . value ;
231+ setAdminPasswordValue ( value ) ;
232+
233+ if ( value && ! validatePasswordUtil ( value ) ) {
234+ setAdminPasswordError ( {
235+ target : "adminPassword" ,
236+ message : t ( "auth.passwordStrengthError" ) || "Password must contain uppercase, lowercase, and digit" ,
237+ } ) ;
238+ return ;
239+ }
240+
241+ setAdminPasswordError ( { target : "" , message : "" } ) ;
242+ const confirmPassword = form . getFieldValue ( "confirmAdminPassword" ) ;
243+ if ( confirmPassword && confirmPassword !== value ) {
244+ setAdminPasswordError ( {
245+ target : "confirmAdminPassword" ,
246+ message : t ( "auth.passwordsDoNotMatch" ) ,
247+ } ) ;
248+ }
249+ } ;
250+
251+ // Handle confirm admin password input change
252+ const handleConfirmAdminPasswordChange = (
253+ e : React . ChangeEvent < HTMLInputElement >
254+ ) => {
255+ const value = e . target . value ;
256+ const password = form . getFieldValue ( "adminPassword" ) ;
257+
258+ if ( password && ! validatePasswordUtil ( password ) ) {
259+ setAdminPasswordError ( {
260+ target : "adminPassword" ,
261+ message : t ( "auth.passwordStrengthError" ) || "Password must contain uppercase, lowercase, and digit" ,
262+ } ) ;
263+ return ;
264+ }
265+
266+ if ( value && value !== password ) {
267+ setAdminPasswordError ( {
268+ target : "confirmAdminPassword" ,
269+ message : t ( "auth.passwordsDoNotMatch" ) ,
270+ } ) ;
271+ } else {
272+ setAdminPasswordError ( { target : "" , message : "" } ) ;
273+ }
274+ } ;
275+
218276 const handleSubmit = async ( ) => {
219277 try {
220278 const values = await form . validateFields ( ) ;
@@ -495,26 +553,86 @@ function TenantList({
495553 < Form . Item
496554 name = "adminPassword"
497555 label = { t ( "tenantResources.tenants.adminPassword" ) }
556+ validateStatus = {
557+ adminPasswordError . target === "adminPassword"
558+ ? "error"
559+ : ""
560+ }
561+ help = {
562+ form . getFieldError ( "adminPassword" ) . length
563+ ? undefined
564+ : adminPasswordError . target === "adminPassword"
565+ ? adminPasswordError . message
566+ : undefined
567+ }
498568 rules = { [
499569 {
500570 required : true ,
501571 message : t ( "tenantResources.tenants.adminPasswordRequired" ) ,
502572 } ,
503573 {
504- min : 6 ,
505- message : t ( "tenantResources.tenants.weakPassword" ) ,
574+ validator : ( _ , value ) => {
575+ if ( ! value ) return Promise . resolve ( ) ;
576+ if ( ! validatePasswordUtil ( value ) ) {
577+ return Promise . reject (
578+ new Error ( t ( "auth.passwordStrengthError" ) || "Password must contain uppercase, lowercase, and digit" )
579+ ) ;
580+ }
581+ return Promise . resolve ( ) ;
582+ } ,
506583 } ,
507584 ] }
585+ hasFeedback
508586 >
509587 < Input . Password
510588 placeholder = { t ( "tenantResources.tenants.adminPassword" ) }
511589 autoComplete = "new-password"
590+ onChange = { handleAdminPasswordChange }
512591 />
513592 </ Form . Item >
514593
594+ { /* Password Strength Indicator */ }
595+ { adminPasswordValue && generateAdminAccount && ( ( ) => {
596+ const checks = getPasswordChecks ( adminPasswordValue ) ;
597+ const levelInfo = getStrengthLevel ( adminPasswordValue , t ) ;
598+ return (
599+ < div className = "mb-4" >
600+ < div className = "flex items-center justify-between mb-1" >
601+ < span className = "text-xs text-gray-500" > { t ( "auth.passwordStrength" ) || "Password strength" } </ span >
602+ < span className = "text-xs font-medium" style = { { color : levelInfo . color } } >
603+ { levelInfo . label }
604+ </ span >
605+ </ div >
606+ < div className = "flex gap-1" >
607+ { [ 0 , 1 , 2 , 3 ] . map ( ( level ) => (
608+ < div
609+ key = { level }
610+ className = "h-1 flex-1 rounded-full transition-colors"
611+ style = { {
612+ backgroundColor : level <= levelInfo . level ? levelInfo . color : "#e5e7eb" ,
613+ } }
614+ />
615+ ) ) }
616+ </ div >
617+ </ div >
618+ ) ;
619+ } ) ( ) }
620+
515621 < Form . Item
516622 name = "confirmAdminPassword"
517623 label = { t ( "tenantResources.tenants.confirmAdminPassword" ) }
624+ validateStatus = {
625+ adminPasswordError . target === "confirmAdminPassword"
626+ ? "error"
627+ : ""
628+ }
629+ help = {
630+ form . getFieldError ( "confirmAdminPassword" ) . length
631+ ? undefined
632+ : adminPasswordError . target === "confirmAdminPassword"
633+ ? adminPasswordError . message
634+ : undefined
635+ }
518636 dependencies = { [ "adminPassword" ] }
519637 rules = { [
520638 {
@@ -523,17 +641,27 @@ function TenantList({
523641 } ,
524642 ( { getFieldValue } ) => ( {
525643 validator ( _ , value ) {
644+ const password = getFieldValue ( "adminPassword" ) ;
645+ if ( password && ! validatePasswordUtil ( password ) ) {
646+ setAdminPasswordError ( {
647+ target : "adminPassword" ,
648+ message : t ( "auth.passwordStrengthError" ) || "Password must contain uppercase, lowercase, and digit" ,
649+ } ) ;
650+ return Promise . reject ( new Error ( t ( "auth.passwordStrengthError" ) || "Password must contain uppercase, lowercase, and digit" ) ) ;
651+ }
526652 if ( ! value || getFieldValue ( "adminPassword" ) === value ) {
527653 return Promise . resolve ( ) ;
528654 }
529655 return Promise . reject ( new Error ( t ( "tenantResources.tenants.passwordsDoNotMatch" ) ) ) ;
530656 } ,
531657 } ) ,
532658 ] }
659+ hasFeedback
533660 >
534661 < Input . Password
535662 placeholder = { t ( "tenantResources.tenants.confirmAdminPassword" ) }
536663 autoComplete = "new-password"
664+ onChange = { handleConfirmAdminPasswordChange }
537665 />
538666 </ Form . Item >
539667 </ >
0 commit comments