내 강좌실 생성11

This commit is contained in:
wallace
2025-11-10 20:13:55 +09:00
parent 6a9cd7c1e4
commit efd5ab915e
4 changed files with 461 additions and 103 deletions

View File

@@ -7,6 +7,7 @@ import RadioOff from '../../public/svg/radio_off';
import RadioOn from '../../public/svg/radio_on';
import CheckOff from '../../public/svg/check_off';
import CheckOn from '../../public/svg/check_on';
import ChevronSmall from '../../public/svg/chevron_small';
const imgRiCheckboxCircleLine = "http://localhost:3845/assets/e4c498605e2559d2764a3112ae9a9019e6ad798e.svg";
const imgFormkitRadio = "http://localhost:3845/assets/ea30a9a80d95ced4bfb1174d3a8475a4a1dbbabb.svg";
@@ -46,6 +47,7 @@ export default function RegisterPage() {
const [isVerificationSent, setIsVerificationSent] = useState(false);
const [isVerificationComplete, setIsVerificationComplete] = useState(false);
const [verificationError, setVerificationError] = useState('');
const [activeSelect, setActiveSelect] = useState<string | null>(null);
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
const { name, value } = e.target;
@@ -66,6 +68,32 @@ export default function RegisterPage() {
setVerificationCode('');
setIsVerificationComplete(false);
}
// 비밀번호 확인 실시간 검증
if (name === 'passwordConfirm' && value && formData.password && value !== formData.password) {
setErrors((prev) => ({
...prev,
passwordConfirm: '비밀번호와 일치하지 않아요.',
}));
} else if (name === 'passwordConfirm' && value && formData.password && value === formData.password) {
setErrors((prev) => ({
...prev,
passwordConfirm: '',
}));
}
// 비밀번호가 변경되면 비밀번호 확인도 다시 검증
if (name === 'password' && formData.passwordConfirm) {
if (formData.passwordConfirm !== value) {
setErrors((prev) => ({
...prev,
passwordConfirm: '비밀번호와 일치하지 않아요.',
}));
} else {
setErrors((prev) => ({
...prev,
passwordConfirm: '',
}));
}
}
};
const handlePhoneChange = (e: React.ChangeEvent<HTMLInputElement>) => {
@@ -258,7 +286,7 @@ export default function RegisterPage() {
name="name"
value={formData.name}
onChange={handleChange}
className={`flex-1 h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border border-[#b9b9b9] placeholder:text-[#b9b9b9] bg-white ${errors.name ? 'border-[#E85D5D] text-[#E85D5D]' : 'text-[#515151]'}`}
className={`flex-1 h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border placeholder:text-[#b9b9b9] bg-white focus:border-[#1669CA] focus:outline-none ${errors.name ? 'border-[#E85D5D] text-[#E85D5D]' : 'border-[#b9b9b9] text-[#515151]'}`}
placeholder="이름을 입력해 주세요."
/>
</div>
@@ -279,7 +307,7 @@ export default function RegisterPage() {
value={formData.phone}
onChange={handlePhoneChange}
maxLength={11}
className={`flex-1 h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border border-[#b9b9b9] placeholder:text-[#b9b9b9] bg-white ${errors.phone ? 'border-[#E85D5D] text-[#E85D5D]' : 'text-[#515151]'}`}
className={`flex-1 h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border placeholder:text-[#b9b9b9] bg-white focus:border-[#1669CA] focus:outline-none ${errors.phone ? 'border-[#E85D5D] text-[#E85D5D]' : 'border-[#b9b9b9] text-[#515151]'}`}
placeholder="-없이 입력해 주세요."
/>
</div>
@@ -299,7 +327,7 @@ export default function RegisterPage() {
name="email"
value={formData.email}
onChange={handleChange}
className={`h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border border-[#b9b9b9] placeholder:text-[#b9b9b9] bg-white w-[401px] ${errors.email ? 'border-[#E85D5D] text-[#E85D5D]' : 'text-[#515151]'}`}
className={`h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border placeholder:text-[#b9b9b9] bg-white w-[401px] focus:border-[#1669CA] focus:outline-none ${errors.email ? 'border-[#E85D5D] text-[#E85D5D]' : 'border-[#b9b9b9] text-[#515151]'}`}
placeholder="이메일을 입력해 주세요."
/>
<button
@@ -325,7 +353,7 @@ export default function RegisterPage() {
type="text"
value={verificationCode}
onChange={handleVerificationCodeChange}
className="h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border border-[#1669ca] text-[#515151] bg-white w-[401px]"
className="h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border border-[#1669ca] text-[#515151] bg-white w-[401px] focus:border-[#1669CA] focus:outline-none"
placeholder="인증번호를 입력해 주세요."
/>
<button
@@ -372,7 +400,7 @@ export default function RegisterPage() {
name="password"
value={formData.password}
onChange={handleChange}
className={`flex-1 h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border border-[#b9b9b9] placeholder:text-[#b9b9b9] bg-white ${errors.password ? 'border-[#E85D5D] text-[#E85D5D]' : 'text-[#515151]'}`}
className={`flex-1 h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border placeholder:text-[#b9b9b9] bg-white focus:border-[#1669CA] focus:outline-none ${errors.password ? 'border-[#E85D5D] text-[#E85D5D]' : 'border-[#b9b9b9] text-[#515151]'}`}
placeholder="8~16자의 영문/숫자/특수문자를 조합해서 입력해 주세요."
/>
</div>
@@ -392,11 +420,19 @@ export default function RegisterPage() {
name="passwordConfirm"
value={formData.passwordConfirm}
onChange={handleChange}
className={`flex-1 h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border border-[#b9b9b9] placeholder:text-[#b9b9b9] bg-white ${errors.passwordConfirm ? 'border-[#E85D5D] text-[#E85D5D]' : 'text-[#515151]'}`}
className={`flex-1 h-[42px] px-[10px] text-[18px] font-medium rounded-[8px] border placeholder:text-[#b9b9b9] bg-white text-[#000000] focus:outline-none ${formData.passwordConfirm && formData.password && formData.passwordConfirm !== formData.password
? 'border-[#E61A1A] focus:border-[#E61A1A]'
: errors.passwordConfirm
? 'border-[#E85D5D] focus:border-[#1669CA]'
: 'border-[#b9b9b9] focus:border-[#1669CA]'
}`}
placeholder="비밀번호를 다시 입력해 주세요."
/>
</div>
{errors.passwordConfirm && (
{formData.passwordConfirm && formData.password && formData.passwordConfirm !== formData.password && (
<p className="text-[13px] text-[#E61A1A] ml-[193px] mt-[16px]"> .</p>
)}
{errors.passwordConfirm && formData.passwordConfirm === formData.password && (
<p className="text-[13px] text-[#E85D5D] ml-[193px] mt-[16px]">{errors.passwordConfirm}</p>
)}
</div>
@@ -441,55 +477,58 @@ export default function RegisterPage() {
name="birthYear"
value={formData.birthYear}
onChange={handleChange}
className="w-full h-[42px] px-[10px] text-[18px] font-medium border border-[#b9b9b9] rounded-[8px] bg-white text-[#515151] appearance-none pr-[30px]"
onFocus={() => setActiveSelect('birthYear')}
onBlur={() => setActiveSelect(null)}
className="w-full h-[42px] px-[10px] pr-[30px] text-[18px] font-medium border border-[#b9b9b9] rounded-[8px] bg-white text-[#515151] appearance-none focus:border-[#1669CA] focus:outline-none"
style={{ color: formData.birthYear ? '#515151' : '#b9b9b9' }}
>
<option value="" className="text-[#b9b9b9]"></option>
<option value="" style={{ color: '#b9b9b9' }}></option>
{[...Array(100)].map((_, idx) => {
const year = new Date().getFullYear() - idx;
return <option key={year} value={year}>{year}</option>
return <option key={year} value={year} style={{ color: '#515151' }}>{year}</option>
})}
</select>
<img
src={imgLsiconDownFilled}
alt=""
className="absolute right-[10px] top-1/2 -translate-y-1/2 w-[16px] h-[16px] pointer-events-none"
/>
<div className={`absolute right-[10px] top-1/2 -translate-y-1/2 pointer-events-none transition-transform ${activeSelect === 'birthYear' ? 'rotate-180' : ''}`}>
<ChevronSmall />
</div>
</div>
<div className="relative flex-1">
<select
name="birthMonth"
value={formData.birthMonth}
onChange={handleChange}
className="w-full h-[42px] px-[10px] text-[18px] font-medium border border-[#b9b9b9] rounded-[8px] bg-white text-[#515151] appearance-none pr-[30px]"
onFocus={() => setActiveSelect('birthMonth')}
onBlur={() => setActiveSelect(null)}
className="w-full h-[42px] px-[10px] pr-[30px] text-[18px] font-medium border border-[#b9b9b9] rounded-[8px] bg-white text-[#515151] appearance-none focus:border-[#1669CA] focus:outline-none"
style={{ color: formData.birthMonth ? '#515151' : '#b9b9b9' }}
>
<option value="" className="text-[#b9b9b9]"></option>
<option value="" style={{ color: '#b9b9b9' }}></option>
{[...Array(12)].map((_, idx) => (
<option key={idx + 1} value={idx + 1}>{idx + 1}</option>
<option key={idx + 1} value={idx + 1} style={{ color: '#515151' }}>{idx + 1}</option>
))}
</select>
<img
src={imgLsiconDownFilled}
alt=""
className="absolute right-[10px] top-1/2 -translate-y-1/2 w-[16px] h-[16px] pointer-events-none"
/>
<div className={`absolute right-[10px] top-1/2 -translate-y-1/2 pointer-events-none transition-transform ${activeSelect === 'birthMonth' ? 'rotate-180' : ''}`}>
<ChevronSmall />
</div>
</div>
<div className="relative flex-1">
<select
name="birthDay"
value={formData.birthDay}
onChange={handleChange}
className="w-full h-[42px] px-[10px] text-[18px] font-medium border border-[#b9b9b9] rounded-[8px] bg-white text-[#515151] appearance-none pr-[30px]"
onFocus={() => setActiveSelect('birthDay')}
onBlur={() => setActiveSelect(null)}
className="w-full h-[42px] px-[10px] pr-[30px] text-[18px] font-medium border border-[#b9b9b9] rounded-[8px] bg-white text-[#515151] appearance-none focus:border-[#1669CA] focus:outline-none"
style={{ color: formData.birthDay ? '#515151' : '#b9b9b9' }}
>
<option value="" className="text-[#b9b9b9]"></option>
<option value="" style={{ color: '#b9b9b9' }}></option>
{[...Array(31)].map((_, idx) => (
<option key={idx + 1} value={idx + 1}>{idx + 1}</option>
<option key={idx + 1} value={idx + 1} style={{ color: '#515151' }}>{idx + 1}</option>
))}
</select>
<img
src={imgLsiconDownFilled}
alt=""
className="absolute right-[10px] top-1/2 -translate-y-1/2 w-[16px] h-[16px] pointer-events-none"
/>
<div className={`absolute right-[10px] top-1/2 -translate-y-1/2 pointer-events-none transition-transform ${activeSelect === 'birthDay' ? 'rotate-180' : ''}`}>
<ChevronSmall />
</div>
</div>
</div>
</div>