feat: Add dashboard for managing portfolio content (blog, experience, projects) and restructure public portfolio routes.
This commit is contained in:
86
src/lib/actions/blog.ts
Normal file
86
src/lib/actions/blog.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
"use server";
|
||||
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { BlogStatus } from "@prisma/client";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { z } from "zod";
|
||||
|
||||
const BlogSchema = z.object({
|
||||
title: z.string().min(1, "Title is required"),
|
||||
slug: z.string().min(1, "Slug is required"),
|
||||
content: z.string().min(1, "Content is required"),
|
||||
excerpt: z.string().optional(),
|
||||
status: z.nativeEnum(BlogStatus),
|
||||
publishedAt: z.coerce.date().nullable().optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
export async function createBlog(data: z.infer<typeof BlogSchema>) {
|
||||
const validated = BlogSchema.parse(data);
|
||||
|
||||
const blog = await prisma.blog.create({
|
||||
data: {
|
||||
title: validated.title,
|
||||
slug: validated.slug,
|
||||
content: validated.content,
|
||||
excerpt: validated.excerpt,
|
||||
status: validated.status,
|
||||
publishedAt: validated.status === BlogStatus.PUBLISHED ? new Date() : null,
|
||||
tags: {
|
||||
connectOrCreate: validated.tags?.map((tag) => ({
|
||||
where: { name: tag },
|
||||
create: { name: tag },
|
||||
})),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
revalidatePath("/dashboard/blog");
|
||||
revalidatePath("/blog");
|
||||
return blog;
|
||||
}
|
||||
|
||||
export async function updateBlog(id: string, data: z.infer<typeof BlogSchema>) {
|
||||
const validated = BlogSchema.parse(data);
|
||||
|
||||
const blog = await prisma.blog.update({
|
||||
where: { id },
|
||||
data: {
|
||||
title: validated.title,
|
||||
slug: validated.slug,
|
||||
content: validated.content,
|
||||
excerpt: validated.excerpt,
|
||||
status: validated.status,
|
||||
publishedAt: validated.status === BlogStatus.PUBLISHED ? new Date() : null,
|
||||
tags: {
|
||||
set: [], // Clear existing tags
|
||||
connectOrCreate: validated.tags?.map((tag) => ({
|
||||
where: { name: tag },
|
||||
create: { name: tag },
|
||||
})),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
revalidatePath("/dashboard/blog");
|
||||
revalidatePath("/blog");
|
||||
revalidatePath(`/blog/${blog.slug}`);
|
||||
return blog;
|
||||
}
|
||||
|
||||
export async function deleteBlog(id: string) {
|
||||
const blog = await prisma.blog.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
revalidatePath("/dashboard/blog");
|
||||
revalidatePath("/blog");
|
||||
return blog;
|
||||
}
|
||||
|
||||
export async function getBlogs() {
|
||||
return await prisma.blog.findMany({
|
||||
orderBy: { createdAt: "desc" },
|
||||
include: { tags: true },
|
||||
});
|
||||
}
|
||||
58
src/lib/actions/experience.ts
Normal file
58
src/lib/actions/experience.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
"use server";
|
||||
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { z } from "zod";
|
||||
|
||||
const ExperienceSchema = z.object({
|
||||
company: z.string().min(1, "Company is required"),
|
||||
role: z.string().min(1, "Role is required"),
|
||||
startDate: z.coerce.date(),
|
||||
endDate: z.coerce.date().nullable().optional(),
|
||||
current: z.boolean().default(false),
|
||||
description: z.string().min(1, "Description is required"),
|
||||
highlights: z.array(z.string()).optional().default([]),
|
||||
techStack: z.array(z.string()).optional().default([]),
|
||||
order: z.coerce.number().default(0),
|
||||
});
|
||||
|
||||
export async function createExperience(data: z.infer<typeof ExperienceSchema>) {
|
||||
const validated = ExperienceSchema.parse(data);
|
||||
|
||||
const experience = await prisma.experience.create({
|
||||
data: validated,
|
||||
});
|
||||
|
||||
revalidatePath("/dashboard/experience");
|
||||
revalidatePath("/experience");
|
||||
return experience;
|
||||
}
|
||||
|
||||
export async function updateExperience(id: string, data: z.infer<typeof ExperienceSchema>) {
|
||||
const validated = ExperienceSchema.parse(data);
|
||||
|
||||
const experience = await prisma.experience.update({
|
||||
where: { id },
|
||||
data: validated,
|
||||
});
|
||||
|
||||
revalidatePath("/dashboard/experience");
|
||||
revalidatePath("/experience");
|
||||
return experience;
|
||||
}
|
||||
|
||||
export async function deleteExperience(id: string) {
|
||||
const experience = await prisma.experience.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
revalidatePath("/dashboard/experience");
|
||||
revalidatePath("/experience");
|
||||
return experience;
|
||||
}
|
||||
|
||||
export async function getExperiences() {
|
||||
return await prisma.experience.findMany({
|
||||
orderBy: { startDate: "desc" },
|
||||
});
|
||||
}
|
||||
57
src/lib/actions/project.ts
Normal file
57
src/lib/actions/project.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
"use server";
|
||||
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { z } from "zod";
|
||||
|
||||
const ProjectSchema = z.object({
|
||||
title: z.string().min(1, "Title is required"),
|
||||
description: z.string().min(1, "Description is required"),
|
||||
imageUrl: z.string().optional().nullable(),
|
||||
liveUrl: z.string().optional().nullable(),
|
||||
githubUrl: z.string().optional().nullable(),
|
||||
techStack: z.array(z.string()).min(1, "At least one tech stack is required"),
|
||||
featured: z.boolean().default(false),
|
||||
order: z.coerce.number().default(0),
|
||||
});
|
||||
|
||||
export async function createProject(data: z.infer<typeof ProjectSchema>) {
|
||||
const validated = ProjectSchema.parse(data);
|
||||
|
||||
const project = await prisma.project.create({
|
||||
data: validated,
|
||||
});
|
||||
|
||||
revalidatePath("/dashboard/projects");
|
||||
revalidatePath("/projects");
|
||||
return project;
|
||||
}
|
||||
|
||||
export async function updateProject(id: string, data: z.infer<typeof ProjectSchema>) {
|
||||
const validated = ProjectSchema.parse(data);
|
||||
|
||||
const project = await prisma.project.update({
|
||||
where: { id },
|
||||
data: validated,
|
||||
});
|
||||
|
||||
revalidatePath("/dashboard/projects");
|
||||
revalidatePath("/projects");
|
||||
return project;
|
||||
}
|
||||
|
||||
export async function deleteProject(id: string) {
|
||||
const project = await prisma.project.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
revalidatePath("/dashboard/projects");
|
||||
revalidatePath("/projects");
|
||||
return project;
|
||||
}
|
||||
|
||||
export async function getProjects() {
|
||||
return await prisma.project.findMany({
|
||||
orderBy: [{ order: "asc" }, { createdAt: "desc" }],
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user