From 2acf3fc2a176bcb811bdc424581f7fa5196427ee Mon Sep 17 00:00:00 2001 From: "lkh14011424@gmail.com" Date: Wed, 18 Sep 2024 04:22:57 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[feat/#46]=20=ED=81=AC=EB=A3=A8=20=EA=B0=80?= =?UTF-8?q?=EC=9E=85,=20=ED=81=AC=EB=A3=A8=20=EC=83=9D=EC=84=B1=20API=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/group.ts | 50 +++++++++++----- src/components/Crew/CrewItem.tsx | 7 ++- src/components/Crew/CrewList.tsx | 4 +- src/components/Modal/CreateCrewModal.tsx | 73 +++++++++++++++++++++--- src/components/Modal/InviteCrewModal.tsx | 1 + src/components/Modal/JoinCrewModal.tsx | 58 +++++++++++++------ src/hooks/useGroupMutation.ts | 17 +++++- src/layouts/AnalysisLayout.tsx | 2 +- 8 files changed, 166 insertions(+), 46 deletions(-) diff --git a/src/api/group.ts b/src/api/group.ts index eec5675..c140593 100644 --- a/src/api/group.ts +++ b/src/api/group.ts @@ -1,5 +1,6 @@ import qs from "qs" import axiosInstance from "./axiosInstance" +import { AxiosError } from "axios" export type sort = "userCount,desc" | "createdAt,desc" @@ -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 { @@ -45,7 +48,7 @@ export interface groupsRes { export interface groupJoinReq { groupId: number - joinCode: string + joinCode?: string } export interface groupJoinRes { @@ -74,9 +77,14 @@ export const getGroup = async (id: number | undefined): Promise => { export const joinGroup = async (groupJoinReq: groupJoinReq): Promise => { 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 } @@ -86,8 +94,20 @@ export const checkGroupName = async (name: string): Promise => { 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 => { + try { + const res = await axiosInstance.post(`groups`, { ...group }) + return res.data.data } catch (e) { throw e } diff --git a/src/components/Crew/CrewItem.tsx b/src/components/Crew/CrewItem.tsx index fa2fef9..f5539c5 100644 --- a/src/components/Crew/CrewItem.tsx +++ b/src/components/Crew/CrewItem.tsx @@ -31,10 +31,13 @@ const CrewItem = (props: CrewItemProps): ReactElement => { {/* detail button */} ) diff --git a/src/components/Crew/CrewList.tsx b/src/components/Crew/CrewList.tsx index 7471844..38efd1b 100644 --- a/src/components/Crew/CrewList.tsx +++ b/src/components/Crew/CrewList.tsx @@ -24,14 +24,14 @@ const CrewList = (): ReactElement => { 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") + refetch() }, }) } diff --git a/src/components/Modal/CreateCrewModal.tsx b/src/components/Modal/CreateCrewModal.tsx index e513356..9354dee 100644 --- a/src/components/Modal/CreateCrewModal.tsx +++ b/src/components/Modal/CreateCrewModal.tsx @@ -3,6 +3,10 @@ import CheckedIcon from "@assets/icons/crew-checked-icon.svg?react" import UnCheckedIcon from "@assets/icons/crew-unckecked-icon.svg?react" import { useState } from "react" import { ModalProps } from "@/contexts/ModalsContext" +import { useCheckGroupName, useCreateGroup } from "@/hooks/useGroupMutation" +import { group } from "@/api" + +type TPossible = "POSSIBLE" | "IMPOSSIBLE" | "NONCHECKED" const CreateCrewModal = (props: ModalProps): React.ReactElement => { const { onClose, onSubmit } = props @@ -11,9 +15,14 @@ const CreateCrewModal = (props: ModalProps): React.ReactElement => { const [description, setDescription] = useState("") const [isHidden, setIsHidden] = useState(false) const [joinCode, setJoinCode] = useState("") + const [isPossible, setIsPossible] = useState(null) + + const checkGroupNameMutation = useCheckGroupName() + const createGroupMutation = useCreateGroup() const onChangeName = (e: React.ChangeEvent): void => { setName(e.target.value) + setIsPossible(null) } const onChangeDescription = (e: React.ChangeEvent): void => { @@ -40,6 +49,38 @@ const CreateCrewModal = (props: ModalProps): React.ReactElement => { setJoinCode("") } + const onCheckGroupName = async (): Promise => { + const _isPossible = await checkGroupNameMutation.mutateAsync(name) + setIsPossible(_isPossible ? "POSSIBLE" : "IMPOSSIBLE") + } + + const getNameCheckedMsg = (_isPossible: TPossible | null): string => { + if (_isPossible === "POSSIBLE") return "사용가능한 크루명입니다." + if (_isPossible === "IMPOSSIBLE") return "이미 사용중인 크루명이에요. 다른 크루명을 사용해주세요." + if (_isPossible === "NONCHECKED") return "중복체크를 해주세요." + return "" + } + + const canCreate = (): string | boolean => { + return name && description && ((isHidden && joinCode.length === 4) || !isHidden) + } + + const handleSubmit = (): void => { + if (isPossible === null) { + setIsPossible("NONCHECKED") + return + } + if (isPossible === "NONCHECKED") return + + let newGroup: group = { name, description } + if (isHidden) newGroup = { ...newGroup, joinCode, isHidden } + createGroupMutation.mutate(newGroup, { + onSuccess: (): void => { + if (onSubmit && typeof onSubmit === "function") onSubmit() + }, + }) + } + return (
@@ -48,22 +89,37 @@ const CreateCrewModal = (props: ModalProps): React.ReactElement => {
{"크루 만들기"}
-
+
{/* crew owner */} -
+
크루명
-
+
-
+
+ {getNameCheckedMsg(isPossible)} +
{/* crew description */} @@ -105,8 +161,11 @@ const CreateCrewModal = (props: ModalProps): React.ReactElement => { {/* button */} diff --git a/src/components/Modal/InviteCrewModal.tsx b/src/components/Modal/InviteCrewModal.tsx index e3efb18..b4e9832 100644 --- a/src/components/Modal/InviteCrewModal.tsx +++ b/src/components/Modal/InviteCrewModal.tsx @@ -26,6 +26,7 @@ const InviteCrewModal = (props: ModalProps): React.ReactElement => { > 초대 링크 복사하기 +
초대 링크가 복사되었어요.
) diff --git a/src/components/Modal/JoinCrewModal.tsx b/src/components/Modal/JoinCrewModal.tsx index 19ad761..4ec380c 100644 --- a/src/components/Modal/JoinCrewModal.tsx +++ b/src/components/Modal/JoinCrewModal.tsx @@ -3,7 +3,8 @@ import CrewJoinUserIcon from "@assets/icons/crew-join-user-icon.svg?react" import PrivateCrewIcon from "@assets/icons/crew-private-icon.svg?react" import { ReactNode, useState } from "react" import { ModalProps } from "@/contexts/ModalsContext" -import { useGetGroup } from "@/hooks/useGroupMutation" +import { useGetGroup, useJoinGroup } from "@/hooks/useGroupMutation" +import { groupJoinReq } from "@/api" const JoinCrewModal = (props: ModalProps): React.ReactElement => { const { onClose, onSubmit, id } = props @@ -12,21 +13,49 @@ const JoinCrewModal = (props: ModalProps): React.ReactElement => { const [isCodeError, setIsCodeError] = useState(false) const { data, isLoading, isError } = useGetGroup(id) + const joinGroupMutation = useJoinGroup() const onChangeJoinCode = (e: React.ChangeEvent): void => { if (isCodeError) setIsCodeError(false) if (e.target.value.length <= 4) setJoinCode(e.target.value) } + const handleSubmit = (): void => { + if (!data?.id) return + let groupJoinReq: groupJoinReq = { groupId: data.id } + if (data?.isHidden) groupJoinReq = { ...groupJoinReq, joinCode } + joinGroupMutation.mutate(groupJoinReq, { + onSuccess: (): void => { + if (onSubmit && typeof onSubmit === "function") onSubmit() + }, + onError: (e): void => { + console.log(e) + }, + }) + } + const createRank = (): ReactNode => { + if (!data?.ranks) return + if (data.ranks.length === 0) return return ( -
- {data?.ranks.map((r) => ( -
-
{`${r.rank}등`}
-
{`${r.name}`}
+
+
+
오늘 바른자세 랭킹
+
크루에 가입하면 볼 수 있어요
+
+
+
+ {data?.ranks.map((r, i) => ( +
+
{`${r.rank}등`}
+
{`${r.name}`}
+
+ ))}
- ))} +
) } @@ -57,7 +86,7 @@ const JoinCrewModal = (props: ModalProps): React.ReactElement => {
크루장
- {data?.ownerUid} + {data?.ownerName}
@@ -70,15 +99,7 @@ const JoinCrewModal = (props: ModalProps): React.ReactElement => {
{/* crew rank */} - {data?.ranks && ( -
-
-
오늘 바른자세 랭킹
-
크루에 가입하면 볼 수 있어요
-
-
{createRank()}
-
- )} + {createRank()}
) : ( // private crew @@ -101,10 +122,11 @@ const JoinCrewModal = (props: ModalProps): React.ReactElement => { {/* button */} +
1개의 크루에만 가입할 수 있어요.
)} diff --git a/src/hooks/useGroupMutation.ts b/src/hooks/useGroupMutation.ts index f5125d9..2c8095b 100644 --- a/src/hooks/useGroupMutation.ts +++ b/src/hooks/useGroupMutation.ts @@ -1,6 +1,7 @@ import { useMutation, UseMutationResult, useQuery, UseQueryResult } from "@tanstack/react-query" import { checkGroupName, + createGroup, getGroup, getGroups, group, @@ -13,7 +14,10 @@ import { export const useGetGroups = (params: groupsReq): UseQueryResult => { // eslint-disable-next-line max-len - return useQuery({ queryKey: ["groups", params], queryFn: () => getGroups(params) }) + return useQuery({ + queryKey: ["groups", params.page, params.size, params.sort], + queryFn: () => getGroups(params), + }) } export const useGetGroup = (id: number | undefined): UseQueryResult => { @@ -42,3 +46,14 @@ export const useCheckGroupName = (): UseMutationResult => { + return useMutation({ + mutationFn: (group: group) => { + return createGroup(group) + }, + onSuccess: (data) => { + console.log(data) + }, + }) +} diff --git a/src/layouts/AnalysisLayout.tsx b/src/layouts/AnalysisLayout.tsx index 19cf049..8f66e9a 100644 --- a/src/layouts/AnalysisLayout.tsx +++ b/src/layouts/AnalysisLayout.tsx @@ -2,7 +2,7 @@ import { Outlet } from "react-router-dom" export default function AnalysisLayout() { return ( -
+
) From fffa9fc8139bdccda057627d675e93cffe852564 Mon Sep 17 00:00:00 2001 From: "lkh14011424@gmail.com" Date: Wed, 18 Sep 2024 08:58:25 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[feat/#46]=20=EC=B4=88=EB=8C=80=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80,=20?= =?UTF-8?q?=EB=82=B4=20=EA=B7=B8=EB=A3=B9=20=EC=97=AC=EB=B6=80=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=9D=BC=20=ED=81=AC=EB=A3=A8=20=EC=83=9D=EC=84=B1,?= =?UTF-8?q?=20=EA=B0=80=EC=9E=85=20=EC=A0=9C=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Crew/CrewList.tsx | 154 +++++++++++++-------- src/components/Crew/CrewRanking.tsx | 85 ++++++++++++ src/components/Crew/MyCrew/CrewRanking.tsx | 6 +- src/components/Modal/InviteCrewModal.tsx | 29 +++- src/components/Modal/JoinCrewModal.tsx | 19 ++- src/components/Modal/Modals.tsx | 2 + src/components/Modal/ToWithdrawModal.tsx | 41 ++++++ src/components/ModalContainer.tsx | 2 +- src/pages/MyCrew.tsx | 12 +- 9 files changed, 280 insertions(+), 70 deletions(-) create mode 100644 src/components/Crew/CrewRanking.tsx create mode 100644 src/components/Modal/ToWithdrawModal.tsx diff --git a/src/components/Crew/CrewList.tsx b/src/components/Crew/CrewList.tsx index c650d9a..1bdf6fc 100644 --- a/src/components/Crew/CrewList.tsx +++ b/src/components/Crew/CrewList.tsx @@ -6,9 +6,11 @@ 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: "크루원 많은 순" }, @@ -16,9 +18,11 @@ const SORT_LIST = [ ] const CrewList = (): ReactElement => { - const { myGroupData, ranks, myRank } = useMyGroup() + const navigate = useNavigate() + const { myGroupData, ranks, myRank, refetchAll } = useMyGroup() const [isDropdownOpen, setIsDropdownOpen] = useState(false) - console.log("myGroupData: ", myGroupData) + const [searchParams, setSearchParams] = useSearchParams() + const [params, setParams] = useState({ page: 0, size: 10000, @@ -26,75 +30,99 @@ const CrewList = (): ReactElement => { }) const { data, isLoading, isError, refetch } = useGetGroups(params) - const { openModal } = useModals() - const openCreateModal = (): void => { - openModal(modals.createCrewModal, { - onSubmit: () => { - refetch() - }, - }) - } - - 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(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
{ - setParams({ ...params, sort: s.sort as sort }) + setParams((prev) => ({ ...prev, sort: s.sort as sort })) setIsDropdownOpen(false) }} > {s.label}
)) - } - - 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 ( +
+ empty crew +
+ {"만들어진 크루가 아직 없습니다."} +
+
+ ) + } - if (_groups.length === 0) { return ( -
- empty crew -
{"만들어진 크루가 아직 없습니다."}
+
+ {_groups.map((g) => ( + openJoinCrewModal(g.id)} /> + ))}
) - } - return ( -
- {_groups.map((g) => ( - openJoinCrewModal(g.id)} /> - ))} -
- ) - } + }, + [openJoinCrewModal] + ) + + // Dropdown 외부 클릭 감지 메모이제이션 + const dropdownRef = useRef(null) useEffect(() => { const handleClickOutside = (event: MouseEvent): void => { @@ -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 (
@@ -120,22 +162,22 @@ const CrewList = (): ReactElement => { openInviteModal={openInviteModal} /> )} + {/* header */}
전체크루 {isLoading ? "" : `(${data?.totalCount})`}
- {!myGroupData || - (myGroupData && Object.keys(myGroupData).length === 0 && ( -
- -
크루 만들기
-
- ))} + {(!myGroupData || (myGroupData && Object.keys(myGroupData).length === 0)) && ( +
+ +
크루 만들기
+
+ )}
{/* sort */} @@ -153,7 +195,7 @@ const CrewList = (): ReactElement => { : "pointer-events-none -translate-y-2 scale-95 opacity-0" }`} > - {createSortList()} + {createSortList}
diff --git a/src/components/Crew/CrewRanking.tsx b/src/components/Crew/CrewRanking.tsx new file mode 100644 index 0000000..f42b58d --- /dev/null +++ b/src/components/Crew/CrewRanking.tsx @@ -0,0 +1,85 @@ +import Crew1stCrownIcon from "@assets/icons/crew-1st-crown.svg?react" + +const RankPillar = ({ rank, name, score, height }: any) => { + const rankStyleMap: { gap: string; bgColor: string; fontSize: string }[] = [ + { + gap: "6", + bgColor: "#8BBAFE", + fontSize: "32px", + }, + { + gap: "6", + bgColor: "#DCEBFD", + fontSize: "22px", + }, + { + gap: "4", + bgColor: "#DCEBFD", + fontSize: "22px", + }, + ] + + const style = rankStyleMap[rank - 1] + + return ( +
+
+
+ {rank === 1 && } +
{rank}등
+
+
{name}
+
틀어짐 {score}회
+
+
+ ) +} + +const RankCard = ({ rank, name, score, isMe }: any) => ( +
+
+
{rank}
+ {isMe ? "나" : name} +
+ 자세경고 {score}회 +
+) + +const CrewRanking = ({ rankings, myRank }: { rankings: any[]; myRank: any }) => { + const topThree = rankings.slice(0, 3) + + return ( +
+ {/* 1, 2, 3등 랭킹 */} +
+ + + +
+ + {/* 전체 랭킹 목록 */} +
+
+
+ +
+
+ {rankings.map((rank, index) => ( + + ))} +
+
+
+
+ ) +} + +export default CrewRanking diff --git a/src/components/Crew/MyCrew/CrewRanking.tsx b/src/components/Crew/MyCrew/CrewRanking.tsx index f42b58d..e7ef7f7 100644 --- a/src/components/Crew/MyCrew/CrewRanking.tsx +++ b/src/components/Crew/MyCrew/CrewRanking.tsx @@ -60,9 +60,9 @@ const CrewRanking = ({ rankings, myRank }: { rankings: any[]; myRank: any }) =>
{/* 1, 2, 3등 랭킹 */}
- - - + {topThree[0] && } + {topThree[1] && } + {topThree[2] && }
{/* 전체 랭킹 목록 */} diff --git a/src/components/Modal/InviteCrewModal.tsx b/src/components/Modal/InviteCrewModal.tsx index b4e9832..d21174b 100644 --- a/src/components/Modal/InviteCrewModal.tsx +++ b/src/components/Modal/InviteCrewModal.tsx @@ -1,8 +1,27 @@ import { ModalProps } from "@/contexts/ModalsContext" import ModalContainer from "@components/ModalContainer" +import { useState } from "react" +import RoutePath from "@/constants/routes.json" -const InviteCrewModal = (props: ModalProps): React.ReactElement => { - const { onClose, onSubmit } = props +const InviteCrewModal = (props: ModalProps & { id: string }): React.ReactElement => { + const { onClose, id } = props + const [isCopied, setIsCopied] = useState(false) + + // 현재 URL을 기반으로 초대 링크 생성 + const currentUrl = `${window.location.protocol}//${window.location.hostname}${ + window.location.port ? `:${window.location.port}` : "" + }${RoutePath.CREW}?groupId=${id}` + + const handleCopy = (): void => { + navigator.clipboard + .writeText(currentUrl) + .then(() => { + setIsCopied(true) + }) + .catch((err) => { + console.error("Failed to copy: ", err) + }) + } return ( @@ -16,17 +35,17 @@ const InviteCrewModal = (props: ModalProps): React.ReactElement => {
아래 초대 링크를 복사해 크루에 초대해 보세요.
-
{"https://alignlab.site/"}
+
{currentUrl}
{/* button */} -
초대 링크가 복사되었어요.
+ {isCopied &&
초대 링크가 복사되었어요.
}
) diff --git a/src/components/Modal/JoinCrewModal.tsx b/src/components/Modal/JoinCrewModal.tsx index 4ec380c..ace8520 100644 --- a/src/components/Modal/JoinCrewModal.tsx +++ b/src/components/Modal/JoinCrewModal.tsx @@ -5,10 +5,11 @@ import { ReactNode, useState } from "react" import { ModalProps } from "@/contexts/ModalsContext" import { useGetGroup, useJoinGroup } from "@/hooks/useGroupMutation" import { groupJoinReq } from "@/api" +import useMyGroup from "@/hooks/useMyGroup" const JoinCrewModal = (props: ModalProps): React.ReactElement => { const { onClose, onSubmit, id } = props - + const { myGroupData } = useMyGroup() const [joinCode, setJoinCode] = useState("") const [isCodeError, setIsCodeError] = useState(false) @@ -17,7 +18,12 @@ const JoinCrewModal = (props: ModalProps): React.ReactElement => { const onChangeJoinCode = (e: React.ChangeEvent): void => { if (isCodeError) setIsCodeError(false) - if (e.target.value.length <= 4) setJoinCode(e.target.value) + + const { value } = e.target + // 숫자만 남기고 업데이트 + if (/^\d*$/.test(value)) { + if (value.length <= 4) setJoinCode(value) + } } const handleSubmit = (): void => { @@ -121,12 +127,17 @@ const JoinCrewModal = (props: ModalProps): React.ReactElement => { {/* button */} -
1개의 크루에만 가입할 수 있어요.
+ {myGroupData && ( +
1개의 크루에만 가입할 수 있어요.
+ )}
)} diff --git a/src/components/Modal/Modals.tsx b/src/components/Modal/Modals.tsx index 10f0cbe..d99745b 100644 --- a/src/components/Modal/Modals.tsx +++ b/src/components/Modal/Modals.tsx @@ -4,12 +4,14 @@ import CreateCrewModal from "./CreateCrewModal" import InviteCrewModal from "./InviteCrewModal" import JoinCrewModal from "./JoinCrewModal" import WithdrawCrewModal from "./WithdrawCrewModal" +import ToWithdrawModal from "./ToWithdrawModal" export const modals = { createCrewModal: CreateCrewModal, inviteCrewModal: InviteCrewModal, joinCrewModal: JoinCrewModal, withdrawCrewModal: WithdrawCrewModal, + ToWithdrawModal: ToWithdrawModal, } const Modals = (): React.ReactNode => { diff --git a/src/components/Modal/ToWithdrawModal.tsx b/src/components/Modal/ToWithdrawModal.tsx new file mode 100644 index 0000000..4c619d8 --- /dev/null +++ b/src/components/Modal/ToWithdrawModal.tsx @@ -0,0 +1,41 @@ +import { ModalProps } from "@/contexts/ModalsContext" +import ModalContainer from "@components/ModalContainer" + +const ToWithdrawModal = (props: ModalProps): React.ReactElement => { + const { onClose, onSubmit } = props + + return ( + +
+ {/* header */} +
+
{"크루 만들기 불가"}
+
+ +
+
+ {"이미 속한 크루가 있어 크루를 만들 수 없어요.\n 탈퇴 후, 크루를 만들어주세요."} +
+
+ + {/* buttons */} +
+ + +
+
+
+ ) +} + +export default ToWithdrawModal diff --git a/src/components/ModalContainer.tsx b/src/components/ModalContainer.tsx index cd92083..bec8e6a 100644 --- a/src/components/ModalContainer.tsx +++ b/src/components/ModalContainer.tsx @@ -13,7 +13,7 @@ const ModalContainer: React.FC = ({ onClose, children }) => } // Modal이 main 안에서 절대적으로 위치하도록 변경 return ReactDOM.createPortal( -
+
{/* Close Button */}
-