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

[#9] 독서 노트 작성 화면 개발 #10

Merged
merged 18 commits into from
Feb 25, 2025
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
1 change: 1 addition & 0 deletions app/notes/create/[isbn]/page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { BookNoteCreatePage as default } from '@/pages/book-note-create';
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@
"react-day-picker": "8.10.1",
"react-dom": "^19.0.0",
"react-error-boundary": "^5.0.0",
"react-hook-form": "^7.54.2",
"react-intersection-observer": "^9.15.1",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7"
"tailwindcss-animate": "^1.0.7",
"ts-pattern": "^5.6.2"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
Expand Down
21 changes: 21 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion src/entities/book/api/book-queries.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { infiniteQueryOptions } from '@tanstack/react-query';
import { infiniteQueryOptions, queryOptions } from '@tanstack/react-query';
import { fetchBooks, FetchBooksParams } from './fetch-books';
import { fetchBook } from './fetch-book';

export const bookQueries = {
all: () => ['books'],
Expand All @@ -19,4 +20,9 @@ export const bookQueries = {
},
select: (data) => data.pages.flatMap((page) => page.books),
}),
detail: (isbn: string) =>
queryOptions({
queryKey: [...bookQueries.all(), isbn],
queryFn: () => fetchBook(isbn),
}),
};
4 changes: 2 additions & 2 deletions src/entities/book/api/fetch-book.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ function isBookDetailSuccessResponse(response: BookDetailResponse): response is
return 'version' in response;
}

