Skip to content

Commit

Permalink
feat: qa사항 적용 (#30)
Browse files Browse the repository at this point in the history
* fix: tooltip에 arrow icon 추가

* fix: 슬로프 명을 고급에서 상급으로 변경

* feat: 웹캠 로딩 추가

* feat: 웹캠 화면을 30초 후 닫도록 수정

* fix: svg console.log에러 해결

* fix: high1 스키장 웹캠 링크 재설정

* fix: 리뷰 대응
  • Loading branch information
Yoon-Hae-Min authored Aug 23, 2024
1 parent 6397e42 commit f7d4a52
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 63 deletions.
44 changes: 22 additions & 22 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,28 @@
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;

--main-1: #7280ff;
--main-2: #a2b6ff;
--main-3: #bdd1fb;
--main-4: #d0dfff;
--main-5: #edf3ff;
--main-6: #cccccc;

--gray-100: #070707;
--gray-90: #171d23;
--gray-80: #222931;
--gray-70: #303a45;
--gray-60: #515e6f;
--gray-50: #878e9b;
--gray-40: #c6c8ce;
--gray-30: #eaebee;
--gray-20: #f7f8f9;
--gray-10: #ffffff;

--sub-5: #4d44ff;
--sub-4: #447eff;
--sub-2: #ff9928;
--sub-1: #ffde6b;
--main-1: 114 128 255;
--main-2: 162 182 255;
--main-3: 189 209 251;
--main-4: 208 223 255;
--main-5: 237 243 255;
--main-6: 204 204 204;

--gray-100: 7 7 7;
--gray-90: 23 29 35;
--gray-80: 34 41 49;
--gray-70: 48 58 69;
--gray-60: 81 94 111;
--gray-50: 135 142 155;
--gray-40: 198 200 206;
--gray-30: 234 235 238;
--gray-20: 247 248 249;
--gray-10: 255 255 255;

--sub-5: 77 68 255;
--sub-4: 68 126 255;
--sub-2: 255 153 40;
--sub-1: 255 222 107;
}

