diff --git a/src/components/base-ui/Join/JoinLayout.tsx b/src/components/base-ui/Join/JoinLayout.tsx index 6bf57fa..cfecc9f 100644 --- a/src/components/base-ui/Join/JoinLayout.tsx +++ b/src/components/base-ui/Join/JoinLayout.tsx @@ -1,3 +1,5 @@ +// JoinLayout.tsx + import React from "react"; interface JoinLayoutProps { @@ -10,7 +12,12 @@ const JoinLayout: React.FC = ({ children }) => { className="flex flex-col items-center justify-center w-full min-h-screen pb-[75px] bg-center bg-cover bg-no-repeat" style={{ backgroundImage: `url('/background.png')` }} > -
+ {/* + Container Layout Rules: + - Mobile: Fixed w-[352px], Fluid px-[24px] + - Desktop: Fixed w-[766px], Strict Padding px-[56px] py-[99px] + */} +
{children}
diff --git a/src/components/base-ui/Join/steps/EmailVerification/EmailVerification.tsx b/src/components/base-ui/Join/steps/EmailVerification/EmailVerification.tsx index e29ac5c..422dafd 100644 --- a/src/components/base-ui/Join/steps/EmailVerification/EmailVerification.tsx +++ b/src/components/base-ui/Join/steps/EmailVerification/EmailVerification.tsx @@ -1,12 +1,15 @@ +// c:\Users\shinwookKang\Desktop\CheckMo\FE\src\components\base-ui\Join\steps\EmailVerification\EmailVerification.tsx + +"use client"; + import React from "react"; import JoinHeader from "../../JoinHeader"; import JoinButton from "../../JoinButton"; import JoinInput from "../../JoinInput"; import { useEmailVerification } from "../useEmailVerification"; -import Toast from "@/components/common/Toast"; interface EmailVerificationProps { - onNext?: () => void; + onNext: () => void; } const EmailVerification: React.FC = ({ onNext }) => { @@ -22,16 +25,18 @@ const EmailVerification: React.FC = ({ onNext }) => { isVerified, handleVerify, showToast, - setShowToast, + isToastVisible, formatTime, } = useEmailVerification(); return ( -
+
-
- {/* 이메일 입력 섹션 */} -
+ + {/* Content Wrapper: Mobile(mt-10 mb-10) -> Tablet(mt-[60px] mb-[80px]) -> Desktop(mt-[90px] mb-[130px]) */} +
+ {/* Email Input Section */} +
= ({ onNext }) => {
- {/* 인증번호 입력 섹션 */} -
+ {/* Verification Code Section */} +
= ({ onNext }) => {
- {/* 하단 버튼 (임시로 클릭 시 다음 단계 이동 동작 연결) */} - + + 다음 + {showToast && ( - setShowToast(false)} - /> +
+ + 인증이 완료되었습니다. + +
)}
); diff --git a/src/components/base-ui/Join/steps/PasswordEntry/PasswordEntry.tsx b/src/components/base-ui/Join/steps/PasswordEntry/PasswordEntry.tsx index aa8fb23..7b1761c 100644 --- a/src/components/base-ui/Join/steps/PasswordEntry/PasswordEntry.tsx +++ b/src/components/base-ui/Join/steps/PasswordEntry/PasswordEntry.tsx @@ -1,3 +1,5 @@ +"use client"; + import React from "react"; import JoinHeader from "../../JoinHeader"; import JoinButton from "../../JoinButton"; @@ -5,7 +7,7 @@ import JoinInput from "../../JoinInput"; import { usePasswordEntry } from "./usePasswordEntry"; interface PasswordEntryProps { - onNext?: () => void; + onNext: () => void; } const PasswordEntry: React.FC = ({ onNext }) => { @@ -13,49 +15,85 @@ const PasswordEntry: React.FC = ({ onNext }) => { password, confirmPassword, isValid, + isComplexityValid, + isMatch, handlePasswordChange, handleConfirmChange, } = usePasswordEntry(); + return ( -
+
-
- {/* Main Container for Inputs */} -
- {/* 비밀번호 입력 필드 (커스텀 라벨) */} -
-
+ {/* Content Wrapper: Mobile(mt-10 mb-10) -> Tablet(mt-[60px] mb-[80px]) -> Desktop(mt-[90px] mb-[130px]) */} +
+
+ {/* Password Input Section */} +
+ {/* Custom Label Layout for Password */} +
비밀번호 - + 비밀번호는 6-12자, 영어 최소 1자 이상, 특수문자 최소 1자 이상
+ 0 + ? "border-red-500" + : "" + }`} + hideLabel // Using custom label above /> + + {!isComplexityValid && password.length > 0 && ( + + 비밀번호 형식이 올바르지 않습니다. + + )}
- {/* 비밀번호 확인 입력 필드 */} - + {/* Confirm Password Input Section */} +
+ {/* Custom Label Layout for Confirm Password */} +
+ + 비밀번호 확인 + +
+ + 0 ? "border-red-500" : "" + }`} + hideLabel + /> + + {!isMatch && confirmPassword.length > 0 && ( + + 비밀번호가 일치하지 않습니다. + + )} +
- + 다음
diff --git a/src/components/base-ui/Join/steps/PasswordEntry/usePasswordEntry.ts b/src/components/base-ui/Join/steps/PasswordEntry/usePasswordEntry.ts index 19f11f1..b1e5ada 100644 --- a/src/components/base-ui/Join/steps/PasswordEntry/usePasswordEntry.ts +++ b/src/components/base-ui/Join/steps/PasswordEntry/usePasswordEntry.ts @@ -1,18 +1,20 @@ -import { useState, useEffect } from "react"; +import { useState } from "react"; export const usePasswordEntry = () => { const [password, setPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); - const [isValid, setIsValid] = useState(false); - useEffect(() => { - // 6-12자, 영문 최소 1자, 특수문자 최소 1자 - const passwordRegex = /^(?=.*[a-zA-Z])(?=.*[!@#$%^&*]).{6,12}$/; - const isPasswordValid = passwordRegex.test(password); - const isMatch = password === confirmPassword; + // 6-12자, 영문 최소 1자, 특수문자 최소 1자 + const passwordRegex = /^(?=.*[a-zA-Z])(?=.*[!@#$%^&*]).{6,12}$/; - setIsValid(isPasswordValid && isMatch && password.length > 0); - }, [password, confirmPassword]); + // Derived State (No useEffect) + const isComplexityValid = passwordRegex.test(password); + const isMatch = password === confirmPassword; + const isValid = + isComplexityValid && + isMatch && + password.length > 0 && + confirmPassword.length > 0; const handlePasswordChange = (e: React.ChangeEvent) => { setPassword(e.target.value); @@ -26,6 +28,8 @@ export const usePasswordEntry = () => { password, confirmPassword, isValid, + isComplexityValid, + isMatch, handlePasswordChange, handleConfirmChange, }; diff --git a/src/components/base-ui/Join/steps/ProfileImage/InterestCategorySelector.tsx b/src/components/base-ui/Join/steps/ProfileImage/InterestCategorySelector.tsx index c0a80fa..584325f 100644 --- a/src/components/base-ui/Join/steps/ProfileImage/InterestCategorySelector.tsx +++ b/src/components/base-ui/Join/steps/ProfileImage/InterestCategorySelector.tsx @@ -12,24 +12,28 @@ const InterestCategorySelector: React.FC = ({ }) => { return (
-
- +
+ {/* Title: Hidden on Mobile (shown in Header instead), Visible on Desktop */} + 관심 카테고리 + {/* Subtitle: Visible & Centered on Mobile */}
(최소 1개, 최대 6개 선택)
-
+ + {/* Grid for Mobile (3 cols), Flex for Desktop */} +
{INTEREST_CATEGORIES.map((category) => { const isSelected = selectedInterests.includes(category); return ( + - -
- {TERMS_DATA.map((term) => ( - - ))} -
- -
- -