diff --git a/app/[id]/page.tsx b/app/[id]/page.tsx
index 2a1a930..5f85b02 100644
--- a/app/[id]/page.tsx
+++ b/app/[id]/page.tsx
@@ -1,67 +1,72 @@
-import { fetchBookById } from '../lib/data';
-import Link from 'next/link';
-import Tile from '../components/tile';
+import { fetchBookById } from "../lib/data";
+import Link from "next/link";
+import Tile from "../components/tile";
export default async function Page({ params }: { params: { id: string } }) {
- const book = await fetchBookById(params.id);
- return (
-
-
- ← Back to all books
-
-
-
-
-
-
-
{book.title}
-
{book.author}
-
-
{book.description}
-
-
-
- );
+ const book = await fetchBookById(params.id);
+ return (
+
+
+ ← Back to all books
+
+
+
+
+
+
+
{book.title}
+
{book.author}
+
+
{book.description}
+
+
+
+ );
}
const StarRating = ({ rating }: { rating: number }) => {
- const totalStars = 5;
- let remainingRating = rating;
+ const totalStars = 5;
+ let remainingRating = rating;
- // Generate stars based on the rating
- const stars = Array.from({ length: totalStars }).map((_, index) => {
- let fill = 'white';
- if (remainingRating >= 1) {
- fill = 'gold';
- remainingRating -= 1;
- } else if (remainingRating > 0) {
- fill = 'half';
- remainingRating = 0;
- }
- return ;
- });
+ // Generate stars based on the rating
+ const stars = Array.from({ length: totalStars }).map((_, index) => {
+ let fill = "white";
+ if (remainingRating >= 1) {
+ fill = "gold";
+ remainingRating -= 1;
+ } else if (remainingRating > 0) {
+ fill = "half";
+ remainingRating = 0;
+ }
+ return ;
+ });
- return {stars}
;
+ return {stars}
;
};
-const SVGStar = ({ fill = 'none' }) => {
- const fillColor = fill === 'half' ? 'url(#half)' : fill;
+const SVGStar = ({ fill = "none" }) => {
+ const fillColor = fill === "half" ? "url(#half)" : fill;
- return (
-
- );
+ return (
+
+ );
};
diff --git a/app/components/panel.tsx b/app/components/panel.tsx
index 0150a65..fe7ca93 100644
--- a/app/components/panel.tsx
+++ b/app/components/panel.tsx
@@ -1,121 +1,126 @@
-'use client';
+"use client";
-import { useRouter } from 'next/navigation';
-import { useOptimistic, useTransition, useState } from 'react';
-import { ChevronDownIcon } from '@heroicons/react/24/outline';
+import { useRouter } from "next/navigation";
+import { useOptimistic, useTransition, useState } from "react";
+import { ChevronDownIcon } from "@heroicons/react/24/outline";
interface ExpandedSections {
- [key: string]: boolean;
+ [key: string]: boolean;
}
export default function Panel({
- authors,
- allAuthors
+ authors,
+ allAuthors,
}: {
- authors: string[];
- allAuthors: string[];
+ authors: string[];
+ allAuthors: string[];
}) {
- let router = useRouter();
- let [pending, startTransition] = useTransition();
- let [optimisticAuthors, setOptimisticAuthors] = useOptimistic(authors);
- let [expandedSections, setExpandedSections] = useState({});
+ let router = useRouter();
+ let [pending, startTransition] = useTransition();
+ let [optimisticAuthors, setOptimisticAuthors] = useOptimistic(authors);
+ let [expandedSections, setExpandedSections] = useState({});
- const authorGroups = allAuthors.reduce(
- (acc: { [key: string]: string[] }, author: string) => {
- const firstLetter = author[0].toUpperCase(); // Get the first letter, capitalize it
- if (!acc[firstLetter]) {
- acc[firstLetter] = []; // Initialize the array if this is the first author with this letter
- }
- acc[firstLetter].push(author); // Add the author to the appropriate array
- return acc; // Return the updated accumulator
- },
- {} as { [key: string]: string[] }
- );
- const toggleSection = (letter: string): void => {
- setExpandedSections((prev: Record) => ({
- ...prev,
- [letter]: !prev[letter]
- }));
- };
+ const authorGroups = allAuthors.reduce(
+ (acc: { [key: string]: string[] }, author: string) => {
+ const firstLetter = author[0].toUpperCase(); // Get the first letter, capitalize it
+ if (!acc[firstLetter]) {
+ acc[firstLetter] = []; // Initialize the array if this is the first author with this letter
+ }
+ acc[firstLetter].push(author); // Add the author to the appropriate array
+ return acc; // Return the updated accumulator
+ },
+ {} as { [key: string]: string[] },
+ );
+ const toggleSection = (letter: string): void => {
+ setExpandedSections((prev: Record) => ({
+ ...prev,
+ [letter]: !prev[letter],
+ }));
+ };
- return (
-
-
-
-
Authors
- {Object.entries(authorGroups).map(([letter, authors]) => (
-
-
-
- {expandedSections[letter] &&
- authors.map((author) => (
-
+ ))}
+
+
- {optimisticAuthors.length > 0 && (
-
-
- {optimisticAuthors.map((author) => (
-
{author}
- ))}
-
-
{
- startTransition(() => {
- setOptimisticAuthors([]);
- router.push(`/`);
- });
- }}
- >
- Clear authors
-
-
- )}
-
- );
+ {optimisticAuthors.length > 0 && (
+
+
+ {optimisticAuthors.map((author) => (
+
{author}
+ ))}
+
+
{
+ startTransition(() => {
+ setOptimisticAuthors([]);
+ router.push(`/`);
+ });
+ }}
+ >
+ Clear authors
+
+
+ )}
+
+ );
}
diff --git a/app/components/tile.tsx b/app/components/tile.tsx
index 8c5a3d9..37da216 100644
--- a/app/components/tile.tsx
+++ b/app/components/tile.tsx
@@ -1,41 +1,41 @@
-'use client';
-import { useState } from 'react';
-import Image from 'next/image';
-import { BookSkeleton } from './loading-skeleton';
-import { PhotoIcon } from '@heroicons/react/24/outline';
+"use client";
+import { useState } from "react";
+import Image from "next/image";
+import { BookSkeleton } from "./loading-skeleton";
+import { PhotoIcon } from "@heroicons/react/24/outline";
const Tile = ({ src, title }: { src: string; title: string }) => {
- const [isOptimized, setIsOptimized] = useState(true);
- const [isLoading, setIsLoading] = useState(true);
- return (
-
- {src ? (
- <>
- {isLoading &&
}
-
{
- setIsOptimized(false);
- }}
- onLoad={() => {
- setIsLoading(false);
- }}
- />
- >
- ) : (
-
- )}
-
- );
+ const [isOptimized, setIsOptimized] = useState(true);
+ const [isLoading, setIsLoading] = useState(true);
+ return (
+
+ {src ? (
+ <>
+ {isLoading &&
}
+
{
+ setIsOptimized(false);
+ }}
+ onLoad={() => {
+ setIsLoading(false);
+ }}
+ />
+ >
+ ) : (
+
+ )}
+
+ );
};
export default Tile;
diff --git a/app/globals.css b/app/globals.css
index 20bc910..d420756 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -3,7 +3,7 @@
@tailwind utilities;
.star {
- color: gold;
- font-size: 24px;
- user-select: none; /* prevent text selection */
+ color: gold;
+ font-size: 24px;
+ user-select: none; /* prevent text selection */
}
diff --git a/app/lib/data.ts b/app/lib/data.ts
index dc40953..626b861 100644
--- a/app/lib/data.ts
+++ b/app/lib/data.ts
@@ -1,20 +1,20 @@
-import { sql } from '@vercel/postgres';
-import { unstable_noStore as noStore } from 'next/cache';
+import { sql } from "@vercel/postgres";
+import { unstable_noStore as noStore } from "next/cache";
const ITEMS_PER_PAGE = 30;
export async function fetchFilteredBooks(
- selectedAuthors: string[],
- query: string,
- currentPage: number
+ selectedAuthors: string[],
+ query: string,
+ currentPage: number,
) {
- noStore();
- const offset = (currentPage - 1) * ITEMS_PER_PAGE;
- if (selectedAuthors.length > 0) {
- try {
- const authorsDelimited = selectedAuthors.join('|');
+ noStore();
+ const offset = (currentPage - 1) * ITEMS_PER_PAGE;
+ if (selectedAuthors.length > 0) {
+ try {
+ const authorsDelimited = selectedAuthors.join("|");
- const books = await sql`
+ const books = await sql`
SELECT ALL
id,
isbn,
@@ -38,15 +38,15 @@ export async function fetchFilteredBooks(
ORDER BY "createdAt" DESC
LIMIT ${ITEMS_PER_PAGE} OFFSET ${offset}
`;
- return books.rows;
- } catch (error) {
- console.error('Database Error:', error);
- throw new Error('Failed to fetch books.');
- }
- }
+ return books.rows;
+ } catch (error) {
+ console.error("Database Error:", error);
+ throw new Error("Failed to fetch books.");
+ }
+ }
- try {
- const books = await sql`
+ try {
+ const books = await sql`
SELECT ALL
id,
isbn,
@@ -68,39 +68,39 @@ export async function fetchFilteredBooks(
ORDER BY "createdAt" DESC
LIMIT ${ITEMS_PER_PAGE} OFFSET ${offset}
`;
- return books.rows;
- } catch (error) {
- console.error('Database Error:', error);
- throw new Error('Failed to fetch books.');
- }
+ return books.rows;
+ } catch (error) {
+ console.error("Database Error:", error);
+ throw new Error("Failed to fetch books.");
+ }
}
export async function fetchBookById(id: string) {
- const data = await sql`SELECT * FROM books WHERE id = ${id}`;
- return data.rows[0];
+ const data = await sql`SELECT * FROM books WHERE id = ${id}`;
+ return data.rows[0];
}
export async function fetchAuthors() {
- try {
- const authors = await sql`
+ try {
+ const authors = await sql`
SELECT DISTINCT "author"
FROM books
ORDER BY "author"
`;
- return authors.rows?.map((row) => row.author);
- } catch (error) {
- console.error('Database Error:', error);
- throw new Error('Failed to fetch authors.');
- }
+ return authors.rows?.map((row) => row.author);
+ } catch (error) {
+ console.error("Database Error:", error);
+ throw new Error("Failed to fetch authors.");
+ }
}
export async function fetchPages(query: string, selectedAuthors: string[]) {
- noStore();
- if (selectedAuthors.length > 0) {
- try {
- const authorsDelimited = selectedAuthors.join('|');
+ noStore();
+ if (selectedAuthors.length > 0) {
+ try {
+ const authorsDelimited = selectedAuthors.join("|");
- const count = await sql`
+ const count = await sql`
SELECT COUNT(*)
FROM books
WHERE
@@ -112,16 +112,18 @@ export async function fetchPages(query: string, selectedAuthors: string[]) {
publisher ILIKE ${`%${query}%`}
)
`;
- const totalPages = Math.ceil(Number(count.rows[0].count) / ITEMS_PER_PAGE);
- return totalPages;
- } catch (error) {
- console.error('Database Error:', error);
- throw new Error('Failed to fetch books.');
- }
- }
+ const totalPages = Math.ceil(
+ Number(count.rows[0].count) / ITEMS_PER_PAGE,
+ );
+ return totalPages;
+ } catch (error) {
+ console.error("Database Error:", error);
+ throw new Error("Failed to fetch books.");
+ }
+ }
- try {
- const count = await sql`
+ try {
+ const count = await sql`
SELECT COUNT(*)
FROM books
WHERE
@@ -131,10 +133,10 @@ export async function fetchPages(query: string, selectedAuthors: string[]) {
"year"::text ILIKE ${`%${query}%`} OR
publisher ILIKE ${`%${query}%`}
`;
- const totalPages = Math.ceil(Number(count.rows[0].count) / ITEMS_PER_PAGE);
- return totalPages;
- } catch (error) {
- console.error('Database Error:', error);
- throw new Error('Failed to fetch total number of books.');
- }
+ const totalPages = Math.ceil(Number(count.rows[0].count) / ITEMS_PER_PAGE);
+ return totalPages;
+ } catch (error) {
+ console.error("Database Error:", error);
+ throw new Error("Failed to fetch total number of books.");
+ }
}
diff --git a/next.config.mjs b/next.config.mjs
index fe45905..e6afc14 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -1,14 +1,14 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
- images: {
- remotePatterns: [
- {
- protocol: 'https',
- hostname: 'i.gr-assets.com',
- port: ''
- }
- ]
- }
+ images: {
+ remotePatterns: [
+ {
+ protocol: "https",
+ hostname: "i.gr-assets.com",
+ port: "",
+ },
+ ],
+ },
};
export default nextConfig;
diff --git a/scripts/seed.mjs b/scripts/seed.mjs
index 1ca77a7..1c347ec 100644
--- a/scripts/seed.mjs
+++ b/scripts/seed.mjs
@@ -1,24 +1,24 @@
-import { db } from '@vercel/postgres';
-import fs from 'fs';
-import path from 'path';
-import Papa from 'papaparse';
-import '../envConfig.mjs';
+import { db } from "@vercel/postgres";
+import fs from "fs";
+import path from "path";
+import Papa from "papaparse";
+import "../envConfig.mjs";
const parseCSV = async (filePath) => {
- const csvFile = fs.readFileSync(path.resolve(filePath), 'utf8');
- return new Promise((resolve) => {
- Papa.parse(csvFile, {
- header: true,
- complete: (results) => {
- resolve(results.data);
- }
- });
- });
+ const csvFile = fs.readFileSync(path.resolve(filePath), "utf8");
+ return new Promise((resolve) => {
+ Papa.parse(csvFile, {
+ header: true,
+ complete: (results) => {
+ resolve(results.data);
+ },
+ });
+ });
};
async function seed(client) {
- // Creating the books table
- const createBooksTable = await client.sql`
+ // Creating the books table
+ const createBooksTable = await client.sql`
CREATE TABLE IF NOT EXISTS books (
id SERIAL PRIMARY KEY,
isbn VARCHAR(255) UNIQUE NOT NULL,
@@ -32,39 +32,42 @@ async function seed(client) {
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
`;
- console.log('Created "books" table');
+ console.log('Created "books" table');
- const bookData = await parseCSV('./books.csv');
+ const bookData = await parseCSV("./books.csv");
- // Inserting book data into the books table
- const promises = bookData.map((book, index) => {
- // An error occurred while attempting to seed the database: error: null value in column "isbn" of relation "books" violates not-null constraint
- if (!book.isbn) {
- console.error(`Skipping book at index ${index} due to missing ISBN`);
- return Promise.resolve();
- }
- return client.sql`
+ // Inserting book data into the books table
+ const promises = bookData.map((book, index) => {
+ // An error occurred while attempting to seed the database: error: null value in column "isbn" of relation "books" violates not-null constraint
+ if (!book.isbn) {
+ console.error(`Skipping book at index ${index} due to missing ISBN`);
+ return Promise.resolve();
+ }
+ return client.sql`
INSERT INTO books (isbn, "title", "author", "year", publisher, "image", "description", "rating")
VALUES (${book.bookId}, ${book.title}, ${book.author}, ${book.publisherDate}, ${book.Publisher}, ${book.coverImg}, ${book.description}, ${book.rating})
ON CONFLICT (isbn) DO NOTHING;
`;
- });
+ });
- const results = await Promise.all(promises);
- console.log(`Seeded ${results.length} books`);
+ const results = await Promise.all(promises);
+ console.log(`Seeded ${results.length} books`);
- return {
- createBooksTable,
- seededBooks: results.length
- };
+ return {
+ createBooksTable,
+ seededBooks: results.length,
+ };
}
async function main() {
- const client = await db.connect();
- await seed(client);
- await client.end();
+ const client = await db.connect();
+ await seed(client);
+ await client.end();
}
main().catch((err) => {
- console.error('An error occurred while attempting to seed the database:', err);
+ console.error(
+ "An error occurred while attempting to seed the database:",
+ err,
+ );
});