Remove unnecessary files
This commit is contained in:
		@ -1,41 +0,0 @@
 | 
			
		||||
import { Image } from "@/generated/prisma";
 | 
			
		||||
import clsx from "clsx";
 | 
			
		||||
import NextImage from "next/image";
 | 
			
		||||
import Link from "next/link";
 | 
			
		||||
 | 
			
		||||
export default function ImageList({ images, gallerySlug, albumSlug }: { images: Image[], gallerySlug: string, albumSlug: string }) {
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <section>
 | 
			
		||||
      <h1 className="text-2xl font-bold mb-4">Images</h1>
 | 
			
		||||
      <div className="grid gap-6 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4">
 | 
			
		||||
        {images ? images.map((img) => (
 | 
			
		||||
          <Link href={`/galleries/${gallerySlug}/${albumSlug}/${img.id}`} key={img.id}>
 | 
			
		||||
            <div className="group rounded-lg border overflow-hidden hover:shadow-lg transition-shadow bg-background">
 | 
			
		||||
              <div className="relative aspect-[4/3] w-full bg-muted items-center justify-center">
 | 
			
		||||
                {img.fileKey ? (
 | 
			
		||||
                  <NextImage
 | 
			
		||||
                    src={`/api/image/thumbnails/${img.fileKey}.webp`}
 | 
			
		||||
                    alt={img.imageName}
 | 
			
		||||
                    fill
 | 
			
		||||
                    className={clsx(
 | 
			
		||||
                      " object-cover",
 | 
			
		||||
                      img.nsfw && "blur-md scale-105"
 | 
			
		||||
                    )}
 | 
			
		||||
                  />
 | 
			
		||||
                ) : (
 | 
			
		||||
                  <div className="flex items-center justify-center h-full text-muted-foreground text-sm">
 | 
			
		||||
                    No cover image
 | 
			
		||||
                  </div>
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
              <div className="p-4">
 | 
			
		||||
                <h2 className="text-lg font-semibold truncate text-center">{img.imageName}</h2>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </Link>
 | 
			
		||||
        )) : "There are no images here!"}
 | 
			
		||||
      </div>
 | 
			
		||||
    </section>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -1,34 +0,0 @@
 | 
			
		||||
"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 (
 | 
			
		||||
    <div className="space-y-4">
 | 
			
		||||
      <button
 | 
			
		||||
        onClick={() => setShowNSFW(!showNSFW)}
 | 
			
		||||
        className="text-sm underline text-muted-foreground"
 | 
			
		||||
      >
 | 
			
		||||
        {showNSFW ? "Hide NSFW" : "Show NSFW"} content
 | 
			
		||||
      </button>
 | 
			
		||||
      <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4">
 | 
			
		||||
        {images.map((img) => (
 | 
			
		||||
          <ImageCard key={img.id} image={img} showNSFW={showNSFW} />
 | 
			
		||||
        ))}
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -1,45 +0,0 @@
 | 
			
		||||
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 (
 | 
			
		||||
    <Link href={href} className="group relative overflow-hidden rounded-lg border bg-background shadow transition hover:shadow-lg">
 | 
			
		||||
      <div className="aspect-[4/5] w-full relative">
 | 
			
		||||
        <NextImage
 | 
			
		||||
          src={`/api/image/${variant.s3Key}`}
 | 
			
		||||
          alt={image.altText || image.imageName}
 | 
			
		||||
          width={variant.width}
 | 
			
		||||
          height={variant.height}
 | 
			
		||||
          className={`object-cover h-full transition duration-200 ease-in-out ${shouldBlur ? "blur-md scale-105" : ""
 | 
			
		||||
            }`}
 | 
			
		||||
        />
 | 
			
		||||
        {image.nsfw && (
 | 
			
		||||
          <div className="absolute top-1 left-1 bg-black/60 text-white text-xs px-2 py-0.5 rounded z-10">
 | 
			
		||||
            NSFW
 | 
			
		||||
          </div>
 | 
			
		||||
        )}
 | 
			
		||||
        <div className="absolute bottom-0 left-0 w-full bg-black/50 text-white text-sm px-2 py-1 line-clamp-1 truncate">
 | 
			
		||||
          {image.imageName}
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </Link>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -1,47 +0,0 @@
 | 
			
		||||
import { Album, Image } from "@/generated/prisma";
 | 
			
		||||
import NextImage from "next/image";
 | 
			
		||||
import Link from "next/link";
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
  album: Album & {
 | 
			
		||||
    coverImage: Image | null;
 | 
			
		||||
  };
 | 
			
		||||
  gallerySlug: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function AlbumCard({ album, gallerySlug }: Props) {
 | 
			
		||||
  const href = `/galleries/${gallerySlug}/${album.slug}`;
 | 
			
		||||
  const cover = album.coverImage;
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Link
 | 
			
		||||
      href={href}
 | 
			
		||||
      className="group relative overflow-hidden rounded-lg border bg-background shadow transition hover:shadow-lg"
 | 
			
		||||
    >
 | 
			
		||||
      <div className="aspect-[4/3] w-full relative">
 | 
			
		||||
        {cover ? (
 | 
			
		||||
          <NextImage
 | 
			
		||||
            src={`/api/image/thumbnails/${cover.fileKey}.webp`}
 | 
			
		||||
            alt={cover.imageName}
 | 
			
		||||
            fill
 | 
			
		||||
            className="object-cover"
 | 
			
		||||
          />
 | 
			
		||||
        ) : (
 | 
			
		||||
          <div className="flex items-center justify-center h-full w-full bg-muted text-muted-foreground text-sm">
 | 
			
		||||
            No cover image
 | 
			
		||||
          </div>
 | 
			
		||||
        )}
 | 
			
		||||
 | 
			
		||||
        {/* Overlay: Album label */}
 | 
			
		||||
        <div className="absolute top-1 left-1 bg-black/60 text-white text-xs px-2 py-0.5 rounded z-10">
 | 
			
		||||
          Album
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        {/* Bottom: Album name */}
 | 
			
		||||
        <div className="absolute bottom-0 left-0 w-full bg-black/50 text-white text-sm px-2 py-1 line-clamp-1 truncate">
 | 
			
		||||
          {album.name}
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </Link>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -1,48 +0,0 @@
 | 
			
		||||
import { Album, Gallery, Image } from "@/generated/prisma";
 | 
			
		||||
import NextImage from "next/image";
 | 
			
		||||
import Link from "next/link";
 | 
			
		||||
 | 
			
		||||
type ImagesWithItems = (Pick<Image, "id" | "fileKey" | "imageName" | "altText" | "nsfw"> & {
 | 
			
		||||
  album?: (Pick<Album, "slug"> & {
 | 
			
		||||
    gallery?: Pick<Gallery, "slug"> | null
 | 
			
		||||
  }) | null
 | 
			
		||||
})[]
 | 
			
		||||
 | 
			
		||||
export default function CategoryImageList({ images }: { images: ImagesWithItems }) {
 | 
			
		||||
  return (
 | 
			
		||||
    <section>
 | 
			
		||||
      <h1 className="text-2xl font-bold mb-4">Images</h1>
 | 
			
		||||
      <div className="grid gap-6 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4">
 | 
			
		||||
        {images ? images.map((img) => {
 | 
			
		||||
          const gallerySlug = img.album?.gallery?.slug;
 | 
			
		||||
          const albumSlug = img.album?.slug;
 | 
			
		||||
          if (!gallerySlug || !albumSlug || !img.id) return null;
 | 
			
		||||
 | 
			
		||||
          return (
 | 
			
		||||
            <Link href={`/${gallerySlug}/${albumSlug}/${img.id}`} key={img.id}>
 | 
			
		||||
              <div className="group rounded-lg border overflow-hidden hover:shadow-lg transition-shadow bg-background">
 | 
			
		||||
                <div className="relative aspect-[4/3] w-full bg-muted items-center justify-center">
 | 
			
		||||
                  {img.fileKey ? (
 | 
			
		||||
                    <NextImage
 | 
			
		||||
                      src={`/api/image/thumbnails/${img.fileKey}.webp`}
 | 
			
		||||
                      alt={img.altText || ""}
 | 
			
		||||
                      fill
 | 
			
		||||
                      className="object-cover"
 | 
			
		||||
                    />
 | 
			
		||||
                  ) : (
 | 
			
		||||
                    <div className="flex items-center justify-center h-full text-muted-foreground text-sm">
 | 
			
		||||
                      No cover image
 | 
			
		||||
                    </div>
 | 
			
		||||
                  )}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className="p-4">
 | 
			
		||||
                  <h2 className="text-lg font-semibold truncate text-center">{img.imageName}</h2>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </Link>
 | 
			
		||||
          )
 | 
			
		||||
        }) : "There are no images here!"}
 | 
			
		||||
      </div>
 | 
			
		||||
    </section>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -1,40 +0,0 @@
 | 
			
		||||
import { Album, Image } from "@/generated/prisma";
 | 
			
		||||
import NextImage from "next/image";
 | 
			
		||||
import Link from "next/link";
 | 
			
		||||
 | 
			
		||||
type AlbumsWithItems = Album & {
 | 
			
		||||
  coverImage: Image | null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function AlbumList({ albums, gallerySlug }: { albums: AlbumsWithItems[], gallerySlug: string }) {
 | 
			
		||||
  return (
 | 
			
		||||
    <section>
 | 
			
		||||
      <h1 className="text-2xl font-bold mb-4">Albums</h1>
 | 
			
		||||
      <div className="grid gap-6 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4">
 | 
			
		||||
        {albums ? albums.map((album) => (
 | 
			
		||||
          <Link href={`/galleries/${gallerySlug}/${album.slug}`} key={album.id}>
 | 
			
		||||
            <div className="group rounded-lg border overflow-hidden hover:shadow-lg transition-shadow bg-background">
 | 
			
		||||
              <div className="relative aspect-[4/3] w-full bg-muted items-center justify-center">
 | 
			
		||||
                {album.coverImage?.fileKey ? (
 | 
			
		||||
                  <NextImage
 | 
			
		||||
                    src={`/api/image/thumbnails/${album.coverImage.fileKey}.webp`}
 | 
			
		||||
                    alt={album.coverImage.imageName}
 | 
			
		||||
                    fill
 | 
			
		||||
                    className="object-cover"
 | 
			
		||||
                  />
 | 
			
		||||
                ) : (
 | 
			
		||||
                  <div className="flex items-center justify-center h-full text-muted-foreground text-sm">
 | 
			
		||||
                    No cover image
 | 
			
		||||
                  </div>
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
              <div className="p-4">
 | 
			
		||||
                <h2 className="text-lg font-semibold truncate text-center">{album.name}</h2>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </Link>
 | 
			
		||||
        )) : "There are no albums here!"}
 | 
			
		||||
      </div>
 | 
			
		||||
    </section>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -1,81 +0,0 @@
 | 
			
		||||
"use client"
 | 
			
		||||
 | 
			
		||||
import { Color, ImageColor, ImageVariant } from "@/generated/prisma"
 | 
			
		||||
import clsx from "clsx"
 | 
			
		||||
import { useTheme } from "next-themes"
 | 
			
		||||
import NextImage from "next/image"
 | 
			
		||||
import { useEffect, useState } from "react"
 | 
			
		||||
 | 
			
		||||
type Colors = ImageColor & {
 | 
			
		||||
  color: Color
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
  alt: string,
 | 
			
		||||
  variant: ImageVariant,
 | 
			
		||||
  colors: Colors[],
 | 
			
		||||
  src: string,
 | 
			
		||||
  revealed?: boolean,
 | 
			
		||||
  className?: string,
 | 
			
		||||
  animate?: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function GlowingImageBorder({
 | 
			
		||||
  alt,
 | 
			
		||||
  variant,
 | 
			
		||||
  colors,
 | 
			
		||||
  src,
 | 
			
		||||
  className,
 | 
			
		||||
  revealed = true,
 | 
			
		||||
  animate = true,
 | 
			
		||||
}: Props) {
 | 
			
		||||
  const { resolvedTheme } = useTheme()
 | 
			
		||||
  const [mounted, setMounted] = useState(false);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setMounted(true);
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const getColor = (type: string) =>
 | 
			
		||||
    colors.find((c) => c.type === type)?.color.hex
 | 
			
		||||
 | 
			
		||||
  const vibrantLight = getColor("Vibrant") || "#ff5ec4"
 | 
			
		||||
  const mutedLight = getColor("Muted") || "#5ecaff"
 | 
			
		||||
 | 
			
		||||
  const darkVibrant = getColor("DarkVibrant") || "#fc03a1"
 | 
			
		||||
  const darkMuted = getColor("DarkMuted") || "#035efc"
 | 
			
		||||
 | 
			
		||||
  const vibrant = resolvedTheme === "dark" ? darkVibrant : vibrantLight
 | 
			
		||||
  const muted = resolvedTheme === "dark" ? darkMuted : mutedLight
 | 
			
		||||
 | 
			
		||||
  if (!mounted) return null;
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      className={clsx(
 | 
			
		||||
        "relative inline-block rounded-xl overflow-hidden p-[12px]",
 | 
			
		||||
        animate ? "glow-border" : "static-glow-border",
 | 
			
		||||
        className
 | 
			
		||||
      )}
 | 
			
		||||
      style={
 | 
			
		||||
        {
 | 
			
		||||
          "--vibrant": vibrant,
 | 
			
		||||
          "--muted": muted,
 | 
			
		||||
        } as React.CSSProperties
 | 
			
		||||
      }
 | 
			
		||||
    >
 | 
			
		||||
      <div className="relative z-10 rounded-xl overflow-hidden group">
 | 
			
		||||
        <NextImage
 | 
			
		||||
          src={src}
 | 
			
		||||
          alt={alt || "Image"}
 | 
			
		||||
          width={variant.width}
 | 
			
		||||
          height={variant.height}
 | 
			
		||||
          className={clsx(
 | 
			
		||||
            "rounded-xl transition duration-300",
 | 
			
		||||
            !revealed && "blur-md scale-105"
 | 
			
		||||
          )}
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
@ -1,56 +0,0 @@
 | 
			
		||||
"use client"
 | 
			
		||||
 | 
			
		||||
import { Color, ImageColor, ImageVariant } from "@/generated/prisma";
 | 
			
		||||
import Link from "next/link";
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
import { Label } from "../ui/label";
 | 
			
		||||
import { Switch } from "../ui/switch";
 | 
			
		||||
import GlowingImageBorder from "./GlowingImageBorder";
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
  variant: ImageVariant;
 | 
			
		||||
  colors: (ImageColor & { color: Color })[];
 | 
			
		||||
  alt: string;
 | 
			
		||||
  src: string;
 | 
			
		||||
  nsfw: boolean;
 | 
			
		||||
  imageId: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function GlowingImageWithToggle({ variant, colors, alt, src, nsfw, imageId }: Props) {
 | 
			
		||||
  const [animate, setAnimate] = useState(true);
 | 
			
		||||
  const [revealed, setRevealed] = useState(!nsfw)
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="relative w-full max-w-fit">
 | 
			
		||||
 | 
			
		||||
      <Link href={`/raw/${imageId}`} passHref>
 | 
			
		||||
        <GlowingImageBorder
 | 
			
		||||
          alt={alt}
 | 
			
		||||
          variant={variant}
 | 
			
		||||
          colors={colors}
 | 
			
		||||
          src={src}
 | 
			
		||||
          animate={animate}
 | 
			
		||||
          revealed={revealed}
 | 
			
		||||
        />
 | 
			
		||||
      </Link>
 | 
			
		||||
 | 
			
		||||
      <div className="flex flex-col items-center gap-4 pt-8">
 | 
			
		||||
        <div className="flex items-center gap-2">
 | 
			
		||||
          <Switch id="animate" checked={animate} onCheckedChange={setAnimate} />
 | 
			
		||||
          <Label htmlFor="animate">Animate glow</Label>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      {
 | 
			
		||||
        nsfw && (
 | 
			
		||||
          <div className="flex flex-col items-center gap-4 pt-8">
 | 
			
		||||
            <div className="flex items-center gap-2">
 | 
			
		||||
              <Switch id="animate" checked={revealed} onCheckedChange={setRevealed} />
 | 
			
		||||
              <Label htmlFor="animate">Reveal NSFW</Label>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
    </div >
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -1,100 +0,0 @@
 | 
			
		||||
import { Image as ImageType } from "@/generated/prisma";
 | 
			
		||||
import Link from "next/link";
 | 
			
		||||
// import { SocialIcon } from "react-social-icons";
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
  image: ImageType & {
 | 
			
		||||
    artist?: {
 | 
			
		||||
      id: string;
 | 
			
		||||
      slug: string;
 | 
			
		||||
      displayName: string;
 | 
			
		||||
      socials?: { type: string; handle: string; link: string }[];
 | 
			
		||||
    };
 | 
			
		||||
    categories?: { id: string; name: string }[];
 | 
			
		||||
    tags?: { id: string; name: string }[];
 | 
			
		||||
    album?: { id: string; name: string; slug: string };
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function ImageInfoPanel({ image }: Props) {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="w-full max-w-2xl mt-8 border border-border rounded-lg p-6 shadow-sm bg-background">
 | 
			
		||||
      {/* Creator */}
 | 
			
		||||
      {image.artist && (
 | 
			
		||||
        <div className="mb-4">
 | 
			
		||||
          <h2 className="text-lg font-semibold mb-1">Creator</h2>
 | 
			
		||||
          <Link
 | 
			
		||||
            href={`/artists/${image.artist.slug}`}
 | 
			
		||||
            className="text-primary hover:underline font-medium"
 | 
			
		||||
          >
 | 
			
		||||
            {image.artist.displayName}
 | 
			
		||||
          </Link>
 | 
			
		||||
 | 
			
		||||
          {image.artist.socials?.length > 0 && (
 | 
			
		||||
            <div className="flex gap-2 mt-2">
 | 
			
		||||
              {image.artist.socials.map((social, i) => (
 | 
			
		||||
                <SocialIcon
 | 
			
		||||
                  key={i}
 | 
			
		||||
                  url={social.link}
 | 
			
		||||
                  target="_blank"
 | 
			
		||||
                  rel="noopener noreferrer"
 | 
			
		||||
                  style={{ height: 28, width: 28 }}
 | 
			
		||||
                  title={social.type}
 | 
			
		||||
                />
 | 
			
		||||
              ))}
 | 
			
		||||
            </div>
 | 
			
		||||
          )}
 | 
			
		||||
        </div>
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      {/* Album */}
 | 
			
		||||
      {image.album && (
 | 
			
		||||
        <div className="mb-4">
 | 
			
		||||
          <h2 className="text-lg font-semibold mb-1">Album</h2>
 | 
			
		||||
          <Link
 | 
			
		||||
            href={`/galleries/${image.album.slug}`}
 | 
			
		||||
            className="text-primary hover:underline"
 | 
			
		||||
          >
 | 
			
		||||
            {image.album.name}
 | 
			
		||||
          </Link>
 | 
			
		||||
        </div>
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      {/* Categories */}
 | 
			
		||||
      {image.categories?.length > 0 && (
 | 
			
		||||
        <div className="mb-4">
 | 
			
		||||
          <h2 className="text-lg font-semibold mb-1">Categories</h2>
 | 
			
		||||
          <div className="flex flex-wrap gap-2">
 | 
			
		||||
            {image.categories.map((cat) => (
 | 
			
		||||
              <Link
 | 
			
		||||
                key={cat.id}
 | 
			
		||||
                href={`/categories/${cat.id}`}
 | 
			
		||||
                className="bg-muted text-sm px-2 py-1 rounded hover:bg-accent"
 | 
			
		||||
              >
 | 
			
		||||
                {cat.name}
 | 
			
		||||
              </Link>
 | 
			
		||||
            ))}
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      {/* Tags */}
 | 
			
		||||
      {image.tags?.length > 0 && (
 | 
			
		||||
        <div>
 | 
			
		||||
          <h2 className="text-lg font-semibold mb-1">Tags</h2>
 | 
			
		||||
          <div className="flex flex-wrap gap-2">
 | 
			
		||||
            {image.tags.map((tag) => (
 | 
			
		||||
              <Link
 | 
			
		||||
                key={tag.id}
 | 
			
		||||
                href={`/tags/${tag.id}`}
 | 
			
		||||
                className="bg-muted text-sm px-2 py-1 rounded hover:bg-accent"
 | 
			
		||||
              >
 | 
			
		||||
                #{tag.name}
 | 
			
		||||
              </Link>
 | 
			
		||||
            ))}
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      )}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user