diff --git a/src/app/_api/collect/getCollection.ts b/src/app/_api/collect/getCollection.ts new file mode 100644 index 00000000..3420942d --- /dev/null +++ b/src/app/_api/collect/getCollection.ts @@ -0,0 +1,29 @@ +import axiosInstance from '@/lib/axios/axiosInstance'; +import { CollectionType } from '@/lib/types/listType'; + +interface GetCollectionType { + folderId: string; + cursorId?: number; +} + +interface ResponseType { + collectionLists: CollectionType[]; + cursorId: number; + hasNext: boolean; +} + +async function getCollection({ folderId, cursorId }: GetCollectionType) { + const params = new URLSearchParams({ + size: '8', + }); + + if (cursorId) { + params.append('cursorId', cursorId.toString()); + } + + const response = await axiosInstance.get(`/folder/${folderId}/collections?${params.toString()}`); + + return response.data; +} + +export default getCollection; diff --git a/src/app/_api/folder/deleteFolder.ts b/src/app/_api/folder/deleteFolder.ts new file mode 100644 index 00000000..32541cf8 --- /dev/null +++ b/src/app/_api/folder/deleteFolder.ts @@ -0,0 +1,7 @@ +import axiosInstance from '@/lib/axios/axiosInstance'; + +const deleteFolder = async (folderId: string) => { + await axiosInstance.delete(`/folders/${folderId}`); +}; + +export default deleteFolder; diff --git a/src/app/_api/folder/updateFolder.ts b/src/app/_api/folder/updateFolder.ts new file mode 100644 index 00000000..0bfcce21 --- /dev/null +++ b/src/app/_api/folder/updateFolder.ts @@ -0,0 +1,12 @@ +import axiosInstance from '@/lib/axios/axiosInstance'; + +interface CollectionFolderProps { + folderId: string; + folderName: string; +} + +const updateCollectionFolder = async ({ folderId, folderName }: CollectionFolderProps) => { + await axiosInstance.put(`/folders/${folderId}`, { folderName }); +}; + +export default updateCollectionFolder; diff --git a/src/app/collection/[folderId]/_components/Collections.css.ts b/src/app/collection/[folderId]/_components/Collections.css.ts new file mode 100644 index 00000000..3e29718c --- /dev/null +++ b/src/app/collection/[folderId]/_components/Collections.css.ts @@ -0,0 +1,13 @@ +import { style } from '@vanilla-extract/css'; +import { vars } from '@/styles/theme.css'; + +export const container = style({ + padding: '2.4rem 1.6rem', + display: 'grid', + gridTemplateColumns: '1fr 1fr', + gridTemplateRows: 'max-content', + gridGap: 12, + alignContent: 'flex-start', + justifyItems: 'center', + backgroundColor: vars.color.bggray, +}); diff --git a/src/app/collection/[folderId]/_components/Collections.tsx b/src/app/collection/[folderId]/_components/Collections.tsx new file mode 100644 index 00000000..dec07b32 --- /dev/null +++ b/src/app/collection/[folderId]/_components/Collections.tsx @@ -0,0 +1,31 @@ +'use client'; + +import { useQuery } from '@tanstack/react-query'; + +import * as styles from './Collections.css'; + +import ShapeSimpleList from '@/components/ShapeSimpleList/ShapeSimpleList'; +import getCollection from '@/app/_api/collect/getCollection'; +import { QUERY_KEYS } from '@/lib/constants/queryKeys'; + +interface CollectionsProps { + folderId: string; +} + +// TODO 무한스크롤 + +export default function Collections({ folderId }: CollectionsProps) { + const { data } = useQuery({ + queryKey: [QUERY_KEYS.getCollection], + queryFn: () => getCollection({ folderId }), + }); + + return ( + + ); +} diff --git a/src/app/collection/[folderId]/_components/HeaderContainer.tsx b/src/app/collection/[folderId]/_components/HeaderContainer.tsx new file mode 100644 index 00000000..e082c453 --- /dev/null +++ b/src/app/collection/[folderId]/_components/HeaderContainer.tsx @@ -0,0 +1,42 @@ +'use client'; + +import BottomSheet from '@/components/BottomSheet/BottomSheet'; +import Header from '@/components/Header/Header'; + +import useBooleanOutput from '@/hooks/useBooleanOutput'; + +interface HeaderContainerProps { + handleSetOnBottomSheet: () => void; + handleSetOnDeleteOption: () => void; +} + +export default function HeaderContainer({ handleSetOnBottomSheet, handleSetOnDeleteOption }: HeaderContainerProps) { + const { isOn, handleSetOn, handleSetOff } = useBooleanOutput(); + + const bottomSheetOptionList = [ + { + key: 'editFolder', + title: '폴더 이름 바꾸기', // TODO locale 적용 + onClick: handleSetOnBottomSheet, + }, + { + key: 'deleteFolder', + title: '폴더 삭제하기', + onClick: handleSetOnDeleteOption, + }, + ]; + + const RightButton = () => { + const handleClickOption = () => { + handleSetOn(); + }; + return ; + }; + + return ( + <> +
} leftClick={() => history.back()} /> + {isOn && } + + ); +} diff --git a/src/app/collection/[folderId]/page.css.ts b/src/app/collection/[folderId]/page.css.ts new file mode 100644 index 00000000..24faafab --- /dev/null +++ b/src/app/collection/[folderId]/page.css.ts @@ -0,0 +1,34 @@ +import { style } from '@vanilla-extract/css'; +import { vars } from '@/styles/theme.css'; +import { Subtitle } from '@/styles/font.css'; + +export const container = style({ + height: '100vh', + backgroundColor: vars.color.bggray, +}); + +// BottomSheet Input +export const contentInput = style({ + padding: '2rem 2.4rem', + backgroundColor: '#F5F6FA', + borderRadius: 18, + + color: vars.color.black, + fontSize: '1.6rem', + fontWeight: 400, +}); + +// BottomSheet Description +export const content = style([ + Subtitle, + { + paddingTop: 30, + fontWeight: 600, + color: vars.color.black, + + display: 'flex', + flexDirection: 'column', + gap: 8, + alignItems: 'center', + }, +]); diff --git a/src/app/collection/[folderId]/page.tsx b/src/app/collection/[folderId]/page.tsx new file mode 100644 index 00000000..b2d5a44a --- /dev/null +++ b/src/app/collection/[folderId]/page.tsx @@ -0,0 +1,130 @@ +'use client'; + +import { useRouter } from 'next/navigation'; +import { ChangeEvent, useState } from 'react'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { isAxiosError } from 'axios'; + +import HeaderContainer from './_components/HeaderContainer'; +import Collections from './_components/Collections'; +import BottomSheet from '@/components/BottomSheet/ver3.0/BottomSheet'; + +import * as styles from './page.css'; + +import useBooleanOutput from '@/hooks/useBooleanOutput'; +import { useLanguage } from '@/store/useLanguage'; +import toasting from '@/lib/utils/toasting'; +import toastMessage from '@/lib/constants/toastMessage'; +import { QUERY_KEYS } from '@/lib/constants/queryKeys'; +import updateCollectionFolder from '@/app/_api/folder/updateFolder'; +import deleteFolder from '@/app/_api/folder/deleteFolder'; + +interface ParamType { + params: { folderId: string }; +} + +// TODO API에 FolderName 필드 추가 요청 => input value에 보여주기 & 헤더 타이틀 +export default function CollectionDetailPage({ params }: ParamType) { + const folderId = params.folderId; + const { isOn, handleSetOn, handleSetOff } = useBooleanOutput(); + const { + isOn: isDeleteOption, + handleSetOn: handleSetOnDeleteOption, + handleSetOff: handleSetOffDeleteOption, + } = useBooleanOutput(); + const queryClient = useQueryClient(); + const { language } = useLanguage(); + const router = useRouter(); + + const [value, setValue] = useState(''); + + // 폴더 수정하기 mutation + const editFolderMutation = useMutation({ + mutationFn: updateCollectionFolder, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.getFolders] }); + setValue(''); + handleSetOff(); + }, + onError: (error) => { + if (isAxiosError(error)) { + const errorData = error.response?.data; + if (errorData.error === 'UNAUTHORIZED') { + toasting({ type: 'error', txt: toastMessage[language].requiredLogin }); + return; + } + if (errorData.code.split('_')[0] === 'DUPLICATE') { + toasting({ type: 'error', txt: toastMessage[language].duplicatedFolderName }); + return; + } + } + toasting({ type: 'error', txt: toastMessage[language].failedFolder }); + }, + }); + + // 폴더 삭제하기 mutation + const deleteFolderMutation = useMutation({ + mutationFn: deleteFolder, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.getFolders] }); + handleSetOffDeleteOption(); + router.push('/collection'); + }, + onError: (error) => { + if (isAxiosError(error)) { + const errorData = error.response?.data; + if (errorData.error === 'UNAUTHORIZED') { + toasting({ type: 'error', txt: toastMessage[language].requiredLogin }); + return; + } + } + }, + }); + + const handleChangeInput = (e: ChangeEvent) => { + setValue(e.target.value); + }; + + const handleEditFolder = () => { + if (!value.trim()) { + toasting({ type: 'warning', txt: toastMessage[language].emptyFolderName }); + return; + } + editFolderMutation.mutate({ folderId, folderName: value }); + }; + + const handleDeleteFolder = () => { + deleteFolderMutation.mutate(folderId); + }; + + return ( +
+ + + + 폴더 이름 바꾸기 + + + {['취소', '만들기']} + + + + + + {['취소', '삭제']} + + +
+ ); +} diff --git a/src/app/collection/[category]/_components/NoData.css.ts b/src/app/collection/_[category]/_components/NoData.css.ts similarity index 100% rename from src/app/collection/[category]/_components/NoData.css.ts rename to src/app/collection/_[category]/_components/NoData.css.ts diff --git a/src/app/collection/[category]/_components/NoData.tsx b/src/app/collection/_[category]/_components/NoData.tsx similarity index 100% rename from src/app/collection/[category]/_components/NoData.tsx rename to src/app/collection/_[category]/_components/NoData.tsx diff --git a/src/app/collection/[category]/_components/Top3Card.css.ts b/src/app/collection/_[category]/_components/Top3Card.css.ts similarity index 100% rename from src/app/collection/[category]/_components/Top3Card.css.ts rename to src/app/collection/_[category]/_components/Top3Card.css.ts diff --git a/src/app/collection/[category]/_components/Top3Card.tsx b/src/app/collection/_[category]/_components/Top3Card.tsx similarity index 100% rename from src/app/collection/[category]/_components/Top3Card.tsx rename to src/app/collection/_[category]/_components/Top3Card.tsx diff --git a/src/app/collection/[category]/_components/Top3CardItem.css.ts b/src/app/collection/_[category]/_components/Top3CardItem.css.ts similarity index 100% rename from src/app/collection/[category]/_components/Top3CardItem.css.ts rename to src/app/collection/_[category]/_components/Top3CardItem.css.ts diff --git a/src/app/collection/[category]/_components/Top3CardItem.tsx b/src/app/collection/_[category]/_components/Top3CardItem.tsx similarity index 100% rename from src/app/collection/[category]/_components/Top3CardItem.tsx rename to src/app/collection/_[category]/_components/Top3CardItem.tsx diff --git a/src/app/collection/[category]/_components/Top3CardSkeleton.tsx b/src/app/collection/_[category]/_components/Top3CardSkeleton.tsx similarity index 100% rename from src/app/collection/[category]/_components/Top3CardSkeleton.tsx rename to src/app/collection/_[category]/_components/Top3CardSkeleton.tsx diff --git a/src/app/collection/[category]/page.css.ts b/src/app/collection/_[category]/page.css.ts similarity index 100% rename from src/app/collection/[category]/page.css.ts rename to src/app/collection/_[category]/page.css.ts diff --git a/src/app/collection/[category]/page.tsx b/src/app/collection/_[category]/page.tsx similarity index 93% rename from src/app/collection/[category]/page.tsx rename to src/app/collection/_[category]/page.tsx index fd29dcb0..a55c528b 100644 --- a/src/app/collection/[category]/page.tsx +++ b/src/app/collection/_[category]/page.tsx @@ -9,10 +9,10 @@ import { QUERY_KEYS } from '@/lib/constants/queryKeys'; import { useEffect, useMemo } from 'react'; import useIntersectionObserver from '@/hooks/useIntersectionObserver'; import getCollection from '@/app/_api/collect/__getCollection'; -import Top3CardSkeleton from '@/app/collection/[category]/_components/Top3CardSkeleton'; -import NoData from '@/app/collection/[category]/_components/NoData'; +import Top3CardSkeleton from '@/app/collection/_[category]/_components/Top3CardSkeleton'; +import NoData from '@/app/collection/_[category]/_components/NoData'; import { CollectionType } from '@/lib/types/listType'; -import Top3Card from '@/app/collection/[category]/_components/Top3Card'; +import Top3Card from '@/app/collection/_[category]/_components/Top3Card'; import { categoriesLocale } from '@/app/collection/locale'; import { useLanguage } from '@/store/useLanguage'; diff --git a/src/app/collection/_components/BottomSheet.tsx b/src/app/collection/_components/BottomSheet.tsx deleted file mode 100644 index 4511e5fa..00000000 --- a/src/app/collection/_components/BottomSheet.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { ChangeEvent, useState } from 'react'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { isAxiosError } from 'axios'; - -import * as styles from './BottomSheet.css'; -import createCollectionFolder from '@/app/_api/folder/createFolder'; -import toasting from '@/lib/utils/toasting'; -import toastMessage from '@/lib/constants/toastMessage'; -import { useLanguage } from '@/store/useLanguage'; -import Modal from '@/components/Modal/Modal'; -import LoginModal from '@/components/login/LoginModal'; -import useBooleanOutput from '@/hooks/useBooleanOutput'; -import { QUERY_KEYS } from '@/lib/constants/queryKeys'; - -interface BottomSheetProps { - isOn: boolean; - onClose: () => void; -} - -export default function BottomSheet({ isOn, onClose }: BottomSheetProps) { - const queryClient = useQueryClient(); - const { language } = useLanguage(); - const { isOn: isLoginModalOn, handleSetOn: loginModalOn, handleSetOff: loginModalOff } = useBooleanOutput(); - - const [value, setValue] = useState(''); - - const createFolderMutation = useMutation({ - mutationFn: createCollectionFolder, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.getFolders] }); - setValue(''); - onClose(); - }, - onError: (error) => { - if (isAxiosError(error)) { - const errorData = error.response?.data; - if (errorData.error === 'UNAUTHORIZED') { - loginModalOn(); - return; - } - if (errorData.code.split('_')[0] === 'DUPLICATE') { - toasting({ type: 'error', txt: toastMessage[language].duplicatedFolderName }); - return; - } - } - toasting({ type: 'error', txt: toastMessage[language].failedFolder }); - }, - }); - - const handleChangeInput = (e: ChangeEvent) => { - setValue(e.target.value); - }; - - const handleCreateFolder = () => { - if (!value.trim()) { - toasting({ type: 'warning', txt: toastMessage[language].emptyFolderName }); - return; - } - createFolderMutation.mutate({ - folderName: value, - }); - }; - - return ( - <> -
-
-

새로운 폴더

- -
- - -
-
-
- - {isLoginModalOn && ( - - - - )} - - ); -} diff --git a/src/app/collection/page.css.ts b/src/app/collection/page.css.ts index 4ea677ab..cdbf7c00 100644 --- a/src/app/collection/page.css.ts +++ b/src/app/collection/page.css.ts @@ -2,14 +2,18 @@ import { style } from '@vanilla-extract/css'; import { vars } from '@/styles/theme.css'; import { Label } from '@/styles/font.css'; +export const wrapper = style({ + height: '100vh', +}); + export const container = style({ position: 'relative', - paddingBottom: 83, height: 'calc(100% - 70px)', + backgroundColor: vars.color.bggray, }); export const folders = style({ - padding: '2.4rem 4.8rem', + padding: '2.4rem 4.8rem 8.3rem 4.8rem', display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gridColumnGap: 34, @@ -123,3 +127,14 @@ export const addFolderButton = style({ fontSize: '1.6rem', fontWeight: 700, }); + +// BottomSheet Input +export const contentInput = style({ + padding: '2rem 2.4rem', + backgroundColor: '#F5F6FA', + borderRadius: 18, + + color: vars.color.black, + fontSize: '1.6rem', + fontWeight: 400, +}); diff --git a/src/app/collection/page.tsx b/src/app/collection/page.tsx index d2374bc5..0c3fd0d8 100644 --- a/src/app/collection/page.tsx +++ b/src/app/collection/page.tsx @@ -1,19 +1,33 @@ 'use client'; +import Link from 'next/link'; import Image from 'next/image'; import { useRouter } from 'next/navigation'; -import { useQuery } from '@tanstack/react-query'; +import { ChangeEvent, useState } from 'react'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { isAxiosError } from 'axios'; import * as styles from './page.css'; import Header from '@/components/Header/Header'; -import BottomSheet from './_components/BottomSheet'; +import BottomSheet from '@/components/BottomSheet/ver3.0/BottomSheet'; + import useBooleanOutput from '@/hooks/useBooleanOutput'; +import { useLanguage } from '@/store/useLanguage'; +import { useUser } from '@/store/useUser'; + +import createCollectionFolder from '../_api/folder/createFolder'; import getFolders, { FoldersResponseType } from '../_api/folder/getFolders'; + +import toasting from '@/lib/utils/toasting'; +import toastMessage from '@/lib/constants/toastMessage'; import { QUERY_KEYS } from '@/lib/constants/queryKeys'; export default function CollectionPage() { const router = useRouter(); + const { user: userMe } = useUser(); + const queryClient = useQueryClient(); + const { language } = useLanguage(); const { data } = useQuery({ queryKey: [QUERY_KEYS.getFolders], queryFn: getFolders, @@ -21,14 +35,52 @@ export default function CollectionPage() { }); const { isOn, handleSetOn, handleSetOff } = useBooleanOutput(false); + const [value, setValue] = useState(''); + + const createFolderMutation = useMutation({ + mutationFn: createCollectionFolder, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.getFolders] }); + setValue(''); + handleSetOff(); + }, + onError: (error) => { + if (isAxiosError(error)) { + const errorData = error.response?.data; + if (errorData.error === 'UNAUTHORIZED') { + toasting({ type: 'error', txt: toastMessage[language].requiredLogin }); + return; + } + if (errorData.code.split('_')[0] === 'DUPLICATE') { + toasting({ type: 'error', txt: toastMessage[language].duplicatedFolderName }); + return; + } + } + toasting({ type: 'error', txt: toastMessage[language].failedFolder }); + }, + }); + + const handleChangeInput = (e: ChangeEvent) => { + setValue(e.target.value); + }; + + const handleCreateFolder = () => { + if (!value.trim()) { + toasting({ type: 'warning', txt: toastMessage[language].emptyFolderName }); + return; + } + createFolderMutation.mutate({ + folderName: value, + }); + }; return ( -
-
router.back()} /> +
+
router.push(`/user/${userMe.id}/mylist`)} />
{data?.folders.map((folder) => ( -
+
@@ -38,7 +90,7 @@ export default function CollectionPage() { {folder.folderName} {`(${folder.listCount})`}

-
+ ))}
@@ -48,7 +100,19 @@ export default function CollectionPage() {
- + + 새로운 폴더 + + + {['취소', '만들기']} + +
); } diff --git a/src/app/collection/_components/BottomSheet.css.ts b/src/components/BottomSheet/ver3.0/BottomSheet.css.ts similarity index 78% rename from src/app/collection/_components/BottomSheet.css.ts rename to src/components/BottomSheet/ver3.0/BottomSheet.css.ts index 919ab11d..a95657e0 100644 --- a/src/app/collection/_components/BottomSheet.css.ts +++ b/src/components/BottomSheet/ver3.0/BottomSheet.css.ts @@ -2,6 +2,7 @@ import { style, styleVariants } from '@vanilla-extract/css'; import { vars } from '@/styles/theme.css'; import { Subtitle } from '@/styles/font.css'; +// BottomSheet Container const containerStyle = style({ position: 'fixed', bottom: 0, @@ -26,6 +27,7 @@ export const container = styleVariants({ close: [containerStyle, { height: 0.5, opacity: 0 }], }); +// BottomSheet Inner Content Warpper export const contents = style({ paddingTop: '3.3rem', paddingLeft: '2rem', @@ -39,6 +41,7 @@ export const contents = style({ gap: 12, }); +// BottomSheet Title export const contentTitle = style([ Subtitle, { @@ -47,16 +50,22 @@ export const contentTitle = style([ }, ]); -export const contentInput = style({ - padding: '2rem 2.4rem', - backgroundColor: '#F5F6FA', - borderRadius: 18, +// BottomSheet Description +export const content = style([ + Subtitle, + { + paddingTop: 30, + fontWeight: 600, + color: vars.color.black, - color: vars.color.black, - fontSize: '1.6rem', - fontWeight: 400, -}); + display: 'flex', + flexDirection: 'column', + gap: 8, + alignItems: 'center', + }, +]); +// BottomSheet Button export const optionButtons = style({ margin: 12, display: 'flex', @@ -79,4 +88,5 @@ const button = style({ export const variantButton = styleVariants({ default: [button], active: [button, { color: vars.color.blue }], + delete: [button, { color: vars.color.red }], }); diff --git a/src/components/BottomSheet/ver3.0/BottomSheet.tsx b/src/components/BottomSheet/ver3.0/BottomSheet.tsx new file mode 100644 index 00000000..97c757b7 --- /dev/null +++ b/src/components/BottomSheet/ver3.0/BottomSheet.tsx @@ -0,0 +1,28 @@ +import { ReactNode } from 'react'; + +import * as styles from './BottomSheet.css'; + +import BottomSheetTitle from './BottomSheetTitle'; +import BottomSheetContent from './BottomSheetContent'; +import BottomSheetButton from './BottomSheetButton'; + +interface BottomSheetProps { + isOn: boolean; + children: ReactNode; +} + +function FolderBottomSheet({ isOn, children }: BottomSheetProps) { + return ( +
+
{children}
+
+ ); +} + +const BottomSheet = Object.assign(FolderBottomSheet, { + Title: BottomSheetTitle, + Content: BottomSheetContent, + Button: BottomSheetButton, +}); + +export default BottomSheet; diff --git a/src/components/BottomSheet/ver3.0/BottomSheetButton.tsx b/src/components/BottomSheet/ver3.0/BottomSheetButton.tsx new file mode 100644 index 00000000..b64ab5ce --- /dev/null +++ b/src/components/BottomSheet/ver3.0/BottomSheetButton.tsx @@ -0,0 +1,23 @@ +import * as styles from './BottomSheet.css'; + +interface BottomSheetButtonProps { + onClose: () => void; + onClick: () => void; + children: [string, string]; + isDelete?: boolean; +} + +export default function BottomSheetButton({ onClose, onClick, children, isDelete = false }: BottomSheetButtonProps) { + const [cancelText, actionText] = children; + + return ( +
+ + +
+ ); +} diff --git a/src/components/BottomSheet/ver3.0/BottomSheetContent.tsx b/src/components/BottomSheet/ver3.0/BottomSheetContent.tsx new file mode 100644 index 00000000..5edcf97d --- /dev/null +++ b/src/components/BottomSheet/ver3.0/BottomSheetContent.tsx @@ -0,0 +1,20 @@ +import { ReactNode } from 'react'; +import * as styles from './BottomSheet.css'; + +interface ContentsType { + text?: string; + subText?: string; +} + +interface BottomSheetContentProps { + contents: ContentsType; +} + +export default function BottomSheetContent({ contents }: BottomSheetContentProps) { + return ( +
+

{contents.text}

+

{contents.subText}

+
+ ); +} diff --git a/src/components/BottomSheet/ver3.0/BottomSheetTitle.tsx b/src/components/BottomSheet/ver3.0/BottomSheetTitle.tsx new file mode 100644 index 00000000..d3b85df3 --- /dev/null +++ b/src/components/BottomSheet/ver3.0/BottomSheetTitle.tsx @@ -0,0 +1,10 @@ +import { ReactNode } from 'react'; +import * as styles from './BottomSheet.css'; + +interface BottomSheetTitleProps { + children: ReactNode; +} + +export default function BottomSheetTitle({ children }: BottomSheetTitleProps) { + return

{children}

; +} diff --git a/src/components/Header/Header.css.ts b/src/components/Header/Header.css.ts index 91846272..0cae8d46 100644 --- a/src/components/Header/Header.css.ts +++ b/src/components/Header/Header.css.ts @@ -1,5 +1,6 @@ import { style } from '@vanilla-extract/css'; import * as fonts from '@/styles/__font.css'; +import { vars } from '@/styles/theme.css'; export const header = style({ width: '100%', @@ -16,9 +17,7 @@ export const header = style({ flexDirection: 'row', alignItems: 'center', - backgroundColor: '#fff', - - borderBottom: '1px solid rgba(0, 0, 0, 0.10)', + backgroundColor: vars.color.bggray, }); export const flexChild = style({ diff --git a/src/components/ShapeSimpleList/ShapeSimpleList.css.ts b/src/components/ShapeSimpleList/ShapeSimpleList.css.ts new file mode 100644 index 00000000..1ad25384 --- /dev/null +++ b/src/components/ShapeSimpleList/ShapeSimpleList.css.ts @@ -0,0 +1,113 @@ +import { createVar, style, styleVariants } from '@vanilla-extract/css'; +import { vars } from '@/styles/theme.css'; +import { Label, LabelSmall } from '@/styles/font.css'; + +export const imageUrl = createVar(); + +export const container = style({ + padding: '2.4rem 1.6rem', + display: 'grid', + gridTemplateColumns: '1fr 1fr', + gridTemplateRows: 'max-content', + gridGap: 12, + alignContent: 'flex-start', + justifyItems: 'center', + backgroundColor: vars.color.bggray, +}); + +const content = style({ + width: 173, + height: 173, + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + + backgroundImage: imageUrl, + backgroundPosition: 'center', + backgroundColor: vars.color.white, +}); + +export const contentVariant = styleVariants({ + round: [content, { borderRadius: '100%' }], + square: [content, { borderRadius: 20 }], +}); + +export const category = style([ + LabelSmall, + { + padding: '2px 6px', + backgroundColor: vars.color.blue, + borderRadius: 20, + color: vars.color.white, + }, +]); + +export const info = style({ + paddingTop: '0.6rem', + paddingBottom: '0.5rem', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: 4, +}); + +// 배경이미지 유무에 따른 스타일 variants +const fontColor = { + white: vars.color.white, + black: vars.color.black, +}; + +export const title = styleVariants(fontColor, (color) => [ + Label, + { + color, + fontWeight: 600, + }, +]); + +export const owner = styleVariants(fontColor, (color) => [ + LabelSmall, + { + color, + fontWeight: 400, + }, +]); + +export const items = style({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: 5, +}); + +const item = style({ + padding: '0.45rem 0.62rem', + borderRadius: 18, + display: 'flex', + gap: 2, + alignItems: 'center', +}); + +export const itemVariant = styleVariants({ + white: [ + item, + { + backgroundColor: '#F5FAFF', + color: vars.color.blue, + }, + ], + blue: [ + item, + { + backgroundColor: 'rgba(245, 250, 255, 0.30)', + color: vars.color.white, + }, + ], +}); + +export const date = styleVariants(fontColor, (color) => ({ + paddingTop: '0.8rem', + fontSize: '0.9rem', + color, +})); diff --git a/src/components/ShapeSimpleList/ShapeSimpleList.tsx b/src/components/ShapeSimpleList/ShapeSimpleList.tsx new file mode 100644 index 00000000..b9353403 --- /dev/null +++ b/src/components/ShapeSimpleList/ShapeSimpleList.tsx @@ -0,0 +1,42 @@ +import Link from 'next/link'; +import { assignInlineVars } from '@vanilla-extract/dynamic'; + +import * as styles from './ShapeSimpleList.css'; + +import formatDate from '@/lib/utils/dateFormat'; +import { CollectionListType } from '@/lib/types/listType'; + +interface ShapeSimpleListProps { + list: CollectionListType; + hasImage: boolean; +} + +export default function ShapeSimpleList({ list, hasImage }: ShapeSimpleListProps) { + return ( + +
{list.category}
+
+

{list.title}

+

{list.ownerNickname}

+
+
    + {list.listItems.map((item) => ( +
  • + + {item.rank} + {`.`} + + {item.title} +
  • + ))} +
+

{formatDate(list.updatedDate)}

+ + ); +} diff --git a/src/lib/types/listType.ts b/src/lib/types/listType.ts index d5f3f713..465448a1 100644 --- a/src/lib/types/listType.ts +++ b/src/lib/types/listType.ts @@ -143,6 +143,7 @@ export interface SearchResultType { hasNext: boolean; } +// 콜렉션 리스트 조회 export interface CollectionType { id: number; list: CollectionListType; @@ -150,9 +151,10 @@ export interface CollectionType { export interface CollectionListType { id: number; + category: string; backgroundColor: string; title: string; - ownerId: string; + ownerId: number; ownerNickname: string; ownerProfileImageUrl: string; updatedDate: Date;