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

Feature: 리스트 생성 Ver3.0 #273

Merged
merged 36 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
4bcef1e
Refactor: ver3.0 리스트생성 폴더 생성 및 공용헤더 ver3.0 디자인 적용
seoyoung-min Sep 18, 2024
0c91ca5
Feat: 리스트생성 1단계 페이지 생성
seoyoung-min Oct 3, 2024
d8866ea
design: 리스트 생성 step1 디자인 입히기
seoyoung-min Oct 3, 2024
0762da4
Feat: step1 글자수세기 및 조건 검사 기능 추가
seoyoung-min Oct 25, 2024
07e8f00
refactor: 카테고리 유효성 검사 추가 및 오류 시 비활성 버튼 css 수정
seoyoung-min Oct 25, 2024
ce04ed2
design: 리스트 소개 textarea 스크롤바 없애기
seoyoung-min Oct 25, 2024
62ec505
refactor: 불필요 코드 삭제 및 todo 추가
seoyoung-min Oct 27, 2024
5cf192e
refactor: step 단계별 이동 리팩토링
seoyoung-min Oct 27, 2024
2065d1f
Chore: 아이콘 파일 추가
seoyoung-min Oct 27, 2024
f592860
Feat: 리스트생성 step2 아코디언, dnd 구현
seoyoung-min Oct 27, 2024
4c2d450
Design: 아이템 리스트 rank 사이즈 스타일 변경
seoyoung-min Oct 30, 2024
51de8b4
Feature: step3 헤더 삽입
seoyoung-min Oct 30, 2024
1205907
Fix: 중복 theme color 제거
seoyoung-min Oct 30, 2024
756eafd
Feat: step3 태그 기능 전체 구현
seoyoung-min Oct 30, 2024
482abf5
Feat: step3 배경색상 및 공개여부 기능 구현
seoyoung-min Nov 3, 2024
3a9978b
Feat: step2 이미지 업로드 기능 추가
seoyoung-min Nov 3, 2024
a985d2b
Feat: step2 링크 첨부 기능 추가
seoyoung-min Nov 10, 2024
2f8fc72
refactor: 라벨(태그) 에러처리에 개별 리액트훅폼 적용
seoyoung-min Nov 10, 2024
5413264
Fix: Step3 라벨 에러 재입력시 사라지도록 수정
seoyoung-min Nov 10, 2024
4162326
Fix: 헤더 컴포넌트 이전 코드로 돌아간 것 다시 복구
seoyoung-min Nov 10, 2024
13c7bbc
Refactor: 코드리뷰 반영(isComposing, chip이름 구체화, 카테고리 staleTime추가)
seoyoung-min Nov 10, 2024
4ca2c9d
Fix: 타입오류 해결
seoyoung-min Nov 10, 2024
76f25c2
Refactor: 리스트 수정 페이지 ver3.0 적용
seoyoung-min Nov 10, 2024
c51998d
Refactor: 이전 리스트 수정 페이지 삭제
seoyoung-min Nov 10, 2024
1143f19
Refactor: 이전 리스트 생성 페이지 삭제
seoyoung-min Nov 10, 2024
277ffd0
Fix: 색상명칭 변경 미적용건 수정
seoyoung-min Nov 10, 2024
8ce5d2a
Refactor: StepOne 불필요 코드 삭제, 타이틀 길이 및 카테고리 선택 스타일링 오류 개선(코드리뷰반영)
seoyoung-min Nov 17, 2024
7cd2729
Refactor: 아코디언 확장/축소 아이콘 범위 확대(코드리뷰반영)
seoyoung-min Nov 17, 2024
dd75857
Feature: 아이템 최소 갯수 3개가 안될 경우 다음으로 이동 불가 및 안내 메시지 노출
seoyoung-min Nov 18, 2024
cf581f2
Refactor: 드래그앤드롭 라이브러리 교체
seoyoung-min Nov 18, 2024
83a276e
Refactor: 태그 스페이스 등록 오류 해결
seoyoung-min Nov 18, 2024
f018cbb
Refactor: 태그 입력 방법에 콤마 추가
seoyoung-min Nov 18, 2024
18037b7
Feature: 선택배경색상 프리뷰 기능 추가
seoyoung-min Nov 18, 2024
0c103ec
Fix: 리액트 타입 패키지 재설치로 타입에러 해결
seoyoung-min Nov 18, 2024
6a1b58b
Refactor: react-beautiful-dnd 라이브러리 제거
seoyoung-min Nov 18, 2024
b24870f
Fix: 리스트 수정 타이틀 검사 타이밍 조정
seoyoung-min Nov 18, 2024
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
2 changes: 1 addition & 1 deletion public/icons/add.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/icons/check_white.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion public/icons/close_button.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/icons/collapse.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion public/icons/dnd.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/icons/expand.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions public/icons/image.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion public/icons/link.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ListDetailType } from '@/lib/types/listType';
import Header from '@/app/list/[listId]/_components/ListDetailInner/Header';
import RankList from '@/app/list/[listId]/_components/ListDetailInner/RankList';
import Footer from '@/app/list/[listId]/_components/ListDetailInner/Footer';
import { BACKGROUND_COLOR_PALETTE_TYPE, BACKGROUND_COLOR_READ } from '@/styles/Color';
import { BACKGROUND_COLOR_READ } from '@/styles/Color';

