Skip to content

Commit

Permalink
Merge pull request #65 from DDD-Community/feat/#46
Browse files Browse the repository at this point in the history
[Feat/#46] 크루 초대, 가입, 생성 기능 추가
  • Loading branch information
lkhoony authored Sep 18, 2024
2 parents f9e64ed + fffa9fc commit 352bdcc
Show file tree
Hide file tree
Showing 13 changed files with 442 additions and 112 deletions.
50 changes: 35 additions & 15 deletions src/api/group.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import qs from "qs"
import axiosInstance from "./axiosInstance"
import { AxiosError } from "axios"

export type sort = "userCount,desc" | "createdAt,desc"

Expand All @@ -10,15 +11,17 @@ export interface sortRes {
}

export interface group {
id: number
name: string
description: string
ownerUid: number
isHidden: boolean
joinCode: string
userCount: number
userCapacity: number
ranks: groupUserRank[]
id?: number
name?: string
description?: string
ownerUid?: number
ownerName?: string
isHidden?: boolean
joinCode?: string
userCount?: number
userCapacity?: number
hasJoined?: boolean
ranks?: groupUserRank[]
}

export interface groupUserRank {
Expand Down Expand Up @@ -50,7 +53,7 @@ export interface groupsRes {

export interface groupJoinReq {
groupId: number
joinCode: string
joinCode?: string
}

export interface groupJoinRes {
Expand Down Expand Up @@ -88,9 +91,14 @@ export const getGroup = async (id: number | undefined): Promise<group> => {

export const joinGroup = async (groupJoinReq: groupJoinReq): Promise<groupJoinRes> => {
try {
// eslint-disable-next-line max-len
const res = await axiosInstance.post(`groups/${groupJoinReq.groupId}/join`, { joinCode: groupJoinReq.joinCode })
return res.data
const res = await axiosInstance.post(
`groups/${groupJoinReq.groupId}/join`,
{}, // POST 요청에 body가 없다면 빈 객체 전달
{
params: groupJoinReq.joinCode ? { joinCode: groupJoinReq.joinCode } : {}, // query string으로 joinCode 전달
}
)
return res.data.data
} catch (e) {
throw e
}
Expand All @@ -100,8 +108,20 @@ export const checkGroupName = async (name: string): Promise<boolean> => {
try {
// eslint-disable-next-line max-len
const res = await axiosInstance.post(`groups/check`, { name })
const errorMessage = res.data?.errorMessage
return errorMessage ? false : true
const errorCode = res.data?.errorCode
return !errorCode
} catch (e) {
const { response } = e as AxiosError
const data = response?.data as { errorCode: string; reason: string }
if (data.errorCode) return false
throw e
}
}

export const createGroup = async (group: group): Promise<group> => {
try {
const res = await axiosInstance.post(`groups`, { ...group })
return res.data.data
} catch (e) {
throw e
}
Expand Down
7 changes: 5 additions & 2 deletions src/components/Crew/CrewItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,13 @@ const CrewItem = (props: CrewItemProps): ReactElement => {
</div>
{/* detail button */}
<button
className="flex w-[114px] cursor-pointer justify-center rounded-full bg-[#1A75FF] py-[6px] text-sm font-semibold text-white"
className={`flex w-[114px] justify-center rounded-full py-[6px] text-sm font-semibold text-white ${
group.hasJoined ? "bg-zinc-800" : group.userCapacity === group.userCount ? "bg-gray-200" : "bg-[#1A75FF]"
}`}
onClick={onClickDetail}
disabled={group.hasJoined || group.userCapacity === group.userCount}
>
크루 상세보기
{group.hasJoined ? "나의 크루" : "크루 상세보기"}
</button>
</div>
)
Expand Down
156 changes: 99 additions & 57 deletions src/components/Crew/CrewList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,95 +6,123 @@ import { useModals } from "@/hooks/useModals"
import useMyGroup from "@/hooks/useMyGroup"
import CreateCrewIcon from "@assets/icons/crew-create-button-icon.svg?react"
import SortCrewIcon from "@assets/icons/crew-sort-icon.svg?react"
import { ReactElement, useEffect, useRef, useState } from "react"
import { ReactElement, useCallback, useEffect, useRef, useState, useMemo } from "react"
import { modals } from "../Modal/Modals"
import MyCrewRankingContainer from "./MyCrew/MyCrewRankingContainer"
import { useNavigate, useSearchParams } from "react-router-dom"
import RoutePath from "@/constants/routes.json"

const SORT_LIST = [
{ sort: "userCount,desc", label: "크루원 많은 순" },
{ sort: "createdAt,desc", label: "최신 생성 크루 순" },
]

const CrewList = (): ReactElement => {
const { myGroupData, ranks, myRank } = useMyGroup()
const navigate = useNavigate()
const { myGroupData, ranks, myRank, refetchAll } = useMyGroup()
const [isDropdownOpen, setIsDropdownOpen] = useState<boolean>(false)
console.log("myGroupData: ", myGroupData)
const [searchParams, setSearchParams] = useSearchParams()

const [params, setParams] = useState<groupsReq>({
page: 0,
size: 10000,
sort: "userCount,desc",
})

const { data, isLoading, isError } = useGetGroups(params)

const { data, isLoading, isError, refetch } = useGetGroups(params)
const { openModal } = useModals()

const openCreateModal = (): void => {
openModal(modals.createCrewModal, {
onSubmit: () => {
console.log("open")
},
})
}

const openJoinCrewModal = (id: number): void => {
openModal(modals.joinCrewModal, {
id,
onSubmit: () => {
console.log("open")
},
})
}
// openCreateModal 메모이제이션
const openCreateModal = useCallback((): void => {
if (myGroupData) {
openModal(modals.ToWithdrawModal, {
onSubmit: () => {
navigate(RoutePath.MYCREW)
},
})
} else {
openModal(modals.createCrewModal, {
onSubmit: () => {
refetch()
refetchAll()
},
})
}
}, [myGroupData, navigate, openModal, refetch, refetchAll])

// openJoinCrewModal 메모이제이션
const openJoinCrewModal = useCallback(
(id: number | undefined): void => {
openModal(modals.joinCrewModal, {
id,
onSubmit: () => {
console.log("open")
},
})
},
[openModal]
)

const openInviteModal = (): void => {
// openInviteModal 메모이제이션
const openInviteModal = useCallback((): void => {
openModal(modals.inviteCrewModal, {
id: Number(myGroupData?.id),
onSubmit: () => {
console.log("open")
},
})
}

const dropdownRef = useRef<HTMLDivElement>(null)
}, [myGroupData, openModal])

// toggleDropdown 함수
const toggleDropdown = (): void => {
setIsDropdownOpen((prev) => !prev)
}

const createSortList = (): JSX.Element[] => {
// createSortList 메모이제이션
const createSortList = useMemo(() => {
return SORT_LIST.map((s) => (
// eslint-disable-next-line max-len
<div
key={`sort-list-${s.sort}`}
className="cursor-pointer text-[13px] font-medium leading-[24px] text-zinc-400"
onClick={() => {
setParams({ ...params, sort: s.sort as sort })
setParams((prev) => ({ ...prev, sort: s.sort as sort }))
setIsDropdownOpen(false)
}}
>
{s.label}
</div>
))
}

const createGroupList = (_groups: group[] | undefined): JSX.Element | null => {
if (!_groups) return null
}, [])

// createGroupList 메모이제이션
const createGroupList = useCallback(
(_groups: group[] | undefined): JSX.Element | null => {
if (!_groups) return null

if (_groups.length === 0) {
return (
<div className="flex flex-grow flex-col items-center justify-center">
<img src={EmptyCrewImage} alt="empty crew" />
<div className="text-center text-[14px] font-semibold leading-[22px]">
{"만들어진 크루가 아직 없습니다."}
</div>
</div>
)
}

if (_groups.length === 0) {
return (
<div className="flex flex-grow flex-col items-center justify-center">
<img src={EmptyCrewImage} alt="empty crew" />
<div className="text-center text-[14px] font-semibold leading-[22px]">{"만들어진 크루가 아직 없습니다."}</div>
<div className="flex flex-grow flex-col gap-[8px]">
{_groups.map((g) => (
<CrewItem key={`crew-item-${g.id}`} group={g} onClickDetail={() => openJoinCrewModal(g.id)} />
))}
</div>
)
}
return (
<div className="flex flex-grow flex-col gap-[8px]">
{_groups.map((g) => (
<CrewItem key={`crew-item-${g.id}`} group={g} onClickDetail={() => openJoinCrewModal(g.id)} />
))}
</div>
)
}
},
[openJoinCrewModal]
)

// Dropdown 외부 클릭 감지 메모이제이션
const dropdownRef = useRef<HTMLDivElement>(null)

useEffect(() => {
const handleClickOutside = (event: MouseEvent): void => {
Expand All @@ -107,7 +135,21 @@ const CrewList = (): ReactElement => {
return () => {
document.removeEventListener("mousedown", handleClickOutside)
}
}, [dropdownRef])
}, [])

// URL에서 groupId 추출 후 모달 열기
useEffect(() => {
const groupId = searchParams.get("groupId")

if (groupId) {
openJoinCrewModal(Number(groupId))
const removeGroupIdFromUrl = (): void => {
searchParams.delete("groupId")
setSearchParams(searchParams)
}
removeGroupIdFromUrl()
}
}, [openJoinCrewModal, searchParams, setSearchParams])

return (
<div className="flex h-full w-full flex-col">
Expand All @@ -120,22 +162,22 @@ const CrewList = (): ReactElement => {
openInviteModal={openInviteModal}
/>
)}

{/* header */}
<div className="mb-[24px] flex w-full items-center">
<div className="flex-grow text-[22px] font-bold text-zinc-900">
<span>전체크루</span>
<span>{isLoading ? "" : `(${data?.totalCount})`}</span>
</div>
{!myGroupData ||
(myGroupData && Object.keys(myGroupData).length === 0 && (
<div
className="flex w-[138px] cursor-pointer items-center justify-center gap-[10px] rounded-[33px] bg-zinc-800 p-[10px] text-sm font-semibold text-white"
onClick={openCreateModal}
>
<CreateCrewIcon />
<div>크루 만들기</div>
</div>
))}
{(!myGroupData || (myGroupData && Object.keys(myGroupData).length === 0)) && (
<div
className="flex w-[138px] cursor-pointer items-center justify-center gap-[10px] rounded-[33px] bg-zinc-800 p-[10px] text-sm font-semibold text-white"
onClick={openCreateModal}
>
<CreateCrewIcon />
<div>크루 만들기</div>
</div>
)}
</div>

{/* sort */}
Expand All @@ -153,7 +195,7 @@ const CrewList = (): ReactElement => {
: "pointer-events-none -translate-y-2 scale-95 opacity-0"
}`}
>
{createSortList()}
{createSortList}
</div>
</div>

Expand Down
Loading

0 comments on commit 352bdcc

Please sign in to comment.