-
- {/* Optional: Subtle overlay on hover */}
-
+
+ {imageUrl ? (
+
+ ) : (
+ // Optional: Placeholder if no image
+
+ No Image
+
+ )}
{/* Content Area */}
diff --git a/lib/directus.ts b/lib/directus.ts
new file mode 100644
index 0000000..aec569b
--- /dev/null
+++ b/lib/directus.ts
@@ -0,0 +1,5 @@
+import { createDirectus, rest } from "@directus/sdk";
+
+export const directus = createDirectus(
+ String(process.env.DIRECTUS_API_ENDPOINT!)
+).with(rest());
diff --git a/lib/query/home.ts b/lib/query/home.ts
new file mode 100644
index 0000000..9d3880c
--- /dev/null
+++ b/lib/query/home.ts
@@ -0,0 +1,8 @@
+import { readItems } from "@directus/sdk";
+import { directus } from "../directus";
+import { HeroSection } from "@/types";
+
+export async function getHome() {
+ // Assuming '1' is the primary key for the singleton "home" item
+ return directus.request(readItems("home")) as unknown as HeroSection;
+}
diff --git a/lib/query/post.ts b/lib/query/post.ts
new file mode 100644
index 0000000..47b540a
--- /dev/null
+++ b/lib/query/post.ts
@@ -0,0 +1,66 @@
+import { directus } from "@/lib/directus";
+import { ItemsQuery, Post } from "@/types";
+import { readItem, readItems } from "@directus/sdk";
+
+// Construct base URL for assets from environment variable
+const assetsUrl = `${process.env.DIRECTUS_API_ENDPOINT}/assets/`;
+
+function getFullImageUrl(imageId: string | null | undefined): string | null {
+ if (!imageId || !assetsUrl) {
+ return null;
+ }
+ return `${assetsUrl}${imageId}`;
+}
+
+// Function to fetch all published posts
+export async function getPosts(): Promise
{
+ try {
+ const postsData = await directus.request(
+ readItems("posts", {
+ fields: [
+ "slug",
+ "title",
+ "excerpt",
+ "featured_image",
+ "date_created",
+ "content",
+ ],
+ filter: {
+ status: { _eq: "published" },
+ },
+ sort: ["-date_created"],
+ })
+ );
+
+ const posts = postsData.map((post) => ({
+ ...post,
+ featured_image: getFullImageUrl(post.featured_image),
+ })) as Post[];
+
+ return posts;
+ } catch (error) {
+ console.error("Error fetching posts:", error);
+ return [];
+ }
+}
+
+// Function to fetch a single post by slug
+export async function getPostBySlug(
+ slug: string,
+ options?: ItemsQuery
+): Promise {
+ try {
+ const postData = await directus.request(readItem("posts", slug, options));
+
+ // Map data to include full image URL
+ const post = {
+ ...postData,
+ featured_image: getFullImageUrl(postData.featured_image),
+ } as Post; // Adjust cast if needed
+
+ return post;
+ } catch (error) {
+ console.error(`Error fetching post with slug ${slug}:`, error);
+ return null;
+ }
+}
diff --git a/next.config.ts b/next.config.ts
index 05c512b..a8c61cf 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -11,6 +11,10 @@ const nextConfig: NextConfig = {
protocol: "https",
hostname: "storage.cvevolve.com",
},
+ {
+ protocol: "https",
+ hostname: process.env.DIRECTUS_API_ENDPOINT!.replace("https://", ""),
+ },
],
},
experimental: {
diff --git a/package-lock.json b/package-lock.json
index f87c7d5..e6d5b84 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,8 +9,10 @@
"version": "0.1.0",
"dependencies": {
"@auth/prisma-adapter": "^2.9.0",
+ "@directus/sdk": "^18.0.3",
"@google/generative-ai": "^0.24.0",
"@prisma/client": "^6.6.0",
+ "date-fns": "^4.1.0",
"minio": "^8.0.5",
"next": "15.3.1",
"next-auth": "^5.0.0-beta.26",
@@ -646,6 +648,18 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@directus/sdk": {
+ "version": "18.0.3",
+ "resolved": "https://registry.npmjs.org/@directus/sdk/-/sdk-18.0.3.tgz",
+ "integrity": "sha512-PnEDRDqr2x/DG3HZ3qxU7nFp2nW6zqJqswjii57NhriXgTz4TBUI8NmSdzQvnyHuTL9J0nedYfQGfW4v8odS1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/directus/directus?sponsor=1"
+ }
+ },
"node_modules/@emnapi/core": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz",
@@ -4604,6 +4618,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/date-fns": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
+ "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
"node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
diff --git a/package.json b/package.json
index e8e55bc..795a5e2 100644
--- a/package.json
+++ b/package.json
@@ -10,8 +10,10 @@
},
"dependencies": {
"@auth/prisma-adapter": "^2.9.0",
+ "@directus/sdk": "^18.0.3",
"@google/generative-ai": "^0.24.0",
"@prisma/client": "^6.6.0",
+ "date-fns": "^4.1.0",
"minio": "^8.0.5",
"next": "15.3.1",
"next-auth": "^5.0.0-beta.26",
diff --git a/types/index.d.ts b/types/index.d.ts
new file mode 100644
index 0000000..34b3d16
--- /dev/null
+++ b/types/index.d.ts
@@ -0,0 +1,79 @@
+export interface OutputData {
+ time: number;
+ blocks: ContentBlock[];
+ version: string;
+}
+
+export interface ContentBlock {
+ id: string;
+ type: string;
+ data:
+ | ParagraphData
+ | HeaderData
+ | ListData
+ | ImageData
+ | QuoteData
+ | CodeData;
+}
+
+export interface ParagraphData {
+ text: string;
+}
+
+export interface HeaderData {
+ text: string;
+ level: number;
+}
+
+export interface ListData {
+ style: "ordered" | "unordered";
+ items: string[];
+}
+
+export interface ImageData {
+ file: {
+ url: string;
+ width?: number;
+ height?: number;
+ };
+ caption?: string;
+}
+
+export interface QuoteData {
+ text: string;
+ caption?: string;
+}
+
+export interface CodeData {
+ code: string;
+}
+
+export interface Post {
+ slug: string;
+ status: string;
+ user_created: string;
+ date_created: string;
+ user_updated: string | null;
+ date_updated: string | null;
+ title: string;
+ content: OutputData | null;
+ excerpt: string | null;
+ featured_image: string | null;
+ imageUrl?: string | null;
+}
+export interface HeroSection {
+ id: string;
+ hero_title?: string;
+ hero_subtitle?: string;
+ hero_cover?: string;
+ hero_buttons?: HeroButton[];
+}
+
+interface HeroButton {
+ label?: string;
+ link?: string;
+}
+
+interface ItemsQuery {
+ fields?: string[];
+}