Axil_website/src/lib/payload.ts
Vadym Samoilenko 3f6dfe36b1 feat: full CMS integration — connect all content to Payload admin panel
- Connect homepage, blog, services, header, footer to Payload CMS via local API
- Add HomePage global with 7 editorial sections (hero, painPoints, solution, whyAxil, audience, process, finalCta)
- Add ServerHeader/ServerFooter async wrappers with unstable_cache + tag-based ISR revalidation
- Rewrite blog/[slug] and services/[slug] pages to fetch from CMS with fallbacks
- Add seed script (src/lib/seed.ts) to populate all collections and globals from hardcoded defaults
- Restructure app into (site)/ route group to fix Payload admin hydration conflicts
- Make root layout a passthrough; (site)/layout.tsx owns html/body/fonts
- Restrict user creation/update/delete to admin role only
- Fix migration imports: type MigrateUpArgs/Down from @payloadcms/db-postgres
- Wrap revalidateTag dynamic imports in try/catch for seed/CLI compatibility
- Skip migrations in dev mode (Payload handles schema push automatically)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-23 21:19:44 +00:00

171 lines
5.5 KiB
TypeScript

import 'server-only';
import { getPayload } from 'payload';
import config from '@payload-config';
import { unstable_cache } from 'next/cache';
import type { Post, Service, Testimonial, Navigation, Footer, HomePage } from '@/payload-types';
// ─── Singleton client ──────────────────────────────────────────────────────
let _client: Awaited<ReturnType<typeof getPayload>> | null = null;
export async function getPayloadClient() {
if (!_client) _client = await getPayload({ config });
return _client;
}
// ─── Navigation ────────────────────────────────────────────────────────────
export const getNavigation = unstable_cache(
async (): Promise<Navigation | null> => {
try {
const payload = await getPayloadClient();
return await payload.findGlobal({ slug: 'navigation' });
} catch {
return null;
}
},
['navigation'],
{ tags: ['navigation'], revalidate: false },
);
// ─── Footer ────────────────────────────────────────────────────────────────
export const getFooter = unstable_cache(
async (): Promise<Footer | null> => {
try {
const payload = await getPayloadClient();
return await payload.findGlobal({ slug: 'footer' });
} catch {
return null;
}
},
['footer'],
{ tags: ['footer'], revalidate: false },
);
// ─── Posts ─────────────────────────────────────────────────────────────────
export const getPublishedPosts = unstable_cache(
async (limit = 10): Promise<Post[]> => {
try {
const payload = await getPayloadClient();
const result = await payload.find({
collection: 'posts',
where: { status: { equals: 'published' } },
sort: '-publishedAt',
limit,
depth: 1,
});
return result.docs as Post[];
} catch {
return [];
}
},
['posts'],
{ tags: ['posts'], revalidate: false },
);
export function getPostBySlugCached(slug: string): Promise<Post | undefined> {
return unstable_cache(
async () => {
try {
const payload = await getPayloadClient();
const result = await payload.find({
collection: 'posts',
where: {
and: [{ slug: { equals: slug } }, { status: { equals: 'published' } }],
},
limit: 1,
depth: 1,
});
return result.docs[0] as Post | undefined;
} catch {
return undefined;
}
},
[`post-${slug}`],
{ tags: ['posts', `post-${slug}`], revalidate: false },
)();
}
// ─── Services ──────────────────────────────────────────────────────────────
export const getPublishedServices = unstable_cache(
async (limit = 10): Promise<Service[]> => {
try {
const payload = await getPayloadClient();
const result = await payload.find({
collection: 'services',
where: { status: { equals: 'published' } },
sort: 'createdAt',
limit,
depth: 1,
});
return result.docs as Service[];
} catch {
return [];
}
},
['services'],
{ tags: ['services'], revalidate: false },
);
export function getServiceBySlugCached(slug: string): Promise<Service | undefined> {
return unstable_cache(
async () => {
try {
const payload = await getPayloadClient();
const result = await payload.find({
collection: 'services',
where: {
and: [{ slug: { equals: slug } }, { status: { equals: 'published' } }],
},
limit: 1,
depth: 2,
});
return result.docs[0] as Service | undefined;
} catch {
return undefined;
}
},
[`service-${slug}`],
{ tags: ['services', `service-${slug}`], revalidate: false },
)();
}
// ─── HomePage Global ───────────────────────────────────────────────────────
export const getHomePage = unstable_cache(
async (): Promise<HomePage | null> => {
try {
const payload = await getPayloadClient();
return await payload.findGlobal({ slug: 'home-page' });
} catch {
return null;
}
},
['homepage'],
{ tags: ['homepage'], revalidate: false },
);
// ─── Testimonials ──────────────────────────────────────────────────────────
export const getFeaturedTestimonials = unstable_cache(
async (limit = 8): Promise<Testimonial[]> => {
try {
const payload = await getPayloadClient();
const result = await payload.find({
collection: 'testimonials',
where: { featured: { equals: true } },
sort: '-publishedAt',
limit,
depth: 0,
});
return result.docs as Testimonial[];
} catch {
return [];
}
},
['testimonials-featured'],
{ tags: ['testimonials'], revalidate: false },
);