Skip to content

Commit

Permalink
[ Feat ] 온보딩 페이지 Mocking API를 연동합니다 (#250)
Browse files Browse the repository at this point in the history
* feat: 온보딩 mock api 생성 (#249)

* feat: url의 이름을 받아오는 api 함수 작성 (#249)

* feat: 온보딩 api 함수 작성 (#249)

* feat: 온보딩 페이지에서 필요한 상수 정의 (#249)

* feat: 리다이렉트 페이지 컴포넌트명 및 리다이렉트 경로 수정

* feat: useFunnel 쿼리 파라미터 없을 경우 처리 코드 추가

* feat: 가독성을 위해서 BoxAllowedService 네이밍 수정 및 컴파운드 컴포넌트 방식으로 수정 (#249)

* feat: favicon 받아오는 유틸 함수 추가 (#249)

* feat: 온보딩 최상위페이지 타입 수정 및 Service Step에서 현재 선택된 분야를 받을 수 있게 prop 추가 (#249)

* feat: 분야 별로 제공하는 서비스를 선택할 수 있는 ButtonService 컴포넌트 로직 추가 (#249)

* feat: 통일성을 위해 Tabs Object.assign 방식으로 수정 (#249)

* feat: 분야 선택 스탭, 허용 서비스 선택 스탭 api 로직 추가 (#249)
  • Loading branch information
suwonthugger authored Jan 19, 2025
1 parent b8ad72a commit d1602ad
Show file tree
Hide file tree
Showing 27 changed files with 570 additions and 163 deletions.
21 changes: 21 additions & 0 deletions src/mocks/common/common.resolvers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { HttpResponse, http } from 'msw';

import { COMMON_RES } from './common.responses';

export const COMMON_MOCK_URL = {
GET_URL_NAME: 'api/v2/tabName',
};

export const commonResolvers = [
http.get(COMMON_MOCK_URL.GET_URL_NAME, async ({ request }) => {
const url = new URL(request.url);
const tabName = url.searchParams.get('tabName');

if (!tabName) {
console.error('경로 파라미터에 tabName이 없습니다.');
throw new HttpResponse(null, { status: 400 });
}

return HttpResponse.json(COMMON_RES.GET_URL_NAME);
}),
];
9 changes: 9 additions & 0 deletions src/mocks/common/common.responses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const COMMON_RES = {
GET_URL_NAME: {
status: 200,
message: '요청이 성공했습니다.',
data: {
tabName: 'NAVER',
},
},
};
4 changes: 3 additions & 1 deletion src/mocks/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { commonResolvers } from './common/common.resolvers';
import { homeResolvers } from './home/resolvers/homeResolvers';
import { onboardingResolvers } from './onboarding/onboarding.resolvers';

export const handlers = [...homeResolvers];
export const handlers = [...homeResolvers, ...onboardingResolvers, ...commonResolvers];
31 changes: 31 additions & 0 deletions src/mocks/onboarding/onboarding.resolvers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { HttpResponse, http } from 'msw';

import { ONBOARDING_RES } from './onboarding.responses';

export const ONBOARDING_MOCK_URL = {
POST_INTEREST_AREA: 'api/v2/onboard',
};

export const onboardingResolvers = [
http.post<never, { siteName: string; siteUrl: string }[]>(
ONBOARDING_MOCK_URL.POST_INTEREST_AREA,
async ({ request }) => {
const url = new URL(request.url);
const interestArea = url.searchParams.get('interestArea');

const body = await request.json();

if (body.length === 0) return HttpResponse.json(ONBOARDING_RES.POST_INTEREST_AREA);

if (
!(body.length > 0 && (typeof body[0].siteName === 'string' || typeof body[0].siteUrl === 'string')) ||
!interestArea
) {
console.error('요청 바디 값과, 쿼리스트링 값을 확인해주세요');
throw new HttpResponse(null, { status: 400 });
}

return HttpResponse.json(ONBOARDING_RES.POST_INTEREST_AREA);
},
),
];
6 changes: 6 additions & 0 deletions src/mocks/onboarding/onboarding.responses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const ONBOARDING_RES = {
POST_INTEREST_AREA: {
status: 200,
message: '요청이 성공했습니다.',
},
};
20 changes: 7 additions & 13 deletions src/pages/OnboardingPage/OnboardingPage.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import { useState } from 'react';

import type { FieldType } from '@/shared/types/fileds';

import StepField from './StepField/StepField';
import StepService from './StepService/StepService';
import StepStart from './StepStart/StepStart';
import { FIELDS } from './constants';
import { useFunnel } from './hooks/useFunnel';

const OnboardingPage = () => {
const { Funnel, Step, setStep } = useFunnel();

const [selectedField, setSelectedField] = useState<string[]>([]);
const [selectedField, setSelectedField] = useState<FieldType | null>(null);

const handleSelectField = (field: string) => {
setSelectedField((prev) =>
prev.includes(field) ? prev.filter((prevField) => prevField !== field) : [...prev, field],
);
const handleSelectField = (field: FieldType) => {
setSelectedField(field);
};

return (
Expand All @@ -24,15 +23,10 @@ const OnboardingPage = () => {
<StepStart setStep={setStep} />
</Step>
<Step name="field">
<StepField
setStep={setStep}
onSelectField={handleSelectField}
selectedField={selectedField}
FIELDS={FIELDS}
/>
<StepField setStep={setStep} onSelectField={handleSelectField} selectedField={selectedField} />
</Step>
<Step name="service">
<StepService setStep={setStep} FIELDS={FIELDS} />
<StepService setStep={setStep} selectedField={selectedField} />
</Step>
</Funnel>
</div>
Expand Down
16 changes: 9 additions & 7 deletions src/pages/OnboardingPage/StepField/StepField.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import HomeLargeBtn from '@/shared/components/ButtonHomeLarge/ButtonHomeLarge';

import type { FieldType } from '@/shared/types/fileds';
import { HomeLargeBtnVariant } from '@/shared/types/global';

import { FIELDS } from '@/shared/constants/fields';

interface StepFieldProps {
setStep: (step: string) => void;
onSelectField: (field: string) => void;
selectedField: string[];
FIELDS: string[];
onSelectField: (field: FieldType) => void;
selectedField: FieldType | null;
}

const StepField = ({ setStep, onSelectField, selectedField, FIELDS }: StepFieldProps) => {
const StepField = ({ setStep, onSelectField, selectedField }: StepFieldProps) => {
return (
<main className="flex min-h-screen w-full flex-col items-center overflow-auto pb-[18.2rem] pt-[18rem] 2xl:pb-0">
<h1 className="mb-[2rem] text-center text-white title-bold-36">주로 어떤 분야에 집중하시나요?</h1>
Expand All @@ -19,11 +21,11 @@ const StepField = ({ setStep, onSelectField, selectedField, FIELDS }: StepFieldP

<div>
<ul className="mb-[11.7rem] flex flex-wrap justify-center gap-[2rem]">
{FIELDS.map((field) => (
{FIELDS.filter((field) => field !== '기타').map((field) => (
<li key={field}>
<button
onClick={() => onSelectField(field)}
className={`flex h-[28rem] w-[22rem] items-center justify-center rounded-[8px] text-white head-bold-24 ${selectedField.includes(field) ? 'border border-mint-01 bg-gray-bg-02' : 'bg-gray-bg-03'}`}
className={`flex h-[28rem] w-[22rem] items-center justify-center rounded-[8px] text-white head-bold-24 ${selectedField === field ? 'border border-mint-01 bg-gray-bg-02' : 'bg-gray-bg-03'}`}
>
{field}
</button>
Expand All @@ -35,7 +37,7 @@ const StepField = ({ setStep, onSelectField, selectedField, FIELDS }: StepFieldP
<HomeLargeBtn
variant={HomeLargeBtnVariant.LARGE}
onClick={() => setStep('service')}
disabled={selectedField.length === 0}
disabled={selectedField === null}
className="mb-[2rem]"
>
다음으로 넘어가기
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { ButtonHTMLAttributes, ReactNode } from 'react';

import HomeLargeBtn from '@/shared/components/ButtonHomeLarge/ButtonHomeLarge';

import { HomeLargeBtnVariant } from '@/shared/types/global';

import ColorIcon from '@/shared/assets/svgs/ic_color.svg?react';
import MinusIcon from '@/shared/assets/svgs/ic_minus.svg?react';
import PencilIcon from '@/shared/assets/svgs/ic_pencil.svg?react';

import { getServiceFavicon } from '../../utils/serviceUrl';

interface AllowedServicesRootProps {
children: ReactNode;
}

const AllowedServicesRoot = ({ children }: AllowedServicesRootProps) => {
return (
<div className="flex h-screen w-[48.7rem] flex-shrink-0 pb-[4.8rem] pr-[4.2rem] pt-[11.8rem]">
<div className="grid w-full grid-rows-[auto,1fr,auto] rounded-[18px] bg-gray-bg-03 p-[2.8rem]">{children}</div>
</div>
);
};

const AllowedServiceHeader = () => {
return (
<div className="flex items-center">
<button>
<ColorIcon />
</button>
<h2 className="ml-[1rem] text-white head-bold-30">나의 허용서비스</h2>
<button className="ml-[1.7rem]">
<PencilIcon />
</button>
</div>
);
};

interface AllowedServiceListProps {
children: ReactNode;
}

const AllowedServiceList = ({ children }: AllowedServiceListProps) => {
return <ul className="mt-[2rem] overflow-auto">{children}</ul>;
};

interface AllowedServiceItemProps {
siteUrl: string;
siteName: string;
onClick: (url: string) => void;
}

const AllowedServiceItem = ({ siteUrl, siteName, onClick }: AllowedServiceItemProps) => {
return (
<li className="flex h-[5.4rem] w-full items-center border-b border-b-gray-bg-04 px-[1rem] py-[1.2rem]">
<img src={getServiceFavicon(siteUrl)} alt={`${siteName} 아이콘`} className="h-[2rem] w-[2rem]" />
<h3 className="ml-[1.2rem] p-0 text-white body-med-16">{siteName}</h3>
<div className="ml-[4.2rem] h-[3.1rem] w-[23.1rem] truncate rounded-[20px] bg-gray-bg-04 px-[1rem] py-[0.6rem] text-gray-04 body-reg-16">
{siteUrl}
</div>
<button
onClick={() => {
onClick(siteUrl);
}}
>
<MinusIcon className="ml-[1.25rem]" />
</button>
</li>
);
};

const AllowedServiceBottomButton = (props: ButtonHTMLAttributes<HTMLButtonElement>) => {
return (
<HomeLargeBtn variant={HomeLargeBtnVariant.MIDDLE} className="mt-[2rem]" {...props}>
{props.children}
</HomeLargeBtn>
);
};

const AllowedService = Object.assign(AllowedServicesRoot, {
Header: AllowedServiceHeader,
List: AllowedServiceList,
Item: AllowedServiceItem,
BottomButton: AllowedServiceBottomButton,
});

export default AllowedService;

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import DesignIcon from '@/shared/assets/svgs/ic_service_design.svg?react';
import { getServiceFavicon } from '../../utils/serviceUrl';

interface ButtonServiceProps {
title: string;
url: string;
onAddSelectedService: (service: string) => void;
}

const ButtonService = ({ onAddSelectedService }: ButtonServiceProps) => {
const ButtonService = ({ title, url, onAddSelectedService }: ButtonServiceProps) => {
return (
<button
onClick={() => onAddSelectedService('https://www.behance.net/')}
onClick={() => onAddSelectedService(url)}
className="flex h-[11rem] w-[34rem] items-center gap-x-[2rem] rounded-[8px] bg-gray-bg-03 p-[2rem]"
>
<DesignIcon />
<img src={getServiceFavicon(url)} alt={`${title} 아이콘`} className="h-[7rem] w-[7rem]" />
<div className="flex flex-col">
<p className="w-[21.3rem] truncate text-start text-white head-bold-24">디자인</p>
<p className="w-[21.3rem] truncate text-start text-gray-03 detail-reg-14">https://www.behance.net/</p>
<p className="w-[21.3rem] truncate text-start text-white head-bold-24">{title}</p>
<p className="w-[21.3rem] truncate text-start text-gray-03 detail-reg-14">{url}</p>
</div>
</button>
);
Expand Down
Loading

0 comments on commit d1602ad

Please sign in to comment.