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/#58] 모니터링 페이지에 가이드 팝업 수정 , 현재 접속된 크루 보기가 기본 off 되어 보이도록 수정, 소켓 … #73

Merged
merged 1 commit into from
Sep 21, 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
4 changes: 2 additions & 2 deletions src/api/notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const getNotification = async (): Promise<notification | null> => {
}
}

export const modifyNotification = async (notification: notification): Promise<notification> => {
export const registerNotification = async (notification: notification): Promise<notification> => {
try {
const res = await axiosInstance.post(`/pose-notifications`, { ...notification })
const { id, duration } = res.data.data
Expand All @@ -31,7 +31,7 @@ export const modifyNotification = async (notification: notification): Promise<no
}
}

export const patchNotification = async (notification: notification): Promise<notification> => {
export const updateNotification = async (notification: notification): Promise<notification> => {
try {
const res = await axiosInstance.patch(`/pose-notifications/${notification.id}`, { ...notification })
const { id, isActive, duration } = res.data.data
Expand Down
Binary file added src/assets/images/posture-snapshot-guide.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/PoseDetector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { drawPose } from "@/utils/drawer"
import { worker } from "@/utils/worker"
import { useCallback, useEffect, useRef, useState } from "react"
import Camera from "./Camera"
import GuidePopup from "./Posture/GuidePopup"
import GuidePopup from "./Posture/GuidePopup/GuidePopup"
import { useSnapshotStore } from "@/store/SnapshotStore"
import { useCreateSnaphot } from "@/hooks/useSnapshotMutation"
import { position } from "@/api"
Expand Down
28 changes: 0 additions & 28 deletions src/components/Posture/GuidePopup.tsx

This file was deleted.

38 changes: 38 additions & 0 deletions src/components/Posture/GuidePopup/GuidePopup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ReactElement, useState } from "react"
import ServiceIntroduction from "./ServiceIntroduction"
import SnapshotGuide from "./SnapshotGuide"

const GuidePopup = ({ onClose }: { onClose: () => void }): ReactElement => {
const [step, setStep] = useState(0)
const onClickNext = () => {
setStep(1)
}
return (
<div className="absolute inset-0 flex items-center justify-center rounded-3xl backdrop-blur-lg">
{/* blur 처리 */}
<div className="pointer-events-auto relative flex h-[504px] w-[800px] flex-col items-center rounded-lg bg-white p-8 shadow-lg">
{step === 0 && (
<>
<ServiceIntroduction />
<button
className="h-[50px] w-[354px] rounded-full bg-[#1A75FF] px-[39px] py-3 text-white"
onClick={onClickNext}
>
다음
</button>
</>
)}
{step === 1 && (
<>
<SnapshotGuide />
<button className="w-[354px] rounded-full bg-[#1A75FF] px-4 py-3 text-white" onClick={onClose}>
모니터링 시작하기
</button>
</>
)}
</div>
</div>
)
}

export default GuidePopup
42 changes: 42 additions & 0 deletions src/components/Posture/GuidePopup/ServiceIntroduction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export default function ServiceIntroduction() {
return (
<>
<div className="mb-8 flex gap-2">
<div className="h-2 w-2 rounded-full bg-[#1A75FF]"></div>
<div className="h-2 w-2 rounded-full bg-zinc-300"></div>
</div>
<div className="mb-6">
<div className="mb-3 text-center text-[30px] font-bold leading-10 text-[#1E2535]">
자세공작소는 실시간 모니터링으로 <br />
바른 자세 유지를 돕는 서비스입니다
</div>
<span className="text-[14px] font-normal text-zinc-500">
더 세밀한 모니터링을 위해, 최초 1회 자세 기준점 설정이 필요합니다.
</span>
</div>
{/* content */}
<div className="mb-8 flex gap-4">
<div className="flex w-80 flex-col items-center rounded-[17px] bg-[#EFEFF0] px-10 py-6">
<div className="mb-4 h-[24px] w-[24px] rounded-full bg-[#5A9CFF] text-center text-[15px] font-semibold text-white">
1
</div>
<span className="mb-2 text-[20px] font-semibold text-[#1E2535]">바른 자세 취하기</span>
<div className="text-center font-normal leading-6 text-zinc-500">
가이드에 따라 <br />
바른 자세를 취해 주세요.
</div>
</div>
<div className="flex w-80 flex-col items-center rounded-[17px] bg-[#EFEFF0] px-10 py-6">
<div className="mb-4 h-[24px] w-[24px] rounded-full bg-[#5A9CFF] text-center text-[15px] font-semibold text-white">
2
</div>
<span className="mb-2 text-[20px] font-semibold text-[#1E2535]">스냅샷 촬영하기</span>
<div className="text-center font-normal leading-6 text-zinc-500">
기준점 설정을 위해 <br />
촬영을 진행해 주세요.
</div>
</div>
</div>
</>
)
}
47 changes: 47 additions & 0 deletions src/components/Posture/GuidePopup/SnapshotGuide.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import CloseCrewPanelIcon from "@assets/images/posture-snapshot-guide.png"

export default function SnapshotGuide() {
return (
<>
<div className="mb-6 flex gap-2">
<div className="h-2 w-2 rounded-full bg-zinc-300"></div>
<div className="h-2 w-2 rounded-full bg-[#1A75FF]"></div>
</div>
<div className="mb-6">
<div className="mb-3 text-center text-[30px] font-bold text-[#1E2535]">바른 자세를 취해주세요</div>
</div>
{/* content */}
<div className="mb-8 flex items-center gap-8">
<div className="flex h-[254px] w-[284px] flex-col items-center justify-end rounded-[17px] bg-[#EFEFF0]">
<img src={CloseCrewPanelIcon} alt="스냅샷 가이드" />
</div>
<div className="flex flex-col gap-5">
<div className="flex gap-3">
<span className="flex h-6 w-6 justify-center rounded-full bg-[#5A9CFF] text-center font-semibold text-white">
1
</span>
<span className="font-[20px] font-semibold text-green-900">머리와 목을 일직선으로 곧게 펴기</span>
</div>
<div className="flex gap-3">
<span className="flex h-6 w-6 justify-center rounded-full bg-[#5A9CFF] text-center font-semibold text-white">
2
</span>
<span className="font-[20px] font-semibold text-green-900">양쪽 어깨 일직선 유지하기</span>
</div>
<div className="flex gap-3">
<span className="flex h-6 w-6 justify-center rounded-full bg-[#5A9CFF] text-center font-semibold text-white">
3
</span>
<span className="font-[20px] font-semibold text-green-900">팔은 책상 위에 수평으로 두기</span>
</div>
<div className="flex gap-3">
<span className="flex h-6 w-6 justify-center rounded-full bg-[#5A9CFF] text-center font-semibold text-white">
4
</span>
<span className="font-[20px] font-semibold text-green-900">등과 허리는 등받이에 지지하기</span>
</div>
</div>
</div>
</>
)
}
43 changes: 34 additions & 9 deletions src/components/Posture/PostrueCrew.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import CloseCrewPanelIcon from "@assets/icons/crew-panel-close-button.svg?react"
import QuestionIcon from "@assets/icons/question-info-icon.svg?react"
import PostureGuide from "@assets/icons/posture-guide-button-icon.svg?react"
import RankingGuideToolTip from "@assets/images/ranking-guide.png"
import { ReactElement, useEffect, useState } from "react"
import { ReactElement, useCallback, useEffect, useState } from "react"
import SelectBox from "@components/SelectBox"
import { useAuthStore } from "@/store"
import { duration, notification } from "@/api/notification"
Expand Down Expand Up @@ -35,11 +35,16 @@ const NOTI_OPTIONS: NotiOption[] = [
{ value: "MIN_60", label: "1시간 간격" },
]

const MAX_RECONNECT_ATTEMPTS = 5
const INITIAL_RECONNECT_DELAY = 1000 //

export default function PostrueCrew(props: PostureCrewProps): ReactElement {
const { toggleSidebar } = props
const accessToken = useAuthStore((state) => state.accessToken)
const [crews, setCrews] = useState<IPostureCrew[]>([])
const [isConnected, setIsConnected] = useState<"loading" | "success" | "disconnected">("loading")
const [socket, setSocket] = useState<WebSocket | null>(null)
const [reconnectAttempts, setReconnectAttempts] = useState(0)

const userNoti = useNotificationStore((state) => state.notification)
const setUserNoti = useNotificationStore((state) => state.setNotification)
Expand All @@ -49,32 +54,52 @@ export default function PostrueCrew(props: PostureCrewProps): ReactElement {
const [isEnabled, setIsEnabled] = useState(userNoti?.isActive)
const [notiAlarmTime, setNotiAlarmTime] = useState(NOTI_OPTIONS.find((n) => n.value === userNoti?.duration)?.label)

useEffect(() => {
const socket = new WebSocket(`wss://api.alignlab.site/ws/v1/groups/1/users?X-HERO-AUTH-TOKEN=${accessToken}`)
const connectWebSocket = useCallback(() => {
const newSocket = new WebSocket(`wss://api.alignlab.site/ws/v1/groups/1/users?X-HERO-AUTH-TOKEN=${accessToken}`)

socket.onopen = () => {
newSocket.onopen = () => {
console.log("WebSocket connected")
setIsConnected("success")
setReconnectAttempts(0)
}

socket.onmessage = (event) => {
newSocket.onmessage = (event) => {
const data = JSON.parse(event.data)
setCrews(data.groupUsers || [])
}

socket.onerror = (error) => {
newSocket.onerror = (error) => {
console.error("WebSocket error:", error)
}

socket.onclose = (event) => {
newSocket.onclose = (event) => {
console.log("WebSocket disconnected. Code:", event.code, "Reason:", event.reason)
setIsConnected("disconnected")

if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
const delay = INITIAL_RECONNECT_DELAY * Math.pow(2, reconnectAttempts)
console.log(`Attempting to reconnect in ${delay}ms...`)
setTimeout(() => {
setReconnectAttempts((prev) => prev + 1)
connectWebSocket()
}, delay)
} else {
console.log("Max reconnection attempts reached. Please try again later.")
}
}

setSocket(newSocket)
}, [accessToken, reconnectAttempts])

useEffect(() => {
connectWebSocket()

return () => {
socket.close()
if (socket) {
socket.close()
}
}
}, [])
}, [connectWebSocket])

const onClickCloseSideNavButton = (): void => {
toggleSidebar()
Expand Down
2 changes: 1 addition & 1 deletion src/components/SelectBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interface SelectBoxOption {
interface SelectBoxProps {
value: string | undefined
options: SelectBoxOption[]
isDisabled: boolean
isDisabled?: boolean
onClick: (selectedOption: SelectBoxOption) => void
}

Expand Down
6 changes: 3 additions & 3 deletions src/hooks/useNotiMutation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getNotification, modifyNotification, notification, patchNotification } from "@/api/notification"
import { getNotification, registerNotification, notification, updateNotification } from "@/api/notification"
import { useMutation, UseMutationResult } from "@tanstack/react-query"

export const useGetNoti = (): UseMutationResult<notification | null, unknown, void, unknown> => {
Expand All @@ -15,7 +15,7 @@ export const useGetNoti = (): UseMutationResult<notification | null, unknown, vo
export const useModifyNoti = (): UseMutationResult<notification, unknown, notification, unknown> => {
return useMutation({
mutationFn: (notification: notification) => {
return modifyNotification(notification)
return registerNotification(notification)
},
onSuccess: (data) => {
console.log(data)
Expand All @@ -26,7 +26,7 @@ export const useModifyNoti = (): UseMutationResult<notification, unknown, notifi
export const usePatchNoti = (): UseMutationResult<notification, unknown, notification, unknown> => {
return useMutation({
mutationFn: (notification: notification) => {
return patchNotification(notification)
return updateNotification(notification)
},
onSuccess: (data) => {
console.log(data)
Expand Down
2 changes: 1 addition & 1 deletion src/pages/MonitoringPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const MonitoringPage: React.FC = () => {
const setSnap = useSnapshotStore((state) => state.setSnapshot)
const snapshot = useSnapshotStore((state) => state.snapshot)

const [isSidebarOpen, setIsSidebarOpen] = useState<boolean>(true)
const [isSidebarOpen, setIsSidebarOpen] = useState<boolean>(false)

const toggleSidebar = (): void => {
setIsSidebarOpen((prev) => !prev)
Expand Down
10 changes: 8 additions & 2 deletions src/pages/MyCrew.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import CrewUserIcon from "@assets/icons/crew-user-icon.svg?react"
import dayjs from "dayjs"
import { modals } from "@/components/Modal/Modals"
import { useNavigate } from "react-router-dom"
import { useCallback } from "react"
import { useCallback, useEffect } from "react"

export default function MyCrew() {
const { myGroupData, ranks, myRank, withdrawFromGroup } = useMyGroup()
const { myGroupData, ranks, myRank, withdrawFromGroup, isLoading } = useMyGroup()
const { openModal } = useModals()
const naviagte = useNavigate()

Expand All @@ -38,6 +38,12 @@ export default function MyCrew() {
})
}

useEffect(() => {
if (!myGroupData && !isLoading) {
naviagte("/crew")
}
}, [myGroupData, isLoading])

const openInviteModal = useCallback((): void => {
openModal(modals.inviteCrewModal, {
id: Number(myGroupData?.id),
Expand Down
Loading