Some refactor
This commit is contained in:
@ -18,10 +18,6 @@ RUN bunx prisma generate
|
|||||||
|
|
||||||
# Uncomment the following line in case you want to disable telemetry during the build.
|
# Uncomment the following line in case you want to disable telemetry during the build.
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
ARG DATABASE_URL
|
|
||||||
ENV DATABASE_URL=$DATABASE_URL
|
|
||||||
ARG BETTER_AUTH_SECRET
|
|
||||||
ENV BETTER_AUTH_SECRET=$BETTER_AUTH_SECRET
|
|
||||||
|
|
||||||
# Copy the rest of the code
|
# Copy the rest of the code
|
||||||
RUN bun run build -d
|
RUN bun run build -d
|
||||||
|
|||||||
22
src/actions/artworks/getArtworks.ts
Normal file
22
src/actions/artworks/getArtworks.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
"use server"
|
||||||
|
|
||||||
|
import { prisma } from "@/lib/prisma"
|
||||||
|
|
||||||
|
export async function getSingleArtwork(id: string) {
|
||||||
|
return await prisma.artwork.findUnique({
|
||||||
|
where: { id },
|
||||||
|
include: {
|
||||||
|
// album: true,
|
||||||
|
// type: true,
|
||||||
|
file: true,
|
||||||
|
gallery: true,
|
||||||
|
metadata: true,
|
||||||
|
albums: true,
|
||||||
|
categories: true,
|
||||||
|
colors: { include: { color: true } },
|
||||||
|
// sortContexts: true,
|
||||||
|
tags: true,
|
||||||
|
variants: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
16
src/actions/categories/getCategories.ts
Normal file
16
src/actions/categories/getCategories.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"use server"
|
||||||
|
|
||||||
|
import { prisma } from "@/lib/prisma"
|
||||||
|
|
||||||
|
export async function getCategoriesWithTags() {
|
||||||
|
return await prisma.artCategory.findMany({ include: { tags: true }, orderBy: { sortIndex: "asc" } })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCategoriesWithCount() {
|
||||||
|
return await prisma.artCategory.findMany({
|
||||||
|
include: {
|
||||||
|
_count: { select: { artworks: true, tags: true } },
|
||||||
|
},
|
||||||
|
orderBy: [{ sortIndex: "asc" }, { name: "asc" }],
|
||||||
|
})
|
||||||
|
}
|
||||||
7
src/actions/tags/getTags.ts
Normal file
7
src/actions/tags/getTags.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
"use server"
|
||||||
|
|
||||||
|
import { prisma } from "@/lib/prisma"
|
||||||
|
|
||||||
|
export async function getTags() {
|
||||||
|
return await prisma.artTag.findMany({ orderBy: { sortIndex: "asc" } })
|
||||||
|
}
|
||||||
@ -1,31 +1,18 @@
|
|||||||
|
import { getSingleArtwork } from "@/actions/artworks/getArtworks";
|
||||||
|
import { getCategoriesWithTags } from "@/actions/categories/getCategories";
|
||||||
|
import { getTags } from "@/actions/tags/getTags";
|
||||||
import ArtworkColors from "@/components/artworks/single/ArtworkColors";
|
import ArtworkColors from "@/components/artworks/single/ArtworkColors";
|
||||||
import ArtworkVariants from "@/components/artworks/single/ArtworkVariants";
|
import ArtworkVariants from "@/components/artworks/single/ArtworkVariants";
|
||||||
import DeleteArtworkButton from "@/components/artworks/single/DeleteArtworkButton";
|
import DeleteArtworkButton from "@/components/artworks/single/DeleteArtworkButton";
|
||||||
import EditArtworkForm from "@/components/artworks/single/EditArtworkForm";
|
import EditArtworkForm from "@/components/artworks/single/EditArtworkForm";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
|
|
||||||
export default async function ArtworkSinglePage({ params }: { params: { id: string } }) {
|
export default async function ArtworkSinglePage({ params }: { params: Promise<{ id: string }> }) {
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
|
|
||||||
const item = await prisma.artwork.findUnique({
|
const item = await getSingleArtwork(id);
|
||||||
where: { id },
|
|
||||||
include: {
|
|
||||||
// album: true,
|
|
||||||
// type: true,
|
|
||||||
file: true,
|
|
||||||
gallery: true,
|
|
||||||
metadata: true,
|
|
||||||
albums: true,
|
|
||||||
categories: true,
|
|
||||||
colors: { include: { color: true } },
|
|
||||||
// sortContexts: true,
|
|
||||||
tags: true,
|
|
||||||
variants: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const categories = await prisma.artCategory.findMany({ include: { tags: true }, orderBy: { sortIndex: "asc" } });
|
const categories = await getCategoriesWithTags();
|
||||||
const tags = await prisma.artTag.findMany({ orderBy: { sortIndex: "asc" } });
|
const tags = await getTags();
|
||||||
|
|
||||||
if (!item) return <div>Artwork with this id not found</div>
|
if (!item) return <div>Artwork with this id not found</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,11 @@
|
|||||||
|
import { getCategoriesWithCount } from "@/actions/categories/getCategories";
|
||||||
import CategoryTable from "@/components/categories/CategoryTable";
|
import CategoryTable from "@/components/categories/CategoryTable";
|
||||||
import { prisma } from "@/lib/prisma";
|
|
||||||
import { PlusCircleIcon } from "lucide-react";
|
import { PlusCircleIcon } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { Suspense } from "react";
|
||||||
|
|
||||||
export default async function CategoriesPage() {
|
export default async function CategoriesPage() {
|
||||||
const items = await prisma.artCategory.findMany({
|
const items = await getCategoriesWithCount();
|
||||||
include: {
|
|
||||||
_count: { select: { artworks: true, tags: true } },
|
|
||||||
},
|
|
||||||
orderBy: [{ sortIndex: "asc" }, { name: "asc" }],
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -19,11 +15,13 @@ export default async function CategoriesPage() {
|
|||||||
<PlusCircleIcon className="h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all text-primary-foreground" /> Add new category
|
<PlusCircleIcon className="h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all text-primary-foreground" /> Add new category
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
{items.length > 0 ? (
|
<Suspense fallback={<p>Loading...</p>}>
|
||||||
<CategoryTable categories={items} />
|
{items.length > 0 ? (
|
||||||
) : (
|
<CategoryTable categories={items} />
|
||||||
<p>There are no categories yet. Consider adding some!</p>
|
) : (
|
||||||
)}
|
<p>There are no categories yet. Consider adding some!</p>
|
||||||
|
)}
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
51
src/app/error.tsx
Normal file
51
src/app/error.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
export default function Error({
|
||||||
|
error,
|
||||||
|
reset,
|
||||||
|
}: {
|
||||||
|
error: Error & { digest?: string };
|
||||||
|
reset: () => void;
|
||||||
|
}) {
|
||||||
|
useEffect(() => {
|
||||||
|
console.error(error);
|
||||||
|
}, [error]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="min-h-dvh flex items-center justify-center px-6">
|
||||||
|
<div className="max-w-lg text-center space-y-6">
|
||||||
|
<h1 className="text-2xl font-semibold tracking-tight">
|
||||||
|
Something went wrong
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
An unexpected error occurred while loading this section.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex justify-center gap-4 pt-2">
|
||||||
|
<button
|
||||||
|
onClick={reset}
|
||||||
|
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition"
|
||||||
|
>
|
||||||
|
Try again
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="/"
|
||||||
|
className="rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted transition"
|
||||||
|
>
|
||||||
|
Go home
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{process.env.NODE_ENV === "development" && (
|
||||||
|
<pre className="mt-6 rounded-md bg-muted p-4 text-left text-xs overflow-auto">
|
||||||
|
{error.message}
|
||||||
|
</pre>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
38
src/app/global-error.tsx
Normal file
38
src/app/global-error.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
export default function GlobalError({
|
||||||
|
error,
|
||||||
|
reset,
|
||||||
|
}: {
|
||||||
|
error: Error & { digest?: string };
|
||||||
|
reset: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body className="min-h-dvh flex items-center justify-center bg-background px-6">
|
||||||
|
<div className="max-w-lg text-center space-y-6">
|
||||||
|
<h1 className="text-3xl font-semibold tracking-tight">
|
||||||
|
Application error
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
A critical error occurred. Please reload the application.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={reset}
|
||||||
|
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition"
|
||||||
|
>
|
||||||
|
Reload
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{process.env.NODE_ENV === "development" && (
|
||||||
|
<pre className="mt-6 rounded-md bg-muted p-4 text-left text-xs overflow-auto">
|
||||||
|
{error.message}
|
||||||
|
</pre>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,3 +1,6 @@
|
|||||||
|
export const dynamic = "force-dynamic";
|
||||||
|
export const revalidate = 0;
|
||||||
|
|
||||||
import { ThemeProvider } from "@/components/global/ThemeProvider";
|
import { ThemeProvider } from "@/components/global/ThemeProvider";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Geist, Geist_Mono } from "next/font/google";
|
import { Geist, Geist_Mono } from "next/font/google";
|
||||||
|
|||||||
17
src/app/loading.tsx
Normal file
17
src/app/loading.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
export default function Loading() {
|
||||||
|
return (
|
||||||
|
<main className="min-h-dvh flex items-center justify-center">
|
||||||
|
<div className="flex flex-col items-center gap-6">
|
||||||
|
{/* Spinner */}
|
||||||
|
<div className="relative h-14 w-14">
|
||||||
|
<div className="absolute inset-0 rounded-full border-4 border-muted opacity-25" />
|
||||||
|
<div className="absolute inset-0 rounded-full border-4 border-t-primary animate-spin" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-sm text-muted-foreground tracking-wide">
|
||||||
|
Loading content...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
31
src/app/not-found.tsx
Normal file
31
src/app/not-found.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
export default function NotFound() {
|
||||||
|
return (
|
||||||
|
<main className="min-h-dvh flex items-center justify-center px-6">
|
||||||
|
<div className="max-w-xl text-center space-y-6">
|
||||||
|
{/* Placeholder artwork */}
|
||||||
|
<div className="mx-auto h-48 w-48 rounded-xl bg-linear-to-br from-muted to-muted/30 flex items-center justify-center">
|
||||||
|
<span className="text-6xl font-serif text-muted-foreground">404</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 className="text-2xl font-semibold tracking-tight">
|
||||||
|
This page does not exist
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
The item you are looking for may have been moved, renamed, or never existed.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex justify-center gap-4 pt-2">
|
||||||
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="inline-flex items-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition"
|
||||||
|
>
|
||||||
|
Go home
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user