From 2e1161b50b921cc7a6427e14f3c6d29d955e4f45 Mon Sep 17 00:00:00 2001 From: Citali Date: Sun, 29 Jun 2025 14:22:26 +0200 Subject: [PATCH] Add artists page --- prisma/schema.prisma | 46 ++++++++++++--- .../(normal)/artists/[artistSlug]/page.tsx | 40 +++++++++++++ .../[albumSlug]/[imageId]/page.tsx | 4 +- .../[gallerySlug]/[albumSlug]/page.tsx | 0 .../{ => galleries}/[gallerySlug]/page.tsx | 0 src/app/(normal)/layout.tsx | 58 +++++-------------- src/app/(raw)/layout.tsx | 39 ++----------- src/app/(raw)/raw/[imageId]/page.tsx | 2 +- src/app/error.tsx | 31 ++++++++++ src/app/layout.tsx | 42 ++++++++++++++ src/app/loading.tsx | 10 ++++ src/app/not-found.tsx | 18 ++++++ src/components/albums/ImageList.tsx | 2 +- src/components/artists/ArtistImageGrid.tsx | 34 +++++++++++ src/components/artists/ArtistInfoBox.tsx | 40 +++++++++++++ src/components/artists/ImageCard.tsx | 45 ++++++++++++++ src/components/galleries/AlbumList.tsx | 2 +- src/components/home/GalleryList.tsx | 2 +- 18 files changed, 321 insertions(+), 94 deletions(-) create mode 100644 src/app/(normal)/artists/[artistSlug]/page.tsx rename src/app/(normal)/{ => galleries}/[gallerySlug]/[albumSlug]/[imageId]/page.tsx (98%) rename src/app/(normal)/{ => galleries}/[gallerySlug]/[albumSlug]/page.tsx (100%) rename src/app/(normal)/{ => galleries}/[gallerySlug]/page.tsx (100%) create mode 100644 src/app/error.tsx create mode 100644 src/app/layout.tsx create mode 100644 src/app/loading.tsx create mode 100644 src/app/not-found.tsx create mode 100644 src/components/artists/ArtistImageGrid.tsx create mode 100644 src/components/artists/ArtistInfoBox.tsx create mode 100644 src/components/artists/ImageCard.tsx diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ab6a8a1..968d44f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -58,7 +58,9 @@ model Artist { slug String @unique displayName String - nickname String? + nickname String? + description String? + source String? socials Social[] images Image[] @@ -71,7 +73,8 @@ model Social { handle String platform String - isPrimary Boolean + isPrimary Boolean @default(false) + isVisible Boolean @default(true) link String? @@ -136,11 +139,14 @@ model Image { extractColors ImageExtractColor[] palettes ImagePalette[] variants ImageVariant[] - - albumCover Album[] @relation("AlbumCoverImage") - galleryCover Gallery[] @relation("GalleryCoverImage") - categories Category[] @relation("ImageCategories") - tags Tag[] @relation("ImageTags") + // pixels PixelSummary[] + // theme ThemeSeed[] + albumCover Album[] @relation("AlbumCoverImage") + galleryCover Gallery[] @relation("GalleryCoverImage") + categories Category[] @relation("ImageCategories") + // colors ImageColor[] @relation("ImageToImageColor") + tags Tag[] @relation("ImageTags") + // palettes ColorPalette[] @relation("ImagePalettes") } model ImageMetadata { @@ -214,6 +220,7 @@ model ColorPalette { items ColorPaletteItem[] images ImagePalette[] + // images Image[] @relation("ImagePalettes") } model ColorPaletteItem { @@ -243,6 +250,7 @@ model ExtractColor { hue Float? saturation Float? + // images Image[] @relation("ImageToExtractColor") images ImageExtractColor[] } @@ -262,6 +270,30 @@ model Color { images ImageColor[] } +// model ThemeSeed { +// id String @id @default(cuid()) +// createdAt DateTime @default(now()) +// updatedAt DateTime @updatedAt + +// imageId String +// seedHex String + +// image Image @relation(fields: [imageId], references: [id]) +// } + +// model PixelSummary { +// id String @id @default(cuid()) +// createdAt DateTime @default(now()) +// updatedAt DateTime @updatedAt + +// imageId String +// channels Int +// height Int +// width Int + +// image Image @relation(fields: [imageId], references: [id]) +// } + model ImagePalette { id String @id @default(cuid()) createdAt DateTime @default(now()) diff --git a/src/app/(normal)/artists/[artistSlug]/page.tsx b/src/app/(normal)/artists/[artistSlug]/page.tsx new file mode 100644 index 0000000..01687c6 --- /dev/null +++ b/src/app/(normal)/artists/[artistSlug]/page.tsx @@ -0,0 +1,40 @@ +import ArtistImageGrid from "@/components/artists/ArtistImageGrid"; +import ArtistInfoBox from "@/components/artists/ArtistInfoBox"; +import prisma from "@/lib/prisma"; + +export default async function ArtistPage({ params }: { params: { artistSlug: string } }) { + const { artistSlug } = await params; + + const artist = await prisma.artist.findUnique({ + where: { + slug: artistSlug + }, + include: { + images: { + include: { + variants: true, + album: { include: { gallery: true } } + } + }, + socials: { where: { isVisible: true }, orderBy: { isPrimary: "desc" } } + } + }) + + if (!artist) { + throw new Error("Artist not found") + } + + return ( +
+
+

Artist: {artist.nickname ? artist.nickname : artist.displayName}

+

+ {artist.description ? artist.description : ""} +

+
+ +

Images

+ +
+ ); +} \ No newline at end of file diff --git a/src/app/(normal)/[gallerySlug]/[albumSlug]/[imageId]/page.tsx b/src/app/(normal)/galleries/[gallerySlug]/[albumSlug]/[imageId]/page.tsx similarity index 98% rename from src/app/(normal)/[gallerySlug]/[albumSlug]/[imageId]/page.tsx rename to src/app/(normal)/galleries/[gallerySlug]/[albumSlug]/[imageId]/page.tsx index bc249be..174597a 100644 --- a/src/app/(normal)/[gallerySlug]/[albumSlug]/[imageId]/page.tsx +++ b/src/app/(normal)/galleries/[gallerySlug]/[albumSlug]/[imageId]/page.tsx @@ -35,7 +35,6 @@ export default async function ImagePage({ params }: { params: { gallerySlug: str

{image.imageName}

- {resizedVariant && - }
{image.artist && } -
+

Image Metadata

Name: {image.imageName}

diff --git a/src/app/(normal)/[gallerySlug]/[albumSlug]/page.tsx b/src/app/(normal)/galleries/[gallerySlug]/[albumSlug]/page.tsx similarity index 100% rename from src/app/(normal)/[gallerySlug]/[albumSlug]/page.tsx rename to src/app/(normal)/galleries/[gallerySlug]/[albumSlug]/page.tsx diff --git a/src/app/(normal)/[gallerySlug]/page.tsx b/src/app/(normal)/galleries/[gallerySlug]/page.tsx similarity index 100% rename from src/app/(normal)/[gallerySlug]/page.tsx rename to src/app/(normal)/galleries/[gallerySlug]/page.tsx diff --git a/src/app/(normal)/layout.tsx b/src/app/(normal)/layout.tsx index 41dd554..46cec16 100644 --- a/src/app/(normal)/layout.tsx +++ b/src/app/(normal)/layout.tsx @@ -1,56 +1,24 @@ import Footer from "@/components/global/Footer"; import Header from "@/components/global/Header"; -import { ThemeProvider } from "@/components/global/ThemeProvider"; -import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; import { Toaster } from "sonner"; -import "../globals.css"; -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); - -export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", -}; - -export default async function RootLayout({ +export default async function NormalLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( - - - -
-
-
-
-
- {children} -
-
-
-
- -
-
- - +
+
+
+
+
+ {children} +
+
+
+
+ +
); } diff --git a/src/app/(raw)/layout.tsx b/src/app/(raw)/layout.tsx index ec01cc0..6634184 100644 --- a/src/app/(raw)/layout.tsx +++ b/src/app/(raw)/layout.tsx @@ -1,42 +1,11 @@ -import { ThemeProvider } from "@/components/global/ThemeProvider"; -import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; -import "../globals.css"; - -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); - -export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", -}; - -export default async function RootLayout({ +export default async function RawLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( - - - - {children} - - - + <> + {children} + ); } diff --git a/src/app/(raw)/raw/[imageId]/page.tsx b/src/app/(raw)/raw/[imageId]/page.tsx index 99ec9fc..e6fcdea 100644 --- a/src/app/(raw)/raw/[imageId]/page.tsx +++ b/src/app/(raw)/raw/[imageId]/page.tsx @@ -31,7 +31,7 @@ export default async function RawImagePage({ params }: { params: { imageId: stri ? { backgroundImage: `linear-gradient(to bottom, ${hexColors.join(", ")})` } : { backgroundColor: "#0f0f0f" }; - const targetHref = `/${image.album?.gallery?.slug}/${image.album?.slug}/${image.id}`; + const targetHref = `/galleries/${image.album?.gallery?.slug}/${image.album?.slug}/${image.id}`; return (
void +}) { + useEffect(() => { + // Log the error to an error reporting service + console.error(error) + }, [error]) + + return ( +
+

Something went wrong

+

+ Please try refreshing the page or contact support if the problem persists. +

+ +
+ ) +} \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..92ab4ed --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,42 @@ +import { ThemeProvider } from "@/components/global/ThemeProvider"; +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import "./globals.css"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; + +export default async function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + + {children} + + + + ); +} diff --git a/src/app/loading.tsx b/src/app/loading.tsx new file mode 100644 index 0000000..f314377 --- /dev/null +++ b/src/app/loading.tsx @@ -0,0 +1,10 @@ +export default function Loading() { + return ( +
+
+
+

Loading content...

+
+
+ ); +} \ No newline at end of file diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx new file mode 100644 index 0000000..46f48fe --- /dev/null +++ b/src/app/not-found.tsx @@ -0,0 +1,18 @@ +import Link from "next/link"; + +export default function NotFound() { + return ( +
+

Page Not Found

+

+ Sorry, we couldn't find the page you were looking for. +

+ + Go back home + +
+ ); +} \ No newline at end of file diff --git a/src/components/albums/ImageList.tsx b/src/components/albums/ImageList.tsx index 48769fc..7585898 100644 --- a/src/components/albums/ImageList.tsx +++ b/src/components/albums/ImageList.tsx @@ -10,7 +10,7 @@ export default function ImageList({ images, gallerySlug, albumSlug }: { images:

Images

{images ? images.map((img) => ( - +
{img.fileKey ? ( diff --git a/src/components/artists/ArtistImageGrid.tsx b/src/components/artists/ArtistImageGrid.tsx new file mode 100644 index 0000000..3366894 --- /dev/null +++ b/src/components/artists/ArtistImageGrid.tsx @@ -0,0 +1,34 @@ +"use client"; + +// import ImageCard from "@/components/image/ImageCard"; // You likely have this already +// import type { Album, Gallery, Image, ImageVariant } from "@prisma/client"; +import { Album, Gallery, Image, ImageVariant } from "@/generated/prisma"; +import { useState } from "react"; +import ImageCard from "./ImageCard"; + +type Props = { + images: (Image & { + album: Album & { gallery: Gallery | null } | null; + variants: ImageVariant[]; + })[]; +}; + +export default function ArtistImageGrid({ images }: Props) { + const [showNSFW, setShowNSFW] = useState(false); + + return ( +
+ +
+ {images.map((img) => ( + + ))} +
+
+ ); +} diff --git a/src/components/artists/ArtistInfoBox.tsx b/src/components/artists/ArtistInfoBox.tsx new file mode 100644 index 0000000..2170e08 --- /dev/null +++ b/src/components/artists/ArtistInfoBox.tsx @@ -0,0 +1,40 @@ +import { Artist } from "@/generated/prisma"; +import { getSocialIcon } from "@/utils/socialIconMap"; +import { LinkIcon } from "lucide-react"; + +type Props = { + artist: Artist & { + socials: { + id: string; + handle: string; + platform: string; + link: string | null; + isPrimary: boolean; + }[]; + }; +}; + +export default function ArtistInfoBox({ artist }: Props) { + return ( +
+

Socials / Links

+
+ {artist.socials.map((social) => { + const Icon = getSocialIcon(social.platform) ?? LinkIcon; + return ( + + ); + })} +
+
+ ); +} diff --git a/src/components/artists/ImageCard.tsx b/src/components/artists/ImageCard.tsx new file mode 100644 index 0000000..0cae72b --- /dev/null +++ b/src/components/artists/ImageCard.tsx @@ -0,0 +1,45 @@ +import { Album, Gallery, Image, ImageVariant } from "@/generated/prisma"; +import NextImage from "next/image"; +import Link from "next/link"; + +type Props = { + image: Image & { + variants: ImageVariant[]; + album: Album & { gallery: Gallery | null } | null; + }; + showNSFW?: boolean; +}; + +export default function ImageCard({ image, showNSFW = false }: Props) { + const variant = image.variants.find(v => v.type === "thumbnail") || image.variants[0]; + if (!variant) return null; + + const href = image.album?.gallery && image.album + ? `/galleries/${image.album.gallery.slug}/${image.album.slug}/${image.id}` + : `/image/${image.id}`; + + const shouldBlur = image.nsfw && !showNSFW; + + return ( + +
+ + {image.nsfw && ( +
+ NSFW +
+ )} +
+ {image.imageName} +
+
+ + ); +} diff --git a/src/components/galleries/AlbumList.tsx b/src/components/galleries/AlbumList.tsx index 1e1a252..8e3a82b 100644 --- a/src/components/galleries/AlbumList.tsx +++ b/src/components/galleries/AlbumList.tsx @@ -12,7 +12,7 @@ export default function AlbumList({ albums, gallerySlug }: { albums: AlbumsWithI

Albums

{albums ? albums.map((album) => ( - +
{album.coverImage?.fileKey ? ( diff --git a/src/components/home/GalleryList.tsx b/src/components/home/GalleryList.tsx index 6cbfe3f..d30c8cc 100644 --- a/src/components/home/GalleryList.tsx +++ b/src/components/home/GalleryList.tsx @@ -14,7 +14,7 @@ export default async function GalleryList() {

Galleries

{galleries ? galleries.map((gallery) => ( - +
{gallery.coverImage?.fileKey ? (