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

refactor: 모달 celuveat UI library 적용 #664

Merged
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
4 changes: 2 additions & 2 deletions frontend/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true
"source.fixAll.eslint": "explicit",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

린트 설정을 사용자가 직접 수정하는 설정으로 하고 있는데 이렇게 바꾼 이유가??

  • 코드의 획일화를 위해 true로 설정하는 것이 낫지 않을까요??

"source.fixAll.stylelint": "explicit"
},
"typescript.validate.enable": true,

Expand Down
70 changes: 70 additions & 0 deletions frontend/src/components/@common/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Modal } from 'celuveat-ui-library';
import React, { ReactNode } from 'react';
import styled from 'styled-components';
import Exit from '~/assets/icons/exit.svg';
import useCeluveatModal from '~/hooks/useCeluveatModal';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요놈은 라이브러리 성격을 띄니까 라이브러리에서 선언을 해도 좋을 것 같아요~


interface Props {
title?: string;
children: ReactNode;
}

function Dialog({ title, children }: Props) {
const { closeModal } = useCeluveatModal();

return (
<>
<Modal.Overlay as={<StyledOverlay />} />
<StyledContent>
<StyledTitle>{title}</StyledTitle>
<StyledExitButton onClick={closeModal} />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

컴파운드 컴포넌트로 모달을 구현을 해주셨는데 컴파운드 컴포넌트를 사용하는 이유는 무엇이라고 생각하나요??

지금 구현하신 모달에서는 거의 모든 기능을 사용자에게 제어권을 위임하는 형태입니다.
이렇게 될 구현을 하실 경우 div 태그를 쓰는 것과 큰 차이가 없다고 판단됩니다.

Modal 라이브러리 사용도 Overlay만 사용되는데 라이브러리까지 만들어서 사용을 한다는 건 너무 오버 엔지니어링 같습니다 ㅜㅜ

각 모달 컴포넌트(Overlay, Content, ExitButton 등)의 요구사항을 잘 파악하고 해당 라이브러리에 각 기능들을 구현, 사용처에서는 해당 기능을 잘 몰라도 사용할 수 있게끔 구현하는 것이 최종 목적이라고 생각이 듭니다.

마지막으로 무엇을 해결하기 위해 컴파운드 컴포넌트를 썼는지 라이브러리를 왜 썼는지 그 이유와 근거에 대해서 더 잘 이해하고 사용해보았으면 더 많은 공부가 될 것 같아요~

게더 ㄱㄱ

{children}
</StyledContent>
</>
);
}

export default Dialog;

const StyledOverlay = styled.div`
position: absolute;
top: 0;

width: 100%;
height: 100%;

background-color: black;

opacity: 0.2;
`;

const StyledContent = styled.div`
position: fixed;
top: 50%;
left: 50vw;

min-width: 540px;
max-width: 66.6%;

padding: 2.4rem;
margin: 0 auto;

border-radius: 12px;
background-color: white;

transform: translateX(-50%) translateY(-50%);
`;

const StyledTitle = styled.h4`
text-align: center;

margin-bottom: 2.4rem;
`;

const StyledExitButton = styled(Exit)`
position: absolute;
top: 12px;
right: 12px;

cursor: pointer;
`;
3 changes: 3 additions & 0 deletions frontend/src/components/@common/Dialog/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Dialog from './Dialog';

export default Dialog;
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ const StyledProfileImageList = styled.div<{ size: string }>`

const StyledProfileImageWrapper = styled.div<{ index: number; hover: boolean; length: number }>`
position: absolute;
z-index: ${({ length, index }) => 10 + length - index};

transition: 0.4s ease-in-out;

Expand Down
119 changes: 119 additions & 0 deletions frontend/src/components/Form/SuggestionForm/SuggestionForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { ChangeEvent, FormEvent, useState } from 'react';
import { useParams } from 'react-router-dom';
import styled from 'styled-components';
import { postRevisedInfo } from '~/api/restaurant';
import TextButton from '~/components/@common/Button';
import Dialog from '~/components/@common/Dialog';
import { BORDER_RADIUS, FONT_SIZE } from '~/styles/common';

const labels = [
'레스토랑이 폐점했어요.',
'레스토랑 이름이 잘못되었어요.',
'레스토랑 주소가 잘못되었어요.',
'레스토랑 전화번호가 잘못되었어요',
];

