diff --git a/backend/src/index.ts b/backend/src/index.ts
index 7b743d1..781d8db 100644
--- a/backend/src/index.ts
+++ b/backend/src/index.ts
@@ -15,9 +15,14 @@ const typeDefs = `#graphql
image_url: String
}
+ type ArtworkPage {
+ artworks: [Artwork!]!
+ totalCount: Int!
+ }
+
type Query {
hello: String!
- artworks: [Artwork!]!
+ artworks(limit: Int!, offset: Int!): ArtworkPage!
}
`;
@@ -26,8 +31,25 @@ const resolvers = {
Query: {
hello: () => "Hello from Artex backend!",
- artworks: async () => {
- return await db.collection("met_artworks").find({}).toArray();
+ artworks: async (
+ _: unknown,
+ { limit, offset }: { limit: number; offset: number },
+ ) => {
+ if (limit < 1 || limit > 100 || offset < 0) {
+ throw new Error("Invalid pagination parameters");
+ }
+ const collection = db.collection("met_artworks");
+
+ const [artworks, totalCount] = await Promise.all([
+ collection.find({}).sort({ id: 1 }).skip(offset).limit(limit).toArray(),
+
+ collection.countDocuments({}),
+ ]);
+
+ return {
+ artworks,
+ totalCount,
+ };
},
},
};
diff --git a/frontend/index.html b/frontend/index.html
index 0fca6f0..241c301 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -2,9 +2,9 @@
-
+
- frontend
+ Artex
diff --git a/frontend/public/favicon.png b/frontend/public/favicon.png
new file mode 100644
index 0000000..28a265f
Binary files /dev/null and b/frontend/public/favicon.png differ
diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg
deleted file mode 100644
index 6893eb1..0000000
--- a/frontend/public/favicon.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/frontend/public/icons.svg b/frontend/public/icons.svg
deleted file mode 100644
index e952219..0000000
--- a/frontend/public/icons.svg
+++ /dev/null
@@ -1,24 +0,0 @@
-
diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg
deleted file mode 100644
index 6c87de9..0000000
--- a/frontend/src/assets/react.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/frontend/src/assets/vite.svg b/frontend/src/assets/vite.svg
deleted file mode 100644
index 5101b67..0000000
--- a/frontend/src/assets/vite.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/frontend/src/components/Pagination.tsx b/frontend/src/components/Pagination.tsx
new file mode 100644
index 0000000..00c4237
--- /dev/null
+++ b/frontend/src/components/Pagination.tsx
@@ -0,0 +1,42 @@
+type PaginationProps = {
+ page: number;
+ totalPages: number;
+ onPageChange: (page: number) => void;
+};
+
+export default function Pagination({
+ page,
+ totalPages,
+ onPageChange,
+}: PaginationProps) {
+ if (totalPages <= 1) return null;
+
+ return (
+
+ );
+}
diff --git a/frontend/src/components/PlaceHolderComp.tsx b/frontend/src/components/PlaceHolderComp.tsx
deleted file mode 100644
index e69de29..0000000
diff --git a/frontend/src/hooks/useArtworks.ts b/frontend/src/hooks/useArtworks.ts
index 1826db4..430c333 100644
--- a/frontend/src/hooks/useArtworks.ts
+++ b/frontend/src/hooks/useArtworks.ts
@@ -1,32 +1,47 @@
import { useEffect, useState } from "react";
import type { Artwork } from "../types/Artwork";
-export function useArtworks() {
+export function useArtworks(page: number, pageSize = 12) {
const [artworks, setArtworks] = useState([]);
+ const [totalCount, setTotalCount] = useState(0);
const [error, setError] = useState("");
const [loading, setLoading] = useState(true);
useEffect(() => {
+ const controller = new AbortController();
+
async function fetchArtworks() {
+ setLoading(true);
+ setError("");
+
try {
const response = await fetch("/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
+ signal: controller.signal,
body: JSON.stringify({
query: `
- query {
- artworks {
- id
- title
- artist_display
- image_url
- }
- }
- `,
+ query GetArtworks($limit: Int!, $offset: Int!) {
+ artworks(limit: $limit, offset: $offset) {
+ totalCount
+ artworks {
+ id
+ title
+ artist_display
+ image_url
+ }
+ }
+ }
+ `,
+ variables: {
+ limit: pageSize,
+ offset: (page - 1) * pageSize,
+ },
}),
});
+
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
@@ -37,16 +52,23 @@ export function useArtworks() {
throw new Error(result.errors[0].message);
}
- setArtworks(result.data.artworks.slice(0, 12));
+ setArtworks(result.data.artworks.artworks);
+ setTotalCount(result.data.artworks.totalCount);
} catch (err) {
+ if (controller.signal.aborted) return;
+
setError(err instanceof Error ? err.message : "Unknown error");
} finally {
- setLoading(false);
+ if (!controller.signal.aborted) {
+ setLoading(false);
+ }
}
}
fetchArtworks();
- }, []);
- return { artworks, loading, error };
+ return () => controller.abort();
+ }, [page, pageSize]);
+
+ return { artworks, totalCount, loading, error };
}
diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx
index 698d708..acb6b66 100644
--- a/frontend/src/pages/HomePage.tsx
+++ b/frontend/src/pages/HomePage.tsx
@@ -1,17 +1,39 @@
+import { useState } from "react";
import ArtworkGrid from "../components/ArtworkGrid";
import Hero from "../components/Hero";
+import Pagination from "../components/Pagination";
import { useArtworks } from "../hooks/useArtworks";
+const PAGE_SIZE = 12;
+
export default function HomePage() {
- const { artworks, loading, error } = useArtworks();
+ const [page, setPage] = useState(1);
+
+ const { artworks, totalCount, loading, error } = useArtworks(page, PAGE_SIZE);
- if (loading) return Loading artworks...
;
- if (error) return Error: {error}
;
+ const totalPages = Math.ceil(totalCount / PAGE_SIZE);
return (
-
+
+
+ {error && Error: {error}
}
+
+ {loading && Loading artworks...
}
+
+
+
+ {!error && (
+
+ )}
+
);
}