Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat/#46] 크루 초대, 가입, 생성 기능 추가 #65

Merged
merged 3 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading