Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#543] [모임 생성] 책 선택 퍼널 #560

Merged
merged 8 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,43 @@ const serviceApi = (path: string) =>

initialize({}, [
rest.get(nextApi('/service-api/*'), async (req, res, ctx) => {
const { pathname } = req.url;
const { pathname, search } = req.url;
const match = /\/service-api(?<path>.*)/g.exec(pathname);

if (!match || !match.groups || !match.groups.path) {
return res(ctx.status(404, 'Invalid Request URL'));
}

const { path } = match.groups;
const originResponse = await ctx.fetch(serviceApi(`/api${path}`));
const originResponse = await ctx.fetch(serviceApi(`/api${path}${search}`));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get 요청 시 query parameter를 포함하여 요청을 보내도록 수정했어요.

const originResponseData = await originResponse.json();

return res(ctx.json({ ...originResponseData }));
}),
rest.post(nextApi('/service-api/*'), async (req, res, ctx) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

msw-storybook-addon에서 post api 요청도 응답하도록 수정했어요.

const { pathname, search } = req.url;
const match = /\/service-api(?<path>.*)/g.exec(pathname);

if (!match || !match.groups || !match.groups.path) {
return res(ctx.status(404, 'Invalid Request URL'));
}

const { path } = match.groups;

const { headers, mode } = req;
const data = await req.json();
const body = JSON.stringify(data);
const originRequest = {
method: 'POST',
body,
headers,
mode,
};

const originResponse = await ctx.fetch(
serviceApi(`/api${path}${search}`),
originRequest
);
const originResponseData = await originResponse.json();

return res(ctx.json({ ...originResponseData }));
Expand Down Expand Up @@ -55,6 +83,7 @@ initialize({}, [
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
retry: false,
},
},
Expand Down
2 changes: 1 addition & 1 deletion public/icons/search.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 19 additions & 10 deletions src/app/book/search/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
'use client';

import { useRouter } from 'next/navigation';
import { Suspense, useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { useInView } from 'react-intersection-observer';

import { APIBook } from '@/types/book';

import useBookSearchQuery from '@/queries/book/useBookSearchQuery';
import { useRecentSearchListQuery } from '@/queries/book/useRecentSearchesQuery';

Expand All @@ -19,7 +22,6 @@ import RecentSearchList, {
RecentSearchListSkeleton,
} from '@/v1/bookSearch/RecentSearchList';
import BookSearchList from '@/v1/bookSearch/BookSearchList';
import { IconSearch } from '@public/icons';

type FormValues = {
searchValue: string;
Expand All @@ -40,14 +42,12 @@ const BookSearchPage = () => {
<>
<TopHeader text={'Discover'} />
<article className="flex max-h-[calc(100%-6rem)] w-full flex-col gap-[3rem]">
<div className="flex w-full items-center gap-[2rem] border-b-[0.05rem] border-black-900 p-[1rem] focus-within:border-main-900 [&>div]:w-full">
<IconSearch className="fill-black h-[2.1rem] w-[2.1rem]" />
<Input
className="w-full appearance-none text-sm font-normal focus:outline-none"
placeholder="책 제목, 작가를 검색해보세요"
{...register('searchValue')}
/>
</div>
<Input
inputStyle="line"
leftIconType="search"
placeholder="책 제목, 작가를 검색해보세요"
{...register('searchValue')}
/>

{/** 최근 검색어 + 베스트 셀러 */}
<section
Expand Down Expand Up @@ -80,6 +80,7 @@ const BookSearchPage = () => {
};

const BookSearchResult = ({ queryKeyword }: { queryKeyword: string }) => {
const router = useRouter();
const { ref: inViewRef, inView } = useInView();

const bookSearchInfo = useBookSearchQuery({
Expand All @@ -95,6 +96,10 @@ const BookSearchResult = ({ queryKeyword }: { queryKeyword: string }) => {
? bookSearchInfo.data.pages[0].totalCount
: 0;

const handleBookClick = ({ bookId }: { bookId: APIBook['bookId'] }) => {
router.push(`/book/${bookId}`);
};

useEffect(() => {
if (inView && bookSearchInfo.hasNextPage) {
bookSearchInfo.fetchNextPage();
Expand All @@ -109,7 +114,11 @@ const BookSearchResult = ({ queryKeyword }: { queryKeyword: string }) => {

return (
<>
<BookSearchList books={searchedBooks} totalCount={totalResultCount} />
<BookSearchList
books={searchedBooks}
totalCount={totalResultCount}
onBookClick={handleBookClick}
/>
<div ref={inViewRef} />
</>
);
Expand Down
7 changes: 6 additions & 1 deletion src/stories/base/Input.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,12 @@ export const Line: Story = {
error: false,
},
render: args => (
<Input inputStyle="line" defaultValue="프롱이 리팩터링 스터디" {...args} />
<Input
inputStyle="line"
defaultValue="프롱이 리팩터링 스터디"
placeholder="제목을 작성해주세요"
{...args}
/>
),
};

Expand Down
38 changes: 38 additions & 0 deletions src/stories/bookGroup/create/funnel/SelectBookStep.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Meta, StoryObj } from '@storybook/react';
import { FormProvider, useForm } from 'react-hook-form';

import { appLayoutMeta } from '@/stories/meta';
import SelectBookStep, {
SelectBookFormValue,
} from '@/v1/bookGroup/create/funnel/SelectBookStep';

const meta: Meta<typeof SelectBookStep> = {
title: 'bookGroup/funnel/SelectBookStep',
component: SelectBookStep,
...appLayoutMeta,
};

export default meta;

type Story = StoryObj<typeof SelectBookStep>;

const RenderSelectBookStep = () => {
const methods = useForm<SelectBookFormValue>();

const goNextStep = () => {
const book = methods.getValues('book');
alert([`title: ${book.title}`, `id: ${book.bookId}`].join('\n'));
};

return (
<FormProvider {...methods}>
<form>
<SelectBookStep onNextStep={goNextStep} />
</form>
</FormProvider>
);
};

export const Default: Story = {
render: RenderSelectBookStep,
};
2 changes: 2 additions & 0 deletions src/types/book.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export interface APISearchedBook extends Omit<APIBook, 'bookId'> {
apiProvider: string;
}

export type SearchedBookWithId = APISearchedBook & Pick<APIBook, 'bookId'>;

export interface APIBookRecentSearchResponse {
keyword: string;
modifiedAt: string;
Expand Down
40 changes: 30 additions & 10 deletions src/v1/base/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,68 @@ import { ComponentPropsWithoutRef, forwardRef, Ref } from 'react';

type InputStyle = 'default' | 'line';
type FontSize = 'small' | 'large';
type LeftIconType = 'search';

interface InputProps extends ComponentPropsWithoutRef<'input'> {
inputStyle?: InputStyle;
leftIconType?: LeftIconType;
fontSize?: FontSize;
error?: boolean;
showSearchIcon?: boolean;
}

const FONT_SIZE_CLASSES = {
small: 'text-sm',
large: 'text-lg font-bold',
small: 'text-sm after:text-sm',
large: 'text-lg font-bold after:text-lg after:font-bold',
};

const getInputStyleClasses = (inputStyle: InputStyle) => {
switch (inputStyle) {
case 'line':
return 'border-b-[0.1rem] border-black-400 bg-transparent';
return 'border-b-[0.1rem] border-black-400';
case 'default':
default:
return 'rounded-[0.5rem] border-[0.05rem] px-[1rem]';
}
};

const getLeftIconClass = (iconType?: LeftIconType) => {
switch (iconType) {
case 'search':
return 'px-[1rem] before:relative before:top-[0.3rem] before:pr-[1rem] before:content-search';
default:
return '';
}
};

const Input = (
{
inputStyle = 'default',
fontSize = 'small',
error = false,
leftIconType,
className = '',
...props
}: InputProps,
ref: Ref<HTMLInputElement>
) => {
const inputStyleClass = getInputStyleClasses(inputStyle);
const leftIconClass = getLeftIconClass(leftIconType);
const fontSizeClass = FONT_SIZE_CLASSES[fontSize];
const borderColorClass = error
? 'border-warning-800 focus:border-warning-800'
: 'border-black-400 focus:border-main-900';
? 'border-warning-800 focus-within:border-warning-800'
: 'border-black-400 focus-within:border-main-900';

return (
<input
className={`w-full py-[1.3rem] outline-none ${fontSizeClass} ${inputStyleClass} ${borderColorClass}`}
{...props}
ref={ref}
/>
<div
className={`flex w-full items-center bg-transparent ${inputStyleClass} ${borderColorClass} ${fontSizeClass} ${leftIconClass} ${className}`}
>
<input
className={`h-[4.4rem] w-full bg-transparent outline-none autofill:shadow-[inset_0_0_0px_1000px_rgb(255,255,255)] ${fontSizeClass}`}
{...props}
ref={ref}
/>
</div>
);
};

Expand Down
2 changes: 1 addition & 1 deletion src/v1/base/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const _Select = (
ref={ref}
defaultValue=""
required
className={`rounded-[0.5rem] border-[0.05rem] px-[1.0rem] py-[1.3rem] outline-none ${borderColor} w-full cursor-pointer appearance-none bg-[url('/icons/select-icon.svg')] bg-[length:1.5rem_1.5rem] bg-[calc(100%-1rem)_center] bg-no-repeat invalid:text-placeholder`}
className={`rounded-[0.5rem] border-[0.05rem] px-[1.0rem] py-[1.1rem] outline-none ${borderColor} w-full cursor-pointer appearance-none bg-[url('/icons/select-icon.svg')] bg-[length:1.5rem_1.5rem] bg-[calc(100%-1rem)_center] bg-no-repeat invalid:text-placeholder`}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InputSelect가 높이 4.4rem을 유지하도록 수정했어요.

{...props}
>
{placeholder && (
Expand Down
Loading
Loading