function SuggestionForm() {
const { id } = useParams();
const [checkedItems, setCheckedItems] = useState<string[]>([]);
const [textareaValue, setTextareaValue] = useState('');

const handleSubmit = (e: FormEvent) => {
e.preventDefault();

const requestData = textareaValue ? [...checkedItems, textareaValue] : checkedItems;
postRevisedInfo({ restaurantId: Number(id), data: { contents: requestData } });
window.location.reload();
};

const clickCheckBox = (e: ChangeEvent<HTMLInputElement>) => {
const { checked, value } = e.target;

setCheckedItems(prev => (checked ? [...prev, value] : prev.filter(item => item !== value)));
};

const onChangeTextarea = (e: ChangeEvent<HTMLTextAreaElement>) => {
setTextareaValue(e.target.value);
};

return (
<Dialog title="정보 수정 제안">
<StyledForm onSubmit={handleSubmit}>
<h5>수정 항목</h5>
<p>잘못되었거나 수정이 필요한 정보들을 모두 선택해주세요.</p>
<StyledUnorderedList>
{labels.map(label => (
<StyledListItem>
<CheckBox value={label} onChange={clickCheckBox} />
<span>{label}</span>
</StyledListItem>
))}
<StyledTextarea placeholder="(선택) 내용을 입력해주세요." onChange={onChangeTextarea} />
</StyledUnorderedList>
<TextButton
type="submit"
text="등록하기"
onClick={handleSubmit}
colorType="light"
disabled={!checkedItems.length && !textareaValue}
/>
</StyledForm>
</Dialog>
);
}

export default SuggestionForm;

const StyledForm = styled.form`
display: flex;
flex-direction: column;
gap: 2rem;

font-size: ${FONT_SIZE.md};

p {
color: var(--gray-3);
}
`;

const StyledUnorderedList = styled.ul`
display: flex;
flex-direction: column;
gap: 2rem;
`;

const StyledListItem = styled.li`
display: flex;
align-items: center;
gap: 0.8rem;
`;

const StyledTextarea = styled.textarea`
height: 128px;

padding: 0.8rem;

border: none;
border-radius: ${BORDER_RADIUS.sm};
background-color: var(--gray-1);
`;

const CheckBox = styled.input.attrs({ type: 'checkbox' })`
width: 24px;
height: 24px;

border: 1px solid #ddd;
border-radius: 50%;
background-image: url('~/assets/icons/unchecked-icon.svg');
background-size: cover;

transition: all 0.2s ease;
appearance: none;

&:checked {
border: 1px solid var(--primary-6);
background-color: var(--primary-6);
background-image: url('~/assets/icons/checked-icon.svg');
background-size: cover;
}
`;
3 changes: 3 additions & 0 deletions frontend/src/components/Form/SuggestionForm/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import SuggestionForm from './SuggestionForm';

export default SuggestionForm;
50 changes: 50 additions & 0 deletions frontend/src/components/FormModal/FormModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import Dialog from '../@common/Dialog';
import RestaurantReviewList from '../RestaurantReviewList/RestaurantReviewList';
import { ReviewDeleteForm, ReviewForm, ReviewReportForm } from '../ReviewForm';

const REVIEW_FORM_TITLE = {
create: '리뷰 작성하기',
update: '리뷰 수정하기',
delete: '리뷰 삭제하기',
report: '리뷰 신고하기',
all: '리뷰 모두 보기',
} as const;

interface FormModalProps {
type: keyof typeof REVIEW_FORM_TITLE;
reviewId?: number;
}

function FormModal({ type, reviewId }: FormModalProps) {
return (
<div>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

&& 를 고치는 방향을 위해서 라이브러리를 만들고 적용하는 것 아니였나용?? ( 그대로 남아있네용 ㅠㅠ )

{type === 'create' && (
<Dialog title={REVIEW_FORM_TITLE[type]}>
<ReviewForm type="create" reviewId={reviewId} />
</Dialog>
)}
{type === 'update' && (
<Dialog title={REVIEW_FORM_TITLE[type]}>
<ReviewForm type="update" reviewId={reviewId} />
</Dialog>
)}
{type === 'delete' && (
<Dialog title={REVIEW_FORM_TITLE[type]}>
<ReviewDeleteForm reviewId={reviewId} />
</Dialog>
)}
{type === 'report' && (
<Dialog title={REVIEW_FORM_TITLE[type]}>
<ReviewReportForm reviewId={reviewId} />
</Dialog>
)}
{type === 'all' && (
<Dialog title={REVIEW_FORM_TITLE[type]}>
<RestaurantReviewList />
</Dialog>
)}
</div>
);
}

