Skip to content

Commit

Permalink
merge : merge to develop
Browse files Browse the repository at this point in the history
  • Loading branch information
wade3420 committed Feb 7, 2024
2 parents 2323d25 + 7a980fa commit 3b2a01d
Show file tree
Hide file tree
Showing 19 changed files with 290 additions and 113 deletions.
18 changes: 18 additions & 0 deletions src/apis/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ interface RegisterRequest {
nickname: string;
}

interface FcmTokenRequest {
fcmToken: string;
}

export const AUTH_APIS = {
login: async (request: LoginRequest): Promise<LoginResponse> => {
const { data } = await apiInstance.post(ROUTER.AUTH.LOGIN, {
Expand Down Expand Up @@ -54,6 +58,13 @@ export const AUTH_APIS = {
});
return data;
},

fcmUpdate: async (request: FcmTokenRequest) => {
const { data } = await apiInstance.patch('/members/fcm-token', {
fcmToken: request.fcmToken,
});
return data;
},
};

export const useTempRegister = (option?: UseMutationOptions<LoginResponse, AxiosError, LoginRequest>) => {
Expand Down Expand Up @@ -97,3 +108,10 @@ export const useNicknameRegister = (option?: UseMutationOptions<RegisterRequest,
...option,
});
};

export const useUpdateMemberFcmToken = (option?: UseMutationOptions<FcmTokenRequest, AxiosError, FcmTokenRequest>) => {
return useMutation({
mutationFn: AUTH_APIS.fcmUpdate,
...option,
});
};
8 changes: 7 additions & 1 deletion src/apis/record.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import getQueryKey from '@/apis/getQueryKey';
import { type RecordType } from '@/apis/schema/record';
import { type ImageFileExtensionType, type UploadBaseRequest } from '@/apis/schema/upload';
import useMutationHandleError from '@/hooks/query/useMutationHandleError';
import {
useMutation,
type UseMutationOptions,
Expand Down Expand Up @@ -109,7 +110,12 @@ export const useGetRecordDetail = (recordId: string, option?: UseQueryOptions<Ge
};

export const useRecordTime = (options?: UseMutationOptions<RecordTimeResponse, unknown, RecordTimeRequest>) =>
useMutation({ mutationFn: RECORD_API.recordTime, ...options });
useMutationHandleError(
{ mutationFn: RECORD_API.recordTime, ...options },
{
offset: 'appBar',
},
);

export const useUpdateRemarkMutation = (
recordId: string,
Expand Down
40 changes: 28 additions & 12 deletions src/app/auth/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
import Script from 'next/script';
import { useSocialLogin } from '@/apis/auth';
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';
Expand All @@ -30,7 +30,8 @@ const initAppleLogin = () => {

export default function LoginPage() {
const router = useRouter();
const { mutateAsync } = useSocialLogin();
const { mutateAsync: socialLoginAsyncMutate } = useSocialLogin();
const { mutate: updateMemberFcmTokenMutate } = useUpdateMemberFcmToken();

const onClickGuest = () => {
router.push(ROUTER.GUEST.MISSION.NEW);
Expand Down Expand Up @@ -66,8 +67,8 @@ export default function LoginPage() {
};

useEffect(() => {
const appleIdSignInOnSuccessHandler = (event: CustomEvent) => {
mutateAsync(
const webAppleIdSignInOnSuccessEventListener = (event: CustomEvent) => {
socialLoginAsyncMutate(
{
provider: AUTH_PROVIDER.APPLE,
idToken: event.detail.authorization.id_token,
Expand All @@ -80,14 +81,17 @@ export default function LoginPage() {
);
};

const handleEvent = (event: CustomEvent) => {
mutateAsync(
const nativeLoginCallbackEventListener = (event: CustomEvent) => {
socialLoginAsyncMutate(
{
provider: event.detail.data.provider,
idToken: event.detail.data.data,
},
{
onSuccess: () => {
if (!!event.detail?.data?.deviceToken) {
updateMemberFcmTokenMutate({ fcmToken: event.detail.data.deviceToken });
}
router.push(ROUTER.HOME);
},
onError: () => {
Expand All @@ -103,18 +107,30 @@ export default function LoginPage() {

document.addEventListener(
WINDOW_CUSTOM_EVENT.APPLE_ID_SIGN_IN_ON_SUCCESS,
appleIdSignInOnSuccessHandler as EventListener,
webAppleIdSignInOnSuccessEventListener as EventListener,
);
document.addEventListener(
NATIVE_CUSTOM_EVENTS.APPLE_LOGIN_CALLBACK,
nativeLoginCallbackEventListener as EventListener,
);
document.addEventListener(
NATIVE_CUSTOM_EVENTS.KAKAO_LOGIN_CALLBACK,
nativeLoginCallbackEventListener as EventListener,
);
document.addEventListener(NATIVE_CUSTOM_EVENTS.APPLE_LOGIN_CALLBACK, handleEvent as EventListener);
document.addEventListener(NATIVE_CUSTOM_EVENTS.KAKAO_LOGIN_CALLBACK, handleEvent as EventListener);

return () => {
document.removeEventListener(
WINDOW_CUSTOM_EVENT.APPLE_ID_SIGN_IN_ON_SUCCESS,
appleIdSignInOnSuccessHandler as EventListener,
webAppleIdSignInOnSuccessEventListener as EventListener,
);
document.removeEventListener(
NATIVE_CUSTOM_EVENTS.APPLE_LOGIN_CALLBACK,
nativeLoginCallbackEventListener as EventListener,
);
document.removeEventListener(
NATIVE_CUSTOM_EVENTS.KAKAO_LOGIN_CALLBACK,
nativeLoginCallbackEventListener as EventListener,
);
document.removeEventListener(NATIVE_CUSTOM_EVENTS.APPLE_LOGIN_CALLBACK, handleEvent as EventListener);
document.removeEventListener(NATIVE_CUSTOM_EVENTS.KAKAO_LOGIN_CALLBACK, handleEvent as EventListener);
};
}, []);

Expand Down
12 changes: 3 additions & 9 deletions src/app/error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,16 @@

import { useEffect } from 'react';
import Image from 'next/image';
import { useRouter } from 'next/navigation';
import { isSeverError } from '@/apis/instance.api';
import { ROUTER } from '@/constants/router';
import { css } from '@/styled-system/css';

export default function Error({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
const onClickResetButton = () => reset();
const router = useRouter();

useEffect(() => {
if (isSeverError(error)) {
if (error.response.status === 401) {
router.push(ROUTER.AUTH.LOGIN);
}
}
// TODO: ga로 쏘거나 하는 등의 플로우는 필요없을지 하는 생각
console.error(error);
}, [error]);

return (
<main className={MainWrapperCss}>
<div className={imageWrapperCss}>
Expand Down
6 changes: 3 additions & 3 deletions src/app/level/guide/Character.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Image from 'next/image';
import MotionDiv from '@/components/Motion/MotionDiv';
import { css, cx } from '@/styled-system/css';

interface Props {
Expand All @@ -11,7 +12,7 @@ interface Props {

function Character(props: Props) {
return (
<div
<MotionDiv
key={props.level}
className={cx(
imageWrapperCss,
Expand Down Expand Up @@ -58,7 +59,7 @@ function Character(props: Props) {
/>
</>
)}
</div>
</MotionDiv>
);
}

Expand All @@ -67,7 +68,6 @@ export default Character;
const imageWrapperCss = css({
position: 'relative',
margin: '0 auto',
animation: 'fadeIn .7s',
height: '100%',
width: '100%',
});
Expand Down
7 changes: 2 additions & 5 deletions src/app/level/guide/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ import GrowthLevel from '@/app/level/guide/GrowthLevel';
import LockedCharacter from '@/components/Level/LockedCharacter';
import LevelStatus from '@/components/LevelStatus/LevelStatus';
import LoadingSpinner from '@/components/Loading/LoadingSpinner';
import MotionDiv from '@/components/Motion/MotionDiv';
import { LEVEL_SYSTEM } from '@/constants/level';
import { defaultFadeInMotion } from '@/constants/style/animation';
import { css, cx } from '@/styled-system/css';
import { getLevel } from '@/utils/result';
import { motion } from 'framer-motion';

function LevelGuidePage() {
const { data, isLoading } = useGetMissionSummary();
Expand Down Expand Up @@ -39,9 +38,7 @@ function LevelGuidePage() {
<section className={levelTextWrapperCss}>
<p className={levelLabelCss}>현재 레벨</p>
<div className={cx(badgeCss)}>
<motion.span key={selectLevelInfo.label} {...defaultFadeInMotion}>
{selectLevelInfo.label}
</motion.span>
<MotionDiv key={selectLevelInfo.label}>{selectLevelInfo.label}</MotionDiv>
</div>
</section>
<section className={characterImageSectionCss}>
Expand Down
22 changes: 20 additions & 2 deletions src/app/mission/[id]/stopwatch/index.hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ export function useUnloadAction(time: number, missionId: string) {
setProgressMissionTime(missionId, time);
}, [time]);

useVisibilityState(onSaveTime);
useVisibilityStateHidden(onSaveTime);
}

function useVisibilityState(onAction: VoidFunction) {
function useVisibilityStateHidden(onAction: VoidFunction) {
const onVisibilityChange = useCallback(() => {
if (document.visibilityState === 'hidden') {
onAction();
Expand All @@ -30,6 +30,24 @@ function useVisibilityState(onAction: VoidFunction) {
}, [onVisibilityChange]); // 빈 의존성 배열을 전달하여 이 훅이 컴포넌트가 마운트되거나 언마운트될 때만 실행되도록 합니다.
}

// visible 상태로 바뀔 때 실행되는 훅
export function useVisibilityStateVisible(onAction: VoidFunction) {
const onVisibilityChange = useCallback(() => {
if (document.visibilityState === 'visible') {
onAction();
}
}, [onAction]);

useEffect(() => {
document.addEventListener('visibilitychange', onVisibilityChange);

// 컴포넌트가 언마운트될 때 이벤트 리스너를 제거합니다.
return () => {
document.removeEventListener('visibilitychange', onVisibilityChange);
};
}, [onVisibilityChange]); // 빈 의존성 배열을 전달하여 이 훅이 컴포넌트가 마운트되거나 언마운트될 때만 실행되도록 합니다.
}

export function useRecordMidTime(time: number, missionId: string) {
const onSaveTime = () => {
eventLogger.logEvent(EVENT_LOG_NAME.STOPWATCH.MID_SAVE_2, EVENT_LOG_CATEGORY.STOPWATCH, { time });
Expand Down
11 changes: 9 additions & 2 deletions src/app/mission/[id]/stopwatch/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { Fragment, useEffect, useState } from 'react';
import { useParams, useRouter } from 'next/navigation';
import { isSeverError } from '@/apis/instance.api';
import { useGetMissionDetailNoSuspense } from '@/apis/mission';
import { useRecordTime } from '@/apis/record';
import { useCustomBack, useRecordMidTime, useUnloadAction } from '@/app/mission/[id]/stopwatch/index.hooks';
Expand Down Expand Up @@ -80,8 +81,14 @@ export default function StopwatchPage() {
setIsMoveLoading(true);
removeProgressMissionData();
},
onError: () => {
setIsMoveLoading(() => false); // 없어도 되는지 확인 필요
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onError: (error) => {
if (isSeverError(error)) {
if (error.response.data.data.errorClassName === 'MISSION_RECORD_ALREADY_EXISTS_TODAY') {
removeProgressMissionData();
router.replace(ROUTER.HOME);
}
}
},
});

Expand Down
40 changes: 26 additions & 14 deletions src/app/result/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Character from '@/app/level/guide/Character';
import AppBarBottom from '@/components/AppBarBottom/AppBarBottom';
import Banner from '@/components/Banner/Banner';
import LinkButton from '@/components/Button/LinkButton';
import MotionDiv from '@/components/Motion/MotionDiv';
import Tab from '@/components/Tab/Tab';
import { ROUTER } from '@/constants/router';
import { css } from '@/styled-system/css';
Expand All @@ -21,7 +22,7 @@ const TAB = [
];

function ResultPage() {
const { data } = useGetMissionSummary();
const { data, isLoading } = useGetMissionSummary();

const symbolStack = data?.symbolStack ?? 0;
const currentLevel = getLevel(symbolStack);
Expand All @@ -36,19 +37,30 @@ function ResultPage() {
레벨 안내
</LinkButton>
</section>
<section className={imageSectionCss}>
<Character width={280} height={210} level={currentLevel.level} isBackground />
</section>
<LevelStatus symbolStack={symbolStack} viewLevel={currentLevel.level} />
<section className={bannerSectionCss}>
<Banner type="card" description="전체 누적 시간" iconUrl="/assets/icons/graph/clock.png" title={totalTime} />
<Banner
type="card"
description="총 미션 달성률"
iconUrl="/assets/icons/graph/chart.png"
title={totalMissionAttainRate}
/>
</section>
{isLoading ? (
<div></div>
) : (
<>
<MotionDiv variants="fadeInUp" className={imageSectionCss}>
<Character width={280} height={210} level={currentLevel.level} isBackground />
</MotionDiv>
<LevelStatus symbolStack={symbolStack} viewLevel={currentLevel.level} />
<MotionDiv className={bannerSectionCss}>
<Banner
type="card"
description="전체 누적 시간"
iconUrl="/assets/icons/graph/clock.png"
title={totalTime}
/>
<Banner
type="card"
description="총 미션 달성률"
iconUrl="/assets/icons/graph/chart.png"
title={totalMissionAttainRate}
/>
</MotionDiv>
</>
)}
<AppBarBottom />
</div>
);
Expand Down
15 changes: 2 additions & 13 deletions src/app/search/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,17 @@ import Link from 'next/link';
import { useSuspenseGetSearchNickname } from '@/apis/member';
import FollowerItem from '@/components/ListItem/Follow/FollowerItem';
import FollowingItem from '@/components/ListItem/Follow/FollowingItem';
import { ProfileItemSkeleton } from '@/components/ListItem/ProfileListItem';
import SearchBar from '@/components/SearchBar/SearchBar';
import { ROUTER } from '@/constants/router';
import { css } from '@/styled-system/css';

function SearchPage() {
const [input, setInput] = useState('');
const filteredInput = input.trim();
return (
<>
<SearchBar placeholder="닉네임을 검색해 주세요." value={input} onChange={setInput} />
<Suspense
fallback={
<>
<ProfileItemSkeleton />
<ProfileItemSkeleton />
<ProfileItemSkeleton />
<ProfileItemSkeleton />
</>
}
>
<List nickname={filteredInput} />
<Suspense fallback={<div></div>}>
<List nickname={input} />
</Suspense>
</>
);
Expand Down
1 change: 0 additions & 1 deletion src/app/template.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { useAuth } from '@/hooks/useAuth';

export default function Template({ children }: { children: React.ReactNode }) {
useAuth();

// const [customMotion, setCustomMotion] = useState(true);

// useEffect(() => {
Expand Down
Loading

0 comments on commit 3b2a01d

Please sign in to comment.