diff --git a/src/hooks/useAuthForm.ts b/src/hooks/useAuthForm.ts new file mode 100644 index 0000000..630e387 --- /dev/null +++ b/src/hooks/useAuthForm.ts @@ -0,0 +1,96 @@ +import { useState, useMemo } from "react"; + +type Mode = "signup" | "signin"; +type UserType = "employee" | "employer"; + +type FormData = { + email: string; + password: string; + confirmPassword?: string; + userType?: UserType; +}; + +type FormErrors = { + email?: string; + password?: string; + confirmPassword?: string; +}; + +export function useAuthForm(mode: Mode) { + const [formData, setFormData] = useState({ + email: "", + password: "", + ...(mode === "signup" && { confirmPassword: "", userType: "employee" }), + }); + + const [errors, setErrors] = useState({}); + + const isFormValid = useMemo(() => { + if (!formData.email || !formData.password) return false; + if (errors.email || errors.password) return false; + + if (mode === "signup") { + if (!formData.confirmPassword || !formData.userType) return false; + if (errors.confirmPassword) return false; + } + + return true; + }, [formData, errors, mode]); + + const handleChange = + (field: keyof FormData) => (e: React.ChangeEvent) => { + const value = e.target.value; + setFormData((prev) => ({ ...prev, [field]: value })); + + if (field === "email") { + const isValidEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value); + setErrors((prev) => ({ + ...prev, + email: isValidEmail ? undefined : "올바른 이메일 형식이 아닙니다.", + })); + } + + if (field === "password") { + setErrors((prev) => ({ + ...prev, + password: + value.length >= 8 ? undefined : "비밀번호는 8자 이상이어야 합니다.", + ...(mode === "signup" && + formData.confirmPassword && { + confirmPassword: + value !== formData.confirmPassword + ? "비밀번호가 일치하지 않습니다." + : undefined, + }), + })); + } + + if (mode === "signup" && field === "confirmPassword") { + setErrors((prev) => ({ + ...prev, + confirmPassword: + value !== formData.password + ? "비밀번호가 일치하지 않습니다." + : undefined, + })); + } + }; + + const resetForm = () => { + setFormData({ + email: "", + password: "", + ...(mode === "signup" && { confirmPassword: "", userType: "employee" }), + }); + setErrors({}); + }; + + return { + formData, + errors, + isFormValid, + handleChange, + setFormData, + resetForm, + }; +} diff --git a/src/hooks/useUserStore.tsx b/src/hooks/useUserStore.tsx new file mode 100644 index 0000000..598888c --- /dev/null +++ b/src/hooks/useUserStore.tsx @@ -0,0 +1,55 @@ +import { create } from "zustand"; + +type User = { + id: string; + email: string; + type: "employer" | "employee"; + name?: string; + phone?: string; + address?: string; + bio?: string; + shopId?: string; +}; + +interface UserState { + user: User | null; + token: string | null; + isLoggedIn: boolean; + setUserAndToken: (user: User, token: string) => void; + updateShopId: (shopId: string) => void; + clearUser: () => void; +} + +export const useUserStore = create((set, get) => ({ + user: null, + token: null, + isLoggedIn: false, + + setUserAndToken: (user, token) => + set(() => ({ + user, + token, + isLoggedIn: true, + })), + + updateShopId: (shopId) => { + const current = get(); + if (!current.user || !current.token) return; + + const updatedUser = { + ...current.user, + shopId, + }; + + set({ + user: updatedUser, + }); + }, + + clearUser: () => + set(() => ({ + user: null, + token: null, + isLoggedIn: false, + })), +})); diff --git a/src/layouts/Header.tsx b/src/layouts/Header.tsx index dbf95d5..e4c17fa 100644 --- a/src/layouts/Header.tsx +++ b/src/layouts/Header.tsx @@ -1,27 +1,31 @@ -import { Link } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; import ActiveAlarmIcon from "../assets/icon/active.svg"; import InActiveAlarmIcon from "../assets/icon/inactive.svg"; import SearchIcon from "../assets/icon/search.svg"; import Logo from "../assets/logo/thejulge.svg"; + +import { useUserStore } from "@/hooks/useUserStore"; + interface HeaderProps { - isLoggedIn: boolean; - userNavLabel?: "내 가게" | "내 프로필"; hasAlarm?: boolean; - onLogout?: () => void; onToggleAlarm?: () => void; } -export default function Header({ - isLoggedIn, - userNavLabel, - hasAlarm, - onLogout, - onToggleAlarm, -}: HeaderProps) { - const userPath = userNavLabel === "내 가게" ? "/shop" : "/profile"; +export default function Header({ hasAlarm, onToggleAlarm }: HeaderProps) { + const navigate = useNavigate(); + const { user, isLoggedIn, clearUser } = useUserStore(); + + const userNavLabel = user?.type === "employer" ? "내 가게" : "내 프로필"; + const userPath = user?.type === "employer" ? "/shop" : "/profile"; + const alarmIcon = hasAlarm ? ActiveAlarmIcon : InActiveAlarmIcon; + const handleLogout = () => { + clearUser(); + navigate("/"); + }; + return (
@@ -47,7 +51,7 @@ export default function Header({ {isLoggedIn ? ( <> {userNavLabel} - + + +
+ + + + + +

+ 이미 가입하셨나요?{" "} + + 로그인하기 + +

+ + {/* 임시 Alert */} + {/* {alertMessage && ( + { + setAlertMessage(""); + if (nextRoute) { + navigate(nextRoute); + setNextRoute(null); + } + }} + /> + )} */} + + ); }