From a7c7a78a037b12afe12835c14db2c9b35f32b00b Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 28 Sep 2023 12:28:58 +0900 Subject: [PATCH 1/5] =?UTF-8?q?chore:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20interval=20Input=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/IntervalInput.stories.tsx | 37 ----- .../songs/components/IntervalInput.tsx | 155 ------------------ 2 files changed, 192 deletions(-) delete mode 100644 frontend/src/features/songs/components/IntervalInput.stories.tsx delete mode 100644 frontend/src/features/songs/components/IntervalInput.tsx diff --git a/frontend/src/features/songs/components/IntervalInput.stories.tsx b/frontend/src/features/songs/components/IntervalInput.stories.tsx deleted file mode 100644 index 3e9c6f78f..000000000 --- a/frontend/src/features/songs/components/IntervalInput.stories.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { useState } from 'react'; -import { VideoPlayerProvider } from '@/features/youtube/components/VideoPlayerProvider'; -import IntervalInput from './IntervalInput'; -import { VoteInterfaceProvider } from './VoteInterfaceProvider'; -import type { Meta, StoryObj } from '@storybook/react'; - -const meta = { - component: IntervalInput, - title: 'IntervalInput', - decorators: [ - (Story) => ( - - - - - - ), - ], -} satisfies Meta; - -export default meta; - -type Story = StoryObj; - -const TestIntervalInput = () => { - const [errorMessage, setErrorMessage] = useState(''); - - const onChangeErrorMessage = (message: string) => { - setErrorMessage(message); - }; - - return ; -}; - -export const Default = { - render: () => , -} satisfies Story; diff --git a/frontend/src/features/songs/components/IntervalInput.tsx b/frontend/src/features/songs/components/IntervalInput.tsx deleted file mode 100644 index 7a3a22f56..000000000 --- a/frontend/src/features/songs/components/IntervalInput.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import { useState } from 'react'; -import { css, styled } from 'styled-components'; -import { secondsToMinSec } from '@/shared/utils/convertTime'; -import { isValidMinSec } from '@/shared/utils/validateTime'; -import ERROR_MESSAGE from '../constants/errorMessage'; -import useVoteInterfaceContext from '../hooks/useVoteInterfaceContext'; -import { isInputName } from '../types/IntervalInput.type'; -import type { IntervalInputType } from '../types/IntervalInput.type'; - -export interface IntervalInputProps { - errorMessage: string; - onChangeErrorMessage: (message: string) => void; -} - -const IntervalInput = ({ errorMessage, onChangeErrorMessage }: IntervalInputProps) => { - const { interval, partStartTime, videoLength, updatePartStartTime } = useVoteInterfaceContext(); - - const [activeInput, setActiveInput] = useState(null); - - const partEndTime = partStartTime + interval; - const { minute: startMinute, second: startSecond } = secondsToMinSec(partStartTime); - const { minute: endMinute, second: endSecond } = secondsToMinSec(partEndTime); - - const onChangeIntervalStart: React.ChangeEventHandler = ({ - currentTarget: { name: timeUnit, value, valueAsNumber }, - }) => { - if (!isValidMinSec(value)) { - onChangeErrorMessage(ERROR_MESSAGE.MIN_SEC); - - return; - } - - onChangeErrorMessage(''); - updatePartStartTime(timeUnit, valueAsNumber); - }; - - const onFocusIntervalStart: React.FocusEventHandler = ({ - currentTarget: { name }, - }) => { - if (isInputName(name)) { - setActiveInput(name); - } - }; - - const onBlurIntervalStart = () => { - if (partStartTime + interval > videoLength) { - const { minute: songMin, second: songSec } = secondsToMinSec(videoLength - interval); - - onChangeErrorMessage(ERROR_MESSAGE.SONG_RANGE(songMin, songSec)); - return; - } - - onChangeErrorMessage(''); - setActiveInput(null); - }; - - return ( - - - - : - - ~ - - : - - - {errorMessage} - - ); -}; - -export default IntervalInput; - -const IntervalContainer = styled.div` - display: flex; - flex-direction: column; - justify-content: space-between; - - padding: 0 24px; - - font-size: 16px; - color: ${({ theme: { color } }) => color.white}; -`; - -const Flex = styled.div` - display: flex; -`; - -const ErrorMessage = styled.p` - margin: 8px 0; - font-size: 12px; - color: ${({ theme: { color } }) => color.error}; -`; - -const Separator = styled.span<{ $inactive?: boolean }>` - flex: none; - - margin: 0 8px; - padding-bottom: 8px; - - color: ${({ $inactive, theme: { color } }) => $inactive && color.subText}; - text-align: center; -`; - -const inputBase = css` - flex: 1; - - width: 16px; - margin: 0 8px; - margin: 0; - padding: 0; - - text-align: center; - - background-color: transparent; - border: none; - border-bottom: 1px solid white; - outline: none; - -webkit-box-shadow: none; - box-shadow: none; -`; - -const InputStart = styled.input<{ $active: boolean }>` - ${inputBase} - color: ${({ theme: { color } }) => color.white}; - border-bottom: 1px solid - ${({ $active, theme: { color } }) => ($active ? color.primary : color.white)}; -`; - -const InputEnd = styled.input` - ${inputBase} - color: ${({ theme: { color } }) => color.subText}; - border-bottom: 1px solid ${({ theme: { color } }) => color.subText}; -`; From 5cda5c36faf0e5a6d04877ddda4a9c31e166c5d7 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 28 Sep 2023 12:30:26 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20interval=EC=97=90=20null=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EC=B6=94=EA=B0=80=20=ED=9B=84=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/songs/components/VoteInterface.tsx | 4 +++- .../songs/components/VoteInterfaceProvider.tsx | 10 ++++++++-- .../features/youtube/components/VideoSlider.tsx | 15 +++++++++------ 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/frontend/src/features/songs/components/VoteInterface.tsx b/frontend/src/features/songs/components/VoteInterface.tsx index 8550ba630..c88bd1b73 100644 --- a/frontend/src/features/songs/components/VoteInterface.tsx +++ b/frontend/src/features/songs/components/VoteInterface.tsx @@ -22,10 +22,12 @@ const VoteInterface = () => { const { user } = useAuthContext(); - const voteTimeText = toPlayingTimeText(partStartTime, partStartTime + interval); + const voteTimeText = interval ? toPlayingTimeText(partStartTime, partStartTime + interval) : ''; const submitKillingPart = async () => { + if (!interval) return; videoPlayer.current?.pauseVideo(); + await createKillingPart(songId, { startSecond: partStartTime, length: interval }); openModal(); }; diff --git a/frontend/src/features/songs/components/VoteInterfaceProvider.tsx b/frontend/src/features/songs/components/VoteInterfaceProvider.tsx index 7f1972355..7d3b78bfc 100644 --- a/frontend/src/features/songs/components/VoteInterfaceProvider.tsx +++ b/frontend/src/features/songs/components/VoteInterfaceProvider.tsx @@ -5,7 +5,7 @@ import type { PropsWithChildren } from 'react'; interface VoteInterfaceContextProps extends VoteInterfaceProviderProps { partStartTime: number; - interval: KillingPartInterval; + interval: KillingPartInterval | null; // NOTE: Why both setState and eventHandler have same naming convention? updatePartStartTime: (timeUnit: string, value: number) => void; updateKillingPartInterval: React.MouseEventHandler; @@ -25,12 +25,17 @@ export const VoteInterfaceProvider = ({ songId, songVideoId, }: PropsWithChildren) => { - const [interval, setInterval] = useState(10); + const [interval, setInterval] = useState(10); const [partStartTime, setPartStartTime] = useState(0); const { videoPlayer } = useVideoPlayerContext(); const updateKillingPartInterval: React.MouseEventHandler = (e) => { const newInterval = Number(e.currentTarget.dataset['interval']) as KillingPartInterval; + if (newInterval === interval) { + setInterval(null); + return; + } + const partEndTime = partStartTime + newInterval; if (partEndTime > videoLength) { @@ -62,6 +67,7 @@ export const VoteInterfaceProvider = ({ }; useEffect(() => { + if (!interval) return; const timer = window.setInterval(() => { videoPlayer.current?.seekTo(partStartTime, true); }, interval * 1000); diff --git a/frontend/src/features/youtube/components/VideoSlider.tsx b/frontend/src/features/youtube/components/VideoSlider.tsx index e69279bd1..963dbe55f 100644 --- a/frontend/src/features/youtube/components/VideoSlider.tsx +++ b/frontend/src/features/youtube/components/VideoSlider.tsx @@ -8,9 +8,12 @@ const VideoSlider = () => { const { interval, partStartTime, videoLength, updatePartStartTime } = useVoteInterfaceContext(); const { videoPlayer } = useVideoPlayerContext(); - const partEndTime = partStartTime + interval; - const partStartTimeText = toMinSecText(partStartTime); - const partEndTimeText = toMinSecText(partEndTime); + const partStartTimeText = interval ? toMinSecText(partStartTime) : toMinSecText(0); + const partEndTimeText = interval + ? toMinSecText(partStartTime + interval) + : toMinSecText(videoLength); + + const maxPlayingTime = interval ? videoLength - interval : videoLength; const changeTime: ChangeEventHandler = ({ currentTarget: { valueAsNumber: currentSelectedTime }, @@ -40,7 +43,7 @@ const VideoSlider = () => { onTouchEnd={seekToTime} onMouseUp={seekToTime} min={0} - max={videoLength - interval} + max={maxPlayingTime} step={1} interval={interval} /> @@ -86,7 +89,7 @@ export const PartEndTime = styled.span` font-weight: 700; `; -const Slider = styled.input<{ interval: number }>` +const Slider = styled.input<{ interval: number | null }>` cursor: pointer; width: 100%; @@ -99,7 +102,7 @@ const Slider = styled.input<{ interval: number }>` position: relative; top: -4px; - width: ${({ interval }) => interval * 6}px; + width: ${({ interval }) => (interval ? interval * 6 : 6)}px; height: 16px; -webkit-appearance: none; From 35843b1d22cb26689f3d51306798b9f64668c1ed Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 28 Sep 2023 12:48:04 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=EA=B5=AC=EA=B0=84=EC=9D=B4=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=EB=90=98=EC=96=B4=EC=9E=88=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EC=9D=80=20=EA=B2=BD=EC=9A=B0=20=EB=93=B1=EB=A1=9D=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20disabled=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../songs/components/VoteInterface.tsx | 24 ++++++++++++------- .../youtube/components/VideoSlider.tsx | 2 +- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/frontend/src/features/songs/components/VoteInterface.tsx b/frontend/src/features/songs/components/VoteInterface.tsx index c88bd1b73..9d995a101 100644 --- a/frontend/src/features/songs/components/VoteInterface.tsx +++ b/frontend/src/features/songs/components/VoteInterface.tsx @@ -16,14 +16,12 @@ const VoteInterface = () => { const { showToast } = useToastContext(); const { interval, partStartTime, songId, songVideoId } = useVoteInterfaceContext(); const { videoPlayer } = useVideoPlayerContext(); - const { createKillingPart } = usePostKillingPart(); const { isOpen, openModal, closeModal } = useModal(); - const { user } = useAuthContext(); const voteTimeText = interval ? toPlayingTimeText(partStartTime, partStartTime + interval) : ''; - + const isDisabledSummit = interval === null; const submitKillingPart = async () => { if (!interval) return; videoPlayer.current?.pauseVideo(); @@ -48,9 +46,15 @@ const VoteInterface = () => { - + 등록 + {isDisabledSummit && ( + <> + + 킬링파트 구간 선택 후 등록이 가능합니다. + + )} @@ -92,15 +96,15 @@ const RegisterTitle = styled.p` `; const Register = styled.button` - cursor: pointer; + cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')}; width: 100%; height: 36px; font-weight: 700; - color: ${({ theme: { color } }) => color.white}; - - background-color: ${({ theme: { color } }) => color.primary}; + color: ${({ theme: { color }, disabled }) => (disabled ? color.disabled : color.white)}; + background-color: ${({ theme: { color }, disabled }) => + disabled ? color.disabledBackground : color.primary}; border: none; border-radius: 10px; `; @@ -152,3 +156,7 @@ const ButtonContainer = styled.div` const Warning = styled.div` color: ${({ theme: { color } }) => color.subText}; `; + +const Information = styled.p` + color: ${({ theme: { color } }) => color.primary}; +`; diff --git a/frontend/src/features/youtube/components/VideoSlider.tsx b/frontend/src/features/youtube/components/VideoSlider.tsx index 963dbe55f..eb62ec6cf 100644 --- a/frontend/src/features/youtube/components/VideoSlider.tsx +++ b/frontend/src/features/youtube/components/VideoSlider.tsx @@ -102,7 +102,7 @@ const Slider = styled.input<{ interval: number | null }>` position: relative; top: -4px; - width: ${({ interval }) => (interval ? interval * 6 : 6)}px; + width: ${({ interval }) => (interval ? interval * 6 : 2)}px; height: 16px; -webkit-appearance: none; From a45c3216bf6903c7c672522f35a935cc928ec73e Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 28 Sep 2023 13:35:26 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20=EB=93=B1=EB=A1=9D=20=EA=B5=AC?= =?UTF-8?q?=EA=B0=84=20=EB=B2=94=EC=9C=84=20=EB=B3=80=EA=B2=BD=20=EC=8B=9C?= =?UTF-8?q?=20=EA=B5=AC=EA=B0=84=20=EC=8B=9C=EC=9E=91=EC=A0=90=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/songs/components/VoteInterfaceProvider.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/features/songs/components/VoteInterfaceProvider.tsx b/frontend/src/features/songs/components/VoteInterfaceProvider.tsx index 7d3b78bfc..481de8188 100644 --- a/frontend/src/features/songs/components/VoteInterfaceProvider.tsx +++ b/frontend/src/features/songs/components/VoteInterfaceProvider.tsx @@ -43,6 +43,7 @@ export const VoteInterfaceProvider = ({ setPartStartTime(partStartTime - overflowedSeconds); } + videoPlayer.current?.seekTo(partStartTime, true); setInterval(newInterval); }; From a1e960ee1f96e7c4ed62dcea55defee22fbdd845 Mon Sep 17 00:00:00 2001 From: ukkodeveloper Date: Thu, 28 Sep 2023 15:25:46 +0900 Subject: [PATCH 5/5] =?UTF-8?q?style:=20style=20lint=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/songs/components/VoteInterface.tsx | 1 + frontend/src/pages/LoginPage.tsx | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/features/songs/components/VoteInterface.tsx b/frontend/src/features/songs/components/VoteInterface.tsx index 9d995a101..7a418fd6d 100644 --- a/frontend/src/features/songs/components/VoteInterface.tsx +++ b/frontend/src/features/songs/components/VoteInterface.tsx @@ -103,6 +103,7 @@ const Register = styled.button` font-weight: 700; color: ${({ theme: { color }, disabled }) => (disabled ? color.disabled : color.white)}; + background-color: ${({ theme: { color }, disabled }) => disabled ? color.disabledBackground : color.primary}; border: none; diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx index 68e9aefe8..2b18b499a 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/pages/LoginPage.tsx @@ -109,11 +109,12 @@ const PlatformName = styled.div` display: flex; align-items: center; justify-content: center; - text-align: center; width: 400px; height: 60px; + text-align: center; + border-radius: 12px; @media (max-width: ${({ theme }) => theme.breakPoints.xs}) {