Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature 006 : 회원가입 페이지 & 비밀번호 찾기 페이지 기능 수정, 인터페이스 추가 #79

Merged
merged 4 commits into from
Feb 15, 2024
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
783 changes: 742 additions & 41 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"axios": "^1.6.4",
"lodash": "^4.17.21",
"react": "^18.2.0",
"react-datepicker": "^6.1.0",
"react-dom": "^18.2.0",
"react-modal": "^3.16.1",
"react-router-dom": "^6.21.2",
Expand All @@ -27,6 +28,7 @@
"devDependencies": {
"@types/lodash": "^4.14.202",
"@types/react": "^18.2.48",
"@types/react-datepicker": "^6.0.0",
"@types/react-dom": "^18.2.18",
"@types/react-router-dom": "^5.3.3",
"@types/styled-components": "^5.1.34",
Expand Down
2 changes: 1 addition & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import CategoryPage from '@/pages/CategoryPage';
import FindEmailPage from '@/pages/FindEmailPage';
import FindPasswordPage from '@/pages/FindPasswordPage';
import HomePage from '@/pages/HomePage';
// import GuestPage from './pages/GuestPage';
//import GuestPage from '@/pages/GuestPage';
import ProfilePage from '@/pages/ProfilePage';
import SearchPage from '@/pages/SearchPage';
import SearchResult from './pages/SearchResultPage';
Expand Down
17 changes: 17 additions & 0 deletions src/apis/sms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { APIResponse } from '@/models/config/axios';
import { sendSMSRequest, checkSMSRequest, smsResponse } from '@/models/sms';

import axios from './config/instance';

const PREFIX = '/sms';

export const sendSMSAPI = (data: sendSMSRequest) => {
return axios.post<APIResponse<smsResponse>>(PREFIX + '/sendSMS', data);

};

export const checkSMSAPI = (data : checkSMSRequest, token : string) => {
return axios.post<APIResponse<smsResponse>>(PREFIX + '/checkSMS', data, {
headers : { Authorization : `Bearer ${token}`}
});
}
14 changes: 14 additions & 0 deletions src/assets/icons/calendar.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/imgslider_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/imgslider_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/imgslider_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/imgslider_4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
108 changes: 108 additions & 0 deletions src/components/Calendar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import DatePicker from "react-datepicker";
import 'react-datepicker/dist/react-datepicker.css';
import { ko } from 'date-fns/locale';
import { getMonth, getYear } from "date-fns";
import { useState, forwardRef, useEffect } from 'react';

import * as Styled from '@/styles/signup/SignuppageStyle';
import CalendarSvg from '@/assets/icons/calendar.svg?react';



const years_range = Array.from({length: new Date().getFullYear() - 1900}, (_, i) => new Date().getFullYear() - i);
const months_range = ['1월','2월','3월','4월','5월','6월','7월','8월','9월','10월','11월','12월'];

interface CalendarProps {
setYear : (year : string) => void;
setMonth : (month : string) => void;
setDate : (day : string) => void;
}

const Calendar : React.FC<CalendarProps> = ({setYear, setMonth, setDate}) => {
const [selectedDate, setSelectedDate] = useState<Date | null>(null);

const CustomCalendar = forwardRef(({ onClick } : React.DOMAttributes<HTMLButtonElement> , ref? : React.ForwardedRef<HTMLButtonElement> | undefined) => (
<Styled.CustomButton className={selectedDate === null ? '' : 'custom-inputSelected'} onClick={onClick} ref={ref}>
<CalendarSvg width={36} height={36}/>
</Styled.CustomButton>
));

const onChangeCalendar = (date : Date | null) => {
if(date !== null){
setSelectedDate(date);
setYear(date.getFullYear().toString());
setMonth((date.getMonth() + 1).toString());
setDate(date.getDate().toString())
}
}
useEffect(() => {
if(selectedDate !== null){

}
}, [selectedDate])

return (
<Styled.CalendarContainer>
<DatePicker
dateFormat='yyyy.MM.dd' // 날짜 형태
shouldCloseOnSelect // 날짜를 선택하면 datepicker가 자동으로 닫힘
minDate={new Date('1900-01-01')} // minDate 이전 날짜 선택 불가
maxDate={new Date()} // maxDate 이후 날짜 선택 불가
selected={selectedDate}
locale={ko}
onChange={(date) => onChangeCalendar(date)}
customInput={<CustomCalendar />}
renderCustomHeader={({
date,
changeYear,
changeMonth,
decreaseMonth,
increaseMonth,
prevMonthButtonDisabled,
nextMonthButtonDisabled
}) => (
<div
style={{
margin: 10,
display: "flex",
gap : '5px',
justifyContent: "left"
}}
>

<select value={getYear(date)} onChange={({ target: { value } }) => changeYear(Number(value))}>
{
years_range.map((option) => {
return (
<option key={option}>{option}</option>
)
})
}
</select>

<select
value={months_range[getMonth(date)]}
onChange={({ target: { value } }) =>
changeMonth(months_range.indexOf(value))
}
>
{months_range.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
<button onClick={decreaseMonth} disabled={prevMonthButtonDisabled}>
{"<"}
</button>
<button onClick={increaseMonth} disabled={nextMonthButtonDisabled}>
{">"}
</button>
</div>
)}
/>
</Styled.CalendarContainer>
)
}

export default Calendar;
44 changes: 44 additions & 0 deletions src/components/ImageSlider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import Slider from "@/styles/ImageSlider";
import imgslider_1 from '@/assets/imgslider_1.png'
import imgslider_2 from '@/assets/imgslider_2.png'
import imgslider_3 from '@/assets/imgslider_3.png'
import imgslider_4 from '@/assets/imgslider_4.png'
import { useEffect, useState } from "react";

const images = [imgslider_1, imgslider_2, imgslider_3, imgslider_4];

const ImageSlider = () => {
const [activeIdx, setActiveIdx] = useState(0);

useEffect(() => {
const Timer = setInterval(() => {
setActiveIdx((prev) => prev === 3 ? 0 : prev + 1);
}, 4000);
return () => clearInterval(Timer);
}, []);

return (
<Slider>
{
images.map((item, index) => {
return(
<img className="slide" src={item} key={index}></img>
);
})
}
<div>
<div className="list">
<ul>
{
images.map((_, index) => {
return ( <li key={index} style={{backgroundColor : index === activeIdx ? '#E8E8E8' : ''}}></li> );
})
}
</ul>
</div>
</div>
</Slider>
);
}

export default ImageSlider;
130 changes: 130 additions & 0 deletions src/components/PhoneCheck.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import React, { useState, useEffect } from "react";
import { sendSMSAPI, checkSMSAPI } from '@/apis/sms';
import Container from '@/styles/PhoneCheck';


interface PhoneCheckProps {
setCheck : (value: boolean) => void;
tel : string;
setTel : (value : string) => void;
}

const PhoneCheck : React.FC<PhoneCheckProps> = ({setCheck, tel, setTel}) => {
const [certifyNum, setCertifyNum] = useState('');
const [token, setToken] = useState('');

const [time, setTime] = useState(5 * 60); // 초 단위

const [isCheck, SetIsCheck] = useState(false);
const [isSend, setIsSend] = useState(false);
const [isCertify, setIsCertify] = useState(false);
const [isTel, setIsTel] = useState(false);
const [isTimer, setIsTimer] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);

useEffect(() => {
if (isTimer) {
const intervalId = setInterval(() => {
setTime(prevTime => {
if (prevTime <= 1) {
clearInterval(intervalId);
setIsTimer(false);
return 0;
} else {
return prevTime - 1;
}
});
}, 1000);

return () => clearInterval(intervalId);
}
}, [isTimer]);

const onChangeCertifyInput = (e : React.ChangeEvent<HTMLInputElement>) => {
const certifyRegex = /^\d{7}$/;
setCertifyNum(e.target.value);
if(certifyRegex.test(e.target.value)){
setIsCertify(true);
} else {
setIsCertify(false);
}
}
const onChangeTel = (e: React.ChangeEvent<HTMLInputElement>) => {
const telRegex = /^01(?:0|1|[6-9])(?:\d{3}|\d{4})\d{4}$/;
const telCurrent = e.target.value;
setTel(telCurrent);

if (!telRegex.test(telCurrent)) {
setIsTel(false);
} else {
setIsTel(true);
}
}

const handleCheckCertify = async () => {
setIsTimer(false);
SetIsCheck(true);
const response = (await checkSMSAPI({
verification_code : Number(certifyNum),
}, token))
if(response.data.success){
setIsSuccess(true);
setCheck(true);
} else {
setIsSuccess(false);
}
}

const handleCertifyNum = async () => {
setIsSend(true)
setIsTimer(true);
if(isSend){
SetIsCheck(false);
setTime(10);
}
const response = (await sendSMSAPI({
phone_number : tel
}))

if(response.data.success){
setToken(response.data.result.token);
}
}

const minutes = Math.floor(time / 60); // 분
const seconds = time % 60;

return(
<Container>
<div className="label">전화번호</div>
<div className="inputwrap">
<input
type="text"
id="tel"
name="tel"
value={tel}
placeholder="휴대폰 번호 입력 (-제외)"
onChange={onChangeTel}
style={{width : '326px'}}
readOnly={isSuccess}
/>
<button onClick = {handleCertifyNum} disabled = {!isTel || isSuccess}>{isSend? '인증번호 재전송':'인증번호 전송'}</button>
</div>
{isSend ? <div className="inputwrap">
<input
type='text'
id='certify'
value={certifyNum}
placeholder='인증번호 입력'
onChange={(e) => onChangeCertifyInput(e)}
style={{width : '326px'}}
readOnly={isSuccess}/>
<button onClick = {handleCheckCertify} disabled = {time <= 0 || !isCertify || isSuccess}>인증번호 확인</button>
</div>: ''}
{isSend && (isCheck === false) && <span className="msg">인증번호가 발송되었어요 (유효시간 {minutes}:{seconds})</span>}
{isCheck === true && <span className="msg" style={{color : isSuccess ? '#3681FE' : '#FF3A4A'}}>{isSuccess ? '인증되었습니다' : '인증번호를 다시 확인해주세요'}</span>}
</Container>
);
}

export default PhoneCheck;
13 changes: 13 additions & 0 deletions src/models/sms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export interface sendSMSRequest {
phone_number: string;
}

export interface checkSMSRequest {
verification_code : number;
}

export interface smsResponse {
success : boolean;
message : string;
token : string;
}
2 changes: 1 addition & 1 deletion src/models/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface JoinRequest {
password: string;
check_password: string;
birth_date: string;
gender: string;
gender: string | null;
phone_number: string;
}

Expand Down
Loading
Loading