diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7351a961..ed807ab5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @wooBottle @wade3420 @junoshon @sumi-0011 \ No newline at end of file +* @wooBottle @wade3420 @sumi-0011 diff --git a/public/assets/character/basic.png b/public/assets/character/basic.png new file mode 100644 index 00000000..6078f620 Binary files /dev/null and b/public/assets/character/basic.png differ diff --git a/public/assets/character/flag.png b/public/assets/character/flag.png new file mode 100644 index 00000000..598d59d8 Binary files /dev/null and b/public/assets/character/flag.png differ diff --git a/public/assets/character/sad.png b/public/assets/character/sad.png new file mode 100644 index 00000000..53a257b3 Binary files /dev/null and b/public/assets/character/sad.png differ diff --git a/src/apis/auth.ts b/src/apis/auth.ts index 88e7eb96..e2337387 100644 --- a/src/apis/auth.ts +++ b/src/apis/auth.ts @@ -18,6 +18,7 @@ interface LoginResponse { accessToken: string; refreshToken: string; memberId?: number; + landingStatus?: 'TO_MAIN' | 'TO_ONBOARDING'; } interface RegisterRequest { diff --git a/src/app/auth/kakaoCallback/page.tsx b/src/app/auth/kakaoCallback/page.tsx index 4df463c5..6eecea38 100644 --- a/src/app/auth/kakaoCallback/page.tsx +++ b/src/app/auth/kakaoCallback/page.tsx @@ -5,6 +5,7 @@ import { useRouter, useSearchParams } from 'next/navigation'; import { useSocialLogin } from '@/apis/auth'; import Loading from '@/components/Loading'; import { AUTH_PROVIDER } from '@/constants/common'; +import { EVENT_LOG_CATEGORY, EVENT_LOG_NAME } from '@/constants/eventLog'; import { ROUTER } from '@/constants/router'; import { eventLogger } from '@/utils'; @@ -32,7 +33,11 @@ export default function KakaoCallbackPage() { if (successData?.memberId) { eventLogger.identify(successData.memberId.toString()); } - + if (successData.landingStatus === 'TO_ONBOARDING') { + eventLogger.logEvent(EVENT_LOG_CATEGORY.ONBOARDING, EVENT_LOG_NAME.ONBOARDING.SUCCESS_SIGNUP); + router.push(ROUTER.ONBOARDING.HOME); + return; + } router.push(params.get('state') ?? ROUTER.HOME); }, }, diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx index 9932bb6e..9e8990c8 100644 --- a/src/app/auth/login/page.tsx +++ b/src/app/auth/login/page.tsx @@ -7,6 +7,7 @@ import { useSocialLogin, useUpdateMemberFcmToken } from '@/apis/auth'; // import Button from '@/components/Button/Button'; import ButtonSocialLogin from '@/components/ButtonSocialLogin/ButtonSocialLogin'; import { AUTH_PROVIDER, WINDOW_CUSTOM_EVENT } from '@/constants/common'; +import { EVENT_LOG_CATEGORY, EVENT_LOG_NAME } from '@/constants/eventLog'; import { NATIVE_CUSTOM_EVENTS } from '@/constants/nativeCustomEvent'; import { ROUTER } from '@/constants/router'; import { eventLogger } from '@/utils'; @@ -35,9 +36,6 @@ export default function LoginPage() { const { mutate: updateMemberFcmTokenMutate } = useUpdateMemberFcmToken(); const search = useSearchParams(); const redirectUrl = search.get('redirect') ?? ROUTER.HOME; - // const onClickGuest = () => { - // router.push(ROUTER.GUEST.MISSION.NEW); - // }; const onClickAppleLogin = () => { if (isWebView()) { @@ -81,6 +79,11 @@ export default function LoginPage() { if (data?.memberId) { eventLogger.identify(data.memberId.toString()); } + if (data.landingStatus === 'TO_ONBOARDING') { + eventLogger.logEvent(EVENT_LOG_CATEGORY.ONBOARDING, EVENT_LOG_NAME.ONBOARDING.SUCCESS_SIGNUP); + router.push(ROUTER.ONBOARDING.HOME); + return; + } router.push(redirectUrl); }, }, @@ -103,6 +106,11 @@ export default function LoginPage() { updateMemberFcmTokenMutate({ fcmToken: event.detail.data.deviceToken }); } // 지금 당장은 필요없지만 나중을 위해 작동하도록 한다 + if (data.landingStatus === 'TO_ONBOARDING') { + eventLogger.logEvent(EVENT_LOG_CATEGORY.ONBOARDING, EVENT_LOG_NAME.ONBOARDING.SUCCESS_SIGNUP); + router.push(ROUTER.ONBOARDING.HOME); + return; + } router.push(redirectUrl); }, onError: () => { diff --git a/src/app/mission/new/MissionRegistration.tsx b/src/app/mission/new/MissionRegistration.tsx deleted file mode 100644 index 510a67f8..00000000 --- a/src/app/mission/new/MissionRegistration.tsx +++ /dev/null @@ -1,96 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { type MissionCategory, type MissionVisibility } from '@/apis/schema/mission'; -import useCreateMissionMutation from '@/app/mission/new/useCreateMissionMutation'; -import Button from '@/components/Button/Button'; -import Input from '@/components/Input/Input'; -import { type DropdownValueType } from '@/components/Input/Input.types'; -import { useSnackBar } from '@/components/SnackBar/SnackBarProvider'; -import { MISSION_CATEGORY_LIST, PUBLIC_SETTING_LIST } from '@/constants/mission'; - -export default function MissionRegistration() { - const { triggerSnackBar } = useSnackBar(); - - const [missionTitleInput, setMissionTitleInput] = useState(''); - const [missionContentInput, setMissionContentInput] = useState(''); - const [missionCategory, setMissionCategory] = useState | null>(null); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [missionPublicSetting, setMissionPublicSetting] = useState>( - PUBLIC_SETTING_LIST[1], - ); - - const isSubmitButtonDisabled = !missionTitleInput || !missionCategory; - - const { mutate } = useCreateMissionMutation(); - // 미션 명 - const handleMissionTitleInput = (value: string) => { - setMissionTitleInput(value); - }; - // 미션 내용 - const handleMissionContentInput = (value: string) => { - setMissionContentInput(value); - }; - - const handleSubmit = () => { - if (!missionCategory) { - triggerSnackBar({ - message: '미션 제목을 입력해주세요.', - }); - return; - } - - mutate({ - name: missionTitleInput, - content: missionContentInput, - category: missionCategory.value, - visibility: missionPublicSetting.value, - }); - }; - - return ( -
- - - - {/* 카테고리 */} - setMissionCategory(item)} - /> - - {/* 공개설정 */} - setMissionPublicSetting(item)} - /> - - -
- ); -} diff --git a/src/app/mission/new/page.tsx b/src/app/mission/new/page.tsx index 8d50a7f6..ef174536 100644 --- a/src/app/mission/new/page.tsx +++ b/src/app/mission/new/page.tsx @@ -1,18 +1,114 @@ -import MissionRegistration from '@/app/mission/new/MissionRegistration'; +'use client'; + +import { useState } from 'react'; +import { type MissionCategory, type MissionVisibility } from '@/apis/schema/mission'; +import useCreateMissionMutation from '@/app/mission/new/useCreateMissionMutation'; import Header from '@/components/Header/Header'; +import Input from '@/components/Input/Input'; +import { type DropdownValueType } from '@/components/Input/Input.types'; +import { useSnackBar } from '@/components/SnackBar/SnackBarProvider'; +import { MISSION_CATEGORY_LIST, PUBLIC_SETTING_LIST } from '@/constants/mission'; +import { flex } from '@/styled-system/patterns'; import { css } from '@styled-system/css'; export default function MissionNewPage() { + const { triggerSnackBar } = useSnackBar(); + + const [missionTitleInput, setMissionTitleInput] = useState(''); + const [missionContentInput, setMissionContentInput] = useState(''); + const [missionCategory, setMissionCategory] = useState | null>(null); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [missionPublicSetting, setMissionPublicSetting] = useState>( + PUBLIC_SETTING_LIST[1], + ); + + const isSubmitButtonDisabled = !missionTitleInput || !missionCategory; + + const { mutate } = useCreateMissionMutation(); + // 미션 명 + const handleMissionTitleInput = (value: string) => { + setMissionTitleInput(value); + }; + // 미션 내용 + const handleMissionContentInput = (value: string) => { + setMissionContentInput(value); + }; + + const handleSubmit = () => { + if (!missionCategory) { + triggerSnackBar({ + message: '미션 제목을 입력해주세요.', + }); + return; + } + + mutate({ + name: missionTitleInput, + content: missionContentInput, + category: missionCategory.value, + visibility: missionPublicSetting.value, + }); + }; return (
-
+

- 하루 10분씩 2주 동안 + 하루 10분씩 2주 동안
어떤 일에 투자하고 싶은가요?

- +
+ + + + {/* 카테고리 */} + setMissionCategory(item)} + /> + {/* TODO: 이후에 삭제 - 미션 기간, 알림 설정 Input 생기며 리스트 여백 수정 */} +
+ + {/* 공개설정 */} + setMissionPublicSetting(item)} + /> +
); @@ -46,3 +142,8 @@ const mainTitleCss = css({ textStyle: 'title2', }, }); + +const sectionCss = flex({ + flexDirection: 'column', + gap: '12px', +}); diff --git a/src/app/onboarding/RecommendFollowItem.tsx b/src/app/onboarding/RecommendFollowItem.tsx new file mode 100644 index 00000000..d740f08d --- /dev/null +++ b/src/app/onboarding/RecommendFollowItem.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import Button from '@/components/Button/Button'; +import Thumbnail from '@/components/Thumbnail/Thumbnail'; +import { css } from '@styled-system/css'; + +interface RecommendFollowItemProps { + id: number; + nickname: string; + profileImageUrl: string | null; + tags: string[]; + onChangeFollow: (id: number) => void; + isFollowing: boolean; +} + +function RecommendFollowItem({ + profileImageUrl, + nickname, + tags, + onChangeFollow, + isFollowing, + id, +}: RecommendFollowItemProps) { + const handleFollow = () => { + onChangeFollow(id); + }; + + return ( +
+
+ +
+

{nickname}

+
    + {tags.map((tag, index) => ( +
  • + {tag} +
  • + ))} +
+
+
+ +
+ ); +} + +export default React.memo(RecommendFollowItem); + +const leftWrapperCss = css({ + display: 'flex', + alignItems: 'center', + gap: '12px', +}); + +const nicknameCss = css({ + textStyle: 'subtitle4', + color: 'text.secondary', +}); +const tagListCss = css({ + display: 'flex', + gap: '6px', +}); + +const tagCss = css({ + textStyle: 'body6', + color: 'text.tertiary', +}); + +const followItemWrapperCss = css({ + display: 'flex', + justifyContent: 'space-between', + + width: '100%', + padding: '8px', +}); diff --git a/src/app/onboarding/onboarding.constants.ts b/src/app/onboarding/onboarding.constants.ts new file mode 100644 index 00000000..c5ffefed --- /dev/null +++ b/src/app/onboarding/onboarding.constants.ts @@ -0,0 +1,65 @@ +export const RECOMMENDATION = [ + { + id: 41, + nickname: '수미미칩', + profileImageUrl: null, + tags: ['#기타연주', '#감사일기', '#개발자'], + }, + { + id: 16, + nickname: '123', + profileImageUrl: 'https://kr.object.ncloudstorage.com/10mm-images/dev/member_profile/16/image.jpeg', + tags: ['#카페출근', '#출근독서', '#스타벅스'], + }, + { + id: 15, + nickname: '1212333331212', + profileImageUrl: 'https://kr.object.ncloudstorage.com/10mm-images/dev/member_profile/15/image.jpeg', + tags: ['#카페출근', '#출근독서', '#스타벅스'], + }, +]; + +export const RECOMMENDATION_REAL = [ + { + id: 4, + nickname: '도모', + profileImageUrl: 'https://image.10mm.today/prod/member_profile/4/38e6a5a9-a547-4051-95ea-cc112feabe21.jpeg', + tags: ['#기타연주', '#감사일기', '#개발자'], + }, + { + id: 2, + nickname: 'ybchar', + profileImageUrl: 'https://image.10mm.today/prod/member_profile/2/2443459a-94b5-4048-98df-5fe356378a62.jpeg', + tags: ['#개발', '#산책하기'], + }, + { + id: 73, + nickname: '안암위스키남', + profileImageUrl: 'https://image.10mm.today/prod/member_profile/73/a362d028-ad4c-49aa-9912-e32bc041793d.jpeg', + tags: ['#공부', '#헬스', '#디자인'], + }, + { + id: 1, + nickname: '우보틀', + profileImageUrl: 'https://image.10mm.today/prod/member_profile/1/b83aeeb8-9acd-4915-aee4-ea87ec142b3b.jpeg', + tags: ['#카페출근', '#출근독서', '#스타벅스'], + }, + { + id: 8, + nickname: '수미칩', + profileImageUrl: 'https://image.10mm.today/prod/member_profile/8/70d5cb9b-95ff-406f-93bd-9a68a2d8d5b9.png', + tags: ['#운동', '#뜨개질', '#폭주기관차'], + }, + { + id: 7, + nickname: '유우비트', + profileImageUrl: 'https://image.10mm.today/prod/member_profile/7/9fbdf25a-101e-4fb5-9956-6030e7407382.jpeg', + tags: ['#운동', '#헬스', '#영화'], + }, + { + id: 9, + nickname: '집가고시퍼', + profileImageUrl: 'https://image.10mm.today/prod/member_profile/9/722d875d-eff0-498d-9644-e1c8eb514414.jpeg', + tags: ['#디자인', '#회의'], + }, +]; diff --git a/src/app/onboarding/page.tsx b/src/app/onboarding/page.tsx new file mode 100644 index 00000000..de4a387f --- /dev/null +++ b/src/app/onboarding/page.tsx @@ -0,0 +1,117 @@ +'use client'; + +import { useCallback, useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { FOLLOW_API } from '@/apis/follow'; +import { RECOMMENDATION, RECOMMENDATION_REAL } from '@/app/onboarding/onboarding.constants'; +import RecommendFollowItem from '@/app/onboarding/RecommendFollowItem'; +import Button from '@/components/Button/Button'; +import CenterTextHeader from '@/components/Header/CenterTextHeader'; +import { EVENT_LOG_CATEGORY, EVENT_LOG_NAME } from '@/constants/eventLog'; +import { ROUTER } from '@/constants/router'; +import { eventLogger } from '@/utils'; +import { getEnv } from '@/utils/appEnv'; +import { css } from '@styled-system/css'; + +function OnboardingPage() { + const [followList, setFollowList] = useState([]); + const router = useRouter(); + const handleFollow = useCallback((id: number) => { + eventLogger.logEvent(EVENT_LOG_CATEGORY.ONBOARDING, EVENT_LOG_NAME.ONBOARDING.SELECT_FOLLOW); + setFollowList((prevState) => { + if (prevState.includes(id)) { + return prevState.filter((followId) => followId !== id); + } + + return [...prevState, id]; + }); + }, []); + + const isSkip = followList.length === 0; + + const handleSkip = () => { + eventLogger.logEvent(EVENT_LOG_CATEGORY.ONBOARDING, EVENT_LOG_NAME.ONBOARDING.CLICK_SKIP); + router.replace(ROUTER.HOME); + }; + + const handleComplete = async () => { + await Promise.all( + followList.map((id) => { + return FOLLOW_API.addFollow(id); + }), + ); + eventLogger.logEvent(EVENT_LOG_CATEGORY.ONBOARDING, EVENT_LOG_NAME.ONBOARDING.CLICK_CONFIRM, { + followList: followList.join(','), + followCount: followList.length, + }); + router.replace(ROUTER.HOME); + }; + + const follows = getEnv() === 'real' ? RECOMMENDATION_REAL : RECOMMENDATION; + + return ( +
+ + 건너뛰기 + + ) : ( + + ) + } + /> +
+ {'10mm +

친구를 팔로우 해보세요!

+

팔로우를 통해 미션과 인증을 공유할 수 있어요.

+
+
    + {follows.map((props) => ( + + ))} +
+
+ ); +} + +export default OnboardingPage; + +const followListCss = css({ + padding: '0 16px', +}); + +const assetImagCss = css({ + width: '112px', + height: '84px', +}); + +const titleCss = css({ + textStyle: 'title3', + color: 'text.primary', + marginBottom: '4px', +}); + +const subTitleCss = css({ + textStyle: 'body4', + color: 'gray.gray600', +}); + +const textSectionCss = css({ + paddingTop: '28px', + paddingBottom: '56px', + + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', +}); diff --git a/src/components/Chip/Chip.stories.ts b/src/components/Chip/Chip.stories.ts new file mode 100644 index 00000000..64dfcb52 --- /dev/null +++ b/src/components/Chip/Chip.stories.ts @@ -0,0 +1,36 @@ +import Chip from '@/components/Chip/Chip'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Component/Chip', + component: Chip, + parameters: { + layout: 'centered', + controls: { + include: ['selected', 'children'], + }, + }, + tags: ['autodocs'], + argTypes: { + selected: { + control: 'boolean', + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + selected: false, + children: '텍스트', + }, +}; + +export const Selected: Story = { + args: { + selected: true, + children: '텍스트', + }, +}; diff --git a/src/components/Chip/Chip.tsx b/src/components/Chip/Chip.tsx new file mode 100644 index 00000000..b5f6081d --- /dev/null +++ b/src/components/Chip/Chip.tsx @@ -0,0 +1,28 @@ +import { cva } from '@/styled-system/css'; +import { styled } from '@/styled-system/jsx'; + +const chipStyle = cva({ + base: { + padding: '6px 14px', + display: 'inline-block', + textStyle: 'body3', + borderRadius: '20px', + transition: 'all 0.3s', + }, + variants: { + selected: { + true: { + background: 'purple.purple300', + color: 'text.primary', + }, + false: { + background: 'gray.gray100', + color: 'text.tertiary', + }, + }, + }, +}); + +const Chip = styled('div', chipStyle); + +export default Chip; diff --git a/src/components/Header/CenterTextHeader.tsx b/src/components/Header/CenterTextHeader.tsx new file mode 100644 index 00000000..e07c52fa --- /dev/null +++ b/src/components/Header/CenterTextHeader.tsx @@ -0,0 +1,43 @@ +import { css, cx } from '@styled-system/css'; +import { center } from '@styled-system/patterns'; + +interface CenterTextHeaderProps { + title: string; + rightComponent?: React.ReactNode; +} + +function CenterTextHeader({ title, rightComponent }: CenterTextHeaderProps) { + return ( +
+
+
+

{title}

+
+
{rightComponent}
+
+ ); +} + +export default CenterTextHeader; + +const headerWrapperCss = css({ + height: '44px', + display: 'flex', +}); + +const titleCss = css({ + color: 'text.primary', + textStyle: 'subtitle1', +}); + +const sectionWrapperCss = css({ + width: '33%', +}); + +const centerCss = center(); + +const flexEndCss = css({ + display: 'flex', + justifyContent: 'flex-end', + alignItems: 'center', +}); diff --git a/src/components/Input/DropdownInput.tsx b/src/components/Input/DropdownInput.tsx index afba72fc..ad34b1b4 100644 --- a/src/components/Input/DropdownInput.tsx +++ b/src/components/Input/DropdownInput.tsx @@ -57,7 +57,6 @@ const selectWrapperCss = css({ alignItems: 'center', borderBottomWidth: '1px', borderColor: 'border.default', - marginBottom: '36px', cursor: 'pointer', }); @@ -70,7 +69,7 @@ const titleCss = css({ const textWrapperCss = css({ width: '100%', textStyle: 'subtitle3', - padding: '14px 4px', + padding: '12px 4px 14px', borderColor: 'border.default', }); diff --git a/src/components/Input/NormalButtonInput.tsx b/src/components/Input/NormalButtonInput.tsx index c25dcd49..55a74fe0 100644 --- a/src/components/Input/NormalButtonInput.tsx +++ b/src/components/Input/NormalButtonInput.tsx @@ -99,8 +99,8 @@ const inputWrapperCss = css({ alignItems: 'center', width: '100%', borderBottomWidth: '1px', - padding: '14px 4px', - height: '50px', + padding: '12px 4px 14px', + height: '48px', _focusWithin: { outline: 'none' }, boxSizing: 'border-box', backgroundColor: 'bg.surface2', diff --git a/src/components/Input/NormalInput.tsx b/src/components/Input/NormalInput.tsx index fe877056..edc8c300 100644 --- a/src/components/Input/NormalInput.tsx +++ b/src/components/Input/NormalInput.tsx @@ -101,8 +101,8 @@ const inputWrapperCss = css({ justifyContent: 'space-between', width: '100%', borderBottomWidth: '1px', - padding: '14px 4px', - height: '50px', + padding: '12px 4px 14px', + height: '48px', _focusWithin: { outline: 'none' }, boxSizing: 'border-box', backgroundColor: 'bg.surface2', diff --git a/src/constants/eventLog.ts b/src/constants/eventLog.ts index c596afde..843881d4 100644 --- a/src/constants/eventLog.ts +++ b/src/constants/eventLog.ts @@ -13,11 +13,18 @@ export const EVENT_LOG_CATEGORY = { FOLLOW_PROFILE: 'follow_profile', FEED: 'feed', REACTION: 'reaction', + ONBOARDING: 'onboarding', }; type EventLogCategoryType = keyof typeof EVENT_LOG_CATEGORY; export const EVENT_LOG_NAME = { + ONBOARDING: { + SUCCESS_SIGNUP: 'success/signUp', + SELECT_FOLLOW: 'select/follow', + CLICK_SKIP: 'click/skip', + CLICK_CONFIRM: 'click/confirm', + }, MISSION_DETAIL: { CLICK_CALENDER_ARROW: 'click/calendarArrow', CLICK_CALENDER: 'click/calendar', diff --git a/src/constants/router.ts b/src/constants/router.ts index 15dd2430..cebbe152 100644 --- a/src/constants/router.ts +++ b/src/constants/router.ts @@ -12,7 +12,9 @@ export const ROUTER = { FEED: { HOME: '/feed', }, - + ONBOARDING: { + HOME: '/onboarding', + }, RECORD: { CREATE: (id: string) => `/record/${id}`, SUCCESS: `/record/success`, // TODO: 여기있는것이 맞ㄴ느가? diff --git a/src/utils/appEnv.ts b/src/utils/appEnv.ts index 85cb1c0c..49c1cd00 100644 --- a/src/utils/appEnv.ts +++ b/src/utils/appEnv.ts @@ -11,3 +11,17 @@ const getUserAgent = () => { export const isWebView = () => RegExp(APP_USER_AGENT).test(getUserAgent()); export const isAndroid = () => RegExp(ANDROID).test(getUserAgent()); export const isIOS = () => RegExp(IOS).test(getUserAgent()); + +type EnvType = 'local' | 'dev' | 'real'; + +export const getEnv = (): EnvType => { + if (process.env.node_env === 'development') { + return 'local'; + } + + if (process.env.NEXT_PUBLIC_ENV === 'real') { + return 'real'; + } + + return 'dev'; +}; diff --git a/src/utils/storage/progressMission.test.ts b/src/utils/storage/progressMission.test.ts index e8a05ff1..16079979 100644 --- a/src/utils/storage/progressMission.test.ts +++ b/src/utils/storage/progressMission.test.ts @@ -98,7 +98,7 @@ describe('getProgressMissionTime 테스팅', () => { expect(time).toBe(0); }); - test('오늘 진행 중인 미션이 없으면 0이 반환되어야합니다.', () => { + test('오늘 시작한 미션이 아니여도 남아있어야합니다. ', () => { const spy = mockTime(1708307992308); setMissionData(TEST_MISSION_ID); setMissionTimeStack(TEST_MISSION_ID, 'start'); @@ -107,10 +107,7 @@ describe('getProgressMissionTime 테스팅', () => { const missionData = localStorage.getItem(STORAGE_KEY.PROGRESS_MISSION.MISSION); expect(missionData).not.toBe(null); - const time2 = getProgressMissionTime(TEST_MISSION_ID); - expect(time2).toBe(0); - - expect(localStorage.getItem(STORAGE_KEY.PROGRESS_MISSION.MISSION)).toBe(null); + expect(localStorage.getItem(STORAGE_KEY.PROGRESS_MISSION.MISSION)).not.toBe(null); }); test('return 값은 number형 정수여야합니다.', () => { @@ -137,84 +134,6 @@ describe('getProgressMissionTime 테스팅', () => { expect(time).toBe(continueSeconds); }); - test('start(0) -> stop(60s) -> current(100s) 상태일 때, 60s가 반환되어야합니다.', () => { - const spy = mockTime(MOCK_TIME_BASE); - startMission(TEST_MISSION_ID); - spy.mockRestore(); - - const continueSeconds = 60; - - const spy2 = mockTime(MOCK_TIME_BASE + continueSeconds * timerMs); - setMissionTimeStack(TEST_MISSION_ID, 'stop'); - spy2.mockRestore(); - - const stopSeconds = 40; - - const spy3 = mockTime(MOCK_TIME_BASE + stopSeconds * timerMs); - const time = getProgressMissionTime(TEST_MISSION_ID); - spy3.mockRestore(); - - expect(time).toBe(continueSeconds); - }); - - test('start(0) -> stop(60s) -> restart(100s) -> current(120s) 상태일 때, 80s가 반환되어야합니다.', () => { - const spy = mockTime(MOCK_TIME_BASE); - startMission(TEST_MISSION_ID); - spy.mockRestore(); - - const continueSeconds = 60; - - const spy2 = mockTime(MOCK_TIME_BASE + continueSeconds * timerMs); - setMissionTimeStack(TEST_MISSION_ID, 'stop'); - spy2.mockRestore(); - - const stopSeconds = 40; - - const spy3 = mockTime(MOCK_TIME_BASE + stopSeconds * timerMs); - setMissionTimeStack(TEST_MISSION_ID, 'restart'); - spy3.mockRestore(); - - const restartSeconds = 20; - - const spy4 = mockTime(MOCK_TIME_BASE + (stopSeconds + restartSeconds) * timerMs); - const time = getProgressMissionTime(TEST_MISSION_ID); - spy4.mockRestore(); - - expect(time).toBe(continueSeconds + restartSeconds); - }); - - test('start(0) -> stop(60s) -> restart(100s) -> stop(120s) -> current(150s) 상태일 때, 90s가 반환되어야합니다.', () => { - const spy = mockTime(MOCK_TIME_BASE); - startMission(TEST_MISSION_ID); - spy.mockRestore(); - - const continueSeconds = 60; - - const spy2 = mockTime(MOCK_TIME_BASE + continueSeconds * timerMs); - setMissionTimeStack(TEST_MISSION_ID, 'stop'); - spy2.mockRestore(); - - const stopSeconds = 40; - - const spy3 = mockTime(MOCK_TIME_BASE + stopSeconds * timerMs); - setMissionTimeStack(TEST_MISSION_ID, 'restart'); - spy3.mockRestore(); - - const restartSeconds = 20; - - const spy4 = mockTime(MOCK_TIME_BASE + (stopSeconds + restartSeconds) * timerMs); - setMissionTimeStack(TEST_MISSION_ID, 'stop'); - spy4.mockRestore(); - - const stopSeconds2 = 30; - - const spy5 = mockTime(MOCK_TIME_BASE + (stopSeconds + restartSeconds + stopSeconds2) * timerMs); - const time = getProgressMissionTime(TEST_MISSION_ID); - spy5.mockRestore(); - - expect(time).toBe(continueSeconds + restartSeconds); - }); - test('start(0) -> stop(60s) -> restart(100s) -> stop(120s) -> restart(150s) -> current(200s) 상태일 때, 130s가 반환되어야합니다.', () => { const spy = mockTime(MOCK_TIME_BASE); startMission(TEST_MISSION_ID); diff --git a/src/utils/storage/progressMission.ts b/src/utils/storage/progressMission.ts index abd8d692..d9b49bac 100644 --- a/src/utils/storage/progressMission.ts +++ b/src/utils/storage/progressMission.ts @@ -83,8 +83,6 @@ const timerMs: number = Number(process.env.NEXT_PUBLIC_TIMER_MS ?? DEFAULT_MS); export const getProgressMissionTime = (missionId: string): number => { if (!checkIsProgressMission(missionId)) return 0; - if (!checkTodayMission(missionId)) return 0; - const progressTimeMs = getProgressMissionTimeToStack(missionId); const progressTime = Math.floor(progressTimeMs / timerMs); @@ -130,20 +128,20 @@ const checkIsProgressMission = (missionId: string) => { return true; }; -// 오늘 날짜에 진행되고 있던 미션인지 확인 -// return : true - 오늘 날짜에 진행되고 있던 미션 데이터, false - 오늘 날짜에 진행되고 있던 미션 데이터가 아니거나 없음 -const checkTodayMission = (missionId: string) => { - const mission = getProgressMissionStartTimeToStorage(missionId); - if (!mission) return false; - const missionDate = new Date(mission).getDate(); - const currentDate = new Date().getDate(); +// // 오늘 날짜에 진행되고 있던 미션인지 확인 +// // return : true - 오늘 날짜에 진행되고 있던 미션 데이터, false - 오늘 날짜에 진행되고 있던 미션 데이터가 아니거나 없음 +// const checkTodayMission = (missionId: string) => { +// const mission = getProgressMissionStartTimeToStorage(missionId); +// if (!mission) return false; +// const missionDate = new Date(mission).getDate(); +// const currentDate = new Date().getDate(); - if (missionDate === currentDate) return true; +// if (missionDate === currentDate) return true; - // 오늘 날짜에 진행되고 있던 미션 데이터가 아님 -> 삭제 - removeProgressMissionData(); - return false; -}; +// // 오늘 날짜에 진행되고 있던 미션 데이터가 아님 -> 삭제 +// // removeProgressMissionData(); +// return false; +// }; const getProgressMissionToStorage = (): MissionData | false => { const progressMissionData = localStorage.getItem(STORAGE_KEY.PROGRESS_MISSION.MISSION);