Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/assets/css/user/Login.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
110 changes: 110 additions & 0 deletions src/assets/css/user/PasswordResetModal.module.css
Original file line number Diff line number Diff line change
@@ -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;
}
14 changes: 12 additions & 2 deletions src/components/user/LoginForm.jsx
Original file line number Diff line number Diff line change
@@ -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: '',
Expand Down Expand Up @@ -79,6 +79,16 @@ const LoginForm = () => {
>
{isSubmitting ? '로그인 중...' : '로그인'}
</button>

<div className={styles.passwordReset}>
<button
type="button"
onClick={onPasswordReset}
className={styles.resetButton}
>
비밀번호 찾기
</button>
</div>

{error && <div className={styles.errorMessage}>{error}</div>}
</Form>
Expand Down
83 changes: 83 additions & 0 deletions src/components/user/PasswordResetModal.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className={styles.modalBackdrop} onClick={onHide}>
<div className={styles.modal} onClick={e => e.stopPropagation()}>
<div className={styles.modalHeader}>
<h4>비밀번호 찾기</h4>
<button
onClick={onHide}
className={styles.closeButton}
>
&times;
</button>
</div>
<div className={styles.modalBody}>
<p className={styles.description}>
가입하신 이메일 주소를 입력하시면<br />
임시 비밀번호를 발송해 드립니다.
</p>
<form onSubmit={handleSubmit}>
<div className={styles.formGroup}>
<label>이메일</label>
<input
type="email"
placeholder="example@email.com"
value={email}
onChange={(e) => {
setEmail(e.target.value)
setError('')
}}
disabled={isSubmitting}
className={styles.input}
/>
{error && <div className={styles.error}>{error}</div>}
</div>
<button
type="submit"
className={styles.submitButton}
disabled={isSubmitting || !email}
>
{isSubmitting ? '처리중...' : '임시 비밀번호 발급'}
</button>
</form>
</div>
</div>
</div>
)
}

export default PasswordResetModal
1 change: 0 additions & 1 deletion src/pages/recipe/Recipe.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
10 changes: 9 additions & 1 deletion src/pages/user/Login.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Container className={styles.container}>
<h2 className={styles.title}>로그인</h2>
<LoginForm />
<LoginForm onPasswordReset={() => setShowResetModal(true)} />
<PasswordResetModal
show={showResetModal}
onHide={() => setShowResetModal(false)}
/>
<p className={styles.joinLink}>
<Link to="/join">
아직 ReciPick의 회원이 아니신가요?
Expand Down
12 changes: 11 additions & 1 deletion src/sources/api/UserAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
},
}