Skip to content

Commit

Permalink
Merge pull request #624 from depromeet/develop
Browse files Browse the repository at this point in the history
배포용 PR
  • Loading branch information
sumi-0011 committed Mar 21, 2024
2 parents 0c7cc1d + 2e4b13a commit a3ce753
Show file tree
Hide file tree
Showing 18 changed files with 791 additions and 517 deletions.
15 changes: 15 additions & 0 deletions src/apis/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,26 @@ const NOTIFICATION_APIS = {
},
};

const NOTIFICATION_MISSIONS_APIS = {
// POST /notifications/missions/remind - 미션 알림
remind: (seconds: number) => {
return apiInstance.post('/notifications/missions/remind', { seconds });
},
};

export default NOTIFICATION_APIS;
export { NOTIFICATION_MISSIONS_APIS };

export const useNotifyUrging = (options?: UseMutationOptions<unknown, unknown, NotifyUrgingRequest>) => {
return useMutationHandleError({
mutationFn: NOTIFICATION_APIS.notifyUrging,
...options,
});
};

export const useRemind = (options?: UseMutationOptions<unknown, unknown, number>) => {
return useMutationHandleError({
mutationFn: NOTIFICATION_MISSIONS_APIS.remind,
...options,
});
};
2 changes: 2 additions & 0 deletions src/app/feed/FeedItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ const missionNameCss = css({
const remarkCss = css({
textStyle: 'body2',
color: 'text.primary',
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
});

const captionCss = css({
Expand Down
24 changes: 13 additions & 11 deletions src/app/guest/mission/stopwatch/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,30 @@ import Header from '@/components/Header/Header';
import Stopwatch from '@/components/Stopwatch/Stopwatch';
import { EVENT_LOG_CATEGORY, EVENT_LOG_NAME } from '@/constants/eventLog';
import { ROUTER } from '@/constants/router';
import useStopwatch from '@/hooks/mission/stopwatch/useStopwatch';
import useStopwatchStatus from '@/hooks/mission/stopwatch/useStopwatchStatus';
import useStopwatchLogic from '@/hooks/mission/stopwatch/useStopwatchLogic';
import useStopwatchStatus, { StopwatchStep } from '@/hooks/mission/stopwatch/useStopwatchStatus';
import useModal from '@/hooks/useModal';
import useSearchParamsTypedValue from '@/hooks/useSearchParamsTypedValue';
import { eventLogger } from '@/utils';
import { formatMMSS } from '@/utils/time';
import { css } from '@styled-system/css';

const GUEST_MISSION_ID = '';

export default function GuestMissionStopwatchPage() {
const router = useRouter();
const category = useGetCategory();

const { step, prevStep, stepLabel, onNextStep } = useStopwatchStatus();
const { seconds, minutes, stepper } = useStopwatch(step, GUEST_MISSION_ID);
const { second } = useStopwatchLogic({ status: step });

const { formattedMinutes: minutes, formattedSeconds: seconds } = formatMMSS(second);
const stepper = second < 60 ? 0 : Math.floor(second / 60 / 10);

const { isOpen, openModal, closeModal } = useModal();

const onFinishButtonClick = () => {
eventLogger.logEvent(EVENT_LOG_NAME.STOPWATCH.CLICK_FINISH_BUTTON, EVENT_LOG_CATEGORY.STOPWATCH, { category });
openModal();
onNextStep('stop');
onNextStep(StopwatchStep.stop);
};

const onFinish = () => {
Expand All @@ -56,15 +58,15 @@ export default function GuestMissionStopwatchPage() {
stopTime: Number(minutes) * 60 + Number(seconds),
isGuest: true,
});
onNextStep('stop');
onNextStep(StopwatchStep.stop);
};

const onStart = () => {
eventLogger.logEvent(EVENT_LOG_NAME.STOPWATCH.CLICK_START, EVENT_LOG_CATEGORY.STOPWATCH, {
category,
isGuest: true,
});
onNextStep('progress');
onNextStep(StopwatchStep.progress);
};

return (
Expand All @@ -90,14 +92,14 @@ export default function GuestMissionStopwatchPage() {
/>
</section>
<section className={buttonContainerCss}>
{step === 'ready' && (
{step === StopwatchStep.ready && (
<div className={fixedButtonContainerCss}>
<Button variant="primary" size="large" type="button" onClick={onStart}>
시작
</Button>
</div>
)}
{step === 'progress' && (
{step === StopwatchStep.progress && (
<>
<Button size="medium" variant="secondary" type="button" onClick={onStop}>
일시 정지
Expand All @@ -109,7 +111,7 @@ export default function GuestMissionStopwatchPage() {
)}
{step === 'stop' && (
<>
<Button size="medium" variant="secondary" type="button" onClick={() => onNextStep('progress')}>
<Button size="medium" variant="secondary" type="button" onClick={() => onNextStep(StopwatchStep.progress)}>
다시 시작
</Button>
<Button size="medium" variant="primary" type="button" onClick={onFinishButtonClick}>
Expand Down
222 changes: 222 additions & 0 deletions src/app/mission/[id]/stopwatch/ButtonSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
'use client';

import { useEffect, useState } from 'react';
import { NOTIFICATION_MISSIONS_APIS } from '@/apis/notifications';
import Button from '@/components/Button/Button';
import { EVENT_LOG_CATEGORY, EVENT_LOG_NAME } from '@/constants/eventLog';
import { StopwatchStep } from '@/hooks/mission/stopwatch/useStopwatchStatus';
import { eventLogger } from '@/utils';
import {
checkPrevProgressMission,
getPrevProgressMissionStatus,
getProgressMissionTime,
setMissionData,
setMissionTimeStack,
} from '@/utils/storage/progressMission';
import { css, cx } from '@styled-system/css';

import { useVisibilityStateVisible } from './index.hooks';
import { useStopwatchModalContext } from './Modal.context';
import { useStopwatchStepContext, useStopwatchTimeContext } from './Stopwatch.context';

function ButtonSection({ missionId }: { missionId: string }) {
const { step } = useStopwatchStepContext();
const { minutes, time } = useStopwatchTimeContext();

const logData = { finishTime: time };

const { onInitStart, onMidStart, onStop, onMidOut, onFinish, onRestart } = useStopwatch(missionId);
const { isPending: isStopwatchPending } = useInitTimeSetting({ missionId });

const onStartButtonClick = () => {
if (time > 0) {
onMidStart();
eventLogger.logEvent(EVENT_LOG_NAME.STOPWATCH.CLICK_RESTART, EVENT_LOG_CATEGORY.STOPWATCH);
return;
}
onInitStart();
eventLogger.logEvent(EVENT_LOG_NAME.STOPWATCH.CLICK_START, EVENT_LOG_CATEGORY.STOPWATCH);

NOTIFICATION_MISSIONS_APIS.remind(600);
};

const onStopButtonClick = () => {
onStop();
eventLogger.logEvent(EVENT_LOG_NAME.STOPWATCH.CLICK_STOP, EVENT_LOG_CATEGORY.STOPWATCH, {
stopTime: time,
});
};

const onRestartButtonClick = () => {
onRestart();
eventLogger.logEvent(EVENT_LOG_NAME.STOPWATCH.CLICK_RESTART, EVENT_LOG_CATEGORY.STOPWATCH);
};

const onFinishButtonClick = () => {
if (Number(minutes) < 10) {
eventLogger.logEvent(
EVENT_LOG_NAME.STOPWATCH.CLICK_FINISH_BUTTON_BEFORE_10MM,
EVENT_LOG_CATEGORY.STOPWATCH,
logData,
);
onMidOut();
return;
}
eventLogger.logEvent(EVENT_LOG_NAME.STOPWATCH.CLICK_FINISH_BUTTON, EVENT_LOG_CATEGORY.STOPWATCH, logData);
onFinish();
};

return (
<section className={cx(buttonContainerCss, opacityAnimation)}>
{step === StopwatchStep.ready && (
<div className={fixedButtonContainerCss}>
<Button
variant="primary"
size="large"
type="button"
onClick={onStartButtonClick}
disabled={isStopwatchPending}
>
시작
</Button>
</div>
)}
{step === 'progress' && (
<>
<Button size="medium" variant="secondary" type="button" onClick={onStopButtonClick}>
일시 정지
</Button>
<Button size="medium" variant="primary" type="button" onClick={onFinishButtonClick}>
끝내기
</Button>
</>
)}
{step === 'stop' && (
<>
<Button size="medium" variant="secondary" type="button" onClick={onRestartButtonClick}>
다시 시작
</Button>
<Button size="medium" variant="primary" type="button" onClick={onFinishButtonClick}>
끝내기
</Button>
</>
)}
</section>
);
}

export default ButtonSection;

const useStopwatch = (missionId: string) => {
const { onNextStep } = useStopwatchStepContext();
const { openMidOutModal, openFinalModal } = useStopwatchModalContext();

const startAction = () => {
onNextStep(StopwatchStep.progress);

// 이전 미션 기록 삭제 - 강제 접근 이슈
checkPrevProgressMission(missionId);
setMissionTimeStack(missionId, 'start');
};

const onInitStart = () => {
startAction();
setMissionData(missionId);
};

const onMidStart = () => {
startAction();
};

const onStop = () => {
onNextStep(StopwatchStep.stop);
setMissionTimeStack(missionId, 'stop');
};

const onRestart = () => {
setMissionTimeStack(missionId, 'restart');
onNextStep(StopwatchStep.progress);
};

const onMidOut = () => {
onNextStep(StopwatchStep.stop);
openMidOutModal();
};

const onFinish = () => {
onNextStep(StopwatchStep.stop);
openFinalModal();
};

return {
onInitStart,
onMidStart,
onStop,
onMidOut,
onFinish,
onRestart,
};
};

const buttonContainerCss = css({
margin: '28px auto',
display: 'flex',
justifyContent: 'center',
gap: '12px',
});

const opacityAnimation = css({
animation: 'fadeIn .7s',
});

const fixedButtonContainerCss = css({
position: 'fixed',
left: '16px',
right: '16px',
bottom: '16px',
width: '100%',
maxWidth: 'calc(475px - 48px)',
margin: '0 auto',
'@media (max-width: 475px)': {
maxWidth: 'calc(100vw - 48px)',
},
});

const MAX_SECONDS = 3600; // max 1 hour

const useInitTimeSetting = ({ missionId }: { missionId: string }) => {
const { onNextStep } = useStopwatchStepContext();
const { setTime: setSecond } = useStopwatchTimeContext();

const [isPending, setIsPending] = useState(true);
const settingInitTime = () => {
const initSeconds = getProgressMissionTime(missionId);

if (!initSeconds) return false;
if (initSeconds >= MAX_SECONDS) {
setSecond(MAX_SECONDS);
} else {
setSecond(initSeconds);
}
return true;
};

// 화면 visible 상태로 변경 시, 시간을 다시 세팅
useVisibilityStateVisible(() => {
setIsPending(true);
settingInitTime();
setIsPending(false);
});

useEffect(() => {
// 해당 미션을 이어 가는 경우. init time setting
const isSettingInit = settingInitTime();
setIsPending(false);
if (!isSettingInit) return;

const prevStatus = getPrevProgressMissionStatus(missionId);
prevStatus && onNextStep?.(prevStatus); // 바로 재시작
}, []);

return { isPending };
};
19 changes: 19 additions & 0 deletions src/app/mission/[id]/stopwatch/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Header from '@/components/Header/Header';
import { StopwatchStep } from '@/hooks/mission/stopwatch/useStopwatchStatus';

import { useStopwatchModalContext } from './Modal.context';
import { useStopwatchStepContext } from './Stopwatch.context';

function StopwatchHeader() {
const { onNextStep } = useStopwatchStepContext();
const { openBackModal } = useStopwatchModalContext();

const onBackAction = () => {
onNextStep(StopwatchStep.stop);
openBackModal();
};

return <Header rightAction="none" onBackAction={onBackAction} />;
}

export default StopwatchHeader;
Loading

0 comments on commit a3ce753

Please sign in to comment.