Add basic CRUD for artists
This commit is contained in:
		
							
								
								
									
										32
									
								
								prisma/migrations/20250625102620_artist_social/migration.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								prisma/migrations/20250625102620_artist_social/migration.sql
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
			
		||||
-- CreateTable
 | 
			
		||||
CREATE TABLE "Artist" (
 | 
			
		||||
    "id" TEXT NOT NULL,
 | 
			
		||||
    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
 | 
			
		||||
    "updatedAt" TIMESTAMP(3) NOT NULL,
 | 
			
		||||
    "displayName" TEXT NOT NULL,
 | 
			
		||||
    "slug" TEXT NOT NULL,
 | 
			
		||||
    "nickname" TEXT,
 | 
			
		||||
 | 
			
		||||
    CONSTRAINT "Artist_pkey" PRIMARY KEY ("id")
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
-- CreateTable
 | 
			
		||||
CREATE TABLE "Social" (
 | 
			
		||||
    "id" TEXT NOT NULL,
 | 
			
		||||
    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
 | 
			
		||||
    "updatedAt" TIMESTAMP(3) NOT NULL,
 | 
			
		||||
    "name" TEXT,
 | 
			
		||||
    "handle" TEXT,
 | 
			
		||||
    "url" TEXT,
 | 
			
		||||
    "type" TEXT,
 | 
			
		||||
    "icon" TEXT,
 | 
			
		||||
    "artistId" TEXT,
 | 
			
		||||
 | 
			
		||||
    CONSTRAINT "Social_pkey" PRIMARY KEY ("id")
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
-- CreateIndex
 | 
			
		||||
CREATE UNIQUE INDEX "Artist_slug_key" ON "Artist"("slug");
 | 
			
		||||
 | 
			
		||||
-- AddForeignKey
 | 
			
		||||
ALTER TABLE "Social" ADD CONSTRAINT "Social_artistId_fkey" FOREIGN KEY ("artistId") REFERENCES "Artist"("id") ON DELETE SET NULL ON UPDATE CASCADE;
 | 
			
		||||
@ -47,3 +47,35 @@ model Album {
 | 
			
		||||
 | 
			
		||||
  // images Image[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
model Artist {
 | 
			
		||||
  id        String   @id @default(cuid())
 | 
			
		||||
  createdAt DateTime @default(now())
 | 
			
		||||
  updatedAt DateTime @updatedAt
 | 
			
		||||
 | 
			
		||||
  displayName String
 | 
			
		||||
  slug        String @unique
 | 
			
		||||
 | 
			
		||||
  nickname String?
 | 
			
		||||
  // mentionUrl String?
 | 
			
		||||
  // contact    String?
 | 
			
		||||
  // urls       String[]
 | 
			
		||||
 | 
			
		||||
  socials Social[]
 | 
			
		||||
  // images  Image[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
model Social {
 | 
			
		||||
  id        String   @id @default(cuid())
 | 
			
		||||
  createdAt DateTime @default(now())
 | 
			
		||||
  updatedAt DateTime @updatedAt
 | 
			
		||||
 | 
			
		||||
  name   String?
 | 
			
		||||
  handle String?
 | 
			
		||||
  url    String?
 | 
			
		||||
  type   String?
 | 
			
		||||
  icon   String?
 | 
			
		||||
 | 
			
		||||
  artistId String?
 | 
			
		||||
  artist   Artist? @relation(fields: [artistId], references: [id])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -3,5 +3,5 @@
 | 
			
		||||
import prisma from "@/lib/prisma";
 | 
			
		||||
 | 
			
		||||
export async function deleteAlbum(id: string) {
 | 
			
		||||
  await prisma.gallery.delete({ where: { id } });
 | 
			
		||||
  await prisma.album.delete({ where: { id } });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								src/actions/artists/createArtist.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/actions/artists/createArtist.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
			
		||||
"use server"
 | 
			
		||||
 | 
			
		||||
import prisma from "@/lib/prisma";
 | 
			
		||||
import { artistSchema } from "@/schemas/artists/artistSchema";
 | 
			
		||||
import * as z from "zod/v4";
 | 
			
		||||
 | 
			
		||||
export async function createArtist(values: z.infer<typeof artistSchema>) {
 | 
			
		||||
  return await prisma.artist.create({
 | 
			
		||||
    data: {
 | 
			
		||||
      displayName: values.displayName,
 | 
			
		||||
      slug: values.slug,
 | 
			
		||||
      nickname: values.nickname
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								src/actions/artists/deleteArtist.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/actions/artists/deleteArtist.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
"use server";
 | 
			
		||||
 | 
			
		||||
import prisma from "@/lib/prisma";
 | 
			
		||||
 | 
			
		||||
export async function deleteArtist(id: string) {
 | 
			
		||||
  await prisma.artist.delete({ where: { id } });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								src/actions/artists/updateArtist.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/actions/artists/updateArtist.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
"use server"
 | 
			
		||||
 | 
			
		||||
import prisma from "@/lib/prisma";
 | 
			
		||||
import { artistSchema } from "@/schemas/artists/artistSchema";
 | 
			
		||||
import * as z from "zod/v4";
 | 
			
		||||
 | 
			
		||||
export async function updateArtist(
 | 
			
		||||
  values: z.infer<typeof artistSchema>, 
 | 
			
		||||
  id: string
 | 
			
		||||
) {
 | 
			
		||||
  return await prisma.artist.update({
 | 
			
		||||
    where: {
 | 
			
		||||
      id: id
 | 
			
		||||
    },
 | 
			
		||||
    data: {
 | 
			
		||||
      displayName: values.displayName,
 | 
			
		||||
      slug: values.slug,
 | 
			
		||||
      nickname: values.nickname
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								src/app/artists/edit/[id]/page.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/app/artists/edit/[id]/page.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
import EditArtistForm from "@/components/artists/edit/EditArtistForm";
 | 
			
		||||
import prisma from "@/lib/prisma";
 | 
			
		||||
 | 
			
		||||
export default async function ArtistsEditPage({ params }: { params: { id: string } }) {
 | 
			
		||||
  const { id } = await params;
 | 
			
		||||
 | 
			
		||||
  const artist = await prisma.artist.findUnique({
 | 
			
		||||
    where: {
 | 
			
		||||
      id,
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      <h1 className="text-2xl font-bold mb-4">Edit album</h1>
 | 
			
		||||
      {artist ? <EditArtistForm artist={artist} /> : 'Artist not found...'}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								src/app/artists/new/page.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/app/artists/new/page.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
import CreateArtistForm from "@/components/artists/new/CreateArtistForm";
 | 
			
		||||
 | 
			
		||||
export default function ArtistsNewPage() {
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      <h1 className="text-2xl font-bold mb-4">New artist</h1>
 | 
			
		||||
      <CreateArtistForm />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								src/app/artists/page.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/app/artists/page.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
			
		||||
import ListArtists from "@/components/artists/list/ListArtists";
 | 
			
		||||
import prisma from "@/lib/prisma";
 | 
			
		||||
import { PlusCircleIcon } from "lucide-react";
 | 
			
		||||
import Link from "next/link";
 | 
			
		||||
 | 
			
		||||
export default async function ArtistsPage() {
 | 
			
		||||
  const artists = await prisma.artist.findMany({ orderBy: { createdAt: "asc" } });
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      <div className="flex gap-4 justify-between">
 | 
			
		||||
        <h1 className="text-2xl font-bold mb-4">Artists</h1>
 | 
			
		||||
        <Link href="/artists/new" className="flex gap-2 items-center cursor-pointer bg-primary hover:bg-primary/90 text-primary-foreground px-4 py-2 rounded">
 | 
			
		||||
          <PlusCircleIcon className="h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all text-primary-foreground" /> Add new Artist
 | 
			
		||||
        </Link>
 | 
			
		||||
      </div>
 | 
			
		||||
      {artists.length > 0 ? <ListArtists artists={artists} /> : <p className="text-muted-foreground italic">No artists found.</p>}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -26,7 +26,7 @@ export default function EditAlbumForm({ album, galleries }: { album: Album, gall
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  async function onSubmit(values: z.infer<typeof albumSchema>) {
 | 
			
		||||
    var updatedAlbum = await updateAlbum(values, album.id)
 | 
			
		||||
    const updatedAlbum = await updateAlbum(values, album.id)
 | 
			
		||||
    if (updatedAlbum) {
 | 
			
		||||
      toast.success("Album updated")
 | 
			
		||||
      router.push(`/albums`)
 | 
			
		||||
 | 
			
		||||
@ -26,7 +26,7 @@ export default function CreateAlbumForm({ galleries }: { galleries: Gallery[] })
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  async function onSubmit(values: z.infer<typeof albumSchema>) {
 | 
			
		||||
    var album = await createAlbum(values)
 | 
			
		||||
    const album = await createAlbum(values)
 | 
			
		||||
    if (album) {
 | 
			
		||||
      toast.success("Album created")
 | 
			
		||||
      router.push(`/albums`)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										94
									
								
								src/components/artists/edit/EditArtistForm.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/components/artists/edit/EditArtistForm.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,94 @@
 | 
			
		||||
"use client"
 | 
			
		||||
 | 
			
		||||
import { updateArtist } from "@/actions/artists/updateArtist";
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { Artist } from "@/generated/prisma";
 | 
			
		||||
import { artistSchema } from "@/schemas/artists/artistSchema";
 | 
			
		||||
import { zodResolver } from "@hookform/resolvers/zod";
 | 
			
		||||
import { useRouter } from "next/navigation";
 | 
			
		||||
import { useForm } from "react-hook-form";
 | 
			
		||||
import { toast } from "sonner";
 | 
			
		||||
import * as z from "zod/v4";
 | 
			
		||||
 | 
			
		||||
export default function EditAlbumForm({ artist }: { artist: Artist }) {
 | 
			
		||||
  const router = useRouter();
 | 
			
		||||
  const form = useForm<z.infer<typeof artistSchema>>({
 | 
			
		||||
    resolver: zodResolver(artistSchema),
 | 
			
		||||
    defaultValues: {
 | 
			
		||||
      displayName: artist.displayName,
 | 
			
		||||
      slug: artist.slug,
 | 
			
		||||
      nickname: artist.nickname || "",
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  async function onSubmit(values: z.infer<typeof artistSchema>) {
 | 
			
		||||
    const updatedArtist = await updateArtist(values, artist.id)
 | 
			
		||||
    if (updatedArtist) {
 | 
			
		||||
      toast.success("Artist updated")
 | 
			
		||||
      router.push(`/artists`)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col gap-8">
 | 
			
		||||
      <Form {...form}>
 | 
			
		||||
        <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
 | 
			
		||||
          <FormField
 | 
			
		||||
            control={form.control}
 | 
			
		||||
            name="displayName"
 | 
			
		||||
            render={({ field }) => (
 | 
			
		||||
              <FormItem>
 | 
			
		||||
                <FormLabel>Artist name</FormLabel>
 | 
			
		||||
                <FormControl>
 | 
			
		||||
                  <Input placeholder="Artist name" {...field} />
 | 
			
		||||
                </FormControl>
 | 
			
		||||
                <FormDescription>
 | 
			
		||||
                  This is your public display name.
 | 
			
		||||
                </FormDescription>
 | 
			
		||||
                <FormMessage />
 | 
			
		||||
              </FormItem>
 | 
			
		||||
            )}
 | 
			
		||||
          />
 | 
			
		||||
          <FormField
 | 
			
		||||
            control={form.control}
 | 
			
		||||
            name="slug"
 | 
			
		||||
            render={({ field }) => (
 | 
			
		||||
              <FormItem>
 | 
			
		||||
                <FormLabel>Artist slug</FormLabel>
 | 
			
		||||
                <FormControl>
 | 
			
		||||
                  <Input placeholder="Artist slug" {...field} />
 | 
			
		||||
                </FormControl>
 | 
			
		||||
                <FormDescription>
 | 
			
		||||
                  Will be used for the navigation.
 | 
			
		||||
                </FormDescription>
 | 
			
		||||
                <FormMessage />
 | 
			
		||||
              </FormItem>
 | 
			
		||||
            )}
 | 
			
		||||
          />
 | 
			
		||||
          <FormField
 | 
			
		||||
            control={form.control}
 | 
			
		||||
            name="nickname"
 | 
			
		||||
            render={({ field }) => (
 | 
			
		||||
              <FormItem>
 | 
			
		||||
                <FormLabel>Artist nickname (optional)</FormLabel>
 | 
			
		||||
                <FormControl>
 | 
			
		||||
                  <Input placeholder="Artist nickname" {...field} />
 | 
			
		||||
                </FormControl>
 | 
			
		||||
                <FormDescription>
 | 
			
		||||
                  Nickname of the artist.
 | 
			
		||||
                </FormDescription>
 | 
			
		||||
                <FormMessage />
 | 
			
		||||
              </FormItem>
 | 
			
		||||
            )}
 | 
			
		||||
          />
 | 
			
		||||
          <div className="flex flex-col gap-4">
 | 
			
		||||
            <Button type="submit">Submit</Button>
 | 
			
		||||
            <Button type="reset" variant="secondary" onClick={() => router.back()}>Cancel</Button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </form>
 | 
			
		||||
      </Form>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								src/components/artists/list/ListArtists.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/components/artists/list/ListArtists.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
 | 
			
		||||
import { Artist } from "@/generated/prisma";
 | 
			
		||||
import Link from "next/link";
 | 
			
		||||
 | 
			
		||||
export default function ListArtists({ artists }: { artists: Artist[] }) {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="grid gap-6 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4">
 | 
			
		||||
      {artists.map((artist) => (
 | 
			
		||||
        <Link href={`/artists/edit/${artist.id}`} key={artist.id}>
 | 
			
		||||
          <Card className="overflow-hidden">
 | 
			
		||||
            <CardHeader>
 | 
			
		||||
              <CardTitle className="text-base truncate">{artist.displayName}</CardTitle>
 | 
			
		||||
            </CardHeader>
 | 
			
		||||
            <CardContent>
 | 
			
		||||
            </CardContent>
 | 
			
		||||
            <CardFooter>
 | 
			
		||||
            </CardFooter>
 | 
			
		||||
          </Card>
 | 
			
		||||
        </Link>
 | 
			
		||||
      ))}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										91
									
								
								src/components/artists/new/CreateArtistForm.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								src/components/artists/new/CreateArtistForm.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,91 @@
 | 
			
		||||
"use client"
 | 
			
		||||
 | 
			
		||||
import { createArtist } from "@/actions/artists/createArtist";
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import { artistSchema } from "@/schemas/artists/artistSchema";
 | 
			
		||||
import { zodResolver } from "@hookform/resolvers/zod";
 | 
			
		||||
import { useRouter } from "next/navigation";
 | 
			
		||||
import { useForm } from "react-hook-form";
 | 
			
		||||
import { toast } from "sonner";
 | 
			
		||||
import * as z from "zod/v4";
 | 
			
		||||
 | 
			
		||||
export default function CreateArtistForm() {
 | 
			
		||||
  const router = useRouter();
 | 
			
		||||
  const form = useForm<z.infer<typeof artistSchema>>({
 | 
			
		||||
    resolver: zodResolver(artistSchema),
 | 
			
		||||
    defaultValues: {
 | 
			
		||||
      displayName: "",
 | 
			
		||||
      slug: "",
 | 
			
		||||
      nickname: "",
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  async function onSubmit(values: z.infer<typeof artistSchema>) {
 | 
			
		||||
    const artist = await createArtist(values)
 | 
			
		||||
    if (artist) {
 | 
			
		||||
      toast.success("Artist created")
 | 
			
		||||
      router.push(`/artists`)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Form {...form}>
 | 
			
		||||
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
 | 
			
		||||
        <FormField
 | 
			
		||||
          control={form.control}
 | 
			
		||||
          name="displayName"
 | 
			
		||||
          render={({ field }) => (
 | 
			
		||||
            <FormItem>
 | 
			
		||||
              <FormLabel>Artist name</FormLabel>
 | 
			
		||||
              <FormControl>
 | 
			
		||||
                <Input placeholder="Artist name" {...field} />
 | 
			
		||||
              </FormControl>
 | 
			
		||||
              <FormDescription>
 | 
			
		||||
                This is your public display name.
 | 
			
		||||
              </FormDescription>
 | 
			
		||||
              <FormMessage />
 | 
			
		||||
            </FormItem>
 | 
			
		||||
          )}
 | 
			
		||||
        />
 | 
			
		||||
        <FormField
 | 
			
		||||
          control={form.control}
 | 
			
		||||
          name="slug"
 | 
			
		||||
          render={({ field }) => (
 | 
			
		||||
            <FormItem>
 | 
			
		||||
              <FormLabel>Artist slug</FormLabel>
 | 
			
		||||
              <FormControl>
 | 
			
		||||
                <Input placeholder="Artist slug" {...field} />
 | 
			
		||||
              </FormControl>
 | 
			
		||||
              <FormDescription>
 | 
			
		||||
                Will be used for the navigation.
 | 
			
		||||
              </FormDescription>
 | 
			
		||||
              <FormMessage />
 | 
			
		||||
            </FormItem>
 | 
			
		||||
          )}
 | 
			
		||||
        />
 | 
			
		||||
        <FormField
 | 
			
		||||
          control={form.control}
 | 
			
		||||
          name="nickname"
 | 
			
		||||
          render={({ field }) => (
 | 
			
		||||
            <FormItem>
 | 
			
		||||
              <FormLabel>Artist nickname</FormLabel>
 | 
			
		||||
              <FormControl>
 | 
			
		||||
                <Input placeholder="Artist nickname" {...field} />
 | 
			
		||||
              </FormControl>
 | 
			
		||||
              <FormDescription>
 | 
			
		||||
                Nickname of the artist.
 | 
			
		||||
              </FormDescription>
 | 
			
		||||
              <FormMessage />
 | 
			
		||||
            </FormItem>
 | 
			
		||||
          )}
 | 
			
		||||
        />
 | 
			
		||||
        <div className="flex flex-col gap-4">
 | 
			
		||||
          <Button type="submit">Submit</Button>
 | 
			
		||||
          <Button type="reset" variant="secondary" onClick={() => router.back()}>Cancel</Button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </form>
 | 
			
		||||
    </Form>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -22,6 +22,11 @@ export default function TopNav() {
 | 
			
		||||
            <Link href="/albums">Albums</Link>
 | 
			
		||||
          </NavigationMenuLink>
 | 
			
		||||
        </NavigationMenuItem>
 | 
			
		||||
        <NavigationMenuItem>
 | 
			
		||||
          <NavigationMenuLink asChild className={navigationMenuTriggerStyle()}>
 | 
			
		||||
            <Link href="/artists">Artists</Link>
 | 
			
		||||
          </NavigationMenuLink>
 | 
			
		||||
        </NavigationMenuItem>
 | 
			
		||||
      </NavigationMenuList>
 | 
			
		||||
    </NavigationMenu>
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								src/schemas/artists/artistSchema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/schemas/artists/artistSchema.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
import * as z from "zod/v4";
 | 
			
		||||
 | 
			
		||||
export const artistSchema = z.object({
 | 
			
		||||
  displayName: z.string().min(3, "Name is required. Min 3 characters."),
 | 
			
		||||
  slug: z.string().min(3, "Slug is required. Min 3 characters.").regex(/^[a-z]+$/, "Only lowercase letters are allowed (no numbers, spaces, or uppercase)"),
 | 
			
		||||
  nickname: z.string().optional(),
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user