Skip to content

Commit

Permalink
Merge pull request #642 from depromeet/TMN-58/onboarding
Browse files Browse the repository at this point in the history
[TMN-58] 회원가입 후 온보딩
  • Loading branch information
wade3420 authored Mar 28, 2024
2 parents bf34af9 + cadc522 commit a651584
Show file tree
Hide file tree
Showing 13 changed files with 344 additions and 5 deletions.
Binary file added public/assets/character/basic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/character/flag.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/character/sad.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/apis/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface LoginResponse {
accessToken: string;
refreshToken: string;
memberId?: number;
landingStatus?: 'TO_MAIN' | 'TO_ONBOARDING';
}

interface RegisterRequest {
Expand Down
7 changes: 6 additions & 1 deletion src/app/auth/kakaoCallback/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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);
},
},
Expand Down
14 changes: 11 additions & 3 deletions src/app/auth/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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);
},
},
Expand All @@ -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: () => {
Expand Down
77 changes: 77 additions & 0 deletions src/app/onboarding/RecommendFollowItem.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className={followItemWrapperCss}>
<div className={leftWrapperCss}>
<Thumbnail size={'h36'} variant={'filled'} url={profileImageUrl} />
<div>
<p className={nicknameCss}>{nickname}</p>
<ul className={tagListCss}>
{tags.map((tag, index) => (
<li className={tagCss} key={index}>
{tag}
</li>
))}
</ul>
</div>
</div>
<Button onClick={handleFollow} size={'small'} variant={isFollowing ? 'secondary' : 'primary'}>
{isFollowing ? '팔로우' : '팔로잉'}
</Button>
</div>
);
}

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',
});
65 changes: 65 additions & 0 deletions src/app/onboarding/onboarding.constants.ts
Original file line number Diff line number Diff line change
@@ -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: ['#디자인', '#회의'],
},
];
117 changes: 117 additions & 0 deletions src/app/onboarding/page.tsx
Original file line number Diff line number Diff line change
@@ -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<number[]>([]);
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 (
<div>
<CenterTextHeader
title={'추천 친구'}
rightComponent={
isSkip ? (
<Button variant={'ghost'} size={'medium'} onClick={handleSkip}>
건너뛰기
</Button>
) : (
<Button variant={'ghost'} size={'medium'} onClick={handleComplete}>
완료
</Button>
)
}
/>
<div className={textSectionCss}>
<img className={assetImagCss} src={'/assets/character/flag.png'} alt={'10mm character flag'} />
<p className={titleCss}>친구를 팔로우 해보세요!</p>
<p className={subTitleCss}>팔로우를 통해 미션과 인증을 공유할 수 있어요.</p>
</div>
<ul className={followListCss}>
{follows.map((props) => (
<RecommendFollowItem
key={props.id.toString()}
{...props}
isFollowing={followList.includes(props.id)}
onChangeFollow={handleFollow}
/>
))}
</ul>
</div>
);
}

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',
});
43 changes: 43 additions & 0 deletions src/components/Header/CenterTextHeader.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className={headerWrapperCss}>
<div className={sectionWrapperCss} />
<div className={cx(sectionWrapperCss, centerCss)}>
<h2 className={titleCss}>{title}</h2>
</div>
<div className={cx(sectionWrapperCss, flexEndCss)}>{rightComponent}</div>
</div>
);
}

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',
});
7 changes: 7 additions & 0 deletions src/constants/eventLog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading

0 comments on commit a651584

Please sign in to comment.