diff --git a/frontend/app/components/book-detail/BookDetailControlButtons.tsx b/frontend/app/components/book-detail/BookDetailControlButtons.tsx
new file mode 100644
index 00000000..b5b420f8
--- /dev/null
+++ b/frontend/app/components/book-detail/BookDetailControlButtons.tsx
@@ -0,0 +1,20 @@
+import BookDetailEditButton from './BookDetailEditButton';
+import { MdDeleteForever } from 'react-icons/md';
+import { Button } from '@mantine/core';
+import { useFetcher } from '@remix-run/react';
+import BookDetailDeleteButton from './BookDetailDeleteButton';
+
+interface BookDetailControlButtonsProps {
+ id: number;
+}
+
+const BookDetailControlButtons = ({ id }: BookDetailControlButtonsProps) => {
+ return (
+ <>
+
+
+ >
+ );
+};
+
+export default BookDetailControlButtons;
diff --git a/frontend/app/components/book-detail/BookDetailControlPanel.tsx b/frontend/app/components/book-detail/BookDetailControlPanel.tsx
index c0d4e236..57788c4d 100644
--- a/frontend/app/components/book-detail/BookDetailControlPanel.tsx
+++ b/frontend/app/components/book-detail/BookDetailControlPanel.tsx
@@ -1,22 +1,27 @@
-import { Button, Stack } from '@mantine/core';
-import { useFetcher } from '@remix-run/react';
+import { Stack } from '@mantine/core';
import { useAtom } from 'jotai';
-import { MdDeleteForever } from 'react-icons/md';
import { userAtom } from '~/stores/userAtom';
-import BookDetailEditButton from './BookDetailEditButton';
import BookDetailThumbnail from './BookDetailThumbnail';
+import BookDetailControlButtons from './BookDetailControlButtons';
+import { useLocation } from '@remix-run/react';
+import GlobalBookDetailControlButtons from '../global-book-detail/GlobalBookDetailControlButtons';
+import { SearchBooks200BooksItem } from 'client/client.schemas';
interface BookDetailControlPanelProps {
- id: number;
+ id?: number;
thumbnail?: string;
+ searchBook?: SearchBooks200BooksItem;
+ totalBook?: number;
}
const BookDetailControlPanel = ({
id,
thumbnail,
+ searchBook,
+ totalBook,
}: BookDetailControlPanelProps) => {
const [user] = useAtom(userAtom);
- const fetcher = useFetcher();
+ const location = useLocation();
return (
- {!!user && }
- {!!user && (
- }
- fz="lg"
- onClick={() =>
- fetcher.submit(
- { bookId: id },
- { action: '/home/books/$bookId', method: 'DELETE' },
- )
- }
- disabled={fetcher.state === 'submitting'}
- >
- 削除
-
- )}
+ {user && location.pathname.includes('global')
+ ? searchBook &&
+ typeof totalBook == 'number' && (
+
+ )
+ : id && }
);
};
diff --git a/frontend/app/components/book-detail/BookDetailDeleteButton.tsx b/frontend/app/components/book-detail/BookDetailDeleteButton.tsx
new file mode 100644
index 00000000..1fd70425
--- /dev/null
+++ b/frontend/app/components/book-detail/BookDetailDeleteButton.tsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import { MdDeleteForever } from 'react-icons/md';
+import { Button } from '@mantine/core';
+import { useFetcher } from '@remix-run/react';
+
+interface BookDetailDeleteButtonProps {
+ bookId: number;
+}
+
+const BookDetailDeleteButton = ({ bookId }: BookDetailDeleteButtonProps) => {
+ const fetcher = useFetcher();
+ return (
+ }
+ fz="lg"
+ onClick={() =>
+ fetcher.submit(
+ { bookId: bookId },
+ { action: '/home/books/$bookId', method: 'DELETE' },
+ )
+ }
+ disabled={fetcher.state === 'submitting'}
+ >
+ 削除
+
+ );
+};
+
+export default BookDetailDeleteButton;
diff --git a/frontend/app/components/global-book-detail/GlobalBookDetailAuthorBadge.tsx b/frontend/app/components/global-book-detail/GlobalBookDetailAuthorBadge.tsx
new file mode 100644
index 00000000..da99c194
--- /dev/null
+++ b/frontend/app/components/global-book-detail/GlobalBookDetailAuthorBadge.tsx
@@ -0,0 +1,24 @@
+import { Badge } from '@mantine/core';
+
+interface GlobalBookDetailAuthorBadgeProps {
+ name: string;
+}
+
+const GlobalBookDetailAuthorBadge = ({
+ name,
+}: GlobalBookDetailAuthorBadgeProps) => {
+ return (
+
+ {name}
+
+ );
+};
+
+export default GlobalBookDetailAuthorBadge;
diff --git a/frontend/app/components/global-book-detail/GlobalBookDetailContent.tsx b/frontend/app/components/global-book-detail/GlobalBookDetailContent.tsx
new file mode 100644
index 00000000..f321d3ae
--- /dev/null
+++ b/frontend/app/components/global-book-detail/GlobalBookDetailContent.tsx
@@ -0,0 +1,32 @@
+import { Stack } from '@mantine/core';
+import { SearchBooks200BooksItem } from 'client/client.schemas';
+import GlobalBookDetailContentTable from './GlobalBookDetailContentTable';
+import BookDetailTitle from '../book-detail/BookDetailTitle';
+import BookDetailDescription from '../book-detail/BookDetailDescription';
+import GlobalBookDetailLink from './GlobalBookDetailLink';
+
+interface GlobalBookDetailContentProps {
+ book: SearchBooks200BooksItem;
+ bookId?: number;
+}
+
+const GlobalBookDetailContent = ({
+ book,
+ bookId,
+}: GlobalBookDetailContentProps) => {
+ return (
+
+
+
+
+ {!!bookId && }
+
+ );
+};
+
+export default GlobalBookDetailContent;
diff --git a/frontend/app/components/global-book-detail/GlobalBookDetailContentTable.tsx b/frontend/app/components/global-book-detail/GlobalBookDetailContentTable.tsx
new file mode 100644
index 00000000..28c4564b
--- /dev/null
+++ b/frontend/app/components/global-book-detail/GlobalBookDetailContentTable.tsx
@@ -0,0 +1,43 @@
+import { Group, rem, Stack, Table, Text } from '@mantine/core';
+import { SearchBooks200BooksItem } from 'client/client.schemas';
+import GlobalBookDetailAuthorBadge from './GlobalBookDetailAuthorBadge';
+
+interface GlobalBookDetailContentTableProps {
+ book: SearchBooks200BooksItem;
+}
+
+const GlobalBookDetailContentTable = ({
+ book,
+}: GlobalBookDetailContentTableProps) => {
+ return (
+
+ 書籍情報
+
+
+ 著者
+
+
+ {book.authors.map((author, id) => (
+
+ ))}
+
+
+
+
+ 出版社
+ {book.publisher}
+
+
+ 出版日
+ {book.publishedDate}
+
+
+ ISBN
+ {book.isbn}
+
+
+
+ );
+};
+
+export default GlobalBookDetailContentTable;
diff --git a/frontend/app/components/global-book-detail/GlobalBookDetailControlButtons.tsx b/frontend/app/components/global-book-detail/GlobalBookDetailControlButtons.tsx
new file mode 100644
index 00000000..7fb7ef03
--- /dev/null
+++ b/frontend/app/components/global-book-detail/GlobalBookDetailControlButtons.tsx
@@ -0,0 +1,44 @@
+import { Button } from '@mantine/core';
+import { useFetcher } from '@remix-run/react';
+import { CreateBookBody, SearchBooks200BooksItem } from 'client/client.schemas';
+import { BiSolidBookAdd } from 'react-icons/bi';
+
+interface GlobalBookDetailControlButtonsProps {
+ searchBook: SearchBooks200BooksItem;
+ totalBook: number;
+}
+
+const GlobalBookDetailControlButtons = ({
+ searchBook,
+ totalBook,
+}: GlobalBookDetailControlButtonsProps) => {
+ const fetcher = useFetcher();
+ const addBookData: CreateBookBody = {
+ authors: searchBook.authors,
+ description: searchBook.description ?? '',
+ isbn: searchBook.isbn ?? '',
+ publishedDate: searchBook.publishedDate ?? '',
+ publisher: searchBook.publisher ?? '',
+ stock: 1,
+ thumbnail: searchBook.thumbnail,
+ title: searchBook.title,
+ };
+ return (
+
+ );
+};
+
+export default GlobalBookDetailControlButtons;
diff --git a/frontend/app/components/global-book-detail/GlobalBookDetailLink.tsx b/frontend/app/components/global-book-detail/GlobalBookDetailLink.tsx
new file mode 100644
index 00000000..d78dfcae
--- /dev/null
+++ b/frontend/app/components/global-book-detail/GlobalBookDetailLink.tsx
@@ -0,0 +1,16 @@
+import { Anchor, Text } from '@mantine/core';
+
+interface GlobalBookDetailLinkProps {
+ bookId: number;
+}
+
+const GlobalBookDetailLink = ({ bookId }: GlobalBookDetailLinkProps) => {
+ return (
+
+ この書籍はすでに登録されています。蔵書の詳細ページは
+ こちら
+
+ );
+};
+
+export default GlobalBookDetailLink;
diff --git a/frontend/app/components/global-books/GlobalBookCard.tsx b/frontend/app/components/global-books/GlobalBookCard.tsx
index 5ff51786..fd0facb2 100644
--- a/frontend/app/components/global-books/GlobalBookCard.tsx
+++ b/frontend/app/components/global-books/GlobalBookCard.tsx
@@ -11,7 +11,8 @@ const GlobalBookCard = ({ book }: GlobalBookCardProps) => {
diff --git a/frontend/app/routes/home._index/route.tsx b/frontend/app/routes/home._index/route.tsx
index 8378a7ba..e3c638bd 100644
--- a/frontend/app/routes/home._index/route.tsx
+++ b/frontend/app/routes/home._index/route.tsx
@@ -26,7 +26,7 @@ interface LoaderData {
};
}
-interface ActionResponse {
+export interface ActionResponse {
method: string;
status: number;
}
diff --git a/frontend/app/routes/home.global.books.$isbn/route.tsx b/frontend/app/routes/home.global.books.$isbn/route.tsx
new file mode 100644
index 00000000..a19ee2b2
--- /dev/null
+++ b/frontend/app/routes/home.global.books.$isbn/route.tsx
@@ -0,0 +1,128 @@
+import { Grid, rem, Stack } from '@mantine/core';
+import {
+ ActionFunctionArgs,
+ json,
+ LoaderFunctionArgs,
+ redirect,
+} from '@remix-run/cloudflare';
+import { useLoaderData } from '@remix-run/react';
+import {
+ createBook,
+ getBooks,
+ searchBooks,
+ searchBooksResponse,
+} from 'client/client';
+import { CreateBookBody } from 'client/client.schemas';
+import BookDetailControlPanel from '~/components/book-detail/BookDetailControlPanel';
+import GlobalBookDetailContent from '~/components/global-book-detail/GlobalBookDetailContent';
+import { commitSession, getSession } from '~/services/session.server';
+import { ActionResponse } from '../home._index/route';
+
+interface LoaderData {
+ searchBooksResponse: searchBooksResponse;
+ totalBook?: number;
+ bookId?: number;
+}
+
+export const loader = async ({ params, request }: LoaderFunctionArgs) => {
+ const session = await getSession(request.headers.get('Cookie'));
+ // 書籍の情報を取得する
+ const isbn = params.isbn ?? '';
+ const searchBooksResponse = await searchBooks({ isbn: isbn });
+ // 既に登録済みであるか確認するため
+ if (session.has('userId')) {
+ const getBookResponse = await getBooks({ isbn: isbn });
+ if (getBookResponse.data.totalBook > 0) {
+ return json({
+ searchBooksResponse: searchBooksResponse,
+ totalBook: getBookResponse.data.totalBook,
+ bookId: getBookResponse.data.books[0].id,
+ });
+ } else {
+ return json({
+ searchBooksResponse: searchBooksResponse,
+ totalBook: getBookResponse.data.totalBook,
+ bookId: undefined,
+ });
+ }
+ } else {
+ // ログイン済みでない場合は、APIを呼び出す回数を減らすために蔵書の情報を取得しない
+ return json({
+ searchBooksResponse: searchBooksResponse,
+ totalBook: undefined,
+ bookId: undefined,
+ });
+ }
+};
+
+export const action = async ({ request }: ActionFunctionArgs) => {
+ const session = await getSession(request.headers.get('Cookie'));
+
+ // 未ログインの場合
+ if (!session.has('userId')) {
+ session.flash('error', 'ログインしてください');
+ return redirect('/login', {
+ headers: {
+ 'Set-Cookie': await commitSession(session),
+ },
+ });
+ }
+
+ const cookieHeader = [
+ `__Secure-user_id=${session.get('userId')};`,
+ `__Secure-session_token=${session.get('sessionToken')}`,
+ ].join('; ');
+
+ const requestBody = await request.json();
+
+ const response = await createBook(requestBody, {
+ headers: { Cookie: cookieHeader },
+ });
+
+ switch (response.status) {
+ case 201:
+ session.flash('success', '書籍を追加しました');
+ return redirect('/home', {
+ headers: { 'Set-Cookie': await commitSession(session) },
+ });
+ case 400:
+ session.flash('error', 'リクエストの中身が誤っています');
+ return json(
+ { method: 'POST', status: response.status },
+ { headers: { 'Set-Cookie': await commitSession(session) } },
+ );
+ case 401:
+ session.flash('error', 'ログインしてください');
+ return redirect('/login', {
+ headers: {
+ 'Set-Cookie': await commitSession(session),
+ },
+ });
+ }
+};
+
+const GlobalBookDetailPage = () => {
+ const { searchBooksResponse, totalBook, bookId } =
+ useLoaderData();
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default GlobalBookDetailPage;