vendor slugs routes instead of ids
This commit is contained in:
@@ -5,15 +5,15 @@ import { VendorDetailPage } from '@/components/vendor/VendorDetailPage'
|
|||||||
|
|
||||||
interface PageProps {
|
interface PageProps {
|
||||||
params: {
|
params: {
|
||||||
id: string
|
slug: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Page({ params }: PageProps) {
|
export default async function Page({ params }: PageProps) {
|
||||||
const { id } = params
|
const { slug } = params
|
||||||
|
|
||||||
const vendor = await prisma.vendor.findUnique({
|
const vendor = await prisma.vendor.findUnique({
|
||||||
where: { id },
|
where: { slug },
|
||||||
include: {
|
include: {
|
||||||
address: true,
|
address: true,
|
||||||
events: {
|
events: {
|
||||||
16
app/api/vendors/[id]/route.ts
vendored
16
app/api/vendors/[id]/route.ts
vendored
@@ -2,6 +2,7 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { prisma } from '@/lib/prisma'
|
import { prisma } from '@/lib/prisma'
|
||||||
import { VendorType, VendorStatus } from '@prisma/client'
|
import { VendorType, VendorStatus } from '@prisma/client'
|
||||||
|
import { generateUniqueSlug, slugify } from '@/lib/utils/slugify'
|
||||||
|
|
||||||
export async function PUT(
|
export async function PUT(
|
||||||
request: NextRequest,
|
request: NextRequest,
|
||||||
@@ -24,6 +25,20 @@ export async function PUT(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let slug = existingVendor.slug
|
||||||
|
if (body.name && body.name !== existingVendor.name) {
|
||||||
|
const baseSlug = slugify(body.name)
|
||||||
|
slug = await generateUniqueSlug(
|
||||||
|
baseSlug,
|
||||||
|
async (testSlug) => {
|
||||||
|
const existing = await prisma.vendor.findUnique({
|
||||||
|
where: { slug: testSlug }
|
||||||
|
})
|
||||||
|
return !!existing && existing.id !== id
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Parse numeric values
|
// Parse numeric values
|
||||||
const parseFloatOrNull = (value: any): number | null => {
|
const parseFloatOrNull = (value: any): number | null => {
|
||||||
if (value === null || value === undefined || value === '') return null
|
if (value === null || value === undefined || value === '') return null
|
||||||
@@ -75,6 +90,7 @@ export async function PUT(
|
|||||||
|
|
||||||
// Prepare update data
|
// Prepare update data
|
||||||
const updateData: any = {
|
const updateData: any = {
|
||||||
|
slug,
|
||||||
name: body.name,
|
name: body.name,
|
||||||
type: body.type as VendorType,
|
type: body.type as VendorType,
|
||||||
description: body.description || null,
|
description: body.description || null,
|
||||||
|
|||||||
13
app/api/vendors/create/route.ts
vendored
13
app/api/vendors/create/route.ts
vendored
@@ -1,6 +1,7 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { prisma } from '@/lib/prisma'
|
import { prisma } from '@/lib/prisma'
|
||||||
import { VendorType, VendorStatus } from '@prisma/client'
|
import { VendorType, VendorStatus } from '@prisma/client'
|
||||||
|
import { generateUniqueSlug, slugify } from '@/lib/utils/slugify'
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
@@ -16,6 +17,17 @@ export async function POST(request: NextRequest) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const baseSlug = slugify(body.name)
|
||||||
|
const uniqueSlug = await generateUniqueSlug(
|
||||||
|
baseSlug,
|
||||||
|
async (slug) => {
|
||||||
|
const existing = await prisma.vendor.findUnique({
|
||||||
|
where: { slug }
|
||||||
|
})
|
||||||
|
return !!existing
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// Check if we have address data
|
// Check if we have address data
|
||||||
const hasAddress = body.street && body.city && body.state && body.postalCode
|
const hasAddress = body.street && body.city && body.state && body.postalCode
|
||||||
|
|
||||||
@@ -34,6 +46,7 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
// Prepare vendor data WITHOUT addressId
|
// Prepare vendor data WITHOUT addressId
|
||||||
const vendorData: any = {
|
const vendorData: any = {
|
||||||
|
slug: uniqueSlug,
|
||||||
name: body.name,
|
name: body.name,
|
||||||
type: body.type as VendorType,
|
type: body.type as VendorType,
|
||||||
description: body.description || null,
|
description: body.description || null,
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { Input } from '../ui/input'
|
|||||||
import { Button } from '../ui/button'
|
import { Button } from '../ui/button'
|
||||||
import { Label } from '../ui/label'
|
import { Label } from '../ui/label'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { Address, Vendor } from '@prisma/client'
|
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'
|
||||||
import { Textarea } from '../ui/textarea'
|
import { Textarea } from '../ui/textarea'
|
||||||
import { Checkbox } from '../ui/checkbox'
|
import { Checkbox } from '../ui/checkbox'
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { useRouter } from 'next/navigation'
|
|||||||
|
|
||||||
interface VendorRow {
|
interface VendorRow {
|
||||||
id: string
|
id: string
|
||||||
|
slug: string
|
||||||
name: string
|
name: string
|
||||||
type: VendorType
|
type: VendorType
|
||||||
description?: string | null
|
description?: string | null
|
||||||
@@ -189,7 +190,7 @@ export default function VendorsTable({ initialVendors }: Props) {
|
|||||||
|
|
||||||
// Handle row click
|
// Handle row click
|
||||||
const handleRowClick = (vendor: VendorRow) => {
|
const handleRowClick = (vendor: VendorRow) => {
|
||||||
router.push(`/vendors/${vendor.id}`)
|
router.push(`/vendors/${vendor.slug}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
30
lib/utils/slugify.ts
Normal file
30
lib/utils/slugify.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
export function slugify(text: string): string {
|
||||||
|
return text
|
||||||
|
.toString()
|
||||||
|
.toLowerCase()
|
||||||
|
.trim()
|
||||||
|
.replace(/\s+/g, '-')
|
||||||
|
.replace(/[^\w\-]+/g, '')
|
||||||
|
.replace(/\-\-+/g, '-')
|
||||||
|
.replace(/^-+/, '')
|
||||||
|
.replace(/-+$/, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateUniqueSlug(
|
||||||
|
baseSlug: string,
|
||||||
|
checkUnique: (slug: string) => Promise<boolean>,
|
||||||
|
maxAttempts = 10
|
||||||
|
): Promise<string> {
|
||||||
|
let slug = baseSlug
|
||||||
|
let attempt = 1
|
||||||
|
|
||||||
|
while (await checkUnique(slug)) {
|
||||||
|
if (attempt >= maxAttempts) {
|
||||||
|
throw new Error(`Could not generate unique slug after ${maxAttempts} attempts`)
|
||||||
|
}
|
||||||
|
slug = `${baseSlug}-${attempt}`
|
||||||
|
attempt++
|
||||||
|
}
|
||||||
|
|
||||||
|
return slug
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- A unique constraint covering the columns `[slug]` on the table `Vendor` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Vendor" ADD COLUMN "slug" TEXT;
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Vendor_slug_key" ON "Vendor"("slug");
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- Made the column `slug` on table `Vendor` required. This step will fail if there are existing NULL values in that column.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Vendor" ALTER COLUMN "slug" SET NOT NULL;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Vendor_slug_idx" ON "Vendor"("slug");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Vendor_name_idx" ON "Vendor"("name");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Vendor_type_idx" ON "Vendor"("type");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Vendor_status_idx" ON "Vendor"("status");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Vendor_isBooked_idx" ON "Vendor"("isBooked");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Vendor_bookedDate_idx" ON "Vendor"("bookedDate");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Vendor_finalCost_idx" ON "Vendor"("finalCost");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Vendor_createdAt_idx" ON "Vendor"("createdAt");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Vendor_type_status_idx" ON "Vendor"("type", "status");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Vendor_isBooked_type_idx" ON "Vendor"("isBooked", "type");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Vendor_depositDueDate_idx" ON "Vendor"("depositDueDate");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Vendor_finalPaymentDue_idx" ON "Vendor"("finalPaymentDue");
|
||||||
@@ -148,6 +148,7 @@ model Vendor {
|
|||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
slug String @unique
|
||||||
|
|
||||||
name String
|
name String
|
||||||
type VendorType
|
type VendorType
|
||||||
@@ -178,6 +179,19 @@ model Vendor {
|
|||||||
events Event[]
|
events Event[]
|
||||||
|
|
||||||
categories Category[]
|
categories Category[]
|
||||||
|
|
||||||
|
@@index([slug])
|
||||||
|
@@index([name])
|
||||||
|
@@index([type])
|
||||||
|
@@index([status])
|
||||||
|
@@index([isBooked])
|
||||||
|
@@index([bookedDate])
|
||||||
|
@@index([finalCost])
|
||||||
|
@@index([createdAt])
|
||||||
|
@@index([type, status])
|
||||||
|
@@index([isBooked, type])
|
||||||
|
@@index([depositDueDate])
|
||||||
|
@@index([finalPaymentDue])
|
||||||
}
|
}
|
||||||
|
|
||||||
model Address {
|
model Address {
|
||||||
|
|||||||
Reference in New Issue
Block a user