- Implement WhatsApp Business API integration via Meta Cloud API - Add phoneNumber field to Order model for storing customer phone numbers - Update admin order detail page with phone number input for collection orders - Send WhatsApp notification when admin marks order as ready for collection - Add WhatsApp service module with phone number formatting and message sending - Create database migrations for collection-related fields - Add WhatsApp credentials to .env configuration Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
84 lines
2.2 KiB
TypeScript
84 lines
2.2 KiB
TypeScript
'use server';
|
|
|
|
import { redirect } from 'next/navigation';
|
|
import { prisma } from '@/lib/prisma';
|
|
|
|
function slugify(input: string) {
|
|
return input
|
|
.toUpperCase()
|
|
.trim()
|
|
.replace(/[^A-Z0-9]+/g, '_')
|
|
.replace(/^_+|_+$/g, '');
|
|
}
|
|
|
|
async function uniqueSlug(base: string) {
|
|
let slug = base || 'GALLERY';
|
|
let n = 2;
|
|
while (await prisma.galleryCategory.findUnique({ where: { slug } })) {
|
|
slug = `${base}_${n}`;
|
|
n += 1;
|
|
}
|
|
return slug;
|
|
}
|
|
|
|
async function fileToDataUrl(file: File): Promise<string> {
|
|
const bytes = Buffer.from(await file.arrayBuffer());
|
|
const mimeType = file.type || 'image/png';
|
|
return `data:${mimeType};base64,${bytes.toString('base64')}`;
|
|
}
|
|
|
|
export async function uploadGalleryPhoto(formData: FormData) {
|
|
const caption = String(formData.get('caption') ?? '').trim();
|
|
const categoryId = String(formData.get('categoryId') ?? '').trim() || null;
|
|
const file = formData.get('image') as File | null;
|
|
|
|
if (!file || file.size === 0) {
|
|
throw new Error('A photo is required.');
|
|
}
|
|
|
|
const imageDataUrl = await fileToDataUrl(file);
|
|
|
|
await prisma.galleryPhoto.create({
|
|
data: {
|
|
imageDataUrl,
|
|
caption: caption || null,
|
|
categoryId,
|
|
},
|
|
});
|
|
|
|
redirect('/admin/gallery');
|
|
}
|
|
|
|
export async function deleteGalleryPhoto(formData: FormData) {
|
|
const id = String(formData.get('id') ?? '');
|
|
if (!id) return;
|
|
|
|
await prisma.galleryPhoto.delete({ where: { id } });
|
|
redirect('/admin/gallery');
|
|
}
|
|
|
|
export async function createGalleryCategory(formData: FormData) {
|
|
const name = String(formData.get('name') ?? '').trim();
|
|
const redirectTo = String(formData.get('redirectTo') ?? '/admin/gallery/categories').trim();
|
|
if (!name) throw new Error('Category name is required.');
|
|
|
|
const slug = await uniqueSlug(slugify(name));
|
|
|
|
await prisma.galleryCategory.create({ data: { name, slug } });
|
|
|
|
redirect(redirectTo);
|
|
}
|
|
|
|
export async function deleteGalleryCategory(formData: FormData) {
|
|
const id = String(formData.get('id') ?? '');
|
|
if (!id) return;
|
|
|
|
const inUse = await prisma.galleryPhoto.count({ where: { categoryId: id } });
|
|
if (inUse > 0) {
|
|
redirect('/admin/gallery/categories?error=in_use');
|
|
}
|
|
|
|
await prisma.galleryCategory.delete({ where: { id } });
|
|
redirect('/admin/gallery/categories');
|
|
}
|