export async function fetchBook(itemId: string) {
export async function fetchBook(isbn: string) {
const searchParams = new URLSearchParams({
itemIdType: 'ISBN13',
cover: 'big',
output: 'js',
version: '20131101',
itemId: itemId,
itemId: isbn,
});

const response = aladinApi.get('ItemLookUp.aspx', { searchParams });
Expand Down
3 changes: 2 additions & 1 deletion src/entities/book/api/mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { BookDetailSuccessResponse, BookListSuccessResponse } from './response';

function adaptBookDTO(dto: BookDTO): Book {
return {
id: dto.itemId,
isbn: dto.isbn13,
title: dto.title,
author: dto.author,
publisher: dto.publisher,
Expand All @@ -28,6 +28,7 @@ export function adaptBookDetailSuccessResponse(response: BookDetailSuccessRespon

return {
...adaptBookDTO(book),
description: book.description,
pageCount: book.subInfo.itemPage,
};
}
2 changes: 1 addition & 1 deletion src/entities/book/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export { default as BookCard } from './ui/BookCard';
export { default as BookList } from './ui/BookList';
export type { Book } from './model/book';
export type { Book, BookDetail } from './model/book';
export { BookListTab } from './model/book-list-tab';
export { bookQueries } from './api/book-queries';
3 changes: 2 additions & 1 deletion src/entities/book/model/book.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export interface Book {
id: number;
isbn: string;
title: string;
author: string;
publisher: string;
Expand All @@ -8,5 +8,6 @@ export interface Book {
}

export interface BookDetail extends Book {
description: string;
pageCount: number;
}
41 changes: 22 additions & 19 deletions src/entities/book/ui/BookCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { memo } from 'react';
import Image from 'next/image';
import Link from 'next/link';
import { Card, CardContent } from '@/shared/ui/card';
import { Book } from '../model/book';

Expand All @@ -8,28 +9,30 @@ interface BookCardProps {
}

function BookCard({ book }: BookCardProps) {
const { coverImage, title, author, publisher, publishDate } = book;
const { isbn, coverImage, title, author, publisher, publishDate } = book;

return (
<Card className="overflow-hidden">
<div className="flex flex-row sm:flex-col">
<div className="w-1/3 sm:w-full">
<Image
src={coverImage}
alt={`${title} 표지`}
width={200}
height={300}
className="size-full object-contain sm:h-64 sm:object-cover"
/>
<Link href={`/notes/create/${isbn}`}>
<Card className="overflow-hidden">
<div className="flex flex-row sm:flex-col">
<div className="w-1/3 sm:w-full">
<Image
src={coverImage}
alt={`${title} 표지`}
width={200}
height={300}
className="size-full object-contain sm:h-64 sm:object-cover"
/>
</div>
<CardContent className="w-2/3 p-4 sm:w-full">
<h3 className="mb-1 text-sm font-bold sm:mb-2 sm:text-lg">{title}</h3>
<p className="mb-1 text-xs text-gray-600 sm:text-sm">{author}</p>
<p className="mb-1 text-xs text-gray-600 sm:text-sm">{publisher}</p>
<p className="text-xs text-gray-600 sm:text-sm">{publishDate}</p>
</CardContent>
</div>
<CardContent className="w-2/3 p-4 sm:w-full">
<h3 className="mb-1 text-sm font-bold sm:mb-2 sm:text-lg">{title}</h3>
<p className="mb-1 text-xs text-gray-600 sm:text-sm">{author}</p>
<p className="mb-1 text-xs text-gray-600 sm:text-sm">{publisher}</p>
<p className="text-xs text-gray-600 sm:text-sm">{publishDate}</p>
</CardContent>
</div>
</Card>
</Card>
</Link>
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/entities/book/ui/BookList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default function BookList() {
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
{books.map((book) => (
<BookCard
key={book.id}
key={book.isbn}
book={book}
/>
))}
Expand Down
2 changes: 1 addition & 1 deletion src/features/book-note/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { default as BookNoteForm } from './ui/book-note-form';
export { default as BookNoteForm } from './ui/BookNoteForm/BookNoteForm';
9 changes: 9 additions & 0 deletions src/features/book-note/model/book-note-form-step.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const BookNoteFormStep = {
READING_INFO: 'READING_INFO',
RATING: 'RATING',
REVIEW: 'REVIEW',
QUOTES: 'QUOTES',
VISIBILITY: 'VISIBILITY',
} as const;

export type BookNoteFormStep = (typeof BookNoteFormStep)[keyof typeof BookNoteFormStep];
12 changes: 12 additions & 0 deletions src/features/book-note/model/book-note-form-values.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ReadingStatus } from './reading-status';

export interface BookNoteFormValues {
readingStatus: ReadingStatus;
startDate: Date;
endDate: Date;
recommended: boolean | null;
overallRating: number;
content: string;
quotes: { text: string; page: string }[];
visibility: boolean;
}
9 changes: 9 additions & 0 deletions src/features/book-note/model/reading-status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const ReadingStatus = {
WANT_TO_READ: 'WANT_TO_READ',
READING: 'READING',
READ: 'READ',
ON_HOLD: 'ON_HOLD',
DROPPED: 'DROPPED',
} as const;

export type ReadingStatus = (typeof ReadingStatus)[keyof typeof ReadingStatus];
53 changes: 53 additions & 0 deletions src/features/book-note/ui/BookNoteForm/BookNoteForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use client';

import { useState } from 'react';
import { useParams } from 'next/navigation';
import { useSuspenseQuery } from '@tanstack/react-query';
import { match } from 'ts-pattern';
import { bookQueries } from '@/entities/book';
import { BookNoteFormStep } from '../../model/book-note-form-step';
import ReadingInfoStep from './step/ReadingInfoStep';
import RatingStep from './step/RatingStep';
import ReviewStep from './step/ReviewStep';
import QuotesStep from './step/QuotesStep';
import VisibilityStep from './step/VisibilityStep';

export default function BookNoteForm() {
const [step, setStep] = useState<BookNoteFormStep>(BookNoteFormStep.READING_INFO);

const { isbn } = useParams<{ isbn: string }>()!;

const { data: book } = useSuspenseQuery(bookQueries.detail(isbn));

return (
<div className="mx-auto w-full max-w-4xl space-y-8 p-4 md:p-6">
{match(step)
.with(BookNoteFormStep.READING_INFO, () => (
<ReadingInfoStep
book={book}
onNext={() => setStep(BookNoteFormStep.RATING)}
/>
))
.with(BookNoteFormStep.RATING, () => (
<RatingStep
onPrevious={() => setStep(BookNoteFormStep.READING_INFO)}
onNext={() => setStep(BookNoteFormStep.REVIEW)}
/>
))
.with(BookNoteFormStep.REVIEW, () => (
<ReviewStep
onPrevious={() => setStep(BookNoteFormStep.RATING)}
onNext={() => setStep(BookNoteFormStep.QUOTES)}
/>
))
.with(BookNoteFormStep.QUOTES, () => (
<QuotesStep
onPrevious={() => setStep(BookNoteFormStep.REVIEW)}
onNext={() => setStep(BookNoteFormStep.VISIBILITY)}
/>
))
.with(BookNoteFormStep.VISIBILITY, () => <VisibilityStep onPrevious={() => setStep(BookNoteFormStep.QUOTES)} />)
.exhaustive()}
Comment on lines +24 to +50
Copy link

Choose a reason for hiding this comment

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

<Switch
  value={step}
  cases={{
    [BookNoteFormStep.READING_INFO]: <ReadingInfoStep
            book={book}
            onNext={() => setStep(BookNoteFormStep.RATING)}
          />,
  }}
/>

</div>
);
}
39 changes: 39 additions & 0 deletions src/features/book-note/ui/BookNoteForm/BookNoteFormActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Button } from '@/shared/ui/button';

interface BookNoteFormActionsProps {
previousLabel?: string;
nextLabel?: string;
previousDisabled?: boolean;
nextDisabled?: boolean;
onPrevious?: () => void;
onNext?: () => void;
}

export default function BookNoteFormActions({
previousLabel = '이전',
nextLabel = '다음',
previousDisabled,
nextDisabled,
onPrevious,
onNext,
}: BookNoteFormActionsProps) {
return (
<div className="flex justify-between pt-6">
<Button
type="button"
onClick={onPrevious}
variant="outline"
disabled={previousDisabled}
>
{previousLabel}
</Button>
<Button
type="button"
disabled={nextDisabled}
onClick={onNext}
>
{nextLabel}
</Button>
</div>
);
}
1 change: 1 addition & 0 deletions src/features/book-note/ui/BookNoteForm/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './BookNoteForm';
Loading
Loading