diff --git a/omechu-app/public/mdi-light_eye.svg b/omechu-app/public/mdi-light_eye.svg new file mode 100644 index 000000000..d604105e4 --- /dev/null +++ b/omechu-app/public/mdi-light_eye.svg @@ -0,0 +1,4 @@ + + + + diff --git "a/omechu-app/public/\353\271\204\353\260\200\353\262\210\355\230\270\353\263\264\352\270\260.svg" "b/omechu-app/public/\353\271\204\353\260\200\353\262\210\355\230\270\353\263\264\352\270\260.svg" deleted file mode 100644 index e4a81563f..000000000 --- "a/omechu-app/public/\353\271\204\353\260\200\353\262\210\355\230\270\353\263\264\352\270\260.svg" +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/omechu-app/src/app/ClientLayout.tsx b/omechu-app/src/app/ClientLayout.tsx index 4db14c3c5..6abe93402 100644 --- a/omechu-app/src/app/ClientLayout.tsx +++ b/omechu-app/src/app/ClientLayout.tsx @@ -33,10 +33,11 @@ export default function ClientLayout({ "/mypage/recommended-list", // 로그인, 회원가입, 온보딩 페이지 - "/auth/signin", + "/auth/login", "/auth/signup", "/auth/find-password", "/auth/find-password/sent", + "/auth/reset-password", "/onboarding/1", "/onboarding/2", "/onboarding/3", diff --git a/omechu-app/src/app/auth/find-password/page.tsx b/omechu-app/src/app/auth/find-password/page.tsx index e15c1d505..be87c51a9 100644 --- a/omechu-app/src/app/auth/find-password/page.tsx +++ b/omechu-app/src/app/auth/find-password/page.tsx @@ -33,77 +33,71 @@ export default function FindPasswordPage() { router.push("/auth/find-password/sent"); } else { setApiError( - "비밀번호 재설정 메일 발송에 실패했습니다. 이메일 주소를 다시 확인해 주세요." + "비밀번호 재설정 메일 발송에 실패했습니다. \n 이메일 주소를 다시 확인해 주세요." ); } }; return ( -
-
-
-

비밀번호 찾기

-

- 가입하신 이메일 주소를 입력하여 -
- 비밀번호를 재설정하실 수 있어요 -

-
+
+
+

비밀번호 찾기

+

+ 가입하신 이메일 주소를 입력하여 +
+ 비밀번호를 재설정하실 수 있어요 +

-
-
-
+ +
+ -
-
- -
-
- 비밀번호가 생각났어요 - - 로그인 하기 - -
-
- 계정이 아직 없어요 - - 회원가입하기 - -
-
+ {isSubmitting ? "메일 발송 중..." : "메일 발송하기"} +
+ - {apiError && ( -
- {apiError} -
- )} +
+
+ 비밀번호가 생각났어요 + + 로그인 하기 + +
+
+ 계정이 아직 없어요 + + 회원가입하기 + +
+ + {apiError && ( +
+ {apiError} +
+ )}
); } diff --git a/omechu-app/src/app/auth/find-password/sent/page.tsx b/omechu-app/src/app/auth/find-password/sent/page.tsx index 57f5b0840..daeee9d22 100644 --- a/omechu-app/src/app/auth/find-password/sent/page.tsx +++ b/omechu-app/src/app/auth/find-password/sent/page.tsx @@ -5,22 +5,20 @@ import Button from "@/app/components/auth/Button"; export default function FindPasswordEmailSentPage() { return ( -
-
-
-

- 비밀번호 재설정 메일을 발송했어요 -

-

- 보내드린 메일을 확인하신 후, -
- 비밀번호를 다시 설정해 주세요 -

-
+
+
+

+ 비밀번호 재설정 메일을 발송했어요 +

+

+ 보내드린 메일을 확인하신 후, +
+ 비밀번호를 다시 설정해 주세요 +

-
+
- diff --git a/omechu-app/src/app/auth/layout.tsx b/omechu-app/src/app/auth/layout.tsx index 6879f62ec..15826e677 100644 --- a/omechu-app/src/app/auth/layout.tsx +++ b/omechu-app/src/app/auth/layout.tsx @@ -4,7 +4,7 @@ export default function AuthLayout({ children: React.ReactNode; }) { return ( -
+
{children}
); diff --git a/omechu-app/src/app/auth/login/page.tsx b/omechu-app/src/app/auth/login/page.tsx index 7e67b826c..0bc0ae47c 100644 --- a/omechu-app/src/app/auth/login/page.tsx +++ b/omechu-app/src/app/auth/login/page.tsx @@ -11,6 +11,7 @@ import Input from "@/app/components/auth/Input"; export default function LoginPage() { const [loginError, setLoginError] = useState(null); + const [showPassword, setShowPassword] = useState(false); const { register, @@ -25,7 +26,7 @@ export default function LoginPage() { // 가상의 로그인 실패 시나리오 await new Promise((resolve) => setTimeout(resolve, 1000)); console.log(data); - setLoginError("이메일 또는 비밀번호가 올바르지 않습니다."); + setLoginError("이메일 또는 비밀번호가 \n 올바르지 않습니다."); }; return ( @@ -54,9 +55,23 @@ export default function LoginPage() { setShowPassword(!showPassword)} + className="p-2" + > + toggle password visibility + + } />
@@ -109,7 +124,7 @@ export default function LoginPage() { {loginError && ( -
+
{loginError}
)} diff --git a/omechu-app/src/app/auth/reset-password/page.tsx b/omechu-app/src/app/auth/reset-password/page.tsx new file mode 100644 index 000000000..aec3b3273 --- /dev/null +++ b/omechu-app/src/app/auth/reset-password/page.tsx @@ -0,0 +1,134 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import Image from "next/image"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { + resetPasswordSchema, + ResetPasswordFormValues, +} from "@/lib/schemas/auth.schema"; +import Button from "@/app/components/auth/Button"; +import Input from "@/app/components/auth/Input"; +import AlertModal from "@/app/components/auth/AlertModal"; + +export default function ResetPasswordPage() { + const [showPassword, setShowPassword] = useState(false); + const [showPasswordConfirm, setShowPasswordConfirm] = useState(false); + const [isModalOpen, setIsModalOpen] = useState(false); + const router = useRouter(); + + const { + register, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm({ + resolver: zodResolver(resetPasswordSchema), + }); + + const onSubmit = async (data: ResetPasswordFormValues) => { + console.log(data); + await new Promise((resolve) => setTimeout(resolve, 1000)); + setIsModalOpen(true); + }; + + const handleModalConfirm = () => { + setIsModalOpen(false); + router.push("/auth/login"); + }; + + return ( + <> +
+
+

+ 비밀번호 재설정 +

+

+ 사용하실 새로운 비밀번호를 설정해 주세요 +

+
+ +
+ setShowPassword(!showPassword)} + className="p-2" + > + toggle password visibility + + } + /> +

+ * 대소문자, 숫자 및 특수문자 포함 8자 이상 +

+ +
+ setShowPasswordConfirm(!showPasswordConfirm)} + className="p-2" + > + toggle password visibility + + } + /> +
+ +
+ +
+
+
+ {isModalOpen && ( + + )} + + ); +} diff --git a/omechu-app/src/app/auth/signup/page.tsx b/omechu-app/src/app/auth/signup/page.tsx index ec041c0e4..f9491b2fc 100644 --- a/omechu-app/src/app/auth/signup/page.tsx +++ b/omechu-app/src/app/auth/signup/page.tsx @@ -5,11 +5,12 @@ import Image from "next/image"; import Button from "@/app/components/auth/Button"; import Input from "@/app/components/auth/Input"; import TermsModal from "@/app/components/auth/TermsModal"; -import PrivacyModal from "@/app/components/auth/PrivacyModal"; -import LocationModal from "@/app/components/auth/LocationModal"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { signupSchema, SignupFormValues } from "@/lib/schemas/auth.schema"; +import { termsForService } from "@/app/constant/terms/service"; +import { termsForPersonlInfo } from "@/app/constant/terms/personlInfo"; +import { termsForLocationlInfo } from "@/app/constant/terms/locationInfo"; export default function Signup() { // const router = useRouter(); @@ -352,19 +353,34 @@ export default function Signup() { {showTermsModal && ( setShowTermsModal(false)} + title="서비스 이용약관" + terms={termsForService} + onConfirm={() => { + setValue("termsService", true, { shouldValidate: true }); + setShowTermsModal(false); + }} onClose={() => setShowTermsModal(false)} /> )} {showPrivacyModal && ( - setShowPrivacyModal(false)} + { + setValue("termsPrivacy", true, { shouldValidate: true }); + setShowPrivacyModal(false); + }} onClose={() => setShowPrivacyModal(false)} /> )} {showLocationModal && ( - setShowLocationModal(false)} + { + setValue("termsLocation", true, { shouldValidate: true }); + setShowLocationModal(false); + }} onClose={() => setShowLocationModal(false)} /> )} diff --git a/omechu-app/src/app/components/auth/AlertModal.tsx b/omechu-app/src/app/components/auth/AlertModal.tsx new file mode 100644 index 000000000..290185e3f --- /dev/null +++ b/omechu-app/src/app/components/auth/AlertModal.tsx @@ -0,0 +1,44 @@ +"use client"; + +import Button from "./Button"; +import { MouseEvent } from "react"; + +interface AlertModalProps { + title: string; + message: string; + onConfirm: () => void; +} + +export default function AlertModal({ + title, + message, + onConfirm, +}: AlertModalProps) { + const handleWrapperClick = (e: MouseEvent) => { + if (e.target === e.currentTarget) { + onConfirm(); + } + }; + + return ( +
+
+
+

{title}

+

{message}

+
+ +
+
+ ); +} diff --git a/omechu-app/src/app/components/auth/Input.tsx b/omechu-app/src/app/components/auth/Input.tsx index f0288ef11..6a7bec1f8 100644 --- a/omechu-app/src/app/components/auth/Input.tsx +++ b/omechu-app/src/app/components/auth/Input.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef, useState } from "react"; +import React, { forwardRef } from "react"; type InputProps = { label?: string; @@ -8,21 +8,8 @@ type InputProps = { const Input = forwardRef( ({ label, name, type, error, rightAddon, ...props }, ref) => { - const [isPasswordVisible, setIsPasswordVisible] = useState(false); - const isPasswordType = type === "password"; - - const togglePasswordVisibility = () => { - setIsPasswordVisible((prev) => !prev); - }; - - const inputType = isPasswordType - ? isPasswordVisible - ? "text" - : "password" - : type; - const baseStyle = - "w-full h-10 px-4 border rounded-md focus:outline-none bg-white placeholder:text-[13px] placeholder:text-[#939393]"; + "w-full h-10 px-4 border rounded-md focus:outline-none bg-white placeholder:text-[13px] placeholder:text-[#939393] text-black"; const normalStyle = "border-[#494949] focus:border-[#494949]"; const errorStyle = "border-red-500 focus:border-red-500"; const inputClassName = `${baseStyle} ${error ? errorStyle : normalStyle}`; @@ -41,58 +28,13 @@ const Input = forwardRef( -
- {isPasswordType ? ( - - ) : ( - rightAddon - )} +
+ {rightAddon}
{error &&

{error}

} diff --git a/omechu-app/src/app/components/auth/LocationModal.tsx b/omechu-app/src/app/components/auth/LocationModal.tsx deleted file mode 100644 index 4e37ea2a4..000000000 --- a/omechu-app/src/app/components/auth/LocationModal.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from "react"; -import Image from "next/image"; -import Button from "./Button"; - -type LocationModalProps = { - onConfirm: () => void; - onClose: () => void; -}; - -const LocationModal = ({ onConfirm, onClose }: LocationModalProps) => { - const title = "위치기반 서비스 이용약관"; - const content = `1. 서비스 내용: 이용자의 현재 위치를 기반으로 주변 음식점 추천, 길찾기 등 위치기반 서비스를 제공합니다. -2. 위치정보 수집 및 이용: 서비스 제공을 위해 이용자의 위치정보를 수집하며, 이는 이용자의 동의 하에 이루어집니다. -3. 위치정보 보유 기간: 수집된 위치정보는 관련 법령에 따라 안전하게 보관되며, 목적 달성 시 파기됩니다. -4. 권리: 이용자는 언제든지 위치정보 제공에 대한 동의를 철회할 수 있습니다.`; - - return ( -
-
-
-

{title}

- -
-
- {content} -
-
- 위의 내용을 모두 확인했으며 이에 동의합니다. -
- -
-
- ); -}; - -export default LocationModal; diff --git a/omechu-app/src/app/components/auth/LoginPromptModal.tsx b/omechu-app/src/app/components/auth/LoginPromptModal.tsx index e46c03f7f..7e0c0ed51 100644 --- a/omechu-app/src/app/components/auth/LoginPromptModal.tsx +++ b/omechu-app/src/app/components/auth/LoginPromptModal.tsx @@ -26,16 +26,16 @@ const LoginPromptModal = ({ onConfirm, onClose }: LoginPromptModalProps) => {
-

더 쉽고 편한 추천을 원하시나요?

+

더 정교한 추천을 원하시나요?

- 로그인하고 더 다양한 서비스를 이용하세요! + 로그인 후 더 다양한 서비스를 누려보세요!

diff --git a/omechu-app/src/app/components/auth/PrivacyModal.tsx b/omechu-app/src/app/components/auth/PrivacyModal.tsx deleted file mode 100644 index 1860ffd65..000000000 --- a/omechu-app/src/app/components/auth/PrivacyModal.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from "react"; -import Image from "next/image"; -import Button from "./Button"; - -type PrivacyModalProps = { - onConfirm: () => void; - onClose: () => void; -}; - -const PrivacyModal = ({ onConfirm, onClose }: PrivacyModalProps) => { - const title = "개인정보 처리방침"; - const content = `1. 수집하는 개인정보 항목: 이메일, 비밀번호, 서비스 이용 기록, 접속 로그 -2. 개인정보 수집 및 이용 목적: 회원제 서비스 제공, 본인 확인, 불량회원 관리, 서비스 개선 -3. 개인정보 보유 및 이용 기간: 회원 탈퇴 시까지. 단, 관계 법령에 따라 보존할 필요가 있는 경우 해당 기간까지 보관합니다. -4. 개인정보의 제3자 제공: 법령에 근거한 경우를 제외하고, 이용자의 사전 동의 없이 개인정보를 제3자에게 제공하지 않습니다.`; - - return ( -
-
-
-

{title}

- -
-
- {content} -
-
- 위의 내용을 모두 확인했으며 이에 동의합니다. -
- -
-
- ); -}; - -export default PrivacyModal; diff --git a/omechu-app/src/app/components/auth/TermsModal.tsx b/omechu-app/src/app/components/auth/TermsModal.tsx index c2ad748ad..60bf4e96f 100644 --- a/omechu-app/src/app/components/auth/TermsModal.tsx +++ b/omechu-app/src/app/components/auth/TermsModal.tsx @@ -1,30 +1,72 @@ -import React from "react"; +import React, { useRef } from "react"; import Image from "next/image"; import Button from "./Button"; +type Term = { + index: number | null; + about: string | null; + content: string; +}; + type TermsModalProps = { + title: string; + terms: Term[]; onConfirm: () => void; onClose: () => void; }; -const TermsModal = ({ onConfirm, onClose }: TermsModalProps) => { - const title = "서비스 이용약관"; - const content = `1. 목적: 본 약관은 오메추가 제공하는 서비스의 이용과 관련하여 제반 권리, 의무 및 책임사항을 규정함을 목적으로 합니다. -2. 이용자 정의: 이 약관에 동의하고 서비스를 이용하는 자를 '이용자'라 합니다. -3. 서비스 제공: 오메추는 이용자에게 메뉴 추천, 식사 기록, 통계 제공 등 기능을 제공합니다. -4. 이용자의 의무: 타인의 정보를 도용하거나 부정한 목적으로 서비스를 사용해서는 안됩니다. 서비스 내 제공된 콘텐츠를 무단으로 복제, 배포할 수 없습니다.`; +const TermsModal = ({ title, terms, onConfirm, onClose }: TermsModalProps) => { + const contentRef = useRef(null); + + const scrollToTop = () => { + if (contentRef.current) { + contentRef.current.scrollTo({ top: 0, behavior: "smooth" }); + } + }; return (
-
+
-

{title}

+

{title}

-
- {content} +
+ {terms.map((item, key) => ( +
+ {item.index && item.about && ( +
+ 제 {item.index}조 ({item.about}) +
+ )} + {!item.index && !item.about && item.content === "부칙" && ( +
{item.content}
+ )} +
+ {item.content} +
+
+ ))} +
+
+
위의 내용을 모두 확인했으며 이에 동의합니다. diff --git a/omechu-app/src/app/components/onboarding/ProfileStep.tsx b/omechu-app/src/app/components/onboarding/ProfileStep.tsx index a92b58885..dff52b42a 100644 --- a/omechu-app/src/app/components/onboarding/ProfileStep.tsx +++ b/omechu-app/src/app/components/onboarding/ProfileStep.tsx @@ -21,22 +21,32 @@ const ProfileStep = () => {
반가워요!
-

+

프로필을 입력해 주세요

-
-
+
+
- +
diff --git a/omechu-app/src/app/onboarding/[step]/page.tsx b/omechu-app/src/app/onboarding/[step]/page.tsx index 0aa3998b1..47515aa5e 100644 --- a/omechu-app/src/app/onboarding/[step]/page.tsx +++ b/omechu-app/src/app/onboarding/[step]/page.tsx @@ -114,44 +114,32 @@ export default function OnboardingPage() { /> -
+
{renderStepComponent()}
-