Skip to content

Commit

Permalink
Merge pull request #104 from DDD-Community/feature/103
Browse files Browse the repository at this point in the history
[Feature/103] 마이페이지, 내 보드 목록 페이지
  • Loading branch information
hwanheejung authored Sep 4, 2024
2 parents 0110906 + 07d7cce commit 8a2faac
Show file tree
Hide file tree
Showing 15 changed files with 223 additions and 29 deletions.
14 changes: 10 additions & 4 deletions src/app/mypage/_components/GotoBoardsBtn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,18 @@ interface GoToBoardsBtnProps {
icon: ReactNode
number: number
className?: React.ComponentProps<'a'>['className']
linkTo: string
}

const GoToBoardsBtn = ({
name,
icon,
number,
className = '',
linkTo = '',
}: GoToBoardsBtnProps) => {
return (
// TODO: JoinedBoard는 /mypage/boards 두번째 탭으로 이동
<Link href="/mypage/boards">
<Link href={linkTo}>
<div
className={twMerge(
'relative flex flex-col items-center gap-2 rounded-[4px] bg-gray-50 px-6 py-3 font-semiBold shadow-myPageBox',
Expand Down Expand Up @@ -48,19 +49,24 @@ export const MyBoard = async () => {
icon={<MyBoardIcon />}
number={totalCount}
className="-rotate-[5deg] transform"
linkTo="/mypage/boards"
/>
</div>
)
}

export const JoinedBoard = () => {
export const JoinedBoard = async () => {
const {
pagination: { totalCount },
} = await getMyBoards(undefined, undefined, 'PARTICIPANT')
return (
<div className="flex h-[108px] w-[140px] items-center justify-center">
<GoToBoardsBtn
name="참여한 보드"
icon={<JoinedBoardIcon className="-rotate-90 transform" />}
number={23}
number={totalCount}
className="rotate-[3deg] transform"
linkTo="/mypage/boards/?participant=true"
/>
</div>
)
Expand Down
15 changes: 13 additions & 2 deletions src/app/mypage/boards/_components/BoardList/BoardEditPopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,29 @@ import React from 'react'
interface BoardEditPopupProps {
isOpen: boolean
clickDelete: (e: React.MouseEvent<HTMLDivElement>) => void
clickChangeName: (e: React.MouseEvent<HTMLDivElement>) => void
close: () => void
}

const BoardEditPopup = ({
isOpen,
clickDelete,
clickChangeName,
close,
}: BoardEditPopupProps) => {
return (
<Popup isOpen={isOpen} close={close}>
<div className="flex w-[168px] cursor-pointer flex-col rounded-lg bg-gray-0 py-2 pl-[22px] shadow-popup">
<div className="text-sm text-negative" onClick={clickDelete}>
<div className="flex w-[168px] cursor-pointer flex-col rounded-lg bg-gray-0 shadow-popup">
<div
className="border-b border-b-gray-300 py-2 pl-[22px] text-sm text-gray-950"
onClick={clickChangeName}
>
<span>보드 주제 수정하기</span>
</div>
<div
className="py-2 pl-[22px] text-sm text-negative"
onClick={clickDelete}
>
<span>보드 삭제하기</span>
</div>
</div>
Expand Down
19 changes: 19 additions & 0 deletions src/app/mypage/boards/_components/BoardList/BoardItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ 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
date: string
id: string
onClickBoard: (boardId: string) => void
onDeleteBoard: (boardId: string) => void
onRefresh: () => void
}

const BoardItem = ({
Expand All @@ -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<HTMLDivElement>) => {
Expand All @@ -32,6 +37,12 @@ const BoardItem = ({
e.stopPropagation()
}

const onClickChangeName = (e: React.MouseEvent<HTMLDivElement>) => {
setIsEditPopupOpen(false)
setIsChangeNameModalOpen(true)
e.stopPropagation()
}

const parseDate = (targetDate: string) => {
return targetDate.split('T')[0].replaceAll('-', '.')
}
Expand All @@ -48,10 +59,18 @@ const BoardItem = ({
<EllipsisIcon />
<BoardEditPopup
isOpen={isEditPopupOpen}
clickChangeName={onClickChangeName}
clickDelete={onClickDelete}
close={closeBoardEditPopup}
/>
</div>
<ChangeBoardNameModal
isOpen={isChangeNameModalOpen}
onClose={closeChangeNameModal}
oldName={title}
boardId={id}
onRefresh={onRefresh}
/>
<DeleteBoardModal
isOpen={isDeleteModalOpen}
onClose={closeDeleteModal}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import Modal from '@/components/Modal'
import TextInput from '@/components/TextInput'
import { changeMyBoardName } from '@/lib'
import ClipIcon from 'public/icons/sketchIcons-paperclip.svg'
import { useState } from 'react'

const MAX_BOARD_NAME_LENGTH = 15

interface ChangeBoardNameModalProps {
isOpen: boolean
onClose: () => 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 (
<Modal isOpen={isOpen} onClose={onClose}>
<Modal.CenterModal icon={<ClipIcon className="scale-[2.5]" />}>
<Modal.Close />
<Modal.Title>보드 주제 수정</Modal.Title>
<div className="mt-3">
<TextInput
errorMessage={`${MAX_BOARD_NAME_LENGTH}자 이내로 입력 가능해요`}
description={`${title.length}/${MAX_BOARD_NAME_LENGTH}자`}
value={title}
hasError={hasError}
setValue={onInput}
icon={null}
/>
</div>
<Modal.CenterConfirm
confirmText="확인"
disabled={hasError || isEmpty}
onConfirm={() => changeBoardName(boardId)}
/>
</Modal.CenterModal>
</Modal>
)
}

export default ChangeBoardNameModal
52 changes: 52 additions & 0 deletions src/app/mypage/boards/_components/BoardList/FilterTabBar.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="mx-7 mt-3 flex">
<button
type="button"
onClick={() => router.replace(pathname)}
className={twMerge(
'w-1/2 rounded-l-lg py-2.5 text-center text-sm',
isParticipant ? unselectedStyle : selectedStyle,
)}
>
내가 만든 보드
</button>
<button
type="button"
onClick={() =>
router.replace(
`${pathname}?${createQueryStringCallback('participant', 'true')}`,
)
}
className={twMerge(
'w-1/2 rounded-r-lg py-2.5 text-center text-sm',
isParticipant ? selectedStyle : unselectedStyle,
)}
>
내가 참여한 보드
</button>
</div>
)
}

export default FilterTabBar
24 changes: 19 additions & 5 deletions src/app/mypage/boards/_components/BoardList/index.tsx
Original file line number Diff line number Diff line change
@@ -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<Pagination>({
totalPage: 0,
totalCount: 0,
Expand All @@ -19,15 +22,21 @@ const BoardList = () => {
const [boards, setBoards] = useState<MyBoard[]>([])

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)
})
}

useEffect(() => {
fetchBoards()
}, [])
}, [searchParams])

useEffect(() => {
console.log(boards)
}, [boards])

const paginate = async (page: number) => {
return fetchBoards(page, pagination.size)
Expand All @@ -45,7 +54,11 @@ const BoardList = () => {

return (
<div className="pb-5">
<ul className="mt-3 overflow-y-hidden pb-12">
<FilterTabBar />
<p className="mx-7 border-b border-b-gray-600 pb-3 pt-5 text-xs text-gray-600">
{pagination.totalCount}
</p>
<ul className="overflow-y-hidden pb-20">
{boards.map((board) => (
<BoardItem
key={board.id}
Expand All @@ -54,6 +67,7 @@ const BoardList = () => {
date={board.createdAt}
onClickBoard={() => goToBoard(board.id)}
onDeleteBoard={() => deleteBoard(board.id)}
onRefresh={() => fetchBoards()}
/>
))}
</ul>
Expand Down
2 changes: 1 addition & 1 deletion src/app/mypage/boards/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ const Page = async () => {
<div className="relative min-h-dvh">
<Header
title="내 보드 목록"
description={`총 ${totalCount}개`}
leftButton={<Header.BackButton />}
shadow={false}
/>
{totalCount === 0 ? <EmptyBoardList /> : <BoardList />}
</div>
Expand Down
15 changes: 6 additions & 9 deletions src/components/CheckNewUser/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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])

Expand Down
2 changes: 1 addition & 1 deletion src/components/Header/HeaderBackButton.tsx
Original file line number Diff line number Diff line change
@@ -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()
Expand Down
6 changes: 5 additions & 1 deletion src/components/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,21 @@ interface HeaderProps {
description?: string
leftButton?: ReactNode
rightButton?: ReactNode
shadow?: boolean
}

const Header = ({
title = '',
description = '',
leftButton = null,
rightButton = null,
shadow = true,
}: HeaderProps) => {
return (
<>
<header className="fixed z-10 flex h-16 w-full max-w-md justify-between bg-gray-0 p-5 shadow-header">
<header
className={`fixed z-10 flex h-16 w-full max-w-md justify-between bg-gray-0 p-5 ${shadow && 'shadow-header'}`}
>
<div className="w-6 cursor-pointer">{leftButton}</div>
<div className="text-gray-700">
<div className="text-center text-md font-semiBold leading-6">
Expand Down
Loading

0 comments on commit 8a2faac

Please sign in to comment.