diff --git a/src/app/mypage/_components/GotoBoardsBtn.tsx b/src/app/mypage/_components/GotoBoardsBtn.tsx index 77d293e..eef1228 100644 --- a/src/app/mypage/_components/GotoBoardsBtn.tsx +++ b/src/app/mypage/_components/GotoBoardsBtn.tsx @@ -10,6 +10,7 @@ interface GoToBoardsBtnProps { icon: ReactNode number: number className?: React.ComponentProps<'a'>['className'] + linkTo: string } const GoToBoardsBtn = ({ @@ -17,10 +18,10 @@ const GoToBoardsBtn = ({ icon, number, className = '', + linkTo = '', }: GoToBoardsBtnProps) => { return ( - // TODO: JoinedBoard는 /mypage/boards 두번째 탭으로 이동 - +
{ icon={} number={totalCount} className="-rotate-[5deg] transform" + linkTo="/mypage/boards" />
) } -export const JoinedBoard = () => { +export const JoinedBoard = async () => { + const { + pagination: { totalCount }, + } = await getMyBoards(undefined, undefined, 'PARTICIPANT') return (
} - number={23} + number={totalCount} className="rotate-[3deg] transform" + linkTo="/mypage/boards/?participant=true" />
) diff --git a/src/app/mypage/boards/_components/BoardList/BoardEditPopup.tsx b/src/app/mypage/boards/_components/BoardList/BoardEditPopup.tsx index cd6f9e0..c286ff2 100644 --- a/src/app/mypage/boards/_components/BoardList/BoardEditPopup.tsx +++ b/src/app/mypage/boards/_components/BoardList/BoardEditPopup.tsx @@ -4,18 +4,29 @@ import React from 'react' interface BoardEditPopupProps { isOpen: boolean clickDelete: (e: React.MouseEvent) => void + clickChangeName: (e: React.MouseEvent) => void close: () => void } const BoardEditPopup = ({ isOpen, clickDelete, + clickChangeName, close, }: BoardEditPopupProps) => { return ( -
-
+
+
+ 보드 주제 수정하기 +
+
보드 삭제하기
diff --git a/src/app/mypage/boards/_components/BoardList/BoardItem.tsx b/src/app/mypage/boards/_components/BoardList/BoardItem.tsx index f202702..2305e5c 100644 --- a/src/app/mypage/boards/_components/BoardList/BoardItem.tsx +++ b/src/app/mypage/boards/_components/BoardList/BoardItem.tsx @@ -2,6 +2,7 @@ import EllipsisIcon from 'public/icons/ellipsis.svg' import React, { useState } from 'react' import DeleteBoardModal from './DeleteBoardModal' import BoardEditPopup from './BoardEditPopup' +import ChangeBoardNameModal from './ChangeBoardNameModal' interface BoardListProps { title: string @@ -9,6 +10,7 @@ interface BoardListProps { id: string onClickBoard: (boardId: string) => void onDeleteBoard: (boardId: string) => void + onRefresh: () => void } const BoardItem = ({ @@ -17,13 +19,16 @@ const BoardItem = ({ id, onClickBoard, onDeleteBoard, + onRefresh, }: BoardListProps) => { const [isEditPopupOpen, setIsEditPopupOpen] = useState(false) const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false) + const [isChangeNameModalOpen, setIsChangeNameModalOpen] = useState(false) const openBoardEditPopup = () => setIsEditPopupOpen(true) const closeBoardEditPopup = () => setIsEditPopupOpen(false) + const closeChangeNameModal = () => setIsChangeNameModalOpen(false) const closeDeleteModal = () => setIsDeleteModalOpen(false) const onClickDelete = (e: React.MouseEvent) => { @@ -32,6 +37,12 @@ const BoardItem = ({ e.stopPropagation() } + const onClickChangeName = (e: React.MouseEvent) => { + setIsEditPopupOpen(false) + setIsChangeNameModalOpen(true) + e.stopPropagation() + } + const parseDate = (targetDate: string) => { return targetDate.split('T')[0].replaceAll('-', '.') } @@ -48,10 +59,18 @@ const BoardItem = ({
+ void + oldName: string + boardId: string + onRefresh: () => void +} + +const ChangeBoardNameModal = ({ + isOpen, + onClose, + oldName, + boardId, + onRefresh, +}: ChangeBoardNameModalProps) => { + const [title, setTitle] = useState(oldName) + const [hasError, setHasError] = useState(false) + const isEmpty = title.length === 0 + + const onInput = (value: string) => { + setTitle(value) + if (value.length > MAX_BOARD_NAME_LENGTH) { + setHasError(true) + } else { + setHasError(false) + } + } + + const changeBoardName = async (id: string) => { + await changeMyBoardName(id, title) + onRefresh() + } + + return ( + + }> + + 보드 주제 수정 +
+ +
+ changeBoardName(boardId)} + /> +
+
+ ) +} + +export default ChangeBoardNameModal diff --git a/src/app/mypage/boards/_components/BoardList/FilterTabBar.tsx b/src/app/mypage/boards/_components/BoardList/FilterTabBar.tsx new file mode 100644 index 0000000..e2946e5 --- /dev/null +++ b/src/app/mypage/boards/_components/BoardList/FilterTabBar.tsx @@ -0,0 +1,52 @@ +import { createQueryString } from '@/lib/utils/query' +import { usePathname, useRouter, useSearchParams } from 'next/navigation' +import { useCallback } from 'react' +import { twMerge } from 'tailwind-merge' + +const FilterTabBar = () => { + const router = useRouter() + const pathname = usePathname() + const searchParams = useSearchParams() + + const createQueryStringCallback = useCallback( + (name: string, value: string) => { + return createQueryString(searchParams, name, value) + }, + [searchParams], + ) + const isParticipant = searchParams.get('participant') === 'true' + + const selectedStyle = 'bg-gray-800 font-semiBold text-gray-0' + const unselectedStyle = 'text-gray-600 border border-gray-500' + + return ( +
+ + +
+ ) +} + +export default FilterTabBar diff --git a/src/app/mypage/boards/_components/BoardList/index.tsx b/src/app/mypage/boards/_components/BoardList/index.tsx index df4985e..bfa871f 100644 --- a/src/app/mypage/boards/_components/BoardList/index.tsx +++ b/src/app/mypage/boards/_components/BoardList/index.tsx @@ -1,15 +1,18 @@ 'use client' import { PaginationProvider } from '@/components/Pagination' -import { useRouter } from 'next/navigation' -import { useEffect, useState } from 'react' import { deleteMyBoard, getMyBoards } from '@/lib/api/myBoard' import { MyBoard, Pagination } from '@/types' +import { useRouter, useSearchParams } from 'next/navigation' +import { useEffect, useState } from 'react' import BoardItem from './BoardItem' import BoardPagination from './BoardPagination' +import FilterTabBar from './FilterTabBar' const BoardList = () => { const router = useRouter() + const searchParams = useSearchParams() + const isParticipant = searchParams.get('participant') === 'true' const [pagination, setPagination] = useState({ totalPage: 0, totalCount: 0, @@ -19,7 +22,9 @@ const BoardList = () => { const [boards, setBoards] = useState([]) const fetchBoards = async (page = 1, size = 10) => { - return getMyBoards(page, size).then((data) => { + const filter = isParticipant ? 'PARTICIPANT' : 'OWNER' + + return getMyBoards(page, size, filter).then((data) => { setBoards(data.boards) setPagination(data.pagination) }) @@ -27,7 +32,11 @@ const BoardList = () => { useEffect(() => { fetchBoards() - }, []) + }, [searchParams]) + + useEffect(() => { + console.log(boards) + }, [boards]) const paginate = async (page: number) => { return fetchBoards(page, pagination.size) @@ -45,7 +54,11 @@ const BoardList = () => { return (
-
    + +

    + 총 {pagination.totalCount}개 +

    +
      {boards.map((board) => ( { date={board.createdAt} onClickBoard={() => goToBoard(board.id)} onDeleteBoard={() => deleteBoard(board.id)} + onRefresh={() => fetchBoards()} /> ))}
    diff --git a/src/app/mypage/boards/page.tsx b/src/app/mypage/boards/page.tsx index b030642..3d6d8fa 100644 --- a/src/app/mypage/boards/page.tsx +++ b/src/app/mypage/boards/page.tsx @@ -11,8 +11,8 @@ const Page = async () => {
    } + shadow={false} /> {totalCount === 0 ? : }
    diff --git a/src/components/CheckNewUser/index.tsx b/src/components/CheckNewUser/index.tsx index c25e127..2059cda 100644 --- a/src/components/CheckNewUser/index.tsx +++ b/src/components/CheckNewUser/index.tsx @@ -7,17 +7,14 @@ const CheckNewUser = () => { const { data: session } = useSession() useEffect(() => { - if (!session) { - // 비회원 - if (localStorage.getItem('needTutorial') === null) { + if (localStorage.getItem('needTutorial') === null) { + if (!session || session?.newUser) { + // 비회원이거나 신규 회원 localStorage.setItem('needTutorial', 'true') + } else { + // 기존 회원 + localStorage.setItem('needTutorial', 'false') } - } else if (session?.newUser) { - // 신규 회원 - localStorage.setItem('needTutorial', 'true') - } else { - // 기존 회원 - localStorage.setItem('needTutorial', 'false') } }, [session]) diff --git a/src/components/Header/HeaderBackButton.tsx b/src/components/Header/HeaderBackButton.tsx index 7495843..448ca61 100644 --- a/src/components/Header/HeaderBackButton.tsx +++ b/src/components/Header/HeaderBackButton.tsx @@ -1,7 +1,7 @@ 'use client' -import BackIcon from 'public/icons/arrow_back_ios.svg' import { useRouter } from 'next/navigation' +import BackIcon from 'public/icons/arrow_back_ios.svg' const HeaderBackButton = () => { const router = useRouter() diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx index 16eddbc..ef53df6 100644 --- a/src/components/Header/index.tsx +++ b/src/components/Header/index.tsx @@ -6,6 +6,7 @@ interface HeaderProps { description?: string leftButton?: ReactNode rightButton?: ReactNode + shadow?: boolean } const Header = ({ @@ -13,10 +14,13 @@ const Header = ({ description = '', leftButton = null, rightButton = null, + shadow = true, }: HeaderProps) => { return ( <> -
    +
    {leftButton}
    diff --git a/src/components/Modal/index.tsx b/src/components/Modal/index.tsx index 9f8db63..5d5b7f8 100644 --- a/src/components/Modal/index.tsx +++ b/src/components/Modal/index.tsx @@ -144,9 +144,11 @@ const ModalBodyContent = ({ children }: { children: ReactNode }) => { const CenterModalConfirm = ({ confirmText, onConfirm = () => {}, + disabled = false, }: { confirmText: ReactNode onConfirm?: () => void + disabled?: boolean }) => { const { onClose } = useContext(ModalContext) @@ -161,6 +163,7 @@ const CenterModalConfirm = ({ size="md" className="my-4 flex items-center justify-center gap-1" onClick={clickHandler} + disabled={disabled} > {confirmText} diff --git a/src/components/Popup/index.tsx b/src/components/Popup/index.tsx index 20ea3e9..b18dd8b 100644 --- a/src/components/Popup/index.tsx +++ b/src/components/Popup/index.tsx @@ -37,7 +37,7 @@ const Popup = ({ if (!isOpen) return null const className = twMerge( - 'absolute z-5', + 'absolute z-[5]', yPosition === 'top' ? 'top' : 'bottom', xPosition === 'left' ? 'left-0' : 'right-0', ) diff --git a/src/lib/api/myBoard.ts b/src/lib/api/myBoard.ts index e8059c8..ded64ec 100644 --- a/src/lib/api/myBoard.ts +++ b/src/lib/api/myBoard.ts @@ -1,15 +1,19 @@ -import { deleteApi, get } from '@/lib/api/base' +import { deleteApi, get, put } from '@/lib/api/base' import { MyBoardList } from '@/types' export const getMyBoards = async ( page = 1, size = 10, + filter: 'OWNER' | 'PARTICIPANT' = 'OWNER', ): Promise => { - const res = await get(`/api/v1/my/boards?page=${page}&size=${size}`, { - next: { - tags: ['myBoard', `myBoard:${page},${size}`], + const res = await get( + `/api/v2/my/boards?page=${page}&size=${size}&filter=${filter}`, + { + next: { + tags: ['myBoard', `myBoard:${page},${size},${filter}`], + }, }, - }) + ) return { pagination: { @@ -22,6 +26,12 @@ export const getMyBoards = async ( } } +export const changeMyBoardName = (id: string, title: string) => { + return put(`/api/v1/my/boards/${id}`, { + body: JSON.stringify({ title }), + }) +} + export const deleteMyBoard = (id: string) => { return deleteApi(`/api/v1/my/boards/${id}`) } diff --git a/src/lib/api/polaroid.ts b/src/lib/api/polaroid.ts index fc157ea..163f957 100644 --- a/src/lib/api/polaroid.ts +++ b/src/lib/api/polaroid.ts @@ -21,6 +21,7 @@ export const postPolaroid = async ( }) revalidateTag(`board:${boardId}`) + revalidateTag('myBoard') return result.data } diff --git a/src/lib/utils/query.ts b/src/lib/utils/query.ts new file mode 100644 index 0000000..9008bdf --- /dev/null +++ b/src/lib/utils/query.ts @@ -0,0 +1,10 @@ +export const createQueryString = ( + searchParams: URLSearchParams, + name: string, + value: string, +): string => { + const params = new URLSearchParams(searchParams.toString()) + params.set(name, value) + + return params.toString() +}