diff --git a/src/assets/css/user/Login.module.css b/src/assets/css/user/Login.module.css index a99b8eb..ad37196 100644 --- a/src/assets/css/user/Login.module.css +++ b/src/assets/css/user/Login.module.css @@ -32,6 +32,24 @@ padding: 0 16px; } +.passwordReset { + text-align: right; + margin-top: 5px; +} + +.resetButton { + background: none; + border: none; + color: #666; + font-size: 15px; + padding: 0; + cursor: pointer; +} + +.resetButton:hover { + text-decoration: underline; +} + .input:focus { border-color: #3FA2F6; background: #fff; diff --git a/src/assets/css/user/PasswordResetModal.module.css b/src/assets/css/user/PasswordResetModal.module.css new file mode 100644 index 0000000..351ea9a --- /dev/null +++ b/src/assets/css/user/PasswordResetModal.module.css @@ -0,0 +1,110 @@ +.modalBackdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; + z-index: 1050; +} + +.modal { + background: white; + border-radius: 8px; + width: 450px; +} + +.modalHeader { + padding: 16px 20px; + border-bottom: 1px solid #eee; + display: flex; + justify-content: space-between; + align-items: center; +} + +.modalHeader h4 { + font-size: 20px; + font-weight: 600; +} + +.closeButton { + background: none; + border: none; + font-size: 24px; + cursor: pointer; + color: #666; + padding: 0; +} + +.closeButton:hover { + color: #333; +} + +.modalBody { + padding: 20px; +} + +.description { + text-align: center; + color: #666; + font-size: 16px; + margin-bottom: 20px; + line-height: 1.6; +} + +.formGroup { + margin-bottom: 16px; +} + +.formGroup label { + display: block; + font-size: 15px; + margin-bottom: 8px; +} + +.input { + width: 100%; + height: 48px; + padding: 0 16px; + border: 1px solid #DDE2E5; + border-radius: 5px; + background: #F8F9FA; + font-size: 14px; + box-sizing: border-box; +} + +.input:focus { + outline: none; + border-color: #3FA2F6; + background: #fff; +} + +.error { + color: #FF3B3B; + font-size: 15px; + margin-top: 8px; +} + +.submitButton { + width: 100%; + height: 48px; + background: #3FA2F6; + border: none; + border-radius: 5px; + color: white; + font-size: 16px; + margin-top: 16px; + cursor: pointer; +} + +.submitButton:hover { + background: #3691E0; +} + +.submitButton:disabled { + background: #A5D3FB; + cursor: not-allowed; +} \ No newline at end of file diff --git a/src/components/user/LoginForm.jsx b/src/components/user/LoginForm.jsx index c831eb5..018b08e 100644 --- a/src/components/user/LoginForm.jsx +++ b/src/components/user/LoginForm.jsx @@ -1,10 +1,10 @@ import { useState } from 'react' -import { Form, Button } from 'react-bootstrap' +import { Form } from 'react-bootstrap' import { userApi } from '../../sources/api/UserAPI' import { useNavigate } from 'react-router-dom' import styles from '../../assets/css/user/Login.module.css' -const LoginForm = () => { +const LoginForm = ({ onPasswordReset }) => { const navigate = useNavigate() const [formData, setFormData] = useState({ userId: '', @@ -79,6 +79,16 @@ const LoginForm = () => { > {isSubmitting ? '로그인 중...' : '로그인'} + +
+ +
{error &&
{error}
} diff --git a/src/components/user/PasswordResetModal.jsx b/src/components/user/PasswordResetModal.jsx new file mode 100644 index 0000000..47aabea --- /dev/null +++ b/src/components/user/PasswordResetModal.jsx @@ -0,0 +1,83 @@ +import { useState, useEffect } from "react" +import { userApi } from "../../sources/api/UserAPI" +import styles from '../../assets/css/user/PasswordResetModal.module.css' + +const PasswordResetModal = ({show, onHide}) => { + const [email, setEmail] = useState('') + const [error, setError] = useState('') + const [isSubmitting, setIsSubmitting] = useState(false) + + useEffect(() => { + if (!show) { + setEmail('') + setError('') + setIsSubmitting(false) + } + }, [show]) + + const handleSubmit = async (e) => { + e.preventDefault() + setIsSubmitting(true) + setError('') + + try { + await userApi.resetPassword({ userEmail: email }) + alert('입력하신 이메일로 임시 비밀번호가 발송되었습니다.') + onHide() + } catch (error) { + setError('비밀번호 재발급 중 오류가 발생했습니다.') + } finally { + setIsSubmitting(false) + } + } + + if (!show) return null + + return ( +
+
e.stopPropagation()}> +
+

비밀번호 찾기

+ +
+
+

+ 가입하신 이메일 주소를 입력하시면
+ 임시 비밀번호를 발송해 드립니다. +

+
+
+ + { + setEmail(e.target.value) + setError('') + }} + disabled={isSubmitting} + className={styles.input} + /> + {error &&
{error}
} +
+ +
+
+
+
+ ) +} + +export default PasswordResetModal \ No newline at end of file diff --git a/src/pages/recipe/Recipe.jsx b/src/pages/recipe/Recipe.jsx index b3298b6..96b9f3a 100644 --- a/src/pages/recipe/Recipe.jsx +++ b/src/pages/recipe/Recipe.jsx @@ -2,7 +2,6 @@ import { useEffect, useState,useCallback } from "react" import { getRecipeList } from "../../sources/api/recipeAPI.jsx"; import {Link, Route, useNavigate} from "react-router-dom"; import { AddRecipe} from "./addRecipe.jsx"; -import {Route, useNavigate} from "react-router-dom"; import RecipeItem from "../../components/recipe/RecipeItem.jsx" import "../../assets/css/recipe/recipe.css" diff --git a/src/pages/user/Login.jsx b/src/pages/user/Login.jsx index 09cd76c..e0f7ea7 100644 --- a/src/pages/user/Login.jsx +++ b/src/pages/user/Login.jsx @@ -2,12 +2,20 @@ import LoginForm from "../../components/user/LoginForm" import { Container } from "react-bootstrap" import { Link } from "react-router-dom" import styles from "../../assets/css/user/Login.module.css" +import { useState } from "react" +import PasswordResetModal from "../../components/user/PasswordResetModal" const Login = () => { + const [showResetModal, setShowResetModal] = useState(false) + return (

로그인

- + setShowResetModal(true)} /> + setShowResetModal(false)} + />

아직 ReciPick의 회원이 아니신가요? diff --git a/src/sources/api/UserAPI.js b/src/sources/api/UserAPI.js index 4225d5a..f141e66 100644 --- a/src/sources/api/UserAPI.js +++ b/src/sources/api/UserAPI.js @@ -30,5 +30,15 @@ export const userApi = { } catch (error) { throw error } - } + }, + + // 비밀번호 재발급 + resetPassword: async (data) => { + try { + const response = await axios.post(`${BASE_URL}/password/reset`, data) + return response.data + } catch (error) { + throw error + } + }, } \ No newline at end of file