.dark {
Expand Down
18 changes: 18 additions & 0 deletions src/entities/slop/model/high1.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ export const HIGH1: ResortInfo = {
top: 'top-[6%]',
left: 'left-[45%]',
},
src: '/api/webcam?url=http://59.30.12.195:1935/live/_definst_/ch1.stream/playlist.m3u8',
scale: 2,
},
{
Expand All @@ -158,6 +159,7 @@ export const HIGH1: ResortInfo = {
top: 'top-[10%]',
left: 'left-[50%]',
},
src: '/api/webcam?url=http://59.30.12.195:1935/live/_definst_/ch2.stream/playlist.m3u8',
scale: 2,
},
{
Expand All @@ -167,6 +169,7 @@ export const HIGH1: ResortInfo = {
top: 'top-[6%]',
left: 'left-[54%]',
},
src: '/api/webcam?url=http://59.30.12.195:1935/live/_definst_/ch3.stream/playlist.m3u8',
scale: 2,
},
{
Expand All @@ -176,6 +179,7 @@ export const HIGH1: ResortInfo = {
top: 'top-[30%]',
left: 'left-[65%]',
},
src: '/api/webcam?url=http://59.30.12.195:1935/live/_definst_/ch4.stream/playlist.m3u8',
scale: 2,
},
{
Expand All @@ -185,6 +189,7 @@ export const HIGH1: ResortInfo = {
top: 'top-[35%]',
left: 'left-[61%]',
},
src: '/api/webcam?url=http://59.30.12.195:1935/live/_definst_/ch5.stream/playlist.m3u8',
scale: 2,
},
{
Expand All @@ -194,6 +199,7 @@ export const HIGH1: ResortInfo = {
top: 'top-[40%]',
left: 'left-[66%]',
},
src: '/api/webcam?url=http://59.30.12.195:1935/live/_definst_/ch6.stream/playlist.m3u8',
scale: 2,
},
{
Expand All @@ -203,6 +209,7 @@ export const HIGH1: ResortInfo = {
top: 'top-[68%]',
left: 'left-[78%]',
},
src: '/api/webcam?url=http://59.30.12.195:1935/live/_definst_/ch7.stream/playlist.m3u8',
scale: 2,
},
{
Expand All @@ -212,6 +219,7 @@ export const HIGH1: ResortInfo = {
top: 'top-[76%]',
left: 'left-[73%]',
},
src: '/api/webcam?url=http://59.30.12.195:1935/live/_definst_/ch8.stream/playlist.m3u8',
scale: 2,
},
{
Expand All @@ -221,6 +229,7 @@ export const HIGH1: ResortInfo = {
top: 'top-[17%]',
left: 'left-[17%]',
},
src: '/api/webcam?url=http://59.30.12.195:1935/live/_definst_/ch9.stream/playlist.m3u8',
scale: 2,
},
{
Expand All @@ -230,6 +239,7 @@ export const HIGH1: ResortInfo = {
top: 'top-[34%]',
left: 'left-[38%]',
},
src: '/api/webcam?url=http://59.30.12.195:1935/live/_definst_/ch10.stream/playlist.m3u8',
scale: 2,
},
{
Expand All @@ -239,6 +249,7 @@ export const HIGH1: ResortInfo = {
top: 'top-[40%]',
left: 'left-[40%]',
},
src: '/api/webcam?url=http://59.30.12.195:1935/live/_definst_/ch11.stream/playlist.m3u8',
scale: 2,
},
{
Expand All @@ -248,6 +259,7 @@ export const HIGH1: ResortInfo = {
top: 'top-[48%]',
left: 'left-[43%]',
},
src: '/api/webcam?url=http://59.30.12.195:1935/live/_definst_/ch12.stream/playlist.m3u8',
scale: 2,
},
{
Expand All @@ -257,6 +269,7 @@ export const HIGH1: ResortInfo = {
top: 'top-[50%]',
left: 'left-[36%]',
},
src: '/api/webcam?url=http://59.30.12.195:1935/live/_definst_/ch13.stream/playlist.m3u8',
scale: 2,
},
{
Expand All @@ -266,6 +279,7 @@ export const HIGH1: ResortInfo = {
top: 'top-[58%]',
left: 'left-[40%]',
},
src: '/api/webcam?url=http://59.30.12.195:1935/live/_definst_/ch14.stream/playlist.m3u8',
scale: 2,
},
{
Expand All @@ -275,6 +289,7 @@ export const HIGH1: ResortInfo = {
top: 'top-[58%]',
left: 'left-[52%]',
},
src: '/api/webcam?url=http://59.30.12.195:1935/live/_definst_/ch15.stream/playlist.m3u8',
scale: 2,
},
{
Expand All @@ -284,6 +299,7 @@ export const HIGH1: ResortInfo = {
top: 'top-[71%]',
left: 'left-[44%]',
},
src: '/api/webcam?url=http://59.30.12.195:1935/live/_definst_/ch16.stream/playlist.m3u8',
scale: 2,
},
{
Expand All @@ -293,6 +309,7 @@ export const HIGH1: ResortInfo = {
top: 'top-[86%]',
left: 'left-[46%]',
},
src: '/api/webcam?url=http://59.30.12.195:1935/live/_definst_/ch17.stream/playlist.m3u8',
scale: 2,
},
{
Expand All @@ -302,6 +319,7 @@ export const HIGH1: ResortInfo = {
top: 'top-[86%]',
left: 'left-[39%]',
},
src: '/api/webcam?url=http://59.30.12.195:1935/live/_definst_/ch18.stream/playlist.m3u8',
scale: 2,
},
],
Expand Down
2 changes: 1 addition & 1 deletion src/entities/slop/ui/level-chip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const LEVEL: Record<Level, { text: string; color: string }> = {
color: 'bg-sub-4',
},
ADVANCED: {
text: '고급',
text: '상급',
color: 'bg-gray-70',
},
EXPERT: {
Expand Down
56 changes: 56 additions & 0 deletions src/features/slop/hooks/useTimer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useState, useEffect, useCallback } from 'react';

interface UseTimerReturn {
timeLeft: number;
isRunning: boolean;
startTimer: (newTime?: number) => void;
pauseTimer: () => void;
resetTimer: (newTime?: number) => void;
}

const useTimer = (initialTime: number, onComplete?: () => void): UseTimerReturn => {
const [timeLeft, setTimeLeft] = useState(initialTime);
const [isRunning, setIsRunning] = useState(false);

useEffect(() => {
let intervalId: NodeJS.Timeout;

if (isRunning && timeLeft > 0) {
intervalId = setInterval(() => {
setTimeLeft((prevTime) => prevTime - 1);
}, 1000);
} else if (timeLeft === 0) {
setIsRunning(false);
if (onComplete) {
onComplete();
}
}

return () => {
if (intervalId) {
clearInterval(intervalId);
}
};
}, [isRunning, timeLeft, onComplete]);

const startTimer = (newTime?: number) => {
if (newTime !== undefined) {
setTimeLeft(newTime);
}
setIsRunning(true);
};

const pauseTimer = useCallback(() => setIsRunning(false), []);

const resetTimer = useCallback(
(newTime?: number) => {
setIsRunning(false);
setTimeLeft(newTime !== undefined ? newTime : initialTime);
},
[initialTime]
);

return { timeLeft, isRunning, startTimer, pauseTimer, resetTimer };
};

