Skip to content

Commit

Permalink
fix(image fetching): add proxy endpoint to bypass CORS (#10)
Browse files Browse the repository at this point in the history
Also added image titles in the gallery
  • Loading branch information
ythepaut committed Sep 5, 2024
2 parents 5958c55 + f28d589 commit a21e2f1
Show file tree
Hide file tree
Showing 9 changed files with 62 additions and 28 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Choose one of the following methods:
APP_URL=

# URL to the image list (c.f. 2.)
IMAGES_URL=http://showcase.ythepaut.com/assets/images.json
IMAGES_URL=

# Your timezone and default locale (only fr and en are supported)
TIMEZONE=Europe/Paris
Expand Down
19 changes: 15 additions & 4 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
"use client";

import Gallery from "../components/gallery/Gallery";
import useImageStore from "../store/image.store";
import { Image } from "../model/image";
import { getAllImages } from "../services/image.service";
import { useEffect, useState } from "react";

export default function HomePage() {

const imageStore = useImageStore();
const [images, setImages] = useState<Image[] | null>(null);

useEffect(() => {
if (images) return;
imageStore.getImages().then(setImages);
}, [images, imageStore]);

export default async function HomePage() {
const images: Image[] = await getAllImages(process.env.imagesUrl ?? "");
return (
<section className="p-2 bg-bg-light" aria-label="Gallery Section">
<Gallery images={images} />
<Gallery images={images ?? []} />
</section>
);
}
8 changes: 5 additions & 3 deletions src/components/gallery/Gallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@ export default function Gallery({ images }: Readonly<Props>): ReactElement {
className={`gap-0 md:max-w-90vw xl:max-w-80vw ${columns === 1 ? "columns-1" : columns === 2 ? "columns-2" : "columns-3"}`}
>
{columnWrappers.flat().map((image: Image) => (
<Link key={image.id} href={`/photo/${image.id}`} passHref>
<ImageTile image={image} />
</Link>
<div key={image.id} className="p-0.5">
<Link href={`/photo/${image.id}`} passHref>
<ImageTile image={image} />
</Link>
</div>
))}
</div>
</div>
Expand Down
8 changes: 7 additions & 1 deletion src/components/gallery/ImageTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface Props {

export default function ImageTile({ image }: Readonly<Props>): ReactElement {
return (
<div className="p-1">
<div className="relative group">
<NextImage
className="w-full rounded-50 cursor-pointer"
src={image.src}
Expand All @@ -25,6 +25,12 @@ export default function ImageTile({ image }: Readonly<Props>): ReactElement {
)
)}`}
/>
<div className="hidden md:block absolute bottom-0 left-0 w-full h-20 rounded-50 bg-gradient-to-t from-black to-transparant opacity-0 group-hover:opacity-40 transition-opacity" />
<div className="hidden md:block px-6 py-4 absolute bottom-0 left-0 w-full opacity-0 group-hover:opacity-100 transition-opacity">
<span className="text-xl text-txt-white font-medium">
{image.title}
</span>
</div>
</div>
);
}
14 changes: 14 additions & 0 deletions src/pages/api/images.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { type NextApiRequest, NextApiResponse } from "next";

export default async function handler(_: NextApiRequest, res: NextApiResponse) {
try {
const response = await fetch(process.env.IMAGES_URL as string, {
method: "GET"
});
const data = await response.json();
res.status(response.status).json(data);
} catch (error) {
console.warn(`Could not fetch images :\n${error}`);
res.status(500).json({ error: "Failed to fetch images" });
}
}
18 changes: 6 additions & 12 deletions src/services/image.service.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import { Image } from "../model/image";

export async function getAllImages(url: string): Promise<Image[]> {
return new Promise<Image[]>((resolve, reject) => {
fetch(url, {cache: "force-cache"})
.then((response) => {
if (!response.ok) {
reject(new Error("Failed to fetch images"));
} else {
resolve(response.json());
}
})
.catch((error) => reject(new Error(error)));
});
export async function getAllImages(): Promise<Image[]> {
const url = new URL("/api/images", window.location.origin);

const response = await fetch(url.toString(), {cache: "force-cache"});
if (!response.ok) return [];
return await response.json();
}
2 changes: 1 addition & 1 deletion src/store/image.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const useImageStore = create<ImageStore>((set, get) => ({
const { images } = get().state;
if (images) return images;

const fetchedImages = await getAllImages(process.env.imagesUrl ?? "");
const fetchedImages = await getAllImages();
set(state => ({ state: { ...state.state, images: fetchedImages } }));
return fetchedImages;
},
Expand Down
1 change: 1 addition & 0 deletions tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ module.exports = {
"800": "var(--colour-grey-800)",
"900": "var(--colour-grey-900)"
},
black: "#000000",
txt: {
DEFAULT: "var(--colour-text)",
muted: "var(--colour-text-muted)",
Expand Down
18 changes: 12 additions & 6 deletions tests/app/index.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,39 @@
import HomePage from "../../src/app/page";
import { expect } from "@jest/globals";
import { render, screen } from "@testing-library/react";
import useImageStore from "../../src/store/image.store";
import { act } from "react";

jest.mock("../../src/components/gallery/Gallery", () => () => <div />);

// mock fetch
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve([]),
} as Response)
);
) as jest.Mock;

process.env.imagesUrl = "http://localhost:3000/assets/test.json";
jest.mock("../../src/store/image.store", () => ({
__esModule: true,
default: jest.fn().mockReturnValue({
getImages: jest.fn().mockResolvedValue([]),
}),
}));

describe("Home Page", () => {
it("should render", async () => {
// When
render(await HomePage());
await act(() => render(<HomePage />));

// Then
expect(screen.getByRole("region")).toBeInTheDocument();
});

it("should fetch image config", async () => {
// When
render(await HomePage());
await act(() => render(<HomePage />));

// Then
expect(global.fetch).toHaveBeenCalledWith("http://localhost:3000/assets/test.json", { cache: "force-cache" });
expect(useImageStore).toHaveBeenCalled();
});
});

0 comments on commit a21e2f1

Please sign in to comment.