Skip to content

Commit

Permalink
Merge pull request #177 from boostcampwm-2024/feature/fe/#59-modal-login
Browse files Browse the repository at this point in the history
[FE][Feat] #59 : AuthModal ์ปดํฌ๋„ŒํŠธ
  • Loading branch information
juwon5272 authored Nov 17, 2024
2 parents f455eaa + 6bd55e6 commit 557873c
Show file tree
Hide file tree
Showing 11 changed files with 474 additions and 0 deletions.
139 changes: 139 additions & 0 deletions frontend/src/component/authmodal/AuthModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import React, { useState } from 'react';
import { Modal } from '@/component/common/modal/Modal';

interface IAuthModalProps {
/** ๋ชจ๋‹ฌ์ด ์—ด๋ ค ์žˆ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. */
isOpen: boolean;
/** ๋ชจ๋‹ฌ์„ ๋‹ซ๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. */
onClose: () => void;
/** ๋ชจ๋‹ฌ์˜ ํƒ€์ž…์„ ๊ฒฐ์ •ํ•˜๋Š” ๊ฐ’์œผ๋กœ, 'login' ๋˜๋Š” 'register'๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค. */
type: 'login' | 'register';
}

export const AuthModal = (props: IAuthModalProps) => {
const [loginData, setLoginData] = useState({
id: '',
pw: '',
});

const [registerData, setRegisterData] = useState({
id: '',
email: '',
name: '',
pw: '',
confirmPw: '',
});

const [modalType, setModalType] = useState<'login' | 'register'>(props.type);

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;

if (modalType === 'login') {
setLoginData(prevState => ({
...prevState,
[name]: value,
}));
} else {
setRegisterData(prevState => ({
...prevState,
[name]: value,
}));
}
};

const handleLoginClick = () => {
console.log('๋กœ๊ทธ์ธ ๋ฐ์ดํ„ฐ:', loginData);
};

const handleSignUpClick = () => {
if (registerData.pw !== registerData.confirmPw) {
alert('๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.');
return;
}
console.log('ํšŒ์›๊ฐ€์ž… ๋ฐ์ดํ„ฐ:', registerData);
};

const switchToRegister = () => {
setModalType('register');
};

const switchToLogin = () => {
setModalType('login');
};

return (
<Modal isOpen={props.isOpen} onClose={props.onClose}>
{modalType === 'login' ? (
<>
<Modal.Header content="Log In" onClose={props.onClose} />
<Modal.Input
title="ID"
name="id"
placeholder="ID"
value={loginData.id}
onChange={handleChange}
/>
<Modal.Input
title="PW"
name="pw"
placeholder="PW"
value={loginData.pw}
onChange={handleChange}
/>
<Modal.Footer
text="๋กœ๊ทธ์ธ"
onClick={handleLoginClick}
text2="ํšŒ์›๊ฐ€์ž…"
onClick2={switchToRegister}
/>
</>
) : (
<>
<Modal.Header content="Sign Up" onClose={props.onClose} />
<Modal.Input
title="ID"
name="id"
placeholder="์‚ฌ์šฉํ•  ID๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."
value={registerData.id}
onChange={handleChange}
/>
<Modal.Input
title="Email"
name="email"
placeholder="Email ์ฃผ์†Œ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."
value={registerData.email}
onChange={handleChange}
/>
<Modal.Input
title="Name"
name="name"
placeholder="์ด๋ฆ„์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."
value={registerData.name}
onChange={handleChange}
/>
<Modal.Input
title="PW"
name="pw"
placeholder="์‚ฌ์šฉํ•  ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."
value={registerData.pw}
onChange={handleChange}
/>
<Modal.Input
title=""
name="confirmPw"
placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํ•œ ๋ฒˆ ๋” ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."
value={registerData.confirmPw}
onChange={handleChange}
/>
<Modal.Footer
text="ํšŒ์›๊ฐ€์ž…"
onClick={handleSignUpClick}
text2="๋กœ๊ทธ์ธ"
onClick2={switchToLogin}
/>
</>
)}
</Modal>
);
};
27 changes: 27 additions & 0 deletions frontend/src/component/common/modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import { ModalHeader } from '@/component/common/modal/ModalHeader';
import { ModalInput } from '@/component/common/modal/ModalInput';
import { ModalFooter } from '@/component/common/modal/ModalFooter';

interface IModalProps {
/** ๋ชจ๋‹ฌ์ด ์—ด๋ ค ์žˆ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. */
isOpen: boolean;
/** ๋ชจ๋‹ฌ ๋‚ด์—์„œ ๋ Œ๋”๋งํ•  ์ž์‹ ์š”์†Œ๋“ค์ž…๋‹ˆ๋‹ค. */
children: React.ReactNode;
}

export const Modal = (props: IModalProps) => {
if (!props.isOpen) return null;

return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-20">
<div className="relative w-[22rem] max-w-lg rounded-2xl bg-white px-6 shadow-lg">
{props.children}
</div>
</div>
);
};

