diff --git a/package.json b/package.json
index fba7525..8a1d560 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
},
"dependencies": {
"@tanstack/react-query": "^5.51.23",
+ "@types/react-lottie": "^1.2.10",
"axios": "1.7.3",
"core-js": "^3.28.0",
"dayjs": "^1.11.13",
@@ -23,6 +24,7 @@
"p5": "^1.9.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-lottie": "^1.2.4",
"react-router-dom": "^6.8.1",
"react-tailwindcss-datepicker": "^1.7.2",
"socket.io-client": "^4.7.5",
diff --git a/src/assets/animation/check-lottie.json b/src/assets/animation/check-lottie.json
new file mode 100644
index 0000000..f8229c4
--- /dev/null
+++ b/src/assets/animation/check-lottie.json
@@ -0,0 +1 @@
+{"v":"5.7.12","fr":24,"ip":0,"op":63,"w":520,"h":520,"nm":"Checklist 2","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Checklist","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":50,"s":[100]},{"t":62,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[259.587,260.119,0],"ix":2,"l":2},"a":{"a":0,"k":[297.587,298.119,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[54.754,-36.121],[-17.487,36.12],[-54.754,-1.147]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":23,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[294.971,298.679],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":24,"s":[100]},{"t":36,"s":[0]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":720,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Cricle","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":50,"s":[100]},{"t":62,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[260,260,0],"ix":2,"l":2},"a":{"a":0,"k":[-7.627,-7.691,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":18,"s":[124.222,124.222,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":20,"s":[134.222,134.222,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":22,"s":[114.222,114.222,100]},{"t":24,"s":[124.222,124.222,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[178.46,178.46],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.239],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"t":24,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.074509803922,0.596078431373,0.349019607843,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.074509803922,0.596078431373,0.349019607843,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":22,"s":[0]},{"t":24,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-7.627,-7.691],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":720,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Line","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":28,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":39,"s":[100]},{"t":50,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[260,260,0],"ix":2,"l":2},"a":{"a":0,"k":[298,298,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":28,"s":[46,46,100]},{"t":50,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-120.208,0],[0,-120.208],[120.207,0],[0,120.208]],"o":[[120.207,0],[0,120.208],[-120.208,0],[0,-120.208]],"v":[[0,-217.655],[217.655,0],[0,217.655],[-217.655,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.65023354923,0.829386991613,0.709956449621,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[297.587,298.119],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":28,"op":720,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shadow","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":22,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":33,"s":[100]},{"t":44,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[260,260,0],"ix":2,"l":2},"a":{"a":0,"k":[298,298,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":22,"s":[60,60,100]},{"t":44,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-94.797,0],[0,-94.797],[94.797,0],[0,94.798]],"o":[[94.797,0],[0,94.798],[-94.797,0],[0,-94.797]],"v":[[0,-171.646],[171.646,0],[0,171.646],[-171.646,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.888151161343,0.944075939702,0.895734480316,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[297.587,298.119],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":22,"op":720,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/src/assets/icons/crew-side-nav-down-arrow.svg b/src/assets/icons/crew-side-nav-down-arrow.svg
new file mode 100644
index 0000000..07c2ed7
--- /dev/null
+++ b/src/assets/icons/crew-side-nav-down-arrow.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/components/Crew/CrewRanking.tsx b/src/components/Crew/CrewRanking.tsx
index 4a0ddca..0240565 100644
--- a/src/components/Crew/CrewRanking.tsx
+++ b/src/components/Crew/CrewRanking.tsx
@@ -25,34 +25,29 @@ const RankPillar = ({ rank, name, score, height }: any) => {
const style = rankStyleMap[rank - 1]
return (
-
-
-
- {rank === 1 && (
-
-
-
- )}
-
- {rank}등
-
+
+
+ {rank === 1 &&
}
+
+ {rank}등
-
{name}
-
자세 경고 {score}회
+
{name}
+
자세 경고 {score}회
)
}
diff --git a/src/components/PoseDetector.tsx b/src/components/PoseDetector.tsx
index f43c7be..a304f70 100644
--- a/src/components/PoseDetector.tsx
+++ b/src/components/PoseDetector.tsx
@@ -2,6 +2,7 @@ import { position } from "@/api"
import { duration } from "@/api/notification"
import { poseType } from "@/api/pose"
import { useCameraPermission } from "@/hooks/useCameraPermission"
+import { useGuidePopup } from "@/hooks/useGuidePopup"
import { useModals } from "@/hooks/useModals"
import useNotification from "@/hooks/useNotification"
import { useSendPose } from "@/hooks/usePoseMutation"
@@ -12,7 +13,9 @@ import type { pose } from "@/utils/detector"
import { detectHandOnChin, detectSlope, detectTailboneSit, detectTextNeck } from "@/utils/detector"
import { drawPose } from "@/utils/drawer"
import { worker } from "@/utils/worker"
+import CheckLottie from "@assets/animation/check-lottie.json"
import { useCallback, useEffect, useRef, useState } from "react"
+import Lottie from "react-lottie"
import { useLocation } from "react-router-dom"
import Camera from "./Camera"
import { modals } from "./Modal/Modals"
@@ -29,6 +32,7 @@ const PoseDetector: React.FC = () => {
const [isHandOnChin, setIsHandOnChin] = useState
(null)
const [isModelLoaded, setIsModelLoaded] = useState(false)
const [isClosedInitialGuidePopup, setIsClosedInitialGuidePopup] = useState(false)
+ const [isSuccessSnapShotSaved, setIsSuccessSnapShotSaved] = useState(false)
// const [isSnapShotSaved, setIsSnapSaved] = useState(false)
const { showNotification, hasPermission: hasNotiPermisson } = usePushNotification()
@@ -54,6 +58,7 @@ const PoseDetector: React.FC = () => {
const { isSnapShotSaved, snapshot, setSnapShot, isInitialSnapShotExist } = useSnapShotStore()
const createSnapMutation = useCreateSnaphot()
const sendPoseMutation = useSendPose()
+ const { isPopupOpen, handleClosePopup } = useGuidePopup()
// const userNoti = useNotificationStore((state) => state.notification)
const { notification } = useNotification()
@@ -144,6 +149,10 @@ const PoseDetector: React.FC = () => {
const detect = useCallback(
(results: pose[]): void => {
+ if (!isSnapShotSaved || !isInitialSnapShotExist || isModalOpen) {
+ if (canvasRef.current) drawPose(results, canvasRef.current)
+ return
+ }
resultRef.current = results
if (snapRef.current) {
const _isShoulderTwist = detectSlope(snapRef.current, results, false)
@@ -175,6 +184,8 @@ const PoseDetector: React.FC = () => {
isSnapShotSaved,
managePoseTimer,
notification,
+ isInitialSnapShotExist,
+ isSnapShotSaved,
]
)
@@ -203,9 +214,12 @@ const PoseDetector: React.FC = () => {
{ points: req },
{
onSuccess: () => {
+ setIsSuccessSnapShotSaved(true)
+ setTimeout(() => {
+ setIsSuccessSnapShotSaved(false)
+ }, 3000)
if (snapRef.current) {
setSnapShot(snapRef.current[0].keypoints)
- // setIsSnapSaved(true)
}
},
}
@@ -284,6 +298,14 @@ const PoseDetector: React.FC = () => {
getScript()
}, [])
+ useEffect(() => {
+ if (isPopupOpen) {
+ openModal(modals.postureGuideModal, {
+ onClose: () => [handleClosePopup()],
+ })
+ }
+ }, [isPopupOpen])
+
useEffect(() => {
if (!isSnapShotSaved || !hasPermission) {
clearTimers() // 스냅샷이 저장되지 않았을 때 타이머들을 초기화
@@ -305,7 +327,7 @@ const PoseDetector: React.FC = () => {
}, [snapshot])
useEffect(() => {
- if (!isSnapShotSaved || !notification) return
+ if (!isSnapShotSaved || !notification || !isInitialSnapShotExist || isModalOpen) return
clearCnt()
clearInterval(notificationTimer.current)
@@ -318,7 +340,7 @@ const PoseDetector: React.FC = () => {
clearCnt()
}, 1000 * 60 * t)
}
- }, [notification, isSnapShotSaved])
+ }, [notification, isSnapShotSaved, isInitialSnapShotExist])
// 즉시 알림을 사용 하는 경우, 푸시를 보낼지 여부를 저장
useEffect(() => {
@@ -364,8 +386,31 @@ const PoseDetector: React.FC = () => {
{!isSnapShotSaved && hasPermission && (
)}
+
+
+ 스냅샷이 성공적으로 저장되었습니다.
+
>
)}
+
{!isClosedInitialGuidePopup && !isInitialSnapShotExist && (
)}
diff --git a/src/components/Posture/PostrueCrew.tsx b/src/components/Posture/PostrueCrew.tsx
index 8c75853..29253e5 100644
--- a/src/components/Posture/PostrueCrew.tsx
+++ b/src/components/Posture/PostrueCrew.tsx
@@ -189,7 +189,7 @@ export default function PostrueCrew(props: PostureCrewProps): ReactElement {
- 자세 알림
+ 자세 알림