Skip to content

Commit

Permalink
[FE] : frontend 브렌치와 머지
Browse files Browse the repository at this point in the history
  • Loading branch information
effozen committed Nov 17, 2024
2 parents 66a14e8 + 557873c commit 2361010
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 2361010

Please sign in to comment.