1- import { FormEvent , useState } from "react" ;
1+ import { useState , type FormEvent } from "react" ;
22
33import { PASSWORD_POLICY , validatePassword } from "@fieldstack/core" ;
44
5+ import { Button , FormField , Input } from "@fieldstack/controls" ;
6+
57import "../styles/change-password.css" ;
68
79interface ChangePasswordViewProps {
8- isFirstLogin : boolean ; // true = 임시 비번 첫 로그인, false = 일반 변경
10+ isFirstLogin : boolean ;
911 onChanged : ( ) => void ;
1012}
1113
@@ -23,14 +25,10 @@ export function ChangePasswordView({ isFirstLogin, onChanged }: ChangePasswordVi
2325 const nextValidation = validatePassword ( next . value ) ;
2426 const confirmMismatch = confirm . value !== next . value ;
2527
26- const showNextErrors = ( next . touched || submitted ) && ! nextValidation . valid ;
27- const showConfirmError = ( confirm . touched || submitted ) && confirmMismatch ;
2828 const showCurrentError = ( current . touched || submitted ) && current . value . length === 0 ;
29+ const showConfirmError = ( confirm . touched || submitted ) && confirmMismatch ;
2930
30- const canSubmit =
31- current . value . length > 0 &&
32- nextValidation . valid &&
33- ! confirmMismatch ;
31+ const canSubmit = current . value . length > 0 && nextValidation . valid && ! confirmMismatch ;
3432
3533 const handleSubmit = ( e : FormEvent ) => {
3634 e . preventDefault ( ) ;
@@ -56,103 +54,74 @@ export function ChangePasswordView({ isFirstLogin, onChanged }: ChangePasswordVi
5654 </ div >
5755
5856 < form className = "stack cpw-form" onSubmit = { handleSubmit } noValidate >
59- { /* 현재(임시) 비밀번호 */ }
60- < label className = "field" >
61- < span > { isFirstLogin ? "임시 비밀번호" : "현재 비밀번호" } </ span >
62- < input
63- className = { `input${ showCurrentError ? " cpw-input-error" : "" } ` }
57+ < FormField
58+ label = { isFirstLogin ? "임시 비밀번호" : "현재 비밀번호" }
59+ htmlFor = "cpw-current"
60+ error = { showCurrentError ? "필수 입력 항목입니다." : undefined }
61+ >
62+ < Input
63+ id = "cpw-current"
6464 type = "password"
6565 autoComplete = "current-password"
6666 value = { current . value }
6767 onChange = { ( e ) => setCurrent ( { value : e . target . value , touched : true } ) }
6868 placeholder = "••••••••"
6969 />
70- { showCurrentError && (
71- < p className = "cpw-field-error" role = "alert" > 필수 입력 항목입니다.</ p >
72- ) }
73- </ label >
74-
75- { /* 새 비밀번호 */ }
76- < label className = "field" >
77- < span > 새 비밀번호</ span >
78- < input
79- className = { `input${ showNextErrors ? " cpw-input-error" : "" } ` }
70+ </ FormField >
71+
72+ < FormField label = "새 비밀번호" htmlFor = "cpw-next" >
73+ < Input
74+ id = "cpw-next"
8075 type = "password"
8176 autoComplete = "new-password"
8277 value = { next . value }
8378 onChange = { ( e ) => setNext ( { value : e . target . value , touched : true } ) }
8479 placeholder = "••••••••"
8580 aria-describedby = "cpw-policy"
8681 />
87- </ label >
82+ </ FormField >
8883
89- { /* 정책 체크리스트 */ }
9084 < ul className = "cpw-policy" id = "cpw-policy" aria-label = "비밀번호 조건" >
9185 < PolicyItem
9286 met = { next . value . length >= PASSWORD_POLICY . minLength && next . value . length <= PASSWORD_POLICY . maxLength }
9387 active = { next . touched || submitted }
9488 label = { `${ PASSWORD_POLICY . minLength } ~${ PASSWORD_POLICY . maxLength } 자` }
9589 />
96- < PolicyItem
97- met = { / [ A - Z ] / . test ( next . value ) }
98- active = { next . touched || submitted }
99- label = "영어 대문자 포함"
100- />
101- < PolicyItem
102- met = { / [ a - z ] / . test ( next . value ) }
103- active = { next . touched || submitted }
104- label = "영어 소문자 포함"
105- />
106- < PolicyItem
107- met = { / \d / . test ( next . value ) }
108- active = { next . touched || submitted }
109- label = "숫자 포함"
110- />
90+ < PolicyItem met = { / [ A - Z ] / . test ( next . value ) } active = { next . touched || submitted } label = "영어 대문자 포함" />
91+ < PolicyItem met = { / [ a - z ] / . test ( next . value ) } active = { next . touched || submitted } label = "영어 소문자 포함" />
92+ < PolicyItem met = { / \d / . test ( next . value ) } active = { next . touched || submitted } label = "숫자 포함" />
11193 < PolicyItem
11294 met = { / [ ^ a - z A - Z 0 - 9 ] / . test ( next . value ) }
11395 active = { next . touched || submitted }
11496 label = "특수문자 포함"
11597 />
11698 </ ul >
11799
118- { /* 비밀번호 확인 */ }
119- < label className = "field" >
120- < span > 새 비밀번호 확인</ span >
121- < input
122- className = { `input${ showConfirmError ? " cpw-input-error" : "" } ` }
100+ < FormField
101+ label = "새 비밀번호 확인"
102+ htmlFor = "cpw-confirm"
103+ error = { showConfirmError ? "비밀번호가 일치하지 않습니다." : undefined }
104+ >
105+ < Input
106+ id = "cpw-confirm"
123107 type = "password"
124108 autoComplete = "new-password"
125109 value = { confirm . value }
126110 onChange = { ( e ) => setConfirm ( { value : e . target . value , touched : true } ) }
127111 placeholder = "••••••••"
128112 />
129- { showConfirmError && (
130- < p className = "cpw-field-error" role = "alert" > 비밀번호가 일치하지 않습니다.</ p >
131- ) }
132- </ label >
133-
134- < button
135- className = "button button-primary button-block"
136- type = "submit"
137- >
113+ </ FormField >
114+
115+ < Button variant = "primary" block type = "submit" >
138116 비밀번호 변경
139- </ button >
117+ </ Button >
140118 </ form >
141119 </ section >
142120 </ main >
143121 ) ;
144122}
145123
146- // ─── Policy Checklist Item ────────────────────────────────────
147- function PolicyItem ( {
148- met,
149- active,
150- label,
151- } : {
152- met : boolean ;
153- active : boolean ;
154- label : string ;
155- } ) {
124+ function PolicyItem ( { met, active, label } : { met : boolean ; active : boolean ; label : string } ) {
156125 const state = ! active ? "idle" : met ? "ok" : "fail" ;
157126 return (
158127 < li className = { `cpw-policy-item cpw-policy-${ state } ` } aria-live = "polite" >
0 commit comments