Skip to content

Commit

Permalink
[feat/#46] 모달 Provider, useModal hook 추가, 폰트 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
lkhoony committed Sep 13, 2024
1 parent 355aff3 commit ff786ca
Show file tree
Hide file tree
Showing 17 changed files with 216 additions and 76 deletions.
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" as="style" crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/[email protected]/dist/web/static/pretendard.min.css" />
<!-- <script src="https://unpkg.com/ml5@1/dist/ml5.min.js"></script> -->
<title>자세 공작소</title>
</head>
Expand Down
5 changes: 4 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
import { Router } from "@/routes"

import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import ModalsProvider from "./providers/ModalsProvider"

const queryClient = new QueryClient()

const App = (): React.ReactElement => {
return (
<QueryClientProvider client={queryClient}>
<Router></Router>
<ModalsProvider>
<Router></Router>
</ModalsProvider>
</QueryClientProvider>
)
}
Expand Down
41 changes: 28 additions & 13 deletions src/components/Crew/CrewList.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { group, groupsReq } from "@/api"
import EmptyCrewImage from "@/assets/images/crew-empty.png"
import CrewItem from "@/components/Crew/CrewItem"
import CreateCrewModal from "@/components/Modal/CreateCrewModal"
import JoinCrewModal from "@/components/Modal/JoinCrewModal"
import { useGetGroups } from "@/hooks/useGroupMutation"
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 { useModals } from "@/hooks/useModals"
import MyCrewRankingContainer from "./MyCrewRankingContainer"
import { modals } from "../Modal/Modals"

const SORT_LIST = [
{ sort: "userCount,desc", label: "크루원 많은 순" },
Expand All @@ -19,8 +18,6 @@ const CrewList = (): ReactElement => {
const [sort, setSort] = useState<number>(0)
const [mode] = useState<"my" | "list">("my")
const [isDropdownOpen, setIsDropdownOpen] = useState<boolean>(false)
const [isJoinModalOpen, setIsJoinModalOpen] = useState<boolean>(false)
const [isCreateModalOpen, setIsCreateModalOpen] = useState<boolean>(false)

const [params] = useState<groupsReq>({
page: 0,
Expand All @@ -30,11 +27,31 @@ const CrewList = (): ReactElement => {

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

const openJoinModal = (): void => setIsJoinModalOpen(true)
const closeJoinModal = (): void => setIsJoinModalOpen(false)
const { openModal } = useModals()

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

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

const openCreateModal = (): void => setIsCreateModalOpen(true)
const closeCreateModal = (): void => setIsCreateModalOpen(false)
const openInviteModal = (): void => {
openModal(modals.inviteCrewModal, {
onSubmit: () => {
console.log("open")
},
})
}

const dropdownRef = useRef<HTMLDivElement>(null)

Expand Down Expand Up @@ -72,7 +89,7 @@ const CrewList = (): ReactElement => {
return (
<div className="flex flex-grow flex-col gap-[8px]">
{_groups.map((g) => (
<CrewItem key={`crew-item-${g.id}`} group={g} onClickDetail={openJoinModal} />
<CrewItem key={`crew-item-${g.id}`} group={g} onClickDetail={openJoinCrewModal} />
))}
</div>
)
Expand All @@ -93,7 +110,7 @@ const CrewList = (): ReactElement => {

return (
<div className="flex h-full w-full flex-col">
{mode === "my" && <MyCrewRankingContainer openCreateModal={openCreateModal} />}
{mode === "my" && <MyCrewRankingContainer openCreateModal={openCreateModal} openInviteModal={openInviteModal} />}
{/* header */}
<div className="mb-[24px] flex w-full items-center">
<div className="flex-grow text-[22px] font-bold text-zinc-900">전체크루(0)</div>
Expand Down Expand Up @@ -129,8 +146,6 @@ const CrewList = (): ReactElement => {

{/* list */}
{isLoading ? "로딩 중입니다..." : isError ? "데이터를 불러오는데 실패했습니다." : createGroupList(data?.data)}
<JoinCrewModal isOpen={isJoinModalOpen} onClose={closeJoinModal} id={0} onSubmit={(): void => {}} />
<CreateCrewModal isOpen={isCreateModalOpen} onClose={closeCreateModal} onSubmit={(): void => {}} />
</div>
)
}
Expand Down
8 changes: 6 additions & 2 deletions src/components/Crew/MyCrewRankingContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import RoutePath from "@/constants/routes.json"

interface MyCrewRankingContainerProps {
openCreateModal: () => void
openInviteModal: () => void
}

export default function MyCrewRankingContainer(props: MyCrewRankingContainerProps) {
const { openCreateModal } = props
const { openCreateModal, openInviteModal } = props
return (
<div className="mb-12">
<div className="mb-[24px] flex w-full items-center">
Expand All @@ -32,7 +33,10 @@ export default function MyCrewRankingContainer(props: MyCrewRankingContainerProp
<CrewUserIcon />
<span className="text-sm font-medium text-zinc-500">7/30명</span>
</div>
<button className="rounded-full border-[1px] border-solid border-gray-200 bg-white">
<button
className="rounded-full border-[1px] border-solid border-gray-200 bg-white"
onClick={openInviteModal}
>
<div className="flex items-center gap-1 px-2 py-1">
<SendInvitationIcon />
<span className="text-sm font-medium text-zinc-400">초대하기</span>
Expand Down
17 changes: 6 additions & 11 deletions src/components/Modal/CreateCrewModal.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import Modal from "@components/Modal"
import ModalContainer from "@components/ModalContainer"
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"

interface CreateCrewModalProps {
isOpen: boolean
onClose: () => void
onSubmit: () => void
}

const CreateCrewModal = (props: CreateCrewModalProps): React.ReactElement => {
const { isOpen, onClose, onSubmit } = props
const CreateCrewModal = (props: ModalProps): React.ReactElement => {
const { onClose, onSubmit } = props

const [name, setName] = useState<string>("")
const [description, setDescription] = useState<string>("")
Expand Down Expand Up @@ -46,7 +41,7 @@ const CreateCrewModal = (props: CreateCrewModalProps): React.ReactElement => {
}

return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalContainer onClose={onClose}>
<div className="flex flex-col items-center">
{/* header */}
<div className="mb-10 flex items-center gap-4">
Expand Down Expand Up @@ -116,7 +111,7 @@ const CreateCrewModal = (props: CreateCrewModalProps): React.ReactElement => {
크루 만들기
</button>
</div>
</Modal>
</ModalContainer>
)
}

Expand Down
17 changes: 6 additions & 11 deletions src/components/Modal/InviteCrewModal.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import Modal from "@components/Modal"
import { ModalProps } from "@/contexts/ModalsContext"
import ModalContainer from "@components/ModalContainer"

interface CreateCrewModalProps {
isOpen: boolean
onClose: () => void
onSubmit: () => void
}

const InviteCrewModal = (props: CreateCrewModalProps): React.ReactElement => {
const { isOpen, onClose, onSubmit } = props
const InviteCrewModal = (props: ModalProps): React.ReactElement => {
const { onClose, onSubmit } = props

return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalContainer onClose={onClose}>
<div className="flex flex-col items-center">
{/* header */}
<div className="mb-3 flex items-center gap-4">
Expand All @@ -32,7 +27,7 @@ const InviteCrewModal = (props: CreateCrewModalProps): React.ReactElement => {
초대 링크 복사하기
</button>
</div>
</Modal>
</ModalContainer>
)
}

Expand Down
18 changes: 6 additions & 12 deletions src/components/Modal/JoinCrewModal.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import Modal from "@components/Modal"
import ModalContainer from "@components/ModalContainer"
import CrewJoinUserIcon from "@assets/icons/crew-join-user-icon.svg?react"
import PrivateCrewIcon from "@assets/icons/crew-private-icon.svg?react"
import { useState } from "react"
import { ModalProps } from "@/contexts/ModalsContext"

interface JoinCrewModalProps {
id: number
isOpen: boolean
onClose: () => void
onSubmit: () => void
}

const JoinCrewModal = (props: JoinCrewModalProps): React.ReactElement => {
const { isOpen, onClose, onSubmit } = props
const JoinCrewModal = (props: ModalProps): React.ReactElement => {
const { onClose, onSubmit } = props

const [joinCode, setJoinCode] = useState<string>("")
const [isCodeError, setIsCodeError] = useState<boolean>(false)
Expand All @@ -22,7 +16,7 @@ const JoinCrewModal = (props: JoinCrewModalProps): React.ReactElement => {
}

return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalContainer onClose={onClose}>
<div className="flex w-full flex-col items-center">
{/* header */}
<div className="mb-[40px] flex w-full items-center gap-[16px]">
Expand Down Expand Up @@ -94,7 +88,7 @@ const JoinCrewModal = (props: JoinCrewModalProps): React.ReactElement => {
크루 가입하기
</button>
</div>
</Modal>
</ModalContainer>
)
}

Expand Down
43 changes: 43 additions & 0 deletions src/components/Modal/Modals.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { ModalsDispatchContext, ModalsStateContext } from "@/contexts/ModalsContext"
import { useContext } from "react"
import CreateCrewModal from "./CreateCrewModal"
import InviteCrewModal from "./InviteCrewModal"
import JoinCrewModal from "./JoinCrewModal"
import WithdrawCrewModal from "./WithdrawCrewModal"

export const modals = {
createCrewModal: CreateCrewModal,
inviteCrewModal: InviteCrewModal,
joinCrewModal: JoinCrewModal,
withdrawCrewModal: WithdrawCrewModal,
}

const Modals = (): React.ReactNode => {
const openedModals = useContext(ModalsStateContext)
const { close } = useContext(ModalsDispatchContext)

return openedModals.map((modal, index) => {
const { Component, props } = modal
if (!props) return null

const { onSubmit, onClose, ...rest } = props

const handleClose = async (): Promise<void> => {
if (typeof onClose === "function") {
await onClose()
}
close(Component)
}

const handleSubmit = async (): Promise<void> => {
if (typeof onSubmit === "function") {
await onSubmit()
}
handleClose()
}

// eslint-disable-next-line max-len
return <Component key={index} onClose={handleClose} onSubmit={handleSubmit} {...rest} />
})
}
export default Modals
17 changes: 6 additions & 11 deletions src/components/Modal/WithdrawCrewModal.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import Modal from "@components/Modal"
import { ModalProps } from "@/contexts/ModalsContext"
import ModalContainer from "@components/ModalContainer"

interface CreateCrewModalProps {
isOpen: boolean
onClose: () => void
onSubmit: () => void
}

const WithdrawCrewModal = (props: CreateCrewModalProps): React.ReactElement => {
const { isOpen, onClose, onSubmit } = props
const WithdrawCrewModal = (props: ModalProps): React.ReactElement => {
const { onClose, onSubmit } = props

return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalContainer onClose={onClose}>
<div className="flex flex-col items-center">
{/* header */}
<div className="mb-12 flex items-center gap-4">
Expand All @@ -37,7 +32,7 @@ const WithdrawCrewModal = (props: CreateCrewModalProps): React.ReactElement => {
</button>
</div>
</div>
</Modal>
</ModalContainer>
)
}

Expand Down
20 changes: 10 additions & 10 deletions src/components/Modal.tsx → src/components/ModalContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import React from "react"
import React, { ReactNode } from "react"
import ReactDOM from "react-dom"
import CloseIcon from "@assets/icons/modal-close-icon.svg?react"

interface ModalProps {
isOpen: boolean
onClose: () => void
children: React.ReactNode
type ModalContainerProps = {
onClose?: () => void
children: ReactNode
}

const Modal: React.FC<ModalProps> = ({ isOpen, onClose, children }) => {
if (!isOpen) return null

const ModalContainer: React.FC<ModalContainerProps> = ({ onClose, children }) => {
const handleClose = (): void => {
if (onClose && typeof onClose === "function") onClose()
}
// Modal이 main 안에서 절대적으로 위치하도록 변경
return ReactDOM.createPortal(
<div className="absolute inset-0 z-50 flex items-center justify-center bg-zinc-900 bg-opacity-20">
<div className="relative w-[640px] rounded-lg bg-white px-10 pb-6 pt-10 shadow-lg">
{/* Close Button */}
<button className="absolute right-10 top-10 text-gray-500 hover:text-gray-800" onClick={onClose}>
<button className="absolute right-10 top-10 text-gray-500 hover:text-gray-800" onClick={handleClose}>
<CloseIcon />
</button>
{/* Modal Content */}
Expand All @@ -27,4 +27,4 @@ const Modal: React.FC<ModalProps> = ({ isOpen, onClose, children }) => {
)
}

export default Modal
export default ModalContainer
3 changes: 2 additions & 1 deletion src/components/PoseDetector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ const PoseDetector: React.FC = () => {
const req = { snapshot: { keypoints, score }, type: poseType }
sendPoseMutation.mutate(req)
cntRef.current = cntRef.current + 1
if (isShowNoti) showNotification(`척추 건강 위험! ${getPoseName(poseType)} 감지! 자세를 바르게 앉아주세요.`)
if (isShowNoti)
showNotification(`척추 건강 위험! ${getPoseName(poseType)} 감지! 자세를 바르게 앉아주세요.`)
}
}, 30 * 1000)
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { default as Camera } from "./Camera"
export { default as PoseDetector } from "./PoseDetector"
export { default as Modal } from "./Modal"
export { default as ModalContainer } from "./ModalContainer"
21 changes: 21 additions & 0 deletions src/contexts/ModalsContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ComponentType, createContext } from "react"

export type ModalProps = {
id?: number
onClose?: () => void
onSubmit?: () => void
}

export type ModalComponent = ComponentType<any>
export type ModalsState = Array<{ Component: ModalComponent; props?: ModalProps }>
export type ModalsDispatch = {
open: (Component: ModalComponent, props: ModalProps) => void
close: (Component: ModalComponent) => void
}

export const ModalsStateContext = createContext<ModalsState>([])

export const ModalsDispatchContext = createContext<ModalsDispatch>({
open: () => {},
close: () => {},
})
Loading

0 comments on commit ff786ca

Please sign in to comment.