Skip to content

Commit

Permalink
Merge pull request #109 from yummy-recipes/implement-gallery
Browse files Browse the repository at this point in the history
Implement gallery
  • Loading branch information
ertrzyiks authored Jan 19, 2025
2 parents 7af9413 + 011975b commit 837b613
Show file tree
Hide file tree
Showing 11 changed files with 244 additions and 51 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- CreateTable
CREATE TABLE "RecipeGalleryImage" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"recipeId" INTEGER NOT NULL,
"imageUrl" TEXT NOT NULL,
"blurDataUrl" TEXT,
"position" INTEGER NOT NULL,
CONSTRAINT "RecipeGalleryImage_recipeId_fkey" FOREIGN KEY ("recipeId") REFERENCES "Recipe" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
2 changes: 1 addition & 1 deletion prisma/migrations/migration_lock.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
# It should be added in your version-control system (e.g., Git)
provider = "sqlite"
10 changes: 10 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ model Recipe {
coverImage String
coverImageBlurDataUrl String?
title String
galleryImages RecipeGalleryImage[]
headline String
preparationTime Int
category Category @relation(fields: [categoryId], references: [id])
Expand All @@ -53,3 +54,12 @@ model Recipe {
instructions RecipeInstructionBlock[]
publishedAt DateTime
}

model RecipeGalleryImage {
id Int @id @default(autoincrement())
recipeId Int
recipe Recipe @relation(fields: [recipeId], references: [id])
imageUrl String
blurDataUrl String?
position Int
}
35 changes: 35 additions & 0 deletions scripts/import.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,34 @@ async function createRecipeBlocks({ recipeId, ingredients, instructions }) {
});
}
}

async function createRecipeGalleryImages({ recipeId, gallery }) {
let position = 0;

for (const image of gallery) {
await prisma.recipeGalleryImage.create({
data: {
imageUrl: image.url,
blurDataUrl: image.placeholderBlurDataUrl,
recipe: {
connect: {
id: recipeId,
},
},
position: position++,
},
});
}
}