Modal.Header = ModalHeader;
Modal.Footer = ModalFooter;
Modal.Input = ModalInput;
29 changes: 29 additions & 0 deletions frontend/src/component/common/modal/ModalFooter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
interface IModalFooterProps {
/** ๋ฉ”์ธ ๋ฒ„ํŠผ์˜ ํ…์ŠคํŠธ์ž…๋‹ˆ๋‹ค. */
text: string;
/** ๋ฉ”์ธ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ํ˜ธ์ถœ๋˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. */
onClick?: () => void;
/** ๋ณด์กฐ ๋ฒ„ํŠผ์˜ ํ…์ŠคํŠธ์ž…๋‹ˆ๋‹ค (์„ ํƒ ์‚ฌํ•ญ) */
text2?: string;
/** ๋ณด์กฐ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ํ˜ธ์ถœ๋˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค (์„ ํƒ ์‚ฌํ•ญ) */
onClick2?: () => void;
}

export const ModalFooter = (props: IModalFooterProps) => (
<div className="flex w-full flex-row-reverse items-center justify-start gap-5 rounded-lg bg-white py-4 shadow-sm">
<button
type="button"
className="bg-blueGray-600 h-[40px] rounded-lg px-4 py-2 text-sm font-semibold text-white hover:bg-blue-800"
onClick={props.onClick}
>
{props.text}
</button>
{props.text2 ? (
<button type="button" className="text-grayscale-400 text-xs" onClick={props.onClick2}>
{props.text2}
</button>
) : (
''
)}
</div>
);
18 changes: 18 additions & 0 deletions frontend/src/component/common/modal/ModalHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { MdClear } from 'react-icons/md';
import { Button } from '../button/Button';

interface IModalHeaderProps {
/** ๋ชจ๋‹ฌ ํ—ค๋”์˜ ์ œ๋ชฉ ํ…์ŠคํŠธ์ž…๋‹ˆ๋‹ค. */
content: string;
/** ๋ชจ๋‹ฌ์„ ๋‹ซ๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. */
onClose: () => void;
}

export const ModalHeader = (props: IModalHeaderProps) => (
<div className="flex flex-row items-start justify-between border-gray-200 py-4">
<h2 className="h-7 text-lg font-bold leading-7">{props.content}</h2>
<Button onClick={props.onClose}>
<MdClear />
</Button>
</div>
);
25 changes: 25 additions & 0 deletions frontend/src/component/common/modal/ModalInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
interface IModalInputProps {
/** ์ž…๋ ฅ ํ•„๋“œ์˜ ์ œ๋ชฉ์ž…๋‹ˆ๋‹ค. */
title: string;
/** ์ž…๋ ฅ ํ•„๋“œ์˜ ์ด๋ฆ„ ์†์„ฑ์ž…๋‹ˆ๋‹ค. */
name: string;
/** ์ž…๋ ฅ ํ•„๋“œ์˜ placeholder ํ…์ŠคํŠธ์ž…๋‹ˆ๋‹ค. */
placeholder: string;
/** ์ž…๋ ฅ ํ•„๋“œ์˜ ํ˜„์žฌ ๊ฐ’์ž…๋‹ˆ๋‹ค. */
value: string;
/** ์ž…๋ ฅ ํ•„๋“œ ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. */
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}

export const ModalInput = (props: IModalInputProps) => (
<div className="flex flex-row items-center justify-between py-2">
<p className="font-medium">{props.title}</p>
<input
className="border-grayscale-75 text-grayscale-50 h-[32px] w-[250px] rounded-md border px-3 focus:outline-none focus:ring-1 focus:ring-blue-500"
name={props.name}
placeholder={props.placeholder}
value={props.value}
onChange={props.onChange}
/>
</div>
);
10 changes: 10 additions & 0 deletions frontend/src/hooks/useModal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useState } from 'react';

export const useModal = () => {
const [isOpen, setIsOpen] = useState<boolean>(false);

const onOpen = () => setIsOpen(true);
const onClose = () => setIsOpen(false);

return { isOpen, onOpen, onClose };
};
59 changes: 59 additions & 0 deletions frontend/src/stories/AuthModal.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React from 'react';
import { Meta, Story } from '@storybook/react';
import { useModal } from '@/hooks/useModal';
import { AuthModal } from '@/component/authmodal/AuthModal';

export default {
title: 'Components/AuthModal',
component: AuthModal,
parameters: {
layout: 'fullscreen',
},
tags: ['autodocs'],
} as Meta<typeof AuthModal>;

const LoginModalTemplate: Story = args => {
const { onClose } = useModal();

return <AuthModal {...args} onClose={onClose} />;
};

const RegisterModalTemplate: Story = args => {
const { onClose } = useModal();

return <AuthModal {...args} onClose={onClose} />;
};

export const LoginModal = LoginModalTemplate.bind({});
LoginModal.args = {
isOpen: true,
type: 'login',
};
LoginModal.parameters = {
docs: {
description: {
story: '๋กœ๊ทธ์ธ ๋ชจ๋‹ฌ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.',
},
},
backgrounds: {
default: 'gray',
values: [{ name: 'gray', value: '#f3f4f6' }],
},
};

export const RegisterModal = RegisterModalTemplate.bind({});
RegisterModal.args = {
isOpen: true,
type: 'register',
};
RegisterModal.parameters = {
docs: {
description: {
story: 'ํšŒ์›๊ฐ€์ž… ๋ชจ๋‹ฌ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.',
},
},
backgrounds: {
default: 'gray',
values: [{ name: 'gray', value: '#f3f4f6' }],
},
};
Loading

0 comments on commit 557873c

Please sign in to comment.