import React from "react";
import { IoClose } from "react-icons/io5";
import { Link } from "react-router-dom";
import { useDispatch } from "react-redux";
import { googleLogin, kakaoLogin } from "../../../redux/authSlice";
import {
ModalContainer,
Background,
IoCloseDiv,
LoginModalImg,
P,
GoogleBtn,
KakaoBtn,
LoginModalBtn,
} from "./LoginModalStyles";
const LoginModal = ({ closeModal }) => {
const dispatch = useDispatch();
const GoogleLogin = () => {
window.location.href = process.env.REACT_APP_GOOGLE_URL;
dispatch(googleLogin());
};
const KakaoLogin = () => {
window.location.href = process.env.REACT_APP_KAKAO_URL;
dispatch(kakaoLogin());
};
return (
<>
<Background onClick={closeModal} />
<ModalContainer>
<IoCloseDiv onClick={closeModal}>
<IoClose />
</IoCloseDiv>
<LoginModalImg
src="/imgs/Character/giftipie-01.png"
alt="pie"
w="100px"
h="100px"
/>
<P fs="20px" fw="700" mt="20px" color="white">
Giftipie에서
</P>
<P fs="20px" fw="700" mt="4px" color="white">
정말 원하는 선물을
</P>
<P fs="20px" fw="700" mt="4px" mb="20px" color="white">
주고받아요
</P>
<Link to="/signup">
<LoginModalBtn>
가입하기
</LoginModalBtn>
</Link>
<Link to="/login">
<LoginModalBtn>
로그인하기
</LoginModalBtn>
</Link>
<GoogleBtn onClick={GoogleLogin}>
<LoginModalImg
src="/imgs/Login/google.png"
alt="google"
w="65%"
mt="10px"
/>
</GoogleBtn>
<KakaoBtn onClick={KakaoLogin}>
<LoginModalImg
src="/imgs/Login/kakao.png"
alt="kakao"
w="65%"
mt="10px"
/>
</KakaoBtn>
</ModalContainer>
</>
);
};
export default LoginModal;
import styled from "styled-components";
import theme from "../../../styles/theme";
export const ModalContainer = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
max-width: 350px;
height: 550px;
background-color: ${theme.gray1};
position: fixed;
bottom: 50%;
right: 50%;
transform: translate(50%, 50%);
border-radius: 10px;
z-index: 10;
padding-bottom: 30px;
`;
export const Background = styled.div`
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #00000080;
z-index: 9;
`;
export const IoCloseDiv = styled.div`
display: flex;
justify-content: flex-end;
align-items: center;
width: 100%;
max-width: 442px;
font-size: 30px;
color: lightgray;
padding: 8px;
&:hover {
color: darkgray;
cursor: pointer;
}
`;
export const LoginModalImg = styled.img`
width: ${(props) => props.w};
height: ${(props) => props.h};
margin-top: ${(props) => props.mt};
margin-bottom: ${(props) => props.mb};
`;
export const P = styled.p`
padding-top: ${(props) => props.pt};
padding-bottom: ${(props) => props.pb};
padding-left: ${(props) => props.pl};
padding-right: ${(props) => props.pr};
margin-top: ${(props) => props.mt};
margin-bottom: ${(props) => props.mb};
margin-left: ${(props) => props.ml};
margin-right: ${(props) => props.mr};
font-size: ${(props) => props.fs};
font-weight: ${(props) => props.fw};
color: ${(props) => props.color};
display: flex;
align-items: center;
`;
export const GoogleBtn = styled.button``;
export const KakaoBtn = styled.button``;
export const LoginModalBtn = styled.button`
background-color: ${(props) => props.color};
padding: 12px 12px;
font-size: 18px;
font-weight: 500;
color: white;
border-radius: 8px;
width: 220px;
margin: 6px;
border: 1px solid lightgray;
`;
import React, { useState } from "react";
import { FaAngleLeft } from "react-icons/fa6";
import { useNavigate } from "react-router-dom";
import { signup } from "../../../apis/auth";
import {
MainContainer,
LeftContainer,
Logo,
P,
RightContainer,
SignupFieldContainer,
Body,
SignupIconDiv,
SignupImg,
SignupInput,
SignupBtn,
SignupEmailHelpDiv,
SignupNicknameHelpDiv,
SignupPasswordHelpDiv,
SignupPhoneNumberHelpDiv,
BlankLine,
} from "./SignupStyles";
// InputField 컴포넌트
const InputField = ({ onChange, onKeyDown, title, type, placeholder }) => (
<div>
<P fs="20px" pb="10px" color="#FFFFFF">
{title}
</P>
<SignupInput
type={type}
placeholder={`${placeholder}`}
onChange={onChange}
onKeyDown={onKeyDown}
/>
</div>
);
const Signup = () => {
const navigate = useNavigate();
const [email, setEmail] = useState("");
const [nickname, setNickname] = useState("");
const [password, setPassword] = useState("");
const [phoneNumber, setPhoneNumber] = useState("");
const [showEmailHelp, setShowEmailHelp] = useState(false);
const [showNicknameHelp, setShowNicknameHelp] = useState(false);
const [showPasswordHelp, setShowPasswordHelp] = useState(false);
const [showPhoneNumberHelp, setShowPhoneNumberHelp] = useState(false);
const handleBackClick = () => {
navigate("/");
};
// Enter키가 눌렸을 때 로그인 처리
const handleKeyDown = (e) => {
if (e.key === "Enter") {
handleSignupClick();
}
};
// 알파벳 대소문자, 숫자, 특수문자, @기호, 도메인 부분은 2자 이상
const isValidEmailFormat = (email) => {
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
return emailRegex.test(email);
};
// 한 글자 이상
const isValidNicknameFormat = (nickname) => {
return nickname.length > 0;
};
// 알파벳 대소문자, 숫자, 특수문자를 조합하여 8자에서 15자 사이의 비밀번호
const isValidPasswordFormat = (password) => {
const passwordRegex =
/^(?=.*[a-zA-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,15}$/;
return passwordRegex.test(password);
};
// 10자에서 11자 사이의 숫자
const isValidPhoneNumberFormat = (phoneNumber) => {
const phoneNumberRegex = /^[0-9]{10,11}$/;
return phoneNumberRegex.test(phoneNumber);
};
// 이메일이 비어있을 때 help 보여주기
const handleEmailChange = (e) => {
setEmail(e.target.value);
setShowEmailHelp(
e.target.value.trim() === "" || !isValidEmailFormat(e.target.value)
);
};
// 닉네임이 비어있을 때 help 보여주기
const handleNicknameChange = (e) => {
setNickname(e.target.value);
setShowNicknameHelp(
e.target.value.trim() === "" || !isValidNicknameFormat(e.target.value)
);
};
// 비밀번호가 비어있을 때 help 보여주기
const handlePasswordChange = (e) => {
setPassword(e.target.value);
setShowPasswordHelp(
e.target.value.trim() === "" || !isValidPasswordFormat(e.target.value)
);
};
// 휴대폰 번호가 비어있을 때 help 보여주기
const handlePhoneNumberChange = (e) => {
setPhoneNumber(e.target.value);
setShowPhoneNumberHelp(
e.target.value.trim() === "" || !isValidPhoneNumberFormat(e.target.value)
);
};
const handleSignupClick = async () => {
try {
await signup({ email, nickname, password, phoneNumber });
navigate("/login");
} catch (error) {
console.error("가입 오류:", error);
}
};
return (
<MainContainer>
<LeftContainer>
<Logo>Giftipie</Logo>
<P pt="25px" fs="16px" fw="800" pb="5px">
기프티파이에서
</P>
<P fs="16px" fw="800" pb="5px">
정말 원하는 선물을
</P>
<P fs="16px" fw="800">
주고 받아요
</P>
</LeftContainer>
<RightContainer>
<Body>
<SignupIconDiv>
<FaAngleLeft onClick={handleBackClick} />
</SignupIconDiv>
<SignupFieldContainer>
<SignupImg src="/imgs/Character/giftipie-02.png" alt="pie" />
<InputField
value={email}
onChange={handleEmailChange}
onKeyDown={handleKeyDown}
title="이메일"
type="email"
placeholder="Email"
/>
{showEmailHelp && email.trim() === "" && (
<SignupEmailHelpDiv>이메일을 입력해 주세요.</SignupEmailHelpDiv>
)}
{showEmailHelp &&
!isValidEmailFormat(email) &&
email.trim() !== "" && (
<SignupEmailHelpDiv>
유효한 이메일 형식이어야 합니다.
</SignupEmailHelpDiv>
)}
<BlankLine h="30px" />
<InputField
value={nickname}
onChange={handleNicknameChange}
onKeyDown={handleKeyDown}
title="닉네임"
type="string"
placeholder="Nickname"
/>
{showNicknameHelp && nickname.trim() === "" && (
<SignupNicknameHelpDiv>
닉네임을 입력해 주세요.
</SignupNicknameHelpDiv>
)}
<BlankLine h="30px" />
<InputField
value={password}
onChange={handlePasswordChange}
onKeyDown={handleKeyDown}
title="비밀번호"
type="password"
placeholder="Password"
/>
{showPasswordHelp && (
<SignupPasswordHelpDiv>
{password.trim() === ""
? "비밀번호를 입력해 주세요."
: "비밀번호는 8자에서 15자 사이의 알파벳 대소문자, 숫자, 특수문자로 구성되어야 합니다."}
</SignupPasswordHelpDiv>
)}
<BlankLine h="30px" />
<InputField
value={phoneNumber}
onChange={handlePhoneNumberChange}
onKeyDown={handleKeyDown}
title="휴대폰 번호"
type="text"
placeholder="Phone Number"
/>
{showPhoneNumberHelp && (
<SignupPhoneNumberHelpDiv>
{phoneNumber.trim() === ""
? "휴대폰 번호를 입력해 주세요."
: !isValidPhoneNumberFormat(phoneNumber)
? "휴대전화 번호는 10자에서 11자 사이의 숫자로 구성되어야 합니다."
: null}
</SignupPhoneNumberHelpDiv>
)}
</SignupFieldContainer>
<SignupBtn onClick={handleSignupClick}>회원가입하기</SignupBtn>
</Body>
</RightContainer>
</MainContainer>
);
};
export default Signup;
import styled from "styled-components";
import theme from "../../../styles/theme";
// 전체 컨테이너
export const MainContainer = styled.div`
display: flex;
justify-content: center;
max-width: 1200px;
min-height: 100vh;
margin: 0 auto;
flex-wrap: wrap;
`;
// 왼쪽 컨테이너
export const LeftContainer = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 500px;
height: 100vh;
padding: 20px;
/* border: 1px solid lightgray; */
margin-right: 100px;
@media (max-width: 1024px) {
display: none;
}
`;
export const Logo = styled.h1`
font-size: 24px;
font-weight: 800;
`;
export const P = styled.p`
padding-top: ${(props) => props.pt};
padding-bottom: ${(props) => props.pb};
padding-left: ${(props) => props.pl};
padding-right: ${(props) => props.pr};
font-size: ${(props) => props.fs};
font-weight: ${(props) => props.fw};
color: ${(props) => props.color};
display: flex;
align-items: center;
`;
// 오른쪽 컨테이너
export const RightContainer = styled.div`
position: relative;
width: 442px;
height: 100vh;
/* border: 1px solid lightgray; */
overflow-y: scroll;
&::-webkit-scrollbar {
display: none;
}
@media (max-width: 442px) {
width: 100%;
}
`;
// 바디 영역
export const Body = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
max-width: 442px;
height: 100vh;
margin: 0 auto;
`;
export const SignupFieldContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
margin-top: auto;
width: 100%;
max-width: 442px;
`;
export const BlankLine = styled.div`
width: ${(props) => props.w};
height: ${(props) => props.h};
`;
export const SignupIconDiv = styled.div`
display: flex;
justify-content: start;
align-items: center;
font-size: 30px;
color: darkgray;
width: 100%;
max-width: 442px;
padding: 20px;
&:hover {
color: gray;
cursor: pointer;
}
`;
export const SignupImg = styled.img`
width: 150px;
height: 150px;
margin-bottom: 50px;
`;
export const SignupInput = styled.input`
width: 300px;
height: 40px;
font-size: 16px;
padding-left: 10px;
border-radius: 5px;
`;
export const SignupBtn = styled.button`
width: 100%;
max-width: 442px;
height: 50px;
font-size: 20px;
margin-top: auto;
margin-bottom: auto;
background-color: ${theme.primary};
transition: all 300ms ease-in-out;
border-radius: 7px;
&:hover {
background-color: ${theme.secondary};
}
`;
export const SignupEmailHelpDiv = styled.div`
width: 300px;
height: 20px;
font-size: 14px;
margin-top: 10px;
color: #f45757;
`;
export const SignupNicknameHelpDiv = styled.div`
width: 300px;
height: 20px;
font-size: 14px;
margin-top: 10px;
color: #f45757;
`;
export const SignupPasswordHelpDiv = styled.div`
width: 300px;
height: 20px;
font-size: 14px;
margin-top: 10px;
color: #f45757;
`;
export const SignupPhoneNumberHelpDiv = styled.div`
width: 300px;
height: 20px;
font-size: 14px;
margin-top: 10px;
color: #f45757;
`;
import React, { useState } from "react";
import { FaAngleLeft } from "react-icons/fa6";
import { useNavigate } from "react-router-dom";
import InputField from "../../../components/LoginInput";
import { login } from "../../../apis/auth";
import { useDispatch } from "react-redux";
import { userLogin } from "../../../redux/authSlice";
import {
MainContainer,
LeftContainer,
Logo,
P,
RightContainer,
InputFieldContainer,
Body,
LoginIconDiv,
LoginImg,
LoginBtn,
BlankLine,
} from "./LoginStyles";
const Login = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [showEmailHelp, setShowEmailHelp] = useState(false);
const [showPasswordHelp, setShowPasswordHelp] = useState(false);
const handleBackClick = () => navigate("/");
// Enter키가 눌렸을 때 로그인 처리
const handleKeyDown = (e) => {
if (e.key === "Enter") {
handleLoginClick();
}
};
const isValidEmailFormat = (email) => {
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
return emailRegex.test(email);
};
const isValidPasswordFormat = (password) => {
const passwordRegex =
/^(?=.*[a-zA-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,15}$/;
return passwordRegex.test(password);
};
// 이메일이 비어있을 때 help 보여주기
const handleEmailChange = (e) => {
setEmail(e.target.value);
setShowEmailHelp(
e.target.value.trim() === "" || !isValidEmailFormat(e.target.value)
);
};
// 비밀번호가 비어있을 때 help 보여주기
const handlePasswordChange = (e) => {
setPassword(e.target.value);
setShowPasswordHelp(
e.target.value.trim() === "" || !isValidPasswordFormat(e.target.value)
);
};
// 빈 칸인 상태에서 로그인을 했을 때 help 보여주기
const handleLoginClick = async () => {
if (email.trim() === "" || !isValidEmailFormat(email)) {
setShowEmailHelp(true);
return;
}
if (password.trim() === "" || !isValidPasswordFormat(password)) {
setShowPasswordHelp(true);
return;
}
// API 호출을 통한 로그인 처리
try {
await login({ email, password });
dispatch(userLogin()); // 로그인 액션 디스패치
navigate("/");
} catch (error) {
console.error("로그인 에러:", error);
}
};
return (
<MainContainer>
<LeftContainer>
<Logo>Giftipie</Logo>
<P pt="25px" fs="16px" fw="800" pb="5px">
기프티파이에서
</P>
<P fs="16px" fw="800" pb="5px">
정말 원하는 선물을
</P>
<P fs="16px" fw="800">
주고 받아요
</P>
</LeftContainer>
<RightContainer>
<Body>
<LoginIconDiv>
<FaAngleLeft onClick={handleBackClick} />
</LoginIconDiv>
<InputFieldContainer>
<LoginImg src="/imgs/Character/giftipie-02.png" alt="pie" />
<InputField
value={email}
onChange={handleEmailChange}
onKeyDown={handleKeyDown}
title="이메일"
type="email"
placeholder="Email"
showHelp={showEmailHelp}
helpMessage={
showEmailHelp && !isValidEmailFormat(email)
? "올바른 이메일 주소 형식으로 입력해주세요."
: ""
}
/>
<BlankLine h="50px" />
<InputField
value={password}
onChange={handlePasswordChange}
onKeyDown={handleKeyDown}
title="비밀번호"
type="password"
placeholder="Password"
showHelp={showPasswordHelp}
helpMessage={
showPasswordHelp && !isValidPasswordFormat(password)
? "비밀번호는 8자에서 15자 사이이어야 하며, 영문, 숫자, 특수문자(@$!%*?&)를 포함해야 합니다."
: ""
}
/>
</InputFieldContainer>
<LoginBtn onClick={handleLoginClick}>로그인하기</LoginBtn>
</Body>
</RightContainer>
</MainContainer>
);
};
export default Login;
import styled from "styled-components";
import theme from "../../../styles/theme";
// 전체 컨테이너
export const MainContainer = styled.div`
display: flex;
justify-content: center;
max-width: 1200px;
min-height: 100vh;
margin: 0 auto;
flex-wrap: wrap;
`;
// 왼쪽 컨테이너
export const LeftContainer = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 500px;
height: 100vh;
padding: 20px;
/* border: 1px solid lightgray; */
margin-right: 100px;
@media (max-width: 1024px) {
display: none;
}
`;
export const Logo = styled.h1`
font-size: 24px;
font-weight: 800;
`;
export const P = styled.p`
padding-top: ${(props) => props.pt};
padding-bottom: ${(props) => props.pb};
padding-left: ${(props) => props.pl};
padding-right: ${(props) => props.pr};
font-size: ${(props) => props.fs};
font-weight: ${(props) => props.fw};
color: ${(props) => props.color};
display: flex;
align-items: center;
`;
// 오른쪽 컨테이너
export const RightContainer = styled.div`
position: relative;
width: 442px;
height: 100vh;
/* border: 1px solid lightgray; */
overflow-y: scroll;
&::-webkit-scrollbar {
display: none;
}
@media (max-width: 442px) {
width: 100%;
}
`;
// 바디 영역
export const Body = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
max-width: 442px;
height: 100vh;
margin: 0 auto;
`;
export const InputFieldContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
margin-top: auto;
width: 100%;
max-width: 390px;
color: white;
`;
export const BlankLine = styled.div`
width: ${(props) => props.w};
height: ${(props) => props.h};
`;
export const LoginIconDiv = styled.div`
display: flex;
justify-content: start;
align-items: center;
font-size: 30px;
color: darkgray;
width: 100%;
max-width: 442px;
padding: 20px;
&:hover {
color: gray;
cursor: pointer;
}
`;
export const LoginImg = styled.img`
width: 150px;
height: 150px;
margin-bottom: 50px;
`;
export const LoginInput = styled.input`
width: 300px;
height: 40px;
font-size: 16px;
padding-left: 10px;
border-radius: 5px;
`;
export const LoginBtn = styled.button`
width: 100%;
max-width: 442px;
height: 50px;
font-size: 20px;
margin-top: auto;
margin-bottom: auto;
background-color: ${theme.primary};
transition: all 300ms ease-in-out;
border-radius: 7px;
&:hover {
background-color: ${theme.secondary};
}
`;
export const LoginHelpDiv = styled.div`
width: 300px;
height: 20px;
font-size: 14px;
margin-top: 10px;
color: #f45757;
`;
const theme = {
primary: "#FF7C7C",
primaryFont: "#FF5959",
white: "#FFFFFF",
black: "#2C2C2C",
gray1: "#3F3F3F",
gray2: "#5F5F5F",
gray3: "#868686",
gray4: "#B6B6B6",
gray5: "#E4E4E4",
gray6: "#ECECEC",
secondary: "#FFE6C1",
};
export default theme;
/* 예시 */
// background-color: ${theme.primary};
// import theme from "../../styles/theme";