export default FormModal;
3 changes: 3 additions & 0 deletions frontend/src/components/FormModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import FormModal from '~/components/FormModal/FormModal';

export default FormModal;
63 changes: 29 additions & 34 deletions frontend/src/components/InfoDropDown/InfoDropDown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import { useNavigate } from 'react-router-dom';
import { styled } from 'styled-components';
import { getProfile } from '~/api/user';
import InfoButton from '~/components/@common/InfoButton';
import LoginModal from '~/components/LoginModal';
import useBooleanState from '~/hooks/useBooleanState';

import { ProfileData } from '~/@types/api.types';
import useCeluveatModal from '~/hooks/useCeluveatModal';

function InfoDropDown() {
const { value: isModalOpen, setTrue: openModal, setFalse: closeModal } = useBooleanState(false);
const { openLoginModal } = useCeluveatModal();
const { data, isSuccess: isLogin } = useQuery<ProfileData>({
queryKey: ['profile'],
queryFn: getProfile,
Expand All @@ -31,38 +30,34 @@ function InfoDropDown() {
};

return (
<>
<DropDown>
<DropDown.Trigger isCustom>
<StyledTriggerWrapper>
<InfoButton profile={data} isSuccess={isLogin} />
</StyledTriggerWrapper>
</DropDown.Trigger>
<DropDown.Options as="ul" isCustom>
<StyledDropDownWrapper>
{isLogin ? (
<>
<DropDown.Option as="li" isCustom externalClick={goMyPage}>
<StyledDropDownOption>마이 페이지</StyledDropDownOption>
</DropDown.Option>
<DropDown.Option as="li" isCustom externalClick={goWishList}>
<StyledDropDownOption>위시리스트</StyledDropDownOption>
</DropDown.Option>
<DropDown.Option as="li" isCustom externalClick={goWithdrawal}>
<StyledDropDownOption>회원 탈퇴</StyledDropDownOption>
</DropDown.Option>
</>
) : (
<DropDown.Option isCustom externalClick={openModal}>
<StyledDropDownOption>로그인</StyledDropDownOption>
<DropDown>
<DropDown.Trigger isCustom>
<StyledTriggerWrapper>
<InfoButton profile={data} isSuccess={isLogin} />
</StyledTriggerWrapper>
</DropDown.Trigger>
<DropDown.Options as="ul" isCustom>
<StyledDropDownWrapper>
{isLogin ? (
<>
<DropDown.Option as="li" isCustom externalClick={goMyPage}>
<StyledDropDownOption>마이 페이지</StyledDropDownOption>
</DropDown.Option>
)}
</StyledDropDownWrapper>
</DropDown.Options>
</DropDown>

<LoginModal close={closeModal} isOpen={isModalOpen} />
</>
<DropDown.Option as="li" isCustom externalClick={goWishList}>
<StyledDropDownOption>위시리스트</StyledDropDownOption>
</DropDown.Option>
<DropDown.Option as="li" isCustom externalClick={goWithdrawal}>
<StyledDropDownOption>회원 탈퇴</StyledDropDownOption>
</DropDown.Option>
</>
) : (
<DropDown.Option isCustom externalClick={openLoginModal}>
<StyledDropDownOption>로그인</StyledDropDownOption>
</DropDown.Option>
)}
</StyledDropDownWrapper>
</DropDown.Options>
</DropDown>
);
}

Expand Down
7 changes: 1 addition & 6 deletions frontend/src/components/LoginModal/LoginModal.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,4 @@ export default meta;

type Story = StoryObj<typeof LoginModal>;

export const Default: Story = {
args: {
isOpen: true,
close: () => {},
},
};
export const Default: Story = {};
13 changes: 4 additions & 9 deletions frontend/src/components/LoginModal/LoginModal.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import { styled } from 'styled-components';
import LoginButton from '~/components/@common/LoginButton';
import Modal from '../@common/Modal';
import Dialog from '../@common/Dialog';

interface LoginModalProps {
isOpen: boolean;
close: VoidFunction;
}

function LoginModal({ isOpen, close }: LoginModalProps) {
function LoginModal() {
return (
<Modal isOpen={isOpen} close={close} title="로그인 및 회원가입">
<Dialog title="로그인 및 회원가입">
<StyledLoginModalContent>
<LoginButton type="kakao" />
<LoginButton type="google" />
</StyledLoginModalContent>
</Modal>
</Dialog>
);
}

Expand Down
Loading
Loading