From 64690781508599bc711ea977e32af0375e68d31b Mon Sep 17 00:00:00 2001 From: Seoyoung Date: Sat, 3 Feb 2024 19:44:21 +0900 Subject: [PATCH 01/15] =?UTF-8?q?Chore:=20=EB=B6=88=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=B0=8F=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C,=20=ED=83=80=EC=9E=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/create/_components/CreateList.css.ts | 3 --- src/app/create/_components/Items.tsx | 6 +++--- src/app/create/page.tsx | 16 ++++++++-------- src/lib/utils/init.ts | 2 -- tsconfig.json | 2 +- yarn.lock | 5 +++++ 6 files changed, 17 insertions(+), 17 deletions(-) delete mode 100644 src/lib/utils/init.ts diff --git a/src/app/create/_components/CreateList.css.ts b/src/app/create/_components/CreateList.css.ts index 98ef3122..0ac4b84b 100644 --- a/src/app/create/_components/CreateList.css.ts +++ b/src/app/create/_components/CreateList.css.ts @@ -1,7 +1,4 @@ import { style } from '@vanilla-extract/css'; -import * as GlobalStyles from '@/styles/globalStyles.css'; - -GlobalStyles; export const header = style({ width: '100%', diff --git a/src/app/create/_components/Items.tsx b/src/app/create/_components/Items.tsx index 4792155a..a6b70c32 100644 --- a/src/app/create/_components/Items.tsx +++ b/src/app/create/_components/Items.tsx @@ -45,17 +45,17 @@ export default function Items() { rules: { minLength: 3, maxLength: 10 }, }); - const [current, setCurrent] = useState(null); + const [currentLink, setCurrentLink] = useState(''); const watchItems = useWatch({ control, name: 'items' }); //--- LinkModal 핸들러 const handleLinkModalOpen = (index: number) => { - setCurrent(getValues().items[index]?.link); + setCurrentLink(getValues().items[index]?.link); }; const handleLinkModalCancel = (index: number) => { - setValue(`items.${index}.link`, current); + setValue(`items.${index}.link`, currentLink); }; const handleLinkModalConfirm = (index: number) => { diff --git a/src/app/create/page.tsx b/src/app/create/page.tsx index a7271021..6ea60700 100644 --- a/src/app/create/page.tsx +++ b/src/app/create/page.tsx @@ -8,8 +8,8 @@ import { useState } from 'react'; interface Item { rank: number; title: string; - comment: string | null; - link: string | null; + comment: string; + link: string; } interface FormValues { @@ -48,20 +48,20 @@ export default function CreatePage() { { rank: 0, title: '', - comment: null, - link: null, + comment: '', + link: '', }, { rank: 0, title: '', - comment: null, - link: null, + comment: '', + link: '', }, { rank: 0, title: '', - comment: null, - link: null, + comment: '', + link: '', }, ], }, diff --git a/src/lib/utils/init.ts b/src/lib/utils/init.ts deleted file mode 100644 index e099ee9f..00000000 --- a/src/lib/utils/init.ts +++ /dev/null @@ -1,2 +0,0 @@ -// 보일러플레이트용 임시 파일 -// 추후 이 파일은 지워주세요 diff --git a/tsconfig.json b/tsconfig.json index 97442403..05d96dd7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,7 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "preserve", + "jsx": "react-jsx", "incremental": true, "plugins": [ { diff --git a/yarn.lock b/yarn.lock index 7f5550b2..e58613a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3682,6 +3682,11 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +"@yaireo/tagify@^4.19.0": + version "4.19.1" + resolved "https://registry.yarnpkg.com/@yaireo/tagify/-/tagify-4.19.1.tgz#7eb0b23fc16e0cf17fa1fa217599969e854b8959" + integrity sha512-H+4yaPsjWrJXmRhnnGs5TQ1kp0SZtPehnsNtA7iv8TgKrFrX8EYEEgdC+vAC0PunAbmycUSd2Nf5guHpi4xCCg== + JSONStream@^1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" From 446a4311e8b20d3bad008d861f9bb7d8316eafed Mon Sep 17 00:00:00 2001 From: Seoyoung Date: Sun, 4 Feb 2024 20:47:18 +0900 Subject: [PATCH 02/15] =?UTF-8?q?Refactor:=20=ED=94=84=EB=A6=AC=EB=B7=B0?= =?UTF-8?q?=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20image,=20link=20?= =?UTF-8?q?=EC=9A=A9=EB=8F=84=EB=B3=84=20=ED=83=80=EC=9E=85=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=8F=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/create/_components/LinkPreview.tsx | 39 ----------- .../{LinkPreview.css.ts => Preview.css.ts} | 11 ++++ src/app/create/_components/Preview.tsx | 66 +++++++++++++++++++ 3 files changed, 77 insertions(+), 39 deletions(-) delete mode 100644 src/app/create/_components/LinkPreview.tsx rename src/app/create/_components/{LinkPreview.css.ts => Preview.css.ts} (73%) create mode 100644 src/app/create/_components/Preview.tsx diff --git a/src/app/create/_components/LinkPreview.tsx b/src/app/create/_components/LinkPreview.tsx deleted file mode 100644 index 85f6c161..00000000 --- a/src/app/create/_components/LinkPreview.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import Image from 'next/image'; - -import ClearBlackIcon from '/public/icons/clear_x_black.svg'; -import LinkIcon from '/public/icons/link.svg'; -import * as styles from './LinkPreview.css'; - -interface LinkPreviewProps { - type: 'link' | 'image'; - url?: string; - domain?: string; - imageUrl?: string; - handleClearButtonClick: () => void; -} - -export default function PreviewBox({ type, url, domain, imageUrl, handleClearButtonClick }: LinkPreviewProps) { - const handlePreviewClick = () => { - window.open(url); - }; - - const handleClearClick = (e: React.MouseEvent) => { - e.stopPropagation(); - handleClearButtonClick(); - }; - - return ( -
- {type === 'link' && ( - <> - -

{domain}

- - )} - {type === 'image' && 첨부 이미지} - -
- ); -} diff --git a/src/app/create/_components/LinkPreview.css.ts b/src/app/create/_components/Preview.css.ts similarity index 73% rename from src/app/create/_components/LinkPreview.css.ts rename to src/app/create/_components/Preview.css.ts index a7c14b02..287c77b9 100644 --- a/src/app/create/_components/LinkPreview.css.ts +++ b/src/app/create/_components/Preview.css.ts @@ -24,3 +24,14 @@ export const clearButton = style({ top: '5px', right: '5px', }); + +export const domainText = style({ + marginTop: '0.5rem', + + fontSize: '1rem', + color: '#61646B', +}); + +export const previewImage = style({ + objectFit: 'cover', +}); diff --git a/src/app/create/_components/Preview.tsx b/src/app/create/_components/Preview.tsx new file mode 100644 index 00000000..dc8666a7 --- /dev/null +++ b/src/app/create/_components/Preview.tsx @@ -0,0 +1,66 @@ +import { useState } from 'react'; +import Image from 'next/image'; + +import ClearBlackIcon from '/public/icons/clear_x_black.svg'; +import LinkIcon from '/public/icons/link.svg'; +import * as styles from './Preview.css'; + +interface PreviewBaseProps { + type: 'link' | 'image'; + handleClearButtonClick: () => void; +} + +type LinkProps = PreviewBaseProps & { + type: 'link'; + url: string; + domain: string; +}; + +type ImageProps = PreviewBaseProps & { + type: 'image'; + imageFile: Blob; +}; + +type PreviewProps = LinkProps | ImageProps; + +export default function Preview(props: PreviewProps) { + const [preview, setPreview] = useState(''); + + const handleClearClick = (e: React.MouseEvent) => { + e.stopPropagation(); + props.handleClearButtonClick(); + }; + + if (props.type === 'image') { + const reader = new FileReader(); + reader.onloadend = () => { + setPreview(reader.result as string); + }; + reader.readAsDataURL(props.imageFile); + } + + return ( +
{ + window.open(props.url); + } + : undefined + } + role="button" + > + {props.type === 'link' && ( + <> + +

{props.domain}

+ + )} + {props.type === 'image' && 첨부 이미지} + +
+ ); +} From bee77d470f411d8f83446f0ee4d8b1f7dba9539c Mon Sep 17 00:00:00 2001 From: Seoyoung Date: Sun, 4 Feb 2024 20:48:44 +0900 Subject: [PATCH 03/15] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EB=B2=84=ED=8A=BC=EC=97=90=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EC=B2=A8=EB=B6=80=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../create/_components/ImageUploader.css.ts | 9 +++++ src/app/create/_components/ImageUploader.tsx | 19 ++++++++++ src/app/create/_components/ItemLayout.tsx | 22 ++++-------- src/app/create/_components/Items.css.ts | 6 ++++ src/app/create/_components/Items.tsx | 35 ++++++++++++++++--- 5 files changed, 71 insertions(+), 20 deletions(-) create mode 100644 src/app/create/_components/ImageUploader.css.ts create mode 100644 src/app/create/_components/ImageUploader.tsx diff --git a/src/app/create/_components/ImageUploader.css.ts b/src/app/create/_components/ImageUploader.css.ts new file mode 100644 index 00000000..599a4526 --- /dev/null +++ b/src/app/create/_components/ImageUploader.css.ts @@ -0,0 +1,9 @@ +import { style } from '@vanilla-extract/css'; + +export const label = style({ + cursor: 'pointer', +}); + +export const input = style({ + display: 'none', +}); diff --git a/src/app/create/_components/ImageUploader.tsx b/src/app/create/_components/ImageUploader.tsx new file mode 100644 index 00000000..720159a5 --- /dev/null +++ b/src/app/create/_components/ImageUploader.tsx @@ -0,0 +1,19 @@ +import { ChangeEvent, ReactNode } from 'react'; +import ImageIcon from '/public/icons/attach_image.svg'; +import * as styles from './ImageUploader.css'; + +interface ImageUploaderProps { + index: number; + children: ReactNode; +} + +export default function ImageUploader({ index, children }: ImageUploaderProps) { + return ( + <> + + {children} + + ); +} diff --git a/src/app/create/_components/ItemLayout.tsx b/src/app/create/_components/ItemLayout.tsx index 17581947..ce003d90 100644 --- a/src/app/create/_components/ItemLayout.tsx +++ b/src/app/create/_components/ItemLayout.tsx @@ -3,8 +3,8 @@ import { ReactNode } from 'react'; import DndIcon from '/public/icons/dnd.svg'; import ClearGrayIcon from '/public/icons/clear_x_gray.svg'; import ClearBlackIcon from '/public/icons/clear_x_black.svg'; -import ImageIcon from '/public/icons/attach_image.svg'; import Label from '@/components/Label/Label'; +import ImageUploader from './ImageUploader'; import * as styles from './ItemLayout.css'; interface ItemLayoutProps { @@ -16,6 +16,8 @@ interface ItemLayoutProps { commentLength: ReactNode; linkModal: ReactNode; linkPreview: ReactNode; + imageInput: ReactNode; + imagePreview: ReactNode; } export default function ItemLayout({ @@ -27,6 +29,8 @@ export default function ItemLayout({ commentLength, linkModal, linkPreview, + imageInput, + imagePreview, }: ItemLayoutProps) { return ( <> @@ -50,26 +54,14 @@ export default function ItemLayout({
{linkModal} - + {imageInput}
{commentLength}
{linkPreview} -
- 사진칸 - -
+ {imagePreview}
diff --git a/src/app/create/_components/Items.css.ts b/src/app/create/_components/Items.css.ts index ef5a48a8..3c0910dd 100644 --- a/src/app/create/_components/Items.css.ts +++ b/src/app/create/_components/Items.css.ts @@ -4,6 +4,8 @@ export const itemsContainer = style({ display: 'flex', flexDirection: 'column', gap: '16px', + + cursor: 'grab', }); export const item = style({ @@ -85,6 +87,10 @@ export const linkInput = style([ }, ]); +export const imageInput = style({ + display: 'none', +}); + export const countLength = style({ //body2 fontSize: '1.5rem', diff --git a/src/app/create/_components/Items.tsx b/src/app/create/_components/Items.tsx index a6b70c32..07d7289f 100644 --- a/src/app/create/_components/Items.tsx +++ b/src/app/create/_components/Items.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { ChangeEvent, useState } from 'react'; import { useFieldArray, useFormContext, useWatch } from 'react-hook-form'; import { DragDropContext, Draggable, DropResult } from 'react-beautiful-dnd'; @@ -8,7 +8,7 @@ import { StrictModeDroppable } from '@/components/StrictModeDroppable'; import { FormErrors } from '../page'; import ItemLayout from './ItemLayout'; import LinkModal from './LinkModal'; -import LinkPreview from './LinkPreview'; +import Preview from './Preview'; import * as styles from './Items.css'; import AddItemButton from './AddItemButton'; @@ -46,6 +46,7 @@ export default function Items() { }); const [currentLink, setCurrentLink] = useState(''); + const [domain, setDomain] = useState(''); //링크 미리보기 const watchItems = useWatch({ control, name: 'items' }); @@ -61,6 +62,7 @@ export default function Items() { const handleLinkModalConfirm = (index: number) => { if (watchItems[index]?.link) { setValue(`items.${index}.link`, ensureHttp(watchItems[index]?.link)); + setDomain(urlToDomain(getValues().items?.[index].link)); } }; @@ -81,11 +83,13 @@ export default function Items() { {(provided) => (
{items.map((item, index) => { - const errorMessage = (field: 'title' | 'comment' | 'link') => + const errorMessage = (field: 'title' | 'comment' | 'link' | 'image') => (errors as FormErrors)?.items?.[index]?.[field]?.message; const titleError = errorMessage('title'); const commentError = errorMessage('comment'); const linkError = errorMessage('link'); + const imageError = errorMessage('image'); + return ( {(provided, snapshot) => ( @@ -148,18 +152,38 @@ export default function Items() {
} + imageInput={ + + } linkPreview={ watchItems[index]?.link && ( - { setValue(`items.${index}.link`, ''); }} /> ) } + imagePreview={ + watchItems[index]?.image && ( + { + setValue(`items.${index}.image`, ''); + }} + /> + ) + } /> )} @@ -175,6 +199,7 @@ export default function Items() { title: '', comment: '', link: '', + image: null, }) } /> From 272bc40de714b689f93365b0dca3773b9fbd8952 Mon Sep 17 00:00:00 2001 From: Seoyoung Date: Sun, 4 Feb 2024 20:50:15 +0900 Subject: [PATCH 04/15] =?UTF-8?q?Feat:=20=ED=97=A4=EB=8D=94=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=EB=B2=84=ED=8A=BC=EC=97=90=20submit=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=97=B0=EA=B2=B0=20=EB=B0=8F=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20api=EB=B3=84=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/app/create/_components/CreateItem.css.ts | 1 + src/app/create/_components/CreateItem.tsx | 9 +- src/app/create/page.tsx | 95 ++++++++--- tsconfig.json | 29 +++- yarn.lock | 171 +++++++++++++++++-- 6 files changed, 254 insertions(+), 53 deletions(-) diff --git a/package.json b/package.json index 605f0417..a36959f0 100644 --- a/package.json +++ b/package.json @@ -40,9 +40,9 @@ "zustand": "^4.4.7" }, "devDependencies": { - "@svgr/webpack": "^8.1.0", "@commitlint/cli": "^18.6.0", "@commitlint/config-conventional": "^18.6.0", + "@hookform/devtools": "^4.3.1", "@svgr/webpack": "^8.1.0", "@testing-library/jest-dom": "^6.2.0", "@testing-library/react": "^14.1.2", diff --git a/src/app/create/_components/CreateItem.css.ts b/src/app/create/_components/CreateItem.css.ts index 0ce41170..6de9e5a9 100644 --- a/src/app/create/_components/CreateItem.css.ts +++ b/src/app/create/_components/CreateItem.css.ts @@ -34,6 +34,7 @@ export const headerNextButtonDisabled = style([ headerNextButton, { color: '#AFB1B6', //활성화 검정, 아닐때는 회색 + cursor: 'default', }, ]); diff --git a/src/app/create/_components/CreateItem.tsx b/src/app/create/_components/CreateItem.tsx index 7cd378e6..4d231fe8 100644 --- a/src/app/create/_components/CreateItem.tsx +++ b/src/app/create/_components/CreateItem.tsx @@ -6,9 +6,10 @@ import * as styles from './CreateItem.css'; interface CreateItemProps { onBackClick: () => void; + onSubmitClick: () => void; } -export default function CreateItem({ onBackClick }: CreateItemProps) { +export default function CreateItem({ onBackClick, onSubmitClick }: CreateItemProps) { const { formState: { isValid }, } = useFormContext(); @@ -21,10 +22,8 @@ export default function CreateItem({ onBackClick }: CreateItemProps) {

리스트 생성

From 6b1809301fbd8ec96c152a06663e67d08e02b2fc Mon Sep 17 00:00:00 2001 From: Seoyoung Date: Mon, 5 Feb 2024 00:25:22 +0900 Subject: [PATCH 06/15] =?UTF-8?q?feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20api=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/_api/list/sendItemImages.ts | 23 ++++++++++++++++++ src/app/create/page.tsx | 36 ++++++++++++++++++----------- src/lib/types/listType.ts | 20 ++++++++++++++++ 3 files changed, 66 insertions(+), 13 deletions(-) create mode 100644 src/app/_api/list/sendItemImages.ts diff --git a/src/app/_api/list/sendItemImages.ts b/src/app/_api/list/sendItemImages.ts new file mode 100644 index 00000000..2b37af22 --- /dev/null +++ b/src/app/_api/list/sendItemImages.ts @@ -0,0 +1,23 @@ +import axiosInstance from '@/lib/axios/axiosInstance'; +import { ItemImagesType, PresignedUrlListType } from '@/lib/types/listType'; +import axios from 'axios'; + +export const sendItemImages = async (imageData: ItemImagesType, imageFileList: File[]) => { + //PresignedUrl 생성 요청 + const response = await axiosInstance.post('/lists/upload-url', imageData); + + //PresignedUrl에 이미지 업로드 + for (let i = 0; i < response.data.length; i++) { + const result = await axios.put(response.data[i].presignedUrl, imageFileList[i], { + headers: { + 'Content-Type': imageFileList[i]?.type, + }, + }); + if (result.status !== 200) return; + } + + //서버에 성공 완료 알림 + if (imageFileList.length !== 0) { + await axiosInstance.post('/lists/upload-complete', imageData); + } +}; diff --git a/src/app/create/page.tsx b/src/app/create/page.tsx index 7abef5cc..7b2c7def 100644 --- a/src/app/create/page.tsx +++ b/src/app/create/page.tsx @@ -5,7 +5,8 @@ import { DevTool } from '@hookform/devtools'; import CreateItem from '@/app/create/_components/CreateItem'; import CreateList from '@/app/create/_components/CreateList'; import { useState } from 'react'; -import axios from 'axios'; +import { sendItemImages } from '../_api/list/sendItemImages'; +import { ItemImagesType } from '@/lib/types/listType'; interface Item { rank: number; @@ -87,30 +88,39 @@ export default function CreatePage() { items: originData.items.map(({ image, ...rest }) => rest), }; - const imageData = { + const imageData: ItemImagesType = { ownerId: originData.ownerId, listId: 1, //temp - extensionsRanks: originData.items + extensionRanks: originData.items .map(({ rank, image }) => { - return { rank: rank, extension: image?.[0]?.type.split('/')[1] }; //type앞에 ? + return { rank: rank, extension: image?.[0]?.type.split('/')[1] as 'jpg' | 'jpeg' | 'png' }; }) - .filter(({ extension }) => { - return extension !== undefined; - }), + .filter(({ extension }) => extension !== null && extension !== undefined), }; - const imageFileList = originData.items.map(({ image }) => image); //추가 + const imageFileList: File[] = originData.items + .map(({ image }) => image?.[0] as File) + .filter((image) => image !== undefined); return { requestData, imageData, imageFileList }; }; + // const handleSubmit = async () => { + // try { + // const formData = formatData().requestData; + // console.log(formData); + // console.log(formatData().imageData); + // const response = await axios.post('https://dev.api.listywave.com/lists', formData); + // console.log('Response:', response.data); + // } catch (error) { + // console.error('Error:', error); + // } + // }; + const handleSubmit = async () => { try { - const formData = formatData().requestData; - console.log(formData); - console.log(formatData().imageData); - const response = await axios.post('https://dev.api.listywave.com/lists', formData); - console.log('Response:', response.data); + const { imageData, imageFileList } = formatData(); + sendItemImages(imageData, imageFileList); } catch (error) { console.error('Error:', error); } diff --git a/src/lib/types/listType.ts b/src/lib/types/listType.ts index 5aeef337..192aa15b 100644 --- a/src/lib/types/listType.ts +++ b/src/lib/types/listType.ts @@ -1 +1,21 @@ // 리스트 관련 타입 +export interface ListIdType { + listId: number; +} + +export interface ItemImageType { + rank: number; + extension: 'jpg' | 'jpeg' | 'png'; +} +export interface ItemImagesType { + ownerId: number; + listId: number; + extensionRanks: ItemImageType[] | []; +} + +export interface PresignedUrlType { + rank: number; + presignedUrl: string; +} + +export type PresignedUrlListType = PresignedUrlType[]; From da8917b2112a3beb9f355f8de9c9346a22981cab Mon Sep 17 00:00:00 2001 From: Seoyoung Date: Mon, 5 Feb 2024 01:02:42 +0900 Subject: [PATCH 07/15] =?UTF-8?q?Fix:=20Merge=20=EC=B6=A9=EB=8F=8C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.json | 8 +- .github/workflows/Dev-CD.yml | 4 +- .gitignore | 2 + .vscode/settings.json | 3 +- public/icons/cancel_button.svg | 3 + public/icons/check_red.svg | 3 + public/icons/clear.svg | 3 + public/icons/collaborators_plus.svg | 3 + public/icons/default_profile_temporary.svg | 13 + public/icons/history.svg | 3 + public/icons/horizontal_line.svg | 3 + public/icons/trash_can.svg | 3 + public/icons/vertical_kebab_button.svg | 3 + settings.json | 3 + .../[userNickname]/[listId]/ListDetail.css.ts | 6 + .../BottomSheet/BottomSheet.css.ts | 74 + .../_components/BottomSheet/BottomSheet.tsx | 41 + .../ListDetailOuter/Collaborators.css.ts | 61 + .../ListDetailOuter/Collaborators.tsx | 55 + .../CollaboratorsPopOver.css.ts | 57 + .../ListDetailOuter/CollaboratorsPopOver.tsx | 36 + .../ListDetailOuter/Comment.css.ts | 64 + .../_components/ListDetailOuter/Comment.tsx | 57 + .../ListDetailOuter/Comments.css.ts | 109 ++ .../_components/ListDetailOuter/Comments.tsx | 56 + .../ListDetailOuter/DeleteModalButton.tsx | 37 + .../_components/ListDetailOuter/Header.css.ts | 34 + .../_components/ListDetailOuter/Header.tsx | 40 + .../ListDetailOuter/ListInformation.css.ts | 83 ++ .../ListDetailOuter/ListInformation.tsx | 55 + .../ListDetailOuter/ModalButtonStyle.css.ts | 10 + .../ListDetailOuter/OpenBottomSheetButton.tsx | 44 + .../ListDetailOuter/PopOverMenu.css.ts | 17 + .../ListDetailOuter/PopOverMenu.tsx | 14 + .../ListDetailOuter/Replies.css.ts | 72 + .../_components/ListDetailOuter/Replies.tsx | 60 + .../[listId]/mockData/mockdata.ts | 515 +++++++ .../[listId]/mockData/mockdataType.ts | 33 + src/app/[userNickname]/[listId]/page.tsx | 15 + src/app/[userNickname]/collabolist/page.tsx | 2 +- src/app/[userNickname]/mylist/page.tsx | 2 +- src/app/_api/category/getCategories.ts | 4 +- src/app/_api/list/createList.ts | 9 +- src/app/_api/user/getUsers.ts | 8 + src/app/create/_components/CreateItem.tsx | 4 +- src/app/create/_components/CreateList.css.ts | 337 ----- src/app/create/_components/CreateList.tsx | 514 ++----- .../_components/item/AddItemButton.css.ts | 23 + .../create/_components/item/AddItemButton.tsx | 14 + .../_components/item/ImageUploader.css.ts | 9 + .../create/_components/item/ImageUploader.tsx | 19 + .../create/_components/item/ItemLayout.css.ts | 76 + .../create/_components/item/ItemLayout.tsx | 69 + src/app/create/_components/item/Items.css.ts | 108 ++ src/app/create/_components/item/Items.tsx | 187 +++ src/app/create/_components/item/LinkModal.tsx | 54 + .../create/_components/item/Preview.css.ts | 37 + src/app/create/_components/item/Preview.tsx | 68 + .../_components/list/ButtonSelector.css.ts | 34 + .../_components/list/ButtonSelector.tsx | 40 + .../_components/list/ColorSelector.css.ts | 33 + .../create/_components/list/ColorSelector.tsx | 39 + .../{ => _components/list}/CreateListMock.ts | 18 +- src/app/create/_components/list/Header.css.ts | 29 + src/app/create/_components/list/Header.tsx | 21 + .../create/_components/list/LabelInput.css.ts | 57 + .../create/_components/list/LabelInput.tsx | 102 ++ .../_components/list/MemberSelector.css.ts | 83 ++ .../_components/list/MemberSelector.tsx | 156 ++ .../create/_components/list/RadioInput.css.ts | 18 + .../create/_components/list/RadioInput.tsx | 56 + .../create/_components/list/Section.css.ts | 15 + src/app/create/_components/list/Section.tsx | 28 + .../_components/list/SimpleInput.css.ts | 50 + .../create/_components/list/SimpleInput.tsx | 75 + src/app/create/page.tsx | 56 +- src/app/layout.tsx | 2 +- src/app/page.tsx | 2 +- src/components/Label/Label.css.ts | 80 +- src/components/Label/Label.tsx | 56 +- src/hooks/init.ts | 2 - src/lib/constants/formInputValidationRules.ts | 19 + src/lib/constants/placeholder.ts | 7 + src/lib/types/categoriesType.ts | 4 - src/lib/types/listType.ts | 24 +- src/lib/types/userProfileType.ts | 10 + src/lib/utils/timeDiff.ts | 37 + src/styles/Color.ts | 8 + .../styles}/Pretendard-Black.woff2 | Bin .../styles}/Pretendard-Bold.woff2 | Bin .../styles}/Pretendard-ExtraBold.woff2 | Bin .../styles}/Pretendard-ExtraLight.woff2 | Bin .../styles}/Pretendard-Light.woff2 | Bin .../styles}/Pretendard-Medium.woff2 | Bin .../styles}/Pretendard-Regular.woff2 | Bin .../styles}/Pretendard-SemiBold.woff2 | Bin .../styles}/Pretendard-Thin.woff2 | Bin tsconfig.json | 33 +- yarn.lock | 1261 +++++++---------- 99 files changed, 4018 insertions(+), 1683 deletions(-) create mode 100644 public/icons/cancel_button.svg create mode 100644 public/icons/check_red.svg create mode 100644 public/icons/clear.svg create mode 100644 public/icons/collaborators_plus.svg create mode 100644 public/icons/default_profile_temporary.svg create mode 100644 public/icons/history.svg create mode 100644 public/icons/horizontal_line.svg create mode 100644 public/icons/trash_can.svg create mode 100644 public/icons/vertical_kebab_button.svg create mode 100644 settings.json create mode 100644 src/app/[userNickname]/[listId]/ListDetail.css.ts create mode 100644 src/app/[userNickname]/[listId]/_components/BottomSheet/BottomSheet.css.ts create mode 100644 src/app/[userNickname]/[listId]/_components/BottomSheet/BottomSheet.tsx create mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/Collaborators.css.ts create mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/Collaborators.tsx create mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/CollaboratorsPopOver.css.ts create mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/CollaboratorsPopOver.tsx create mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comment.css.ts create mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comment.tsx create mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comments.css.ts create mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comments.tsx create mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/DeleteModalButton.tsx create mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/Header.css.ts create mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/Header.tsx create mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/ListInformation.css.ts create mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/ListInformation.tsx create mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/ModalButtonStyle.css.ts create mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/OpenBottomSheetButton.tsx create mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/PopOverMenu.css.ts create mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/PopOverMenu.tsx create mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/Replies.css.ts create mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/Replies.tsx create mode 100644 src/app/[userNickname]/[listId]/mockData/mockdata.ts create mode 100644 src/app/[userNickname]/[listId]/mockData/mockdataType.ts create mode 100644 src/app/[userNickname]/[listId]/page.tsx create mode 100644 src/app/_api/user/getUsers.ts create mode 100644 src/app/create/_components/item/AddItemButton.css.ts create mode 100644 src/app/create/_components/item/AddItemButton.tsx create mode 100644 src/app/create/_components/item/ImageUploader.css.ts create mode 100644 src/app/create/_components/item/ImageUploader.tsx create mode 100644 src/app/create/_components/item/ItemLayout.css.ts create mode 100644 src/app/create/_components/item/ItemLayout.tsx create mode 100644 src/app/create/_components/item/Items.css.ts create mode 100644 src/app/create/_components/item/Items.tsx create mode 100644 src/app/create/_components/item/LinkModal.tsx create mode 100644 src/app/create/_components/item/Preview.css.ts create mode 100644 src/app/create/_components/item/Preview.tsx create mode 100644 src/app/create/_components/list/ButtonSelector.css.ts create mode 100644 src/app/create/_components/list/ButtonSelector.tsx create mode 100644 src/app/create/_components/list/ColorSelector.css.ts create mode 100644 src/app/create/_components/list/ColorSelector.tsx rename src/app/create/{ => _components/list}/CreateListMock.ts (89%) create mode 100644 src/app/create/_components/list/Header.css.ts create mode 100644 src/app/create/_components/list/Header.tsx create mode 100644 src/app/create/_components/list/LabelInput.css.ts create mode 100644 src/app/create/_components/list/LabelInput.tsx create mode 100644 src/app/create/_components/list/MemberSelector.css.ts create mode 100644 src/app/create/_components/list/MemberSelector.tsx create mode 100644 src/app/create/_components/list/RadioInput.css.ts create mode 100644 src/app/create/_components/list/RadioInput.tsx create mode 100644 src/app/create/_components/list/Section.css.ts create mode 100644 src/app/create/_components/list/Section.tsx create mode 100644 src/app/create/_components/list/SimpleInput.css.ts create mode 100644 src/app/create/_components/list/SimpleInput.tsx delete mode 100644 src/hooks/init.ts create mode 100644 src/lib/types/userProfileType.ts create mode 100644 src/lib/utils/timeDiff.ts create mode 100644 src/styles/Color.ts rename {public/fonts => src/styles}/Pretendard-Black.woff2 (100%) rename {public/fonts => src/styles}/Pretendard-Bold.woff2 (100%) rename {public/fonts => src/styles}/Pretendard-ExtraBold.woff2 (100%) rename {public/fonts => src/styles}/Pretendard-ExtraLight.woff2 (100%) rename {public/fonts => src/styles}/Pretendard-Light.woff2 (100%) rename {public/fonts => src/styles}/Pretendard-Medium.woff2 (100%) rename {public/fonts => src/styles}/Pretendard-Regular.woff2 (100%) rename {public/fonts => src/styles}/Pretendard-SemiBold.woff2 (100%) rename {public/fonts => src/styles}/Pretendard-Thin.woff2 (100%) diff --git a/.eslintrc.json b/.eslintrc.json index 6109d668..cd332853 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -24,6 +24,12 @@ "react/react-in-jsx-scope": "off", // react 17부턴 import 안해도돼서 기능 끔 // 경고표시, 파일 확장자를 .ts나 .tsx 모두 허용함 "react/jsx-filename-extension": ["warn", { "extensions": [".ts", ".tsx"] }], - "no-useless-catch": "off" // 불필요한 catch 못쓰게 하는 기능 끔 + "no-useless-catch": "off", // 불필요한 catch 못쓰게 하는 기능 끔 + "prettier/prettier": [ + "error", + { + "endOfLine": "auto" + } + ] } } diff --git a/.github/workflows/Dev-CD.yml b/.github/workflows/Dev-CD.yml index 05f4a249..479af180 100644 --- a/.github/workflows/Dev-CD.yml +++ b/.github/workflows/Dev-CD.yml @@ -2,7 +2,7 @@ name: FrontEnd Dev CD on: push: - branches: [ "dev" ] + branches: ['dev'] workflow_dispatch: jobs: @@ -11,7 +11,7 @@ jobs: env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - + steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 diff --git a/.gitignore b/.gitignore index fd3dbb57..d0a27563 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,8 @@ yarn-debug.log* yarn-error.log* # local env files +.env +.env* .env*.local # vercel diff --git a/.vscode/settings.json b/.vscode/settings.json index 7a73a41b..0967ef42 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,2 +1 @@ -{ -} \ No newline at end of file +{} diff --git a/public/icons/cancel_button.svg b/public/icons/cancel_button.svg new file mode 100644 index 00000000..6bb7a974 --- /dev/null +++ b/public/icons/cancel_button.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/check_red.svg b/public/icons/check_red.svg new file mode 100644 index 00000000..ed672394 --- /dev/null +++ b/public/icons/check_red.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/clear.svg b/public/icons/clear.svg new file mode 100644 index 00000000..0356b6b1 --- /dev/null +++ b/public/icons/clear.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/collaborators_plus.svg b/public/icons/collaborators_plus.svg new file mode 100644 index 00000000..b645dc62 --- /dev/null +++ b/public/icons/collaborators_plus.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/public/icons/default_profile_temporary.svg b/public/icons/default_profile_temporary.svg new file mode 100644 index 00000000..a99b8e97 --- /dev/null +++ b/public/icons/default_profile_temporary.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/icons/history.svg b/public/icons/history.svg new file mode 100644 index 00000000..40ae8256 --- /dev/null +++ b/public/icons/history.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/horizontal_line.svg b/public/icons/horizontal_line.svg new file mode 100644 index 00000000..98de4a91 --- /dev/null +++ b/public/icons/horizontal_line.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/trash_can.svg b/public/icons/trash_can.svg new file mode 100644 index 00000000..f1b24bc8 --- /dev/null +++ b/public/icons/trash_can.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/vertical_kebab_button.svg b/public/icons/vertical_kebab_button.svg new file mode 100644 index 00000000..d8cb2fca --- /dev/null +++ b/public/icons/vertical_kebab_button.svg @@ -0,0 +1,3 @@ + + + diff --git a/settings.json b/settings.json new file mode 100644 index 00000000..ad92582b --- /dev/null +++ b/settings.json @@ -0,0 +1,3 @@ +{ + "editor.formatOnSave": true +} diff --git a/src/app/[userNickname]/[listId]/ListDetail.css.ts b/src/app/[userNickname]/[listId]/ListDetail.css.ts new file mode 100644 index 00000000..dd923729 --- /dev/null +++ b/src/app/[userNickname]/[listId]/ListDetail.css.ts @@ -0,0 +1,6 @@ +import { style } from '@vanilla-extract/css'; + +export const wrapper = style({ + position: 'relative', + height: '100vh', +}); diff --git a/src/app/[userNickname]/[listId]/_components/BottomSheet/BottomSheet.css.ts b/src/app/[userNickname]/[listId]/_components/BottomSheet/BottomSheet.css.ts new file mode 100644 index 00000000..e2734537 --- /dev/null +++ b/src/app/[userNickname]/[listId]/_components/BottomSheet/BottomSheet.css.ts @@ -0,0 +1,74 @@ +import { keyframes, style } from '@vanilla-extract/css'; + +export const backGround = style({ + position: 'fixed', + top: 0, + left: 0, + bottom: 0, + right: 0, + background: 'rgba(0,0,0,0.3)', + zIndex: 999, +}); + +export const wrapper = style({ + padding: '37px 0 43px', + + position: 'fixed', + bottom: 0, + left: 0, + right: 0, + + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + + backgroundColor: '#ffffff', + borderTopLeftRadius: '25px', + borderTopRightRadius: '25px', + + transitionProperty: 'all', + transitionDuration: '0.2s', +}); + +const slideIn = keyframes({ + from: { transform: 'translateY(100%)' }, + to: { transform: 'translateY(0)' }, +}); + +export const sheetActive = style({ + animation: `${slideIn} 0.2s ease-in-out`, +}); + +export const sheetItemWrapper = style({ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + + ':hover': { + backgroundColor: '#EFEFF0', + }, +}); + +export const checkIcon = style({ + display: 'none', + marginRight: '28px', + + selectors: { + [`${sheetItemWrapper}:hover &`]: { + display: 'block', + }, + }, +}); + +export const sheetItem = style({ + width: '100%', + fontSize: '1.4rem', + cursor: 'pointer', + padding: '2.5rem 2.8rem 2.5rem', + + selectors: { + [`${sheetItemWrapper}:hover &`]: { + color: '#FF5454', + }, + }, +}); diff --git a/src/app/[userNickname]/[listId]/_components/BottomSheet/BottomSheet.tsx b/src/app/[userNickname]/[listId]/_components/BottomSheet/BottomSheet.tsx new file mode 100644 index 00000000..d89d4769 --- /dev/null +++ b/src/app/[userNickname]/[listId]/_components/BottomSheet/BottomSheet.tsx @@ -0,0 +1,41 @@ +'use client'; + +import * as styles from './BottomSheet.css'; +import { MouseEventHandler } from 'react'; +import CheckIcon from '/public/icons/check_red.svg'; +import useOnClickOutside from '@/hooks/useOnClickOutside'; //바깥 영역 클릭시 바텀시트 닫히는 함수 + +interface BottomSheetOptionsProps { + key: string; + title: string; + onClick: () => void; +} + +interface BottomSheetProps { + onClose: MouseEventHandler; + isActive?: boolean; //Boolean -> boolean 수정 , 옵셔널 하게 수정 + optionList: BottomSheetOptionsProps[]; +} + +function BottomSheet({ onClose, isActive, optionList }: BottomSheetProps) { + const { ref } = useOnClickOutside(() => { + onClose; + }); + + return ( +
+
+ {optionList.map((option) => ( +
+
+ {option.title} +
+ +
+ ))} +
+
+ ); +} + +export default BottomSheet; diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Collaborators.css.ts b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Collaborators.css.ts new file mode 100644 index 00000000..bbbc1c23 --- /dev/null +++ b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Collaborators.css.ts @@ -0,0 +1,61 @@ +import { style } from '@vanilla-extract/css'; + +export const collaboratorWrapper = style({ + position: 'relative', + display: 'flex', +}); + +export const wrapper = style({ + display: 'flex', + flexDirection: 'row-reverse', + justifyContent: 'center', + alignItems: 'cemter', + transform: 'translateZ(0px)', +}); + +export const ProfileImg = style({ + marginRight: '-10px', + width: '36px', + height: '36px', + + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + + outline: '3px solid #ffffff', + borderRadius: '9999px', +}); + +export const profilePlus = style({ + backgroundColor: '#AEB0B6', +}); + +export const profileText = style({ + color: '#DEE7EE', + fontSize: '2rem', +}); + +export const collaboratorTitle = style({ + marginRight: '11px', + + fontSize: '1rem', + fontWeight: 600, +}); + +export const collaboratorsPopOverWrapper = style({ + display: 'none', + + selectors: { + [`${collaboratorWrapper}:hover &`]: { + display: 'block', + }, + }, +}); + +export const defaultProfile = style({ + width: '36px', + height: '36px', + + outline: '3px solid #ffffff', + borderRadius: '9999px', +}); diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Collaborators.tsx b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Collaborators.tsx new file mode 100644 index 00000000..a302edfb --- /dev/null +++ b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Collaborators.tsx @@ -0,0 +1,55 @@ +import Image from 'next/image'; +import CollaboratorsPopOver from '@/app/[userNickname]/[listId]/_components/ListDetailOuter/CollaboratorsPopOver'; +import * as styles from './Collaborators.css'; +import DefaultProfile from '/public/icons/default_profile_temporary.svg'; +import PlusIcon from '/public/icons/collaborators_plus.svg'; +import { CollaboratorType } from '../../mockData/mockdataType'; + +interface CollaboratorsProps { + collaborators: CollaboratorType[] | null; +} + +function Collaborators({ collaborators }: CollaboratorsProps) { + const collaboratorsList = collaborators && collaborators?.length >= 3 ? collaborators?.slice(0, 3) : collaborators; + + return ( + <> + {collaborators && ( +
+
+ +
+ 콜라보레이터 +
+
+ {`${collaborators && collaborators?.length - 3}`} + +
+ {collaboratorsList?.map((item: CollaboratorType) => { + return ( +
+ {item.profileImageUrl ? ( + 사용자 프로필 이미지 + ) : ( + + )} +
+ ); + })} +
+
+ )} + + ); +} + +export default Collaborators; diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/CollaboratorsPopOver.css.ts b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/CollaboratorsPopOver.css.ts new file mode 100644 index 00000000..fe8a5160 --- /dev/null +++ b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/CollaboratorsPopOver.css.ts @@ -0,0 +1,57 @@ +import { style } from '@vanilla-extract/css'; + +export const wrapper = style({ + padding: '12px', + height: '142px', + width: 'auto', + + position: 'absolute', + top: '33px', + right: '-10px', + + overflow: 'scroll', + borderRadius: '10px', + background: 'rgba(255, 255, 255, 0.8)', + boxShadow: '0px 4px 4px 0px rgba(0, 0, 0, 0.25)', + zIndex: 20, + + '::-webkit-scrollbar': { + display: 'none', + }, +}); + +export const listWrapper = style({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + gap: '8px', +}); + +export const itemWrapper = style({ + display: 'flex', + alignItems: 'center', + gap: '8px', +}); + +export const profileImage = style({ + width: '25px', + height: '25px', + + borderRadius: '9999px', +}); + +export const nickname = style({ + width: '85px', + + fontSize: '1.2rem', + fontWeight: 600, + letterSpacing: '-0.36px', +}); + +export const defaultProfileImage = style({ + width: '25px', + height: '25px', + + borderRadius: '9999px', + backgroundColor: '#494949', +}); diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/CollaboratorsPopOver.tsx b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/CollaboratorsPopOver.tsx new file mode 100644 index 00000000..03b8d1d5 --- /dev/null +++ b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/CollaboratorsPopOver.tsx @@ -0,0 +1,36 @@ +import Image from 'next/image'; +import * as styles from './CollaboratorsPopOver.css'; +import { CollaboratorType } from '../../mockData/mockdataType'; + +interface CollaboratorsProps { + collaborators: CollaboratorType[] | null; +} + +function CollaboratorsPopOver({ collaborators }: CollaboratorsProps) { + return ( +
+
    + {collaborators?.map((item) => { + return ( +
  • + {item.profileImageUrl ? ( + 사용자 프로필 이미지 + ) : ( +
    + )} + {`${item.nickname}`} +
  • + ); + })} +
+
+ ); +} + +export default CollaboratorsPopOver; diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comment.css.ts b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comment.css.ts new file mode 100644 index 00000000..c5d190ac --- /dev/null +++ b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comment.css.ts @@ -0,0 +1,64 @@ +import { style } from '@vanilla-extract/css'; + +export const commentOuterWrapper = style({ + marginBottom: '10px', + + position: 'relative', + + display: 'flex', + justifyContent: 'space-between', +}); + +export const profileImage = style({ + width: '30px', + minWidth: '30px', + height: '30px', + flex: '0 0 1', + + borderRadius: '16px', + backgroundColor: '#909090', +}); + +export const commentWrapper = style({ + display: 'flex', + gap: '8px', + alignItems: 'center', +}); + +export const commentContainer = style({ + display: 'flex', + flexDirection: 'column', + gap: '5px', +}); + +export const commentInformationWrapper = style({ + display: 'flex', + alignItems: 'baseline', + gap: '8px', +}); + +export const commentWriter = style({ + fontSize: '1.2rem', + fontWeight: 600, +}); + +export const commentCreatedTime = style({ + fontSize: '1rem', + fontWeight: 500, + color: '#494949', +}); + +export const commentContent = style({ + fontSize: '1.2rem', + fontWeight: 500, +}); + +export const createReplyButton = style({ + padding: '0 0 0 36px', + marginBottom: '16px', + + background: 'none', + fontSize: '1rem', + color: '#494949', + fontWeight: 500, +}); diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comment.tsx b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comment.tsx new file mode 100644 index 00000000..577b39a9 --- /dev/null +++ b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comment.tsx @@ -0,0 +1,57 @@ +'use client'; +import Image from 'next/image'; +import Replies from '@/app/[userNickname]/[listId]/_components/ListDetailOuter/Replies'; +import DeleteModalButton from '@/app/[userNickname]/[listId]/_components/ListDetailOuter/DeleteModalButton'; +import timeDiff from '@/lib/utils/timeDiff'; +import * as styles from './Comment.css'; +import DefaultProfile from '/public/icons/default_profile_temporary.svg'; +import { CommentType } from '../../mockData/mockdataType'; + +interface CommentProps { + comment: CommentType | undefined; + onUpdate: (userName: string | null) => void; + activeNickname?: string | null; +} + +function Comment({ comment, onUpdate }: CommentProps) { + const handleActiveNicknameUpdate = () => { + const currentUserName = comment?.userName; + if (currentUserName) { + onUpdate(currentUserName); + } + }; + + return ( + <> +
+
+ {comment && comment.userProfileImageUrl ? ( + 프로필 이미지 + ) : ( + + )} +
+
+ {comment?.userName} + {comment && timeDiff(comment?.createdDate)} +
+
{comment?.content}
+
+
+ +
+ + + + ); +} + +export default Comment; diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comments.css.ts b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comments.css.ts new file mode 100644 index 00000000..bc86fa97 --- /dev/null +++ b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comments.css.ts @@ -0,0 +1,109 @@ +import { style } from '@vanilla-extract/css'; + +export const wrapper = style({ + height: 'auto', + padding: '20px 24px 80px', +}); + +export const formWrapperOuter = style({ + padding: '15px 10px', + + width: '100%', + height: 'auto', + + position: 'fixed', + bottom: 0, + left: 0, + right: 0, + + display: 'flex', + alignItems: 'center', + gap: '4px', + + border: '1px solid rgba(0, 0, 0, 0.10)', + background: '#fff', + zIndex: 30, +}); + +export const formWrapperInner = style({ + width: '100%', + height: 'auto', + padding: '8px 12px', + + display: 'flex', + flexDirection: 'column', + gap: '4px', + + borderRadius: '50px', + border: '1px solid #D9D9D9', +}); + +export const activeFormWrapper = style({ + borderRadius: '10px', +}); + +export const formContainer = style({ + display: 'flex', + justifyContent: 'space-between', +}); + +export const formInput = style({ + width: 'inherit', + height: 'auto', + + flex: '1 0 0', + wordBreak: 'break-all', + wordWrap: 'break-word', + whiteSpace: 'pre-wrap', + resize: 'none', +}); + +export const replyNickname = style({ + marginRight: '8px', + + flex: '1 0 0', + + fontSize: '1.2rem', + fontWeight: 400, + color: '#AFB1B6', +}); + +export const formButton = style({ + marginLeft: '45px', + + background: 'none', + fontSize: '1.2rem', + fontWeight: 500, + letterSpacing: '-0.36px', +}); + +export const totalCount = style({ + marginBottom: '15px', + + fontSize: '1.2rem', + fontWeight: 600, +}); + +export const profileImage = style({ + width: '30px', + minWidth: '30px', + height: '30px', + flex: '0 0 1', + + borderRadius: '16px', + backgroundColor: '#909090', +}); + +export const activeReplyWrapper = style({ + display: 'flex', + justifyContent: 'space-between', +}); + +export const replyNicknameWrapper = style({ + display: 'flex', + alignItems: 'center', +}); + +export const clearButton = style({ + cursor: 'pointer', +}); diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comments.tsx b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comments.tsx new file mode 100644 index 00000000..8f66675f --- /dev/null +++ b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comments.tsx @@ -0,0 +1,56 @@ +'use client'; +import { useState } from 'react'; +import Image from 'next/image'; +import Comment from './Comment'; +import * as styles from './Comments.css'; +import { MOCKDATA_COMMENTS } from '../../mockData/mockdata'; +import CancelButton from '/public/icons/cancel_button.svg'; + +const COMMENTS = MOCKDATA_COMMENTS[1]; + +function Comments() { + const [activeNickname, setActiveNickname] = useState(null); + + const handleActiveNicknameDelete = () => { + if (activeNickname) { + setActiveNickname(null); + } + }; + + return ( +
+
+ {/* {유저 정보 들어오면 이미지 src 추가할 예정} */} + 프로필 이미지 +
+ {activeNickname && ( +
+ {`@${activeNickname}님에게 남긴 답글`} + +
+ )} +
+ + +
+
+
+
{`${COMMENTS.totalCount}개의 댓글`}
+ {COMMENTS.comments.map((item) => { + return ( +
+ +
+ ); + })} +
+ ); +} + +export default Comments; diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/DeleteModalButton.tsx b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/DeleteModalButton.tsx new file mode 100644 index 00000000..009d0038 --- /dev/null +++ b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/DeleteModalButton.tsx @@ -0,0 +1,37 @@ +import { ReactNode } from 'react'; +import Modal from '@/components/Modal/Modal'; +import useBooleanOutput from '@/hooks/useBooleanOutput'; +import * as styles from './ModalButtonStyle.css'; +import DeleteButton from '/public/icons/trash_can.svg'; + +interface DeleteModalProps { + children?: ReactNode; +} + +export default function DeleteModal({ children }: DeleteModalProps) { + const { isOn, handleSetOff, handleSetOn } = useBooleanOutput(); //모달 열림,닫힘 상태 관리 + const handleConfirmButtonClick = () => { + //확인버튼 클릭시 실행될 로직() + handleSetOff(); //닫기 + }; + + return ( + <> + {/*👆 누르면 모달이 열리는 트리거 버튼*/} + + + {/*✨ 조합한 모달 */} + {isOn && ( + + 정말로 삭제하시겠습니까? + {children} + + 확인 + + + )} + + ); +} diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Header.css.ts b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Header.css.ts new file mode 100644 index 00000000..ef6a60de --- /dev/null +++ b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Header.css.ts @@ -0,0 +1,34 @@ +import { style } from '@vanilla-extract/css'; + +export const wrapper = style({ + height: '90px', + padding: '0 16px', + + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + + borderBottom: '1px solid rgba(0, 0, 0, 0.10)', +}); + +export const title = style({ + fontWeight: 600, + fontSize: '2rem', +}); + +export const buttonResetStyle = style({ + width: '24px', + height: '24px', + + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + + background: 'none', +}); + +export const headerRightWrapper = style({ + display: 'flex', + alignItems: 'center', + gap: '12px', +}); diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Header.tsx b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Header.tsx new file mode 100644 index 00000000..2e12cc4d --- /dev/null +++ b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Header.tsx @@ -0,0 +1,40 @@ +'use client'; +import { useRouter, useParams } from 'next/navigation'; +import OpenBottomSheetButton from './OpenBottomSheetButton'; +import * as styles from './Header.css'; +import BackButton from '/public/icons/back.svg'; +import HistoryButton from '/public/icons/history.svg'; + +function Header() { + const router = useRouter(); + const params = useParams<{ userNickname: string; listId: string }>(); + + const handleBackButtonClick = () => { + router.back(); + }; + + const handleHistoryButtonClick = () => { + router.push(`/${params.userNickname}/${params.listId}/history`); + }; + + return ( + <> +
+ +
리스트
+
+ +
+ +
+
+
+ + ); +} + +export default Header; diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/ListInformation.css.ts b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/ListInformation.css.ts new file mode 100644 index 00000000..54f91d15 --- /dev/null +++ b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/ListInformation.css.ts @@ -0,0 +1,83 @@ +import { style } from '@vanilla-extract/css'; + +export const wrapper = style({ + padding: '48px 38px', +}); + +export const categoryWrapper = style({ + marginBottom: '25px', + + display: 'flex', + justifyContent: 'flex-start', + alignItems: 'center', + + fontSize: '.75rem', +}); + +export const labelWrapper = style({ + marginRight: '8px', +}); + +export const listTitle = style({ + marginBottom: '1.6rem', + + fontSize: '2rem', + fontWeight: 600, +}); + +export const listDescription = style({ + fontSize: '1.5rem', + fontWeight: 500, + lineHeight: '25px', + color: '#909090', +}); + +export const listComponentTemporary = style({ + padding: '0 38px', + + height: '604px', +}); + +export const bottomWrapper = style({ + padding: '21px 24px', + + display: 'flex', + justifyContent: 'space-between', +}); + +export const bottomLeftWrapper = style({ + display: 'flex', + alignItems: 'center', + gap: '7px', +}); + +export const informationWrapper = style({ + display: 'flex', + flexDirection: 'column', + gap: '2px', +}); + +export const profileImage = style({ + width: '36px', + height: '36px', + + borderRadius: '9999px', + backgroundColor: '#909090', +}); + +export const listOwnerNickname = style({ + fontSize: '1.2rem', + fontWeight: 600, +}); + +export const infoDetailWrapper = style({ + display: 'flex', + gap: '7.5px', + + fontSize: '1rem', + color: '#909090', +}); + +export const collaboratorWrapper = style({ + display: 'flex', +}); diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/ListInformation.tsx b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/ListInformation.tsx new file mode 100644 index 00000000..4df16726 --- /dev/null +++ b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/ListInformation.tsx @@ -0,0 +1,55 @@ +import Image from 'next/image'; +import Label from '@/components/Label/Label'; +import Collaborators from '@/app/[userNickname]/[listId]/_components/ListDetailOuter/Collaborators'; +import timeDiff from '@/lib/utils/timeDiff'; +import * as styles from './ListInformation.css'; +import { MOCKDATA_LIST } from '../../mockData/mockdata'; + +const LIST = MOCKDATA_LIST[0]; + +function ListInformation() { + return ( + <> +
+
+
+ +
+ {LIST.labels.map((item, idx) => { + return ( +
+ +
+ ); + })} +
+
{LIST.title}
+
{LIST.description}
+
+
리스트 컴포넌트
+
+
+ 사용자 프로필 이미지 +
+
{LIST.ownerNickname}
+
+ {timeDiff(LIST.createdDate)} + {LIST.isPublic ? '공개' : '비공개'} +
+
+
+
+ +
+
+ + ); +} + +export default ListInformation; diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/ModalButtonStyle.css.ts b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/ModalButtonStyle.css.ts new file mode 100644 index 00000000..5fd1169d --- /dev/null +++ b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/ModalButtonStyle.css.ts @@ -0,0 +1,10 @@ +import { style } from '@vanilla-extract/css'; + +export const resetButtonStyle = style({ + background: 'none', +}); + +export const buttonCursor = style({ + cursor: 'pointer', + marginTop: '4px', +}); diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/OpenBottomSheetButton.tsx b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/OpenBottomSheetButton.tsx new file mode 100644 index 00000000..29f03497 --- /dev/null +++ b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/OpenBottomSheetButton.tsx @@ -0,0 +1,44 @@ +import useBooleanOutput from '@/hooks/useBooleanOutput'; +import BottomSheet from '@/app/[userNickname]/[listId]/_components/BottomSheet/BottomSheet'; +import KebabButton from '/public/icons/vertical_kebab_button.svg'; +import * as styles from './ModalButtonStyle.css'; + +export default function OpenBottomSheetButton({}) { + const { isOn, handleSetOff, handleSetOn } = useBooleanOutput(); //바텀시트 열림,닫힘 상태 관리 + const bottomSheetOptionList = [ + { + key: 'editList', + title: '리스트 수정하기', + onClick: () => { + handleEditClick(); + }, + }, + { + key: 'deleteList', + title: '리스트 삭제하기', + onClick: () => { + handleDeleteClick(); + }, + }, + ]; + + const handleEditClick = () => { + //확인버튼 클릭시 실행될 로직() + handleSetOff(); //닫기 + }; + + const handleDeleteClick = () => { + //확인버튼 클릭시 실행될 로직() + handleSetOff(); //닫기 + }; + + return ( + <> + + + {isOn && } + + ); +} diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/PopOverMenu.css.ts b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/PopOverMenu.css.ts new file mode 100644 index 00000000..7ea7c566 --- /dev/null +++ b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/PopOverMenu.css.ts @@ -0,0 +1,17 @@ +import { style } from '@vanilla-extract/css'; + +export const wrapper = style({ + padding: '10px', + + position: 'absolute', + top: '30px', + right: '3px', + + display: 'flex', + flexDirection: 'column', + gap: '20px', + + borderRadius: '8px', + backgroundColor: '#ffffff', + boxShadow: '0 0 10px 0.8px', +}); diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/PopOverMenu.tsx b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/PopOverMenu.tsx new file mode 100644 index 00000000..ea06e826 --- /dev/null +++ b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/PopOverMenu.tsx @@ -0,0 +1,14 @@ +'use client'; + +import * as styles from './PopOverMenu.css'; + +function PopOverMenu() { + return ( +
+ + +
+ ); +} + +export default PopOverMenu; diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Replies.css.ts b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Replies.css.ts new file mode 100644 index 00000000..f54b5d6d --- /dev/null +++ b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Replies.css.ts @@ -0,0 +1,72 @@ +import { style } from '@vanilla-extract/css'; +import './Comment.css'; + +export const repliesOuterWrapper = style({ + display: 'flex', + justifyContent: 'space-between', + marginBottom: '20px', +}); + +export const repliesWrapper = style({}); + +export const replyWrapper = style({ + marginLeft: '30px', + + display: 'flex', + gap: '8px', +}); + +export const showMoreRepliesWrapper = style({ + marginLeft: '30px', + marginBottom: '25px', + + display: 'flex', + alignItems: 'center', + gap: '3px', +}); + +export const showMoreReplies = style({ + fontSize: '1rem', +}); + +export const deleteButton = style({ + cursor: 'pointer', +}); + +export const profileImage = style({ + width: '30px', + minWidth: '30px', + height: '30px', + flex: '0 0 1', + + borderRadius: '16px', + backgroundColor: '#909090', +}); + +export const replyContainer = style({ + display: 'flex', + flexDirection: 'column', + gap: '5px', +}); + +export const replyInformationWrapper = style({ + display: 'flex', + alignItems: 'baseline', + gap: '8px', +}); + +export const replyWriter = style({ + fontSize: '1.2rem', + fontWeight: 600, +}); + +export const replyCreatedTime = style({ + fontSize: '1rem', + fontWeight: 500, + color: '#494949', +}); + +export const replyContent = style({ + fontSize: '1.2rem', + fontWeight: 500, +}); diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Replies.tsx b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Replies.tsx new file mode 100644 index 00000000..e2683188 --- /dev/null +++ b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Replies.tsx @@ -0,0 +1,60 @@ +'use client'; +import { useState } from 'react'; +import Image from 'next/image'; +import timeDiff from '@/lib/utils/timeDiff'; +import DeleteModalButton from '@/app/[userNickname]/[listId]/_components/ListDetailOuter/DeleteModalButton'; +import * as styles from './Replies.css'; +import Line from '/public/icons/horizontal_line.svg'; +import { ReplyType } from '../../mockData/mockdataType'; + +interface RepliesProps { + replies: ReplyType[] | null | undefined; +} + +function Replies({ replies }: RepliesProps) { + const [showReplies, setShowReplies] = useState(false); + + const handleShowReplies = () => { + setShowReplies((prev) => !prev); + }; + + return ( + <> + {replies && !showReplies && ( +
+ +
{`답글 ${replies?.length}개 더 보기`}
+
+ )} + {showReplies && ( +
    + {replies?.map((item: ReplyType) => { + return ( +
  • +
    + 사용자 프로필 이미지 +
    +
    + {item.userNickName} + {timeDiff(item.createdDate)} +
    +
    {item.content}
    +
    +
    + +
  • + ); + })} +
+ )} + + ); +} + +export default Replies; diff --git a/src/app/[userNickname]/[listId]/mockData/mockdata.ts b/src/app/[userNickname]/[listId]/mockData/mockdata.ts new file mode 100644 index 00000000..ee7a4a4e --- /dev/null +++ b/src/app/[userNickname]/[listId]/mockData/mockdata.ts @@ -0,0 +1,515 @@ +export const MOCKDATA_LIST = [ + { + category: '음악', + labels: [ + { + name: '세븐틴', + }, + { + name: '음악의 신', + }, + { + name: '최애 멤버', + }, + ], + title: '세븐틴 최애 멤버 Top 5', + description: + '내가 좋아하는 세븐틴에서 최애 멤버 top5 내가 좋아하는 세븐틴에서 최애 멤버 top5 내가 좋아하는 세븐틴에서 최애 멤버 top5 내가 좋아하는 세븐틴에서 최애 멤버 top5', + createdDate: '2022-01-15T11:00:05.817Z', + lastUpdatedDate: '2024-01-20T13:00:05.817Z', // 가장 마지막에 저장된 히스토리의 날짜 + ownerId: 'tarea202001@gmail.com', + ownerNickname: 'nabongee', + ownerProfileImageUrl: 'https://cdn.pixabay.com/photo/2020/05/17/20/21/cat-5183427_1280.jpg', + collaborators: [ + // Nullable + { + id: 1, + nickname: '현지', + profileImageUrl: null, + }, + { + id: 2, + nickname: '도우너', + profileImageUrl: 'https://cdn.pixabay.com/photo/2023/07/16/12/32/cat-8130611_640.jpg', + }, + { + id: 3, + nickname: '또치', + profileImageUrl: 'https://cdn.pixabay.com/photo/2023/07/16/12/32/cat-8130611_640.jpg', + }, + { + id: 4, + nickname: 'Samuel', + profileImageUrl: 'https://cdn.pixabay.com/photo/2023/07/16/12/32/cat-8130611_640.jpg', + }, + { + id: 5, + nickname: 'Clark', + profileImageUrl: 'https://cdn.pixabay.com/photo/2023/07/16/12/32/cat-8130611_640.jpg', + }, + { + id: 6, + nickname: '나봉이', + profileImageUrl: 'https://cdn.pixabay.com/photo/2023/07/16/12/32/cat-8130611_640.jpg', + }, + { + id: 7, + nickname: '미니언', + profileImageUrl: 'https://cdn.pixabay.com/photo/2023/07/16/12/32/cat-8130611_640.jpg', + }, + ], + items: [ + { + id: 1, + rank: 1, + title: '전원우', + comment: '전원우 존잘임', // Nullable + link: null, // Nullable + imageUrl: + 'https://yt3.googleusercontent.com/AmdlIs_zKRbg1LUzyDC2aQu9UHklGlWibXNVolKlgseHaCKEOLDESXNwYX0hQp2lSGJoQDBN=s900-c-k-c0x00ffffff-no-rj', // Nullable + }, + { + id: 2, + rank: 2, + title: '호시', + comment: '작은 아기 호랑이', + link: null, + imageUrl: 'https://image.bugsm.co.kr/artist/images/1000/802323/80232314.jpg', + }, + { + id: 3, + rank: 3, + title: '부승관', + comment: 'boo', + link: null, + imageUrl: + 'https://talkimg.imbc.com/TVianUpload/tvian/TViews/image/2023/10/10/33d10ea7-d2c4-4260-8381-cbf644c9972e.jpg', + }, + { + id: 4, + rank: 4, + title: '정한', + comment: '남신', + link: null, + imageUrl: 'https://news.kbs.co.kr/data/news/2022/04/13/20220413_c85vo7.jpg', + }, + { + id: 5, + rank: 5, + title: '디에잇', + comment: '웃김', + link: null, + imageUrl: + 'data:image/jpeg;base64,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', + }, + ], + isCollected: true, + isPublic: false, + backgroundColor: null, + collectCount: 34, + viewCount: 24, + }, + { + category: '장소', + labels: [ + { + name: '서울', + }, + { + name: '카페', + }, + { + name: '디저트', + }, + ], + title: '맛있는 을지로 카페 Top5', + description: '커피가 맛있는 서울 카페들', + createdDate: '2024-01-16T13:00:05.817Z', + lastUpdatedDate: '2024-01-23T13:00:05.817Z', // 가장 마지막에 저장된 히스토리의 날짜 + ownerId: 'tarea202001@gmail.com', + ownerNickname: 'nabongee', + ownerProfileImageUrl: 'https://cdn.pixabay.com/photo/2020/05/17/20/21/cat-5183427_1280.jpg', + collaborators: null, + items: [ + { + id: 1, + rank: 1, + title: '이프프커피', + comment: '을지로 크림카페, 티라미수 맛집', // Nullable + link: 'https://map.naver.com/p/search/%EC%9D%84%EC%A7%80%EB%A1%9C%20%EC%B9%B4%ED%8E%98/place/1113055605?c=15.00,0,0,0,dh&placePath=%3Fentry%3Dbmp', // Nullable + imageUrl: + 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20240125_142%2F1706170547004iEWpa_JPEG%2F%25BF%25A1%25BD%25BA%25C7%25C1%25B7%25B9%25BC%25D2_%25BA%25B9%25BB%25E7%25BA%25BB.jpg', // Nullable + }, + { + id: 2, + rank: 2, + title: '을지로 문덕 커피', + comment: '촙촙 바로 옆 건물 골목길 초입', + link: 'https://map.naver.com/p/search/%EC%9D%84%EC%A7%80%EB%A1%9C%20%EC%B9%B4%ED%8E%98/place/1046777005?c=15.00,0,0,0,dh&placePath=%3Fentry%3Dbmp', + imageUrl: + 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20220322_105%2F1647938541721kvaqf_JPEG%2FIMG_20220320_181609_849.jpg', + }, + { + id: 3, + rank: 3, + title: '보잉', + comment: '공항 컨셉 카페', + link: 'https://m.place.naver.com/share?id=1055469623&tabsPath=%2Fhome&appMode=detail', + imageUrl: + 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20240123_153%2F1705941421630Jj4f6_JPEG%2FIMG_3251.jpeg', + }, + { + id: 4, + rank: 4, + title: '공간갑', + comment: '공간이 갑이에요', + link: 'https://m.place.naver.com/share?id=1053282197&tabsPath=%2Fhome&appMode=detail', + imageUrl: + 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20210820_184%2F1629432280312yxh2Q_JPEG%2FUuWYISVR47gu08_pqiflKOPd.jpg', + }, + { + id: 5, + rank: 5, + title: '커피한약방', + comment: '허준 선생님의 혜민서를 개조한 카페', + link: 'https://naver.me/IIqf14Bp', + imageUrl: + 'https://search.pstatic.net/common/?src=https%3A%2F%2Fpup-review-phinf.pstatic.net%2FMjAyNDAxMjZfMTY0%2FMDAxNzA2MjM4NDU4NjU1.wUX3yX7JmXR1p9WgkkVfRHL4_KLEBbl25r3p3kLmKgkg.F8nRZinwmV_VxMXWbZ2MPcZPpcLzUxurYOTQrn6SFdgg.JPEG%2F763BC882-BB25-4788-809B-DBAED88B1652.jpeg', + }, + ], + isCollected: false, + isPublic: true, + backgroundColor: null, + collectCount: 10, + viewCount: 55, + }, + { + category: '음악', + labels: [ + { + name: '세븐틴', + }, + { + name: '음악의 신', + }, + { + name: '최애 멤버', + }, + ], + title: '세븐틴 최애 멤버 Top 5', + description: '세븐틴에서 최애 멤버 top5', + createdDate: '2024-01-15T13:00:05.817Z', + lastUpdatedDate: '2024-01-20T13:00:05.817Z', // 가장 마지막에 저장된 히스토리의 날짜 + ownerId: 'tarea202001@gmail.com', + ownerNickname: 'nabongee', + ownerProfileImageUrl: 'https://cdn.pixabay.com/photo/2020/05/17/20/21/cat-5183427_1280.jpg', + collaborators: [ + // Nullable + { + id: 1, + nickname: '현지', + profileImageUrl: 'https://cdn.pixabay.com/photo/2023/07/16/12/32/cat-8130611_640.jpg', + }, + ], + items: [ + { + id: 1, + rank: 1, + title: '전원우', + comment: '전원우 존잘임', // Nullable + link: null, // Nullable + imageUrl: + 'https://yt3.googleusercontent.com/AmdlIs_zKRbg1LUzyDC2aQu9UHklGlWibXNVolKlgseHaCKEOLDESXNwYX0hQp2lSGJoQDBN=s900-c-k-c0x00ffffff-no-rj', // Nullable + }, + { + id: 2, + rank: 2, + title: '호시', + comment: '작은 아기 호랑이', + link: null, + imageUrl: 'https://image.bugsm.co.kr/artist/images/1000/802323/80232314.jpg', + }, + { + id: 3, + rank: 3, + title: '부승관', + comment: 'boo', + link: null, + imageUrl: + 'https://talkimg.imbc.com/TVianUpload/tvian/TViews/image/2023/10/10/33d10ea7-d2c4-4260-8381-cbf644c9972e.jpg', + }, + { + id: 4, + rank: 4, + title: '정한', + comment: '남신', + link: null, + imageUrl: 'https://news.kbs.co.kr/data/news/2022/04/13/20220413_c85vo7.jpg', + }, + { + id: 5, + rank: 5, + title: '디에잇', + comment: '웃김', + link: null, + imageUrl: + 'data:image/jpeg;base64,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', + }, + ], + isCollected: false, + }, + { + category: '장소', + labels: [ + { + name: '서울', + }, + { + name: '카페', + }, + { + name: '디저트', + }, + ], + title: '맛있는 을지로 카페 Top5', + description: '커피가 맛있는 서울 카페들', + createdDate: '2024-01-16T13:00:05.817Z', + lastUpdatedDate: '2024-01-23T13:00:05.817Z', // 가장 마지막에 저장된 히스토리의 날짜 + ownerId: 'tarea202001@gmail.com', + ownerNickname: 'nabongee', + ownerProfileImageUrl: 'https://cdn.pixabay.com/photo/2020/05/17/20/21/cat-5183427_1280.jpg', + collaborators: null, + items: [ + { + id: 1, + rank: 1, + title: '이프프커피', + comment: '을지로 크림카페, 티라미수 맛집', // Nullable + link: 'https://map.naver.com/p/search/%EC%9D%84%EC%A7%80%EB%A1%9C%20%EC%B9%B4%ED%8E%98/place/1113055605?c=15.00,0,0,0,dh&placePath=%3Fentry%3Dbmp', // Nullable + imageUrl: + 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20240125_142%2F1706170547004iEWpa_JPEG%2F%25BF%25A1%25BD%25BA%25C7%25C1%25B7%25B9%25BC%25D2_%25BA%25B9%25BB%25E7%25BA%25BB.jpg', // Nullable + }, + { + id: 2, + rank: 2, + title: '을지로 문덕 커피', + comment: '촙촙 바로 옆 건물 골목길 초입', + link: 'https://map.naver.com/p/search/%EC%9D%84%EC%A7%80%EB%A1%9C%20%EC%B9%B4%ED%8E%98/place/1046777005?c=15.00,0,0,0,dh&placePath=%3Fentry%3Dbmp', + imageUrl: + 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20220322_105%2F1647938541721kvaqf_JPEG%2FIMG_20220320_181609_849.jpg', + }, + { + id: 3, + rank: 3, + title: '보잉', + comment: '공항 컨셉 카페', + link: 'https://m.place.naver.com/share?id=1055469623&tabsPath=%2Fhome&appMode=detail', + imageUrl: + 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20240123_153%2F1705941421630Jj4f6_JPEG%2FIMG_3251.jpeg', + }, + { + id: 4, + rank: 4, + title: '공간갑', + comment: '공간이 갑이에요', + link: 'https://m.place.naver.com/share?id=1053282197&tabsPath=%2Fhome&appMode=detail', + imageUrl: + 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20210820_184%2F1629432280312yxh2Q_JPEG%2FUuWYISVR47gu08_pqiflKOPd.jpg', + }, + { + id: 5, + rank: 5, + title: '커피한약방', + comment: '허준 선생님의 혜민서를 개조한 카페', + link: 'https://naver.me/IIqf14Bp', + imageUrl: + 'https://search.pstatic.net/common/?src=https%3A%2F%2Fpup-review-phinf.pstatic.net%2FMjAyNDAxMjZfMTY0%2FMDAxNzA2MjM4NDU4NjU1.wUX3yX7JmXR1p9WgkkVfRHL4_KLEBbl25r3p3kLmKgkg.F8nRZinwmV_VxMXWbZ2MPcZPpcLzUxurYOTQrn6SFdgg.JPEG%2F763BC882-BB25-4788-809B-DBAED88B1652.jpeg', + }, + ], + isCollected: false, + }, + { + category: '장소', + labels: [ + { + name: '청라', + }, + { + name: '맛집', + }, + ], + title: '호주에서 갓 귀국한 내가 가고싶은 청라 맛집 Top 7', + description: '청라에서 제일 가고싶은 맛집 순위', + createdDate: '2024-01-15T13:00:05.817Z', + lastUpdatedDate: '2024-01-20T13:00:05.817Z', // 가장 마지막에 저장된 히스토리의 날짜 + ownerId: '', + ownerNickname: 'minchi', + ownerProfileImageUrl: 'https://www.ynow.co.kr/data/photos/20230728/art_16894886847033_93d9b7.png', + collaborators: [ + // Nullable + { + id: 1, + nickname: 'nabongee', + profileImageUrl: 'https://cdn.pixabay.com/photo/2020/05/17/20/21/cat-5183427_1280.jpg', + }, + ], + items: [ + { + id: 1, + rank: 1, + title: '우리동네쭈꾸미', + comment: '쭈꾸미 존맛', // Nullable + link: 'https://naver.me/Gi9EzhJA', // Nullable + imageUrl: + 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20180326_180%2F1522025612368rCHNT_JPEG%2FtR5PurBnequRELzopz7GEM1Z.jpg', // Nullable + }, + { + id: 2, + rank: 2, + title: '명태어장', + comment: '명태 존맛집', + link: 'https://naver.me/GJrF376V', + imageUrl: + 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20171107_168%2F1510060558275xNH3k_JPEG%2F_6kwBr6aasuwJKu7m1QGzdvO.jpg', + }, + { + id: 3, + rank: 3, + title: '한촌설렁탕', + comment: '설렁탕 맛집', + link: 'https://naver.me/FzH4YkvZ', + imageUrl: + 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20211118_275%2F16372277273592SGLC_JPEG%2FKakaoTalk_20211118_182752353_01.jpg', + }, + { + id: 4, + rank: 4, + title: '양양입암막국수', + comment: '막국수 존맛집', + link: 'https://naver.me/FiLOFLrN', + imageUrl: + 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20190617_242%2F1560762962654Ezyom_JPEG%2F6dB5oiAUzHvqsq3j5aDWiG4p.jpg', + }, + { + id: 5, + rank: 5, + title: '진천토종순대국', + comment: '순대국 존맛집', + link: 'https://naver.me/F9QppBBM', + imageUrl: + 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20180904_125%2F1536063146640tTQ9B_JPEG%2Fopi3rrEu2yGDbmLTLAjE451c.jpg', + }, + { + id: 6, + rank: 6, + title: '우리할매떡볶이', + comment: '떡볶이는 여기서만 먹어', + link: 'https://naver.me/x50vcmB9', + imageUrl: + 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20210517_241%2F16212167904568orwP_JPEG%2FAT_ddo48d0TcgCi1tyyp_sQb.jpg', + }, + { + id: 7, + rank: 7, + title: '신간짬뽕', + comment: '간짬뽕 존맛탱', + link: 'https://naver.me/GWozhL8h', + imageUrl: + 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20220218_274%2F1645146029164uJlrY_JPEG%2FKakaoTalk_20220218_091621489.jpg', + }, + ], + isCollected: true, + }, +]; + +export const MOCKDATA_COMMENTS = [ + { + totalCount: 2, + comments: [ + { + id: 1, + userId: 24, + userName: '전원우 아내', + userProfileImageUrl: 'https://img.hankyung.com/photo/202006/03.22990548.1.jpg', + createdDate: '2024-01-25T06:50:12.962Z', + content: '전원우 고앵이!', + replies: [ + { + id: 3, + userId: 5, + userNickName: '나현', + userProfileImageUrl: 'https://cdn.pixabay.com/photo/2020/05/17/20/21/cat-5183427_1280.jpg', + createdDate: '2024-01-25T18:50:12.962Z', + content: '애기 고양이', + }, + ], + }, + ], + }, + { + totalCount: 5, + comments: [ + { + id: 2, + userId: 36, + userName: '승화', + userProfileImageUrl: + 'https://yt3.googleusercontent.com/mFpxH8XLL_uupbtB1U489iAss84q-Phzdz9hZBZcxtQvlKH292PdCwtfd6Lf6YwCQHbtjvhSCXY=s900-c-k-c0x00ffffff-no-rj', + createdDate: '2024-01-24T06:50:12.962Z', + content: '여기 저도 너무 좋아하는 곳들이에요~~', + replies: [ + { + id: 3, + userId: 5, + userNickName: '나현', + userProfileImageUrl: 'https://cdn.pixabay.com/photo/2020/05/17/20/21/cat-5183427_1280.jpg', + createdDate: '2024-01-25T18:50:12.962Z', + content: '매니저님~ 혹시 여기 말고 또 추천해주실 카페 있으신가요?~', + }, + { + id: 4, + userId: 36, + userNickName: '승화', + userProfileImageUrl: + 'https://yt3.googleusercontent.com/mFpxH8XLL_uupbtB1U489iAss84q-Phzdz9hZBZcxtQvlKH292PdCwtfd6Lf6YwCQHbtjvhSCXY=s900-c-k-c0x00ffffff-no-rj', + createdDate: '2024-01-26T18:50:12.962Z', + content: '물론이죠~ 00이라는 카페에 가보세요!!', + }, + ], + }, + { + id: 5, + userId: 37, + userName: '유진', + userProfileImageUrl: + 'https://yt3.googleusercontent.com/mFpxH8XLL_uupbtB1U489iAss84q-Phzdz9hZBZcxtQvlKH292PdCwtfd6Lf6YwCQHbtjvhSCXY=s900-c-k-c0x00ffffff-no-rj', + createdDate: '2024-01-25T06:50:12.962Z', + content: '저 더 맛있는 데 알고 있어요~~', + replies: [ + { + id: 3, + userId: 5, + userNickName: '나현', + userProfileImageUrl: 'https://cdn.pixabay.com/photo/2020/05/17/20/21/cat-5183427_1280.jpg', + createdDate: '2024-01-25T18:50:12.962Z', + content: '어디에요 당장 공유해주세요', + }, + ], + }, + ], + }, + { + totalCount: 1, + comments: [ + { + id: 2, + userId: 36, + userName: '나현', + userProfileImageUrl: 'https://cdn.pixabay.com/photo/2020/05/17/20/21/cat-5183427_1280.jpg', + createdDate: '2024-01-24T06:50:12.962Z', + content: '털기 성공??', + replies: null, + }, + ], + }, +]; diff --git a/src/app/[userNickname]/[listId]/mockData/mockdataType.ts b/src/app/[userNickname]/[listId]/mockData/mockdataType.ts new file mode 100644 index 00000000..7c989b6d --- /dev/null +++ b/src/app/[userNickname]/[listId]/mockData/mockdataType.ts @@ -0,0 +1,33 @@ +export interface RepliesType { + id: number; + userId: number; + userNickName: string; + userProfileImageUrl: string; + createdDate: string; + content: string; +} + +export interface ReplyType { + id: number; + userId: number; + userNickName: string; + userProfileImageUrl: string; + createdDate: string; + content: string; +} + +export interface CommentType { + id: number; + userId: number; + userName: string; + userProfileImageUrl: string; + createdDate: string; + content: string; + replies: RepliesType[] | null; +} + +export interface CollaboratorType { + id?: number; + nickname: string; + profileImageUrl: string | null; +} diff --git a/src/app/[userNickname]/[listId]/page.tsx b/src/app/[userNickname]/[listId]/page.tsx new file mode 100644 index 00000000..e499e160 --- /dev/null +++ b/src/app/[userNickname]/[listId]/page.tsx @@ -0,0 +1,15 @@ +'use client'; +import * as styles from './ListDetail.css'; +import Comments from '@/app/[userNickname]/[listId]/_components/ListDetailOuter/Comments'; +import Header from '@/app/[userNickname]/[listId]/_components/ListDetailOuter/Header'; +import ListInformation from '@/app/[userNickname]/[listId]/_components/ListDetailOuter/ListInformation'; + +export default function ListDetail() { + return ( +
+
+ + +
+ ); +} diff --git a/src/app/[userNickname]/collabolist/page.tsx b/src/app/[userNickname]/collabolist/page.tsx index 02058891..7e772a2d 100644 --- a/src/app/[userNickname]/collabolist/page.tsx +++ b/src/app/[userNickname]/collabolist/page.tsx @@ -5,7 +5,7 @@ - [ ] 반응형 UI 구현 */ -import '@/styles/globalStyles.css'; +import '@/styles/GlobalStyles.css'; import { USER_DATA_ME } from '../mockData/user'; // 삭제 예정 diff --git a/src/app/[userNickname]/mylist/page.tsx b/src/app/[userNickname]/mylist/page.tsx index d4feb866..7a30978e 100644 --- a/src/app/[userNickname]/mylist/page.tsx +++ b/src/app/[userNickname]/mylist/page.tsx @@ -5,7 +5,7 @@ - [ ] 반응형 UI 구현 */ -import '@/styles/globalStyles.css'; +import '@/styles/GlobalStyles.css'; import { USER_DATA_ME } from '../mockData/user'; // 삭제 예정 diff --git a/src/app/_api/category/getCategories.ts b/src/app/_api/category/getCategories.ts index 637001e9..816c8f7d 100644 --- a/src/app/_api/category/getCategories.ts +++ b/src/app/_api/category/getCategories.ts @@ -1,8 +1,8 @@ import axiosInstance from '@/lib/axios/axiosInstance'; -import { CategoriesType } from '@/lib/types/categoriesType'; +import { CategoryType } from '@/lib/types/categoriesType'; export const getCategories = async () => { - const response = await axiosInstance.get('/categories'); + const response = await axiosInstance.get('/categories'); return response.data; }; diff --git a/src/app/_api/list/createList.ts b/src/app/_api/list/createList.ts index 99d78ac3..28e0a8dc 100644 --- a/src/app/_api/list/createList.ts +++ b/src/app/_api/list/createList.ts @@ -1 +1,8 @@ -// 리스트 생성 api +import axiosInstance from '@/lib/axios/axiosInstance'; +import { ListCreateType } from '@/lib/types/listType'; + +export const createList = async (data: ListCreateType) => { + const response = await axiosInstance.post('/lists', data); + + return response.data; +}; diff --git a/src/app/_api/user/getUsers.ts b/src/app/_api/user/getUsers.ts new file mode 100644 index 00000000..73b94d11 --- /dev/null +++ b/src/app/_api/user/getUsers.ts @@ -0,0 +1,8 @@ +import axiosInstance from '@/lib/axios/axiosInstance'; +import { UserProfilesType } from '@/lib/types/userProfileType'; + +export const getUsers = async () => { + const response = await axiosInstance.get('/users'); + + return response.data; +}; diff --git a/src/app/create/_components/CreateItem.tsx b/src/app/create/_components/CreateItem.tsx index 4d231fe8..ae19a3f3 100644 --- a/src/app/create/_components/CreateItem.tsx +++ b/src/app/create/_components/CreateItem.tsx @@ -1,7 +1,7 @@ import { useFormContext } from 'react-hook-form'; import BackIcon from '/public/icons/back.svg'; -import Items from './Items'; +import Items from './item/Items'; import * as styles from './CreateItem.css'; interface CreateItemProps { @@ -23,7 +23,7 @@ export default function CreateItem({ onBackClick, onSubmitClick }: CreateItemPro

리스트 생성

- +
- {/* 제목 */} -
-
- 타이틀 * -
-
-
- - { - setValue('title', ''); - }} - /> - {errors.title &&
{errors.title.message?.toString()}
} -
-
-
- - {/* 한 줄 소개 */} -
-
소개
-
-
-