export default useTimer;
10 changes: 7 additions & 3 deletions src/features/slop/ui/slop-camera.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { toast } from 'sonner';
import type { Position, Webcam } from '@/entities/slop/model/model';
import ArrowRightIcon from '@/shared/icons/arrow-right';
import NeutralFace from '@/shared/icons/neutral-face';
import { cn } from '@/shared/lib';
import CameraButton from '@/shared/ui/cam-button';
Expand Down Expand Up @@ -62,9 +63,12 @@ const SlopCamera = ({
>
<div className={cn('relative')}>
<Tooltip trigger={<CameraButton />} isOpen={isOpen}>
<p className={cn('body3-medium')} onClick={toggleVideo}>
{name}
</p>
<div className={cn('flex items-center')}>
<p className={cn('body3-medium')} onClick={toggleVideo}>
{name}
</p>
<ArrowRightIcon />
</div>
</Tooltip>
</div>
</div>
Expand Down
51 changes: 34 additions & 17 deletions src/features/slop/ui/slop-video.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import dynamic from 'next/dynamic';
import React, { useEffect } from 'react';
import React from 'react';
import { cn } from '@/shared/lib';
import CloseButton from '@/shared/ui/close-button';
import Loading from '@/shared/ui/loading';
import useTimer from '../hooks/useTimer';

const ReactHlsPlayer = dynamic(() => import('react-hls-player'), { ssr: false });

Expand All @@ -12,33 +14,48 @@ interface SlopVideoProps {

const SlopVideo = ({ src, closeVideo }: SlopVideoProps) => {
const playerRef = React.useRef<HTMLVideoElement>(null);
const { isRunning, startTimer, timeLeft } = useTimer(30, () => {
handleVideoClose();
});

useEffect(() => {
const player = playerRef.current;
function fireOnVideoStart() {
document.body.classList.add('video-active');
playerRef?.current?.focus();
startTimer();
}

function fireOnVideoStart() {
document.body.classList.add('video-active');
player?.focus();
}

player?.addEventListener('play', fireOnVideoStart);

return () => {
player?.removeEventListener('play', fireOnVideoStart);
document.body.classList.remove('video-active');
};
}, [playerRef]);
const handleVideoClose = () => {
document.body.classList.remove('video-active');
closeVideo();
};

return (
<div className={cn('absolute top-0 z-50 h-full w-full bg-black')}>
<div
className={cn('absolute top-0 z-50 flex h-full w-full items-center justify-center bg-black')}
>
<Loading />

<ReactHlsPlayer
className={cn('absolute top-0 z-50 h-full w-full')}
playerRef={playerRef}
src={src}
autoPlay={true}
controls={false}
onPlay={() => {
fireOnVideoStart();
}}
/>
{isRunning && (
<div className="absolute bottom-0 z-[51] flex w-full justify-center bg-gray-100 bg-opacity-50 py-[9px] text-center">
<p className={cn('body1-medium text-white')}>
웹캠 화면은 {timeLeft}초 후 자동으로 닫힙니다.
</p>
</div>
)}
<CloseButton
className="video-close absolute right-4 top-4 z-[51]"
onClick={handleVideoClose}
/>
<CloseButton className="video-close absolute right-4 top-4 z-[51]" onClick={closeVideo} />
</div>
);
};
Expand Down
17 changes: 17 additions & 0 deletions src/shared/icons/arrow-right.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';

const ArrowRightIcon = () => {
return (
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M6.5 4.5L10.5 8.5L6.5 12.5"
stroke="white"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};

export default ArrowRightIcon;
15 changes: 15 additions & 0 deletions src/shared/ui/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import { cn } from '../lib';

interface LoadingProps {
className?: string;
}
const Loading = ({ className }: LoadingProps) => {
return (
<div
className={cn('absolute h-4 w-4 animate-mulShdSpin rounded-full bg-black', className)}
></div>
);
};

export default Loading;
Loading

0 comments on commit f7d4a52

Please sign in to comment.