Skip to content

Commit

Permalink
refactor: 모달 celuveat UI library 적용 (#664)
Browse files Browse the repository at this point in the history
* feat: 모달 라이브러리 적용 (jeremy-reusable-modal)

* style: isLiked시 하트 색깔 변경 로직 줄 축수

* refactor: jeremy-reusable-modal에서 celuveat-ui-library로 마이그레이션
  • Loading branch information
shackstack authored Dec 29, 2023
1 parent 12ef742 commit fbcc768
Show file tree
Hide file tree
Showing 30 changed files with 1,649 additions and 3,108 deletions.
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",
"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';

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} />
{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>
{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

0 comments on commit fbcc768

Please sign in to comment.