async function createOrUpdateRecipes({
title,
slug,
category,
coverImage,
coverImageBlurDataUrl,
headline,
gallery,
preparationTime,
publishedAt,
ingredients,
Expand All @@ -125,6 +146,12 @@ async function createOrUpdateRecipes({
},
});

await prisma.recipeGalleryImage.deleteMany({
where: {
recipeId: recipe.id,
},
});

await prisma.recipe.update({
where: {
id: recipe.id,
Expand Down Expand Up @@ -153,6 +180,7 @@ async function createOrUpdateRecipes({
ingredients,
instructions,
});
await createRecipeGalleryImages({ recipeId: recipe.id, gallery });
return;
}

Expand All @@ -177,6 +205,7 @@ async function createOrUpdateRecipes({
});

await createRecipeBlocks({ recipeId: result.id, ingredients, instructions });
await createRecipeGalleryImages({ recipeId: result.id, gallery });
}

function loadCategories() {
Expand Down Expand Up @@ -223,6 +252,11 @@ function loadRecipes(after = null, limit) {
id
content
}
gallery {
id
url
placeholderBlurDataUrl
}
}
}
`,
Expand Down Expand Up @@ -283,6 +317,7 @@ async function main() {
category: { id: categoryMap[recipe.category.slug].id },
ingredients: recipe.ingredients,
instructions: recipe.instructions,
gallery: recipe.gallery,
publishedAt: recipe.publishedAt,
tags: dbTags,
});
Expand Down
27 changes: 27 additions & 0 deletions src/app/[categorySlug]/[recipeSlug]/[imageId]/dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use client";

import { Dialog, DialogPanel } from "@headlessui/react";
import { redirect } from "next/navigation";

interface Props {
categorySlug: string;
recipeSlug: string;
children: React.ReactNode;
}
export function GalleryDialog({ categorySlug, recipeSlug, children }: Props) {
const handleClose = () => {
redirect(`/${categorySlug}/${recipeSlug}`);
};

return (
<Dialog open onClose={handleClose} className="relative z-50">
{/* The backdrop, rendered as a fixed sibling to the panel container */}
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />
<div className="fixed inset-0 flex w-screen items-center justify-center p-4">
<DialogPanel className="w-full max-w-sm rounded bg-white">
{children}
</DialogPanel>
</div>
</Dialog>
);
}
60 changes: 60 additions & 0 deletions src/app/[categorySlug]/[recipeSlug]/[imageId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { prisma } from "@/data";
import Image from "next/image";
import { notFound } from "next/navigation";
import { GalleryDialog } from "./dialog";

interface Params {
categorySlug: string;
recipeSlug: string;
imageId: string;
}

interface Props {
params: Promise<Params>;
}
export default async function Page({ params }: Props) {
const { categorySlug, recipeSlug, imageId } = await params;

const galleryImage = await prisma.recipeGalleryImage.findUnique({
where: {
id: parseInt(imageId),
},
});

if (!galleryImage) {
return notFound();
}

return (
<GalleryDialog categorySlug={categorySlug} recipeSlug={recipeSlug}>
<Image
className="w-full object-cover"
src={galleryImage.imageUrl}
width={600}
height={400}
alt={`Zdjęcie`}
/>
</GalleryDialog>
);
}

export const dynamicParams = false;

export async function generateStaticParams() {
const recipes = await prisma.recipe.findMany({
include: {
category: true,
galleryImages: true,
},
});

return (
recipes.flatMap((recipe) =>
recipe.galleryImages?.map((galleryImage) => ({
categorySlug: recipe.category.slug,
recipeSlug: recipe.slug,
imageId: galleryImage.id.toString(),
})),
) ?? []
);
}
58 changes: 58 additions & 0 deletions src/app/[categorySlug]/[recipeSlug]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { notFound } from "next/navigation";
import { prisma } from "@/data";
import { Recipe } from "@/components/recipe/recipe";

interface Params {
categorySlug: string;
recipeSlug: string;
}

interface Props {
params: Promise<Params>;
children: React.ReactNode;
}

export default async function Layout({ params, children }: Props) {
const { categorySlug, recipeSlug } = await params;
const category = await prisma.category.findUnique({
where: {
slug: categorySlug,
},
});

if (!category) {
return notFound();
}

const recipe = await prisma.recipe.findUnique({
where: {
slug: recipeSlug,
},
include: {
category: true,
instructions: true,
ingredients: true,
galleryImages: true,
},
});

if (!recipe) {
return notFound();
}

return (
<div className="max-w-screen-xl mx-auto">
<Recipe
title={recipe.title}
coverImage={recipe.coverImage}
coverImageBlurDataUrl={recipe.coverImageBlurDataUrl}
galleryImages={recipe.galleryImages}
slug={recipe.slug}
categorySlug={recipe.category.slug}
ingredients={recipe.ingredients}
instructions={recipe.instructions}
/>
{children}
</div>
);
}
50 changes: 2 additions & 48 deletions src/app/[categorySlug]/[recipeSlug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,7 @@
import { notFound } from "next/navigation";
import { prisma } from "@/data";
import { Recipe } from "@/components/recipe/recipe";

interface Params {
categorySlug: string;
recipeSlug: string;
}

interface Props {
params: Promise<Params>;
}

export default async function Page({ params }: Props) {
const { categorySlug, recipeSlug } = await params;
const category = await prisma.category.findUnique({
where: {
slug: categorySlug,
},
});

if (!category) {
return notFound();
}

const recipe = await prisma.recipe.findUnique({
where: {
slug: recipeSlug,
},
include: {
instructions: true,
ingredients: true,
},
});

if (!recipe) {
return notFound();
}

return (
<div className="max-w-screen-xl mx-auto">
<Recipe
title={recipe.title}
coverImage={recipe.coverImage}
coverImageBlurDataUrl={recipe.coverImageBlurDataUrl}
ingredients={recipe.ingredients}
instructions={recipe.instructions}
/>
</div>
);
export default function Page() {
return null;
}

export const dynamicParams = false;
Expand Down
11 changes: 11 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@
--background-end-rgb: 255, 255, 255;
}

::backdrop {
background-image: linear-gradient(
45deg,
magenta,
rebeccapurple,
dodgerblue,
green
);
opacity: 0.75;
}

@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
Expand Down
27 changes: 27 additions & 0 deletions src/components/recipe/recipe.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
import Image from "next/image";
import { markdownToHtml } from "@/lib/markdown";
import Link from "next/link";

interface Props {
slug: string;
categorySlug: string;
title: string;
coverImage: string;
coverImageBlurDataUrl: string | null;
ingredients: { id: number; content: string }[];
instructions: { id: number; content: string }[];
galleryImages: { id: number; imageUrl: string; blurDataUrl: string | null }[];
}

export async function Recipe({
slug,
categorySlug,
title,
coverImage,
coverImageBlurDataUrl,
ingredients,
instructions,
galleryImages,
}: Props) {
return (
<div className="flex flex-col w-full">
Expand Down Expand Up @@ -58,6 +65,26 @@ export async function Recipe({
))}
</div>
</div>

{galleryImages && (
<div>
{galleryImages.map((galleryImage) => (
<Link
href={`/${categorySlug}/${slug}/${galleryImage.id}`}
key={galleryImage.id}
className="mb-4"
>
<Image
className="w-12 object-cover"
src={galleryImage.imageUrl}
width={300}
height={200}
alt={`Zdjęcie ${title}`}
/>
</Link>
))}
</div>
)}
</div>
);
}
Loading

0 comments on commit 837b613

Please sign in to comment.