interface OptionsProps {
value: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import ClearBlackIcon from '/public/icons/clear_x_black.svg';
import * as styles from './Preview.css';
import fileToBase64 from '@/lib/utils/fileToBase64';
import { useLanguage } from '@/store/useLanguage';
import { itemLocale } from '@/app/list/create/locale';
import { listLocale } from '@/app/list/create/locale';

type ImagePreviewProps = {
handleClearButtonClick: () => void;
Expand Down Expand Up @@ -39,12 +39,12 @@ export default function ImagePreview({ handleClearButtonClick, image }: ImagePre
<Image
className={styles.previewImage}
src={preview || '/icons/attach_image.svg'}
alt={itemLocale[language].imageAlt}
alt={listLocale[language].imageAlt}
fill
/>
)}
<button className={styles.clearButton} onClick={handleClearClick}>
<ClearBlackIcon alt={itemLocale[language].deleteLinkAlt} />
<ClearBlackIcon alt={listLocale[language].deleteLinkAlt} />
</button>
</div>
);
Expand Down
99 changes: 99 additions & 0 deletions src/app/list/_create/locale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
export const itemLocale = {
ko: {
addItem: '아이템 추가',
backIconAlt: '뒤로가기 버튼',
createList: '리스트 생성',
editList: '리스트 수정',
complete: '완료',
imageAlt: '첨부 이미지',
deleteLinkAlt: '링크 삭제 버튼',
dragAndDrop: '드래그앤드롭',
deleteItem: '아이템 삭제',
rank: '위',
addLink: '링크 추가',
confirm: '확인',
itemCreateMessage1: '최소 3개, 최대 10개까지 아이템을 추가할 수 있어요.',
itemCreateMessage2: '아이템의 순서대로 순위가 정해져요.',
},
en: {
addItem: 'Add Item',
backIconAlt: 'Back button',
createList: 'Create list',
editList: 'Edit list',
complete: 'Complete',
imageAlt: 'Attached image',
deleteLinkAlt: 'Delete link button',
dragAndDrop: 'Drag and drop',
deleteItem: 'Delete item',
rank: 'No.',
addLink: 'Add link',
confirm: 'Confirm',
itemCreateMessage1: 'You can add a minimum of 3 and a maximum of 10 items.',
itemCreateMessage2: 'The ranking is determined in the order of the items.',
},
};

export const listLocale = {
ko: {
closeButtonAlt: '닫기버튼',
createList: '리스트 생성',
editList: '리스트 수정',
next: '다음',
noData: '검색결과가 없어요.',
public: '공개',
private: '비공개',
title: '타이틀',
description: '소개',
category: '카테고리',
label: '라벨',
addCollaborator: '콜라보레이터 추가',
addCollaboratorError: '콜라보레이터는 최대 20명까지 지정할 수 있어요.',
backgroundcolor: '배경 색상',
publicSetting: '공개 설정',
publicMessage: '모든 사람이 이 리스트를 볼 수 있어요.',
privateMessage: '이 리스트는 나만 볼 수 있어요.',
},
en: {
closeButtonAlt: 'Close button',
createList: 'Create list',
editList: 'Edit list',
next: 'Next',
noData: 'There are no search results.',
public: 'Public',
private: 'Private',
title: 'Title',
description: 'Introduction',
category: 'Category',
label: 'Label',
addCollaborator: 'Add collaborator',
addCollaboratorError: 'You can designate up to 20 collaborators.',
backgroundcolor: 'Background color',
publicSetting: 'Public Settings',
publicMessage: 'Everyone can see this list.',
privateMessage: 'Only I can see this list.',
},
};

type PaletteLocale = {
ko: {
[key: string]: string;
};
en: {
[key: string]: string;
};
};

export const paletteLocale: PaletteLocale = {
ko: {
PASTEL: '파스텔',
VIVID: '비비드',
GRAY: '그레이',
LISTY: '리스티',
},
en: {
PASTEL: 'Pastel',
VIVID: 'Vivid',
GRAY: 'Gray',
LISTY: 'Listy',
},
};
187 changes: 187 additions & 0 deletions src/app/list/_create/page.tsx
Copy link
Contributor

Choose a reason for hiding this comment

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

서영님 이 파일은 ver2.0 파일이 맞는 것이죠?!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

네 맞습니다!! 정상 동작 체크한 후 추후에 삭제하겠습니다~!! :)

Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
'use client';

import { useState } from 'react';
import { FieldErrors, FormProvider, useForm } from 'react-hook-form';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/navigation';

import CreateItem from '@/app/list/create/_components/CreateItem';
import CreateList from '@/app/list/create/_components/CreateList';
import { ItemImagesType, ListCreateType } from '@/lib/types/listType';
import toasting from '@/lib/utils/toasting';
import toastMessage from '@/lib/constants/toastMessage';
import { QUERY_KEYS } from '@/lib/constants/queryKeys';
import createList from '@/app/_api/list/createList';
import uploadItemImages from '@/app/_api/list/uploadItemImages';
import { useLanguage } from '@/store/useLanguage';
import { useUser } from '@/store/useUser';

export type FormErrors = FieldErrors<ListCreateType>;

export default function CreatePage() {
const { language } = useLanguage();
const { user: userMeData } = useUser();
const queryClient = useQueryClient();
const [step, setStep] = useState<'list' | 'item'>('list');
const router = useRouter();

const methods = useForm<ListCreateType>({
mode: 'onChange',
defaultValues: {
category: 'culture',
labels: [],
collaboratorIds: [],
title: '',
description: '',
isPublic: true,
backgroundPalette: 'PASTEL',
backgroundColor: 'PASTEL_PINK',
items: [
{
rank: 0,
title: '',
comment: '',
link: '',
imageUrl: '',
},
{
rank: 0,
title: '',
comment: '',
link: '',
imageUrl: '',
},
{
rank: 0,
title: '',
comment: '',
link: '',
imageUrl: '',
},
],
},
});

const handleStepChange = (step: 'list' | 'item') => {
setStep(step);
};

//request용 데이터 만드는 함수.
const formatData = () => {
const originData = methods.getValues();

//rank 정리
originData.items.forEach((item, index) => {
item.rank = index + 1;
});

//데이터 쪼개기
const listData: ListCreateType = {
...originData,
items: originData.items.map(({ imageUrl, ...rest }) => {
return {
...rest,
imageUrl: '',
};
}),
};

const imageData: ItemImagesType = {
listId: 0, //temp
extensionRanks: originData.items
.filter(({ imageUrl }) => imageUrl !== '')
.map(({ rank, imageUrl }) => {
return {
rank: rank,
extension:
typeof imageUrl === 'object' ? (imageUrl?.[0]?.type.split('/')[1] as 'jpg' | 'jpeg' | 'png') : '',
};
}),
};

const imageFileList: File[] = originData.items
.filter(({ imageUrl }) => imageUrl !== '')
.map(({ imageUrl }) => imageUrl?.[0] as File);

return { listData, imageData, imageFileList };
};

const { mutate: uploadImageMutate, isPending: isUploadingImage } = useMutation({
mutationFn: uploadItemImages,
retry: 3,
retryDelay: 1000,
onError: () => {
toasting({ type: 'error', txt: toastMessage[language].uploadImageError });
},
});

const {
mutate: createListMutate,
isPending: isCreatingList,
isSuccess,
} = useMutation({
mutationFn: createList,
onSuccess: (data) => {
if (formatData().imageData.extensionRanks.length !== 0) {
uploadImageMutate({
listId: data.listId,
imageData: formatData().imageData,
imageFileList: formatData().imageFileList,
});
}
queryClient.invalidateQueries({
queryKey: [
QUERY_KEYS.getAllList,
userMeData.id + '',
formatData().listData.collaboratorIds.length === 0 ? 'my' : 'collabo',
],
});
router.replace(`/list/${data.listId}`);
},
onError: () => {
toasting({ type: 'error', txt: toastMessage[language].createListError });
},
});

//아이템 중복 확인
const getIsAllUnique = () => {
const allTitles = methods.getValues().items.map((item, itemIndex) => {
return item.title === '' ? itemIndex : item.title;
});
const isAllUnique = new Set(allTitles).size === allTitles.length;
return isAllUnique;
};

const handleSubmit = () => {
if (getIsAllUnique()) {
const { listData } = formatData();
createListMutate(listData);
} else {
toasting({ type: 'error', txt: toastMessage[language].duplicatedItemError });
}
};

return (
<>
<FormProvider {...methods}>
{step === 'list' ? (
<CreateList
onNextClick={() => {
handleStepChange('item');
}}
type="create"
/>
) : (
<CreateItem
onBackClick={() => {
handleStepChange('list');
}}
onSubmitClick={handleSubmit}
isSubmitting={isUploadingImage || isCreatingList || isSuccess}
type="create"
/>
)}
</FormProvider>
</>
);
}
Loading
Loading