# Website API Integration Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Replace all mock data on the public website with live API data, using Next.js Server Components and ISR (60-second revalidation) for optimal SEO and performance.

**Architecture:** A server-only `lib/website-api.ts` module wraps `fetch()` with `next: { revalidate: 60 }`. Affected pages become `async` Server Components that read `searchParams` for pagination, fetch data, and pass typed props to presentational/client components. The contact form is wired via a Server Action in `app/actions/contact.ts`.

**Tech Stack:** Next.js 16 App Router, TypeScript, `server-only`, Server Actions, next-intl `getTranslations`, existing `Pagination` component from `@/components/ui/pagination`

**Base API URL:** `https://dev-lam.maaal.site`
**Note:** `dev-lam.maaal.site` is already in `next.config.ts` `remotePatterns` — no image config changes needed.

---

### Task 1: Create the server-side data layer

**Files:**
- Create: `lib/website-api.ts`

- [ ] **Step 1: Create `lib/website-api.ts`**

```ts
import "server-only";

const BASE_URL =
  (process.env.NEXT_PUBLIC_API_URL ?? "https://dev-lam.maaal.site").replace(
    /\/+$/,
    ""
  );

// ── Types ────────────────────────────────────────────────────────────────────

export interface BlogItem {
  id: number;
  title: string;
  description: string; // HTML string
  image: string | null;
  created_at: string;
}

export interface LifiItem {
  id: number;
  title: string;
  file: string; // PDF URL
  created_at: string;
}

export interface TeamMember {
  id: number;
  name: string;
  email: string;
  image: string;
  created_at: string;
}

export interface Paginator {
  total_count: number;
  total_pages: number;
  current_page: number;
  per_page: number;
}

interface ApiEnvelope<T> {
  status: boolean;
  data: { list: T[]; paginator: Paginator };
  message: string | null;
  code: number;
}

const DEFAULT_PAGINATOR: Paginator = {
  total_count: 0,
  total_pages: 1,
  current_page: 1,
  per_page: 10,
};

// ── Internal fetch helper ─────────────────────────────────────────────────────

async function fetchList<T>(
  path: string
): Promise<{ items: T[]; paginator: Paginator }> {
  try {
    const res = await fetch(`${BASE_URL}${path}`, {
      next: { revalidate: 60 },
    });
    if (!res.ok) return { items: [], paginator: DEFAULT_PAGINATOR };
    const json: ApiEnvelope<T> = await res.json();
    return {
      items: json.data?.list ?? [],
      paginator: json.data?.paginator ?? DEFAULT_PAGINATOR,
    };
  } catch {
    return { items: [], paginator: DEFAULT_PAGINATOR };
  }
}

// ── Public API ────────────────────────────────────────────────────────────────

export async function getBlogs(page = 1, perPage = 10) {
  return fetchList<BlogItem>(
    `/api/get-blog?page=${page}&per_page=${perPage}`
  );
}

export async function getLifi(page = 1, perPage = 12) {
  return fetchList<LifiItem>(
    `/api/get-lfii?page=${page}&per_page=${perPage}`
  );
}

/** Returns flat array — team has no server-side pagination needed. */
export async function getTeam(perPage = 20): Promise<TeamMember[]> {
  const { items } = await fetchList<TeamMember>(
    `/api/get-teamwork?per_page=${perPage}`
  );
  return items;
}

/** Strip HTML tags to produce a plain-text excerpt. */
export function stripHtml(html: string): string {
  return html.replace(/<[^>]*>/g, "").trim();
}
```

- [ ] **Step 2: Verify build passes**

```bash
npm run build
```

Expected: `✓ Compiled successfully`

- [ ] **Step 3: Commit**

```bash
git add lib/website-api.ts
git commit -m "feat(website): add server-only website API data layer"
```

---

### Task 2: Create the contact Server Action

**Files:**
- Create: `app/actions/contact.ts`

- [ ] **Step 1: Create `app/actions/contact.ts`**

```ts
"use server";

const BASE_URL = (
  process.env.NEXT_PUBLIC_API_URL ?? "https://dev-lam.maaal.site"
).replace(/\/+$/, "");

export interface ContactPayload {
  name: string;
  email: string;
  code: string;
  phone: string;
  message: string;
}

export async function submitContact(
  payload: ContactPayload
): Promise<{ ok: boolean; message: string }> {
  if (!payload.name || !payload.email || !payload.message) {
    return { ok: false, message: "Please fill in all required fields." };
  }

  try {
    const res = await fetch(`${BASE_URL}/api/create-contact-us`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(payload),
      cache: "no-store",
    });
    const json = await res.json().catch(() => ({}));
    if (res.ok && json.status !== false) {
      return {
        ok: true,
        message: json.message ?? "Message sent successfully.",
      };
    }
    return {
      ok: false,
      message: json.message ?? "Something went wrong. Please try again.",
    };
  } catch {
    return { ok: false, message: "Network error. Please try again." };
  }
}
```

- [ ] **Step 2: Verify build passes**

```bash
npm run build
```

Expected: `✓ Compiled successfully`

- [ ] **Step 3: Commit**

```bash
git add app/actions/contact.ts
git commit -m "feat(website): add contact form Server Action"
```

---

### Task 3: Fetch real data in the Home page

**Files:**
- Modify: `app/[locale]/(website)/page.tsx`

- [ ] **Step 1: Update the home page to fetch and pass real data**

Replace the full content of `app/[locale]/(website)/page.tsx`:

```tsx
import Hero from "@/components/website/hero";
import ContactUs from "@/components/website/home/contact-us";
import LifiIndex from "@/components/website/home/lifi-index";
import OurBlogs from "@/components/website/home/our-blogs";
import OurClients from "@/components/website/home/our-clients";
import OurServices from "@/components/website/home/our-services";
import OurTeam from "@/components/website/home/our-team";
import OurWorks from "@/components/website/home/our-works";
import WhoWeAre from "@/components/website/home/who-we-are";
import { FadeIn } from "@/components/website/ui/fade-in";
import { getBlogs, getTeam } from "@/lib/website-api";
import type { Metadata } from "next";
import { getTranslations } from "next-intl/server";

export async function generateMetadata({
  params,
}: {
  params: Promise<{ locale: string }>;
}): Promise<Metadata> {
  const { locale } = await params;
  const t = await getTranslations({ locale, namespace: "metadata" });
  return {
    title: t("title"),
    description: t("description"),
  };
}

export default async function Home(): Promise<React.ReactNode> {
  const t = await getTranslations();

  // Fetch home-page preview data in parallel (ISR-cached)
  const [{ items: blogs }, teamMembers] = await Promise.all([
    getBlogs(1, 3),
    getTeam(4),
  ]);

  return (
    <div className="min-h-screen bg-background">
      <Hero
        title={t("hero.mainTitle")}
        description={t("hero.mainDescription")}
        image={{
          type: "illustration",
          src: "/images/hero/objects.svg",
          alt: t("hero.dataAnalyticsAlt"),
        }}
        buttons={[
          {
            text: t("hero.contactUs"),
            href: "/contact",
            variant: "primary",
            icon: true,
          },
          {
            text: t("common.browseServices"),
            href: "/services",
            variant: "outline",
          },
        ]}
        showBackgroundDecor={true}
      />
      <FadeIn delay={0.2}>
        <WhoWeAre />
      </FadeIn>
      <FadeIn delay={0.2}>
        <OurServices />
      </FadeIn>
      <FadeIn delay={0.2}>
        <OurWorks />
      </FadeIn>
      <FadeIn delay={0.2}>
        <LifiIndex />
      </FadeIn>
      <FadeIn delay={0.2}>
        <OurTeam members={teamMembers} />
      </FadeIn>
      <FadeIn delay={0.2}>
        <OurClients />
      </FadeIn>
      <FadeIn delay={0.2}>
        <OurBlogs blogs={blogs} />
      </FadeIn>
      <FadeIn delay={0.2}>
        <ContactUs />
      </FadeIn>
    </div>
  );
}
```

- [ ] **Step 2: Verify build passes**

```bash
npm run build
```

Expected: `✓ Compiled successfully` (will see type errors — fixed in Tasks 4 & 5)

- [ ] **Step 3: Commit**

```bash
git add "app/[locale]/(website)/page.tsx"
git commit -m "feat(website): fetch real blogs + team data in home page"
```

---

### Task 4: Update OurBlogs to render real API data

**Files:**
- Modify: `components/website/home/our-blogs.tsx`

- [ ] **Step 1: Replace full content of `components/website/home/our-blogs.tsx`**

```tsx
/* eslint-disable @next/next/no-img-element */
"use client";

import { Button } from "@/components/ui/button";
import { Link } from "@/i18n/routing";
import type { BlogItem } from "@/lib/website-api";
import { stripHtml } from "@/lib/website-api";
import { cn } from "@/lib/utils";
import { useTranslations } from "next-intl";

const FALLBACK_IMAGES = [
  "/images/blogs/blog1.jpg",
  "/images/blogs/blog2.jpg",
  "/images/blogs/blog3.jpg",
];

type OurBlogsProps = {
  title?: string;
  ctaLabel?: string;
  ctaHref?: string;
  blogs?: BlogItem[];
  className?: string;
};

export default function OurBlogs({
  title,
  ctaLabel,
  ctaHref = "/blogs",
  blogs,
  className,
}: OurBlogsProps) {
  const t = useTranslations("ourBlogs");

  const displayTitle = title ?? t("title");
  const displayCtaLabel = ctaLabel ?? t("ctaLabel");

  const displayBlogs: BlogItem[] =
    blogs && blogs.length > 0
      ? blogs
      : [0, 1, 2].map((i) => ({
          id: i,
          title: t("blogTitle"),
          description: t("blogDescription"),
          image: FALLBACK_IMAGES[i],
          created_at: "",
        }));

  return (
    <section
      className={cn(
        "w-full px-4 sm:px-6 md:px-8 lg:px-12 xl:px-[120px] py-14 sm:py-16 md:py-20",
        className
      )}
    >
      <div className="mx-auto flex max-w-[1280px] flex-col gap-10">
        <div className="flex flex-col gap-4 items-start justify-between md:flex-row md:items-center">
          <h2 className="text-3xl sm:text-[32px] md:text-[36px] lg:text-[40px] font-bold text-foreground">
            {displayTitle}
          </h2>
          <Link href={ctaHref} className="self-start">
            <Button className="h-12 rounded-[12px] border border-white bg-[#1c75bc] px-6 text-base font-semibold text-white hover:bg-[#1c75bc]/90 gap-2">
              {displayCtaLabel}
              <img
                src="/images/who-we-are/arrow-right.svg"
                alt=""
                className="rotate-180"
                width={20}
                height={20}
              />
            </Button>
          </Link>
        </div>

        <div className="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
          {displayBlogs.map((blog, idx) => {
            const imageSrc = blog.image ?? FALLBACK_IMAGES[idx % 3];
            const excerpt = stripHtml(blog.description).slice(0, 160);
            return (
              <Link
                key={blog.id}
                href={`/blogs/${blog.id}`}
                className="flex h-full flex-col gap-6 rounded-[16px] border border-border bg-white p-6 shadow-[8px_13px_16px_rgba(147,147,147,0.1)] hover:shadow-[8px_13px_24px_rgba(147,147,147,0.15)] transition-shadow"
              >
                <div className="relative h-[234px] w-full overflow-hidden rounded-[8px] bg-[#efefef]">
                  <img
                    src={imageSrc}
                    alt={blog.title}
                    className="h-full w-full object-cover"
                    loading="lazy"
                  />
                </div>
                <div className="flex flex-col gap-6 text-start">
                  <div className="space-y-3">
                    <p className="text-[17px] sm:text-[18px] font-bold leading-[1.4] text-[#0c314f]">
                      {blog.title}
                    </p>
                    <p className="text-[14px] leading-[1.6] text-muted-foreground line-clamp-3">
                      {excerpt}
                    </p>
                  </div>
                  <div className="flex items-center justify-start gap-2 text-[#1c75bc]">
                    <span className="text-[14px] font-medium">
                      {t("readMore")}
                    </span>
                    <img
                      src="/images/blogs/arrow-left.svg"
                      alt=""
                      className="size-4"
                      width={16}
                      height={16}
                    />
                  </div>
                </div>
              </Link>
            );
          })}
        </div>
      </div>
    </section>
  );
}
```

- [ ] **Step 2: Verify build passes**

```bash
npm run build
```

Expected: `✓ Compiled successfully`

- [ ] **Step 3: Commit**

```bash
git add components/website/home/our-blogs.tsx
git commit -m "feat(website): wire OurBlogs to real BlogItem[] API data"
```

---

### Task 5: Update OurTeam (home section) to render real API data

**Files:**
- Modify: `components/website/home/our-team.tsx`

- [ ] **Step 1: Replace full content of `components/website/home/our-team.tsx`**

```tsx
/* eslint-disable @next/next/no-img-element */
"use client";

import { Button } from "@/components/ui/button";
import { Link } from "@/i18n/routing";
import type { TeamMember } from "@/lib/website-api";
import { cn } from "@/lib/utils";
import { useTranslations } from "next-intl";

const FALLBACK_IMAGES = [
  "/images/team/member1.jpg",
  "/images/team/member2.jpg",
  "/images/team/member3.jpg",
  "/images/team/member4.jpg",
];

type OurTeamProps = {
  title?: string;
  ctaLabel?: string;
  ctaHref?: string;
  members?: TeamMember[];
  className?: string;
};

export default function OurTeam({
  title,
  ctaLabel,
  ctaHref = "/our-team",
  members,
  className,
}: OurTeamProps) {
  const t = useTranslations("ourTeam");

  const displayTitle = title ?? t("title");
  const displayCtaLabel = ctaLabel ?? t("ctaLabel");

  // Fallback to mock members when API returns nothing
  const displayMembers: TeamMember[] =
    members && members.length > 0
      ? members
      : [0, 1, 2, 3].map((i) => ({
          id: i,
          name: t("memberName"),
          email: "",
          image: FALLBACK_IMAGES[i],
          created_at: "",
        }));

  return (
    <section
      className={cn(
        "w-full px-4 sm:px-6 md:px-8 lg:px-12 xl:px-[120px] py-14 sm:py-16 md:py-20",
        className
      )}
    >
      <div className="mx-auto flex max-w-[1280px] flex-col gap-10">
        <div className="flex flex-col gap-4 items-start justify-between md:flex-row md:items-center">
          <h2 className="text-3xl sm:text-[32px] md:text-[36px] lg:text-[40px] font-bold text-foreground">
            {displayTitle}
          </h2>
          <Link href={ctaHref} className="self-start">
            <Button className="h-12 rounded-[12px] border border-white bg-[#1c75bc] px-6 text-base font-semibold text-white hover:bg-[#1c75bc]/90 gap-2">
              {displayCtaLabel}
              <img
                src="/images/who-we-are/arrow-right.svg"
                alt=""
                className="rotate-180"
                width={20}
                height={20}
              />
            </Button>
          </Link>
        </div>

        <div className="grid grid-cols-1 gap-8 md:grid-cols-2 xl:grid-cols-4">
          {displayMembers.map((member, index) => {
            const imageSrc =
              member.image && member.image.startsWith("http")
                ? member.image
                : FALLBACK_IMAGES[index % 4];
            return (
              <div
                key={member.id}
                className="flex h-full flex-col items-center gap-6 text-center"
              >
                <div className="relative h-[200px] w-[200px]">
                  <div className="absolute inset-0 rounded-full border-4 border-[#f4b400] p-2">
                    <div className="h-full w-full overflow-hidden rounded-full bg-neutral-100">
                      <img
                        src={imageSrc}
                        alt={member.name}
                        className="h-full w-full object-cover"
                        loading="lazy"
                      />
                    </div>
                  </div>
                </div>
                <div className="flex flex-col gap-3">
                  <p className="text-[20px] sm:text-[21px] md:text-[22px] font-bold text-[#0c314f] leading-[1.4]">
                    {member.name}
                  </p>
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </section>
  );
}
```

- [ ] **Step 2: Verify build passes**

```bash
npm run build
```

Expected: `✓ Compiled successfully`

- [ ] **Step 3: Commit**

```bash
git add components/website/home/our-team.tsx
git commit -m "feat(website): wire OurTeam to real TeamMember[] API data"
```

---

### Task 6: Convert blogs list page to Server Component

**Files:**
- Modify: `app/[locale]/(website)/blogs/page.tsx`

- [ ] **Step 1: Replace full content of `app/[locale]/(website)/blogs/page.tsx`**

```tsx
import BlogsContent from "@/components/website/blogs/blogs-content";
import Hero from "@/components/website/hero";
import { FadeIn } from "@/components/website/ui/fade-in";
import { getBlogs } from "@/lib/website-api";
import { getTranslations } from "next-intl/server";

const BLOGS_PER_PAGE = 9;

export default async function BlogsPage({
  searchParams,
}: {
  searchParams: Promise<{ page?: string }>;
}) {
  const { page } = await searchParams;
  const currentPage = Math.max(1, Number(page ?? 1));

  const t = await getTranslations("blogsPage");
  const { items, paginator } = await getBlogs(currentPage, BLOGS_PER_PAGE);

  return (
    <div className="bg-background relative">
      <Hero
        title={t("hero.title")}
        description={t("hero.description")}
        showOnlyText={true}
      />
      <FadeIn delay={0.2}>
        <BlogsContent
          items={items}
          paginator={paginator}
          currentPage={currentPage}
        />
      </FadeIn>
    </div>
  );
}
```

- [ ] **Step 2: Verify build passes** (will show type errors — fixed in Task 7)

```bash
npm run build
```

- [ ] **Step 3: Commit**

```bash
git add "app/[locale]/(website)/blogs/page.tsx"
git commit -m "feat(website): convert blogs page to async Server Component with pagination"
```

---

### Task 7: Rebuild BlogsContent with real server data

**Files:**
- Modify: `components/website/blogs/blogs-content.tsx`

- [ ] **Step 1: Replace full content of `components/website/blogs/blogs-content.tsx`**

```tsx
"use client";

import { Pagination } from "@/components/ui/pagination";
import BlogCard from "@/components/website/blogs/blog-card";
import { FadeIn } from "@/components/website/ui/fade-in";
import type { BlogItem, Paginator } from "@/lib/website-api";
import { stripHtml } from "@/lib/website-api";
import { Search } from "lucide-react";
import { useTranslations } from "next-intl";
import { useMemo, useState } from "react";

interface BlogsContentProps {
  items: BlogItem[];
  paginator: Paginator;
  currentPage: number;
}

export default function BlogsContent({
  items,
  paginator,
  currentPage,
}: BlogsContentProps) {
  const t = useTranslations("blogsPage");
  const tPagination = useTranslations("ourWorks.pagination");

  // Client-side search filters only the current page (no search API endpoint)
  const [searchQuery, setSearchQuery] = useState("");

  const filteredItems = useMemo(
    () =>
      searchQuery.trim()
        ? items.filter((b) =>
            b.title.toLowerCase().includes(searchQuery.toLowerCase())
          )
        : items,
    [items, searchQuery]
  );

  return (
    <section className="w-full px-4 sm:px-6 md:px-8 lg:px-12 xl:px-[120px] pt-12 sm:pt-14 md:pt-16 pb-20 sm:pb-24 md:pb-28 lg:pb-32 bg-background">
      <div className="flex flex-col gap-6 sm:gap-8 md:gap-10 w-full max-w-[1200px] mx-auto">
        {/* Search Bar */}
        <div className="relative w-full">
          <input
            type="text"
            placeholder={t("searchPlaceholder")}
            value={searchQuery}
            onChange={(e) => setSearchQuery(e.target.value)}
            className="w-full h-14 px-4 pe-12 bg-card border border-border rounded-[12px] text-[14px] text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary"
          />
          <Search className="absolute end-4 top-1/2 -translate-y-1/2 size-6 text-muted-foreground" />
        </div>

        {/* Blogs Grid */}
        {filteredItems.length === 0 ? (
          <p className="py-12 text-center text-muted-foreground">
            {searchQuery ? t("noResults") : t("noBlogs")}
          </p>
        ) : (
          <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 w-full">
            {filteredItems.map((blog, index) => (
              <FadeIn key={blog.id} delay={0.1 * (index % 3)}>
                <BlogCard
                  id={blog.id}
                  title={blog.title}
                  description={stripHtml(blog.description)}
                  image={blog.image ?? "/images/blogs/blog1.jpg"}
                  imageAlt={blog.title}
                />
              </FadeIn>
            ))}
          </div>
        )}

        {/* Pagination — URL-based, triggers server re-render */}
        {paginator.total_pages > 1 && (
          <Pagination
            currentPage={currentPage}
            totalPages={paginator.total_pages}
            totalItems={paginator.total_count}
            itemsPerPage={paginator.per_page}
            showItemsPerPage={false}
            className="border-t-0"
            labels={{
              previous: tPagination("previous"),
              next: tPagination("next"),
              showing: tPagination("showing"),
              of: tPagination("of"),
              itemsPerPage: tPagination("itemsPerPage"),
            }}
          />
        )}
      </div>
    </section>
  );
}
```

- [ ] **Step 2: Add missing translation keys** — open `messages/ar.json` and find `blogsPage`. Add if missing:

```json
"noResults": "لا توجد نتائج للبحث",
"noBlogs": "لا توجد مقالات حاليًا"
```

Open `messages/en.json` and add under `blogsPage`:

```json
"noResults": "No results found",
"noBlogs": "No blogs available"
```

- [ ] **Step 3: Verify build passes**

```bash
npm run build
```

Expected: `✓ Compiled successfully`

- [ ] **Step 4: Commit**

```bash
git add components/website/blogs/blogs-content.tsx messages/ar.json messages/en.json
git commit -m "feat(website): wire BlogsContent to server-fetched data with URL pagination"
```

---

### Task 8: Convert lifi page to Server Component

**Files:**
- Modify: `app/[locale]/(website)/lifi/page.tsx`

- [ ] **Step 1: Replace full content of `app/[locale]/(website)/lifi/page.tsx`**

```tsx
import Hero from "@/components/website/hero";
import LifiContent from "@/components/website/lifi/lifi-content";
import { getLifi } from "@/lib/website-api";
import { getTranslations } from "next-intl/server";

const LIFI_PER_PAGE = 12;

export default async function LifiPage({
  searchParams,
}: {
  searchParams: Promise<{ page?: string }>;
}) {
  const { page } = await searchParams;
  const currentPage = Math.max(1, Number(page ?? 1));

  const t = await getTranslations("lifi");
  const { items, paginator } = await getLifi(currentPage, LIFI_PER_PAGE);

  return (
    <div className="bg-background">
      <Hero
        title={t("hero.title")}
        description={t("hero.description")}
        showOnlyText={true}
      />
      <LifiContent
        items={items}
        paginator={paginator}
        currentPage={currentPage}
      />
    </div>
  );
}
```

- [ ] **Step 2: Verify build passes** (type errors fixed in Task 9)

```bash
npm run build
```

- [ ] **Step 3: Commit**

```bash
git add "app/[locale]/(website)/lifi/page.tsx"
git commit -m "feat(website): convert lifi page to async Server Component with pagination"
```

---

### Task 9: Update LifiCard to support PDF link + rebuild LifiContent

**Files:**
- Modify: `components/website/lifi/lifi-card.tsx`
- Modify: `components/website/lifi/lifi-content.tsx`

- [ ] **Step 1: Update `components/website/lifi/lifi-card.tsx`** to accept `fileUrl` and format date

```tsx
import Image from "next/image";

interface LifiCardProps {
  title: string;
  date: string;       // ISO string e.g. "2026-05-19T18:49:44.000000Z"
  fileUrl?: string;   // PDF link
  coverImage?: string;
}

function formatMonthYear(isoDate: string): string {
  if (!isoDate) return "";
  try {
    return new Date(isoDate).toLocaleDateString("ar-SA", {
      month: "long",
      year: "numeric",
    });
  } catch {
    return isoDate;
  }
}

export default function LifiCard({
  title,
  date,
  fileUrl,
  coverImage = "/images/lifi/book-cover.png",
}: LifiCardProps) {
  const formattedDate = formatMonthYear(date);

  const inner = (
    <div className="bg-card border border-border rounded-2xl p-6 shadow-[8px_13px_16px_0px_rgba(147,147,147,0.1)] flex flex-col gap-10 items-center transition-all hover:shadow-[8px_13px_24px_0px_rgba(147,147,147,0.15)] hover:border-primary-500/20 h-full">
      <div className="relative w-[173px] h-[250px] shrink-0 overflow-hidden">
        <Image
          src={coverImage}
          alt={title}
          fill
          className="object-contain"
          priority={false}
        />
      </div>
      <div className="flex flex-col gap-4 items-start w-full">
        <h3 className="font-bold text-xl leading-[1.4] text-foreground text-start w-full">
          {title}
        </h3>
        {formattedDate && (
          <p className="font-normal text-base leading-[1.4] text-muted-foreground text-start w-full">
            {formattedDate}
          </p>
        )}
      </div>
    </div>
  );

  if (fileUrl) {
    return (
      <a href={fileUrl} target="_blank" rel="noopener noreferrer" className="block h-full">
        {inner}
      </a>
    );
  }

  return inner;
}
```

- [ ] **Step 2: Replace full content of `components/website/lifi/lifi-content.tsx`**

```tsx
"use client";

import { Button } from "@/components/ui/button";
import { Pagination } from "@/components/ui/pagination";
import { Input } from "@/components/ui/input";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { FadeIn } from "@/components/website/ui/fade-in";
import LifiCard from "./lifi-card";
import type { LifiItem, Paginator } from "@/lib/website-api";
import { Search } from "lucide-react";
import { useTranslations } from "next-intl";
import { useMemo, useState } from "react";

const YEARS = ["2026", "2025", "2024", "2023", "2022"];

interface LifiContentProps {
  items: LifiItem[];
  paginator: Paginator;
  currentPage: number;
}

export default function LifiContent({
  items,
  paginator,
  currentPage,
}: LifiContentProps) {
  const t = useTranslations("lifi");
  const tPagination = useTranslations("ourWorks.pagination");

  const MONTHS = [
    t("months.january"), t("months.february"), t("months.march"),
    t("months.april"), t("months.may"), t("months.june"),
    t("months.july"), t("months.august"), t("months.september"),
    t("months.october"), t("months.november"), t("months.december"),
  ];

  const [selectedYear, setSelectedYear] = useState(YEARS[0]);
  const [selectedMonth, setSelectedMonth] = useState(MONTHS[0]);
  const [searchQuery, setSearchQuery] = useState("");

  // Client-side search on current page
  const filteredItems = useMemo(
    () =>
      searchQuery.trim()
        ? items.filter((item) =>
            item.title.toLowerCase().includes(searchQuery.toLowerCase())
          )
        : items,
    [items, searchQuery]
  );

  return (
    <section className="w-full px-4 sm:px-6 md:px-8 lg:px-12 xl:px-[120px] pt-12 sm:pt-14 md:pt-16 pb-20 sm:pb-24 md:pb-28 lg:pb-32">
      <FadeIn delay={0.2}>
        <div className="flex flex-col gap-8 sm:gap-10 md:gap-12 items-start w-full">

          {/* Search and Year Filter */}
          <div className="flex flex-col sm:flex-row gap-4 sm:gap-6 items-stretch sm:items-center justify-start w-full">
            <div className="relative flex-1 order-2 sm:order-1">
              <Input
                type="text"
                placeholder={t("searchPlaceholder")}
                value={searchQuery}
                onChange={(e) => setSearchQuery(e.target.value)}
                className="h-12 sm:h-14 bg-card border-border rounded-xl ps-12 text-sm placeholder:text-muted-foreground"
              />
              <div className="absolute start-4 top-1/2 -translate-y-1/2 pointer-events-none">
                <Search className="w-6 h-6 text-muted-foreground" />
              </div>
            </div>
            <Select value={selectedYear} onValueChange={setSelectedYear}>
              <SelectTrigger className="py-7 sm:h-14 bg-card border-border rounded-xl px-6 text-xl text-foreground order-1 sm:order-2 w-full sm:w-auto">
                <SelectValue />
              </SelectTrigger>
              <SelectContent align="end">
                {YEARS.map((year) => (
                  <SelectItem key={year} value={year}>{year}</SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>

          {/* Year Title and Month Filters */}
          <div className="flex flex-col gap-4 sm:gap-6 w-full">
            <h2 className="font-bold text-2xl sm:text-3xl leading-[1.4] text-foreground text-start w-full">
              {t("yearTitle", { year: selectedYear })}
            </h2>
            <div className="border border-border rounded-full w-full overflow-hidden">
              <div className="overflow-x-scroll scrollbar-hide px-3 py-2">
                <div className="flex gap-3 items-center justify-start flex-row-reverse w-max">
                  {MONTHS.map((month) => (
                    <Button
                      key={month}
                      onClick={() => setSelectedMonth(month)}
                      variant={selectedMonth === month ? "default" : "ghost"}
                      size="default"
                      className={`px-6 h-auto py-2 rounded-full text-base font-medium whitespace-nowrap shrink-0 ${
                        selectedMonth === month
                          ? "bg-[#1c75bc] hover:bg-[#1c75bc]/90 text-white"
                          : "text-foreground hover:bg-muted"
                      }`}
                    >
                      {month}
                    </Button>
                  ))}
                </div>
              </div>
            </div>
          </div>

          {/* Lifi Grid */}
          {filteredItems.length === 0 ? (
            <p className="py-12 text-center text-muted-foreground w-full">
              {t("noResults")}
            </p>
          ) : (
            <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 sm:gap-5 md:gap-6 w-full">
              {filteredItems.map((item, index) => (
                <FadeIn key={item.id} delay={0.1 * (index % 4)}>
                  <LifiCard
                    title={item.title}
                    date={item.created_at}
                    fileUrl={item.file}
                  />
                </FadeIn>
              ))}
            </div>
          )}

          {/* Pagination */}
          {paginator.total_pages > 1 && (
            <Pagination
              currentPage={currentPage}
              totalPages={paginator.total_pages}
              totalItems={paginator.total_count}
              itemsPerPage={paginator.per_page}
              showItemsPerPage={false}
              className="border-t-0 w-full"
              labels={{
                previous: tPagination("previous"),
                next: tPagination("next"),
                showing: tPagination("showing"),
                of: tPagination("of"),
                itemsPerPage: tPagination("itemsPerPage"),
              }}
            />
          )}
        </div>
      </FadeIn>
    </section>
  );
}
```

- [ ] **Step 3: Add missing translation key** — in both `messages/ar.json` and `messages/en.json`, under the `lifi` key, add:

`ar.json`:
```json
"noResults": "لا توجد نتائج"
```

`en.json`:
```json
"noResults": "No results found"
```

- [ ] **Step 4: Verify build passes**

```bash
npm run build
```

Expected: `✓ Compiled successfully`

- [ ] **Step 5: Commit**

```bash
git add components/website/lifi/lifi-card.tsx components/website/lifi/lifi-content.tsx messages/ar.json messages/en.json
git commit -m "feat(website): wire LifiContent to real API data with PDF links and pagination"
```

---

### Task 10: Convert our-team page to Server Component

**Files:**
- Modify: `app/[locale]/(website)/our-team/page.tsx`

- [ ] **Step 1: Replace full content of `app/[locale]/(website)/our-team/page.tsx`**

```tsx
import Hero from "@/components/website/hero";
import OurTeam from "@/components/website/home/our-team";
import { getTeam } from "@/lib/website-api";
import { getTranslations } from "next-intl/server";

export default async function OurTeamPage() {
  const t = await getTranslations("ourTeamPage");
  const members = await getTeam(50);

  return (
    <div className="bg-background relative">
      <Hero
        title={t("hero.title")}
        description={t("hero.description")}
        showOnlyText={true}
      />
      <OurTeam members={members} />
    </div>
  );
}
```

- [ ] **Step 2: Verify build passes**

```bash
npm run build
```

Expected: `✓ Compiled successfully`

- [ ] **Step 3: Commit**

```bash
git add "app/[locale]/(website)/our-team/page.tsx"
git commit -m "feat(website): convert our-team page to Server Component with real API data"
```

---

### Task 11: Convert contact-us page to Server Component

**Files:**
- Modify: `app/[locale]/(website)/contact-us/page.tsx`

- [ ] **Step 1: Replace full content of `app/[locale]/(website)/contact-us/page.tsx`**

```tsx
import Hero from "@/components/website/hero";
import ContactUs from "@/components/website/home/contact-us";
import { getTranslations } from "next-intl/server";

export default async function ContactUsPage() {
  const t = await getTranslations("contactUsPage");

  return (
    <div className="bg-background relative">
      <Hero
        title={t("hero.title")}
        description={t("hero.description")}
        showOnlyText={true}
      />
      <ContactUs />
    </div>
  );
}
```

- [ ] **Step 2: Verify build passes**

```bash
npm run build
```

Expected: `✓ Compiled successfully`

- [ ] **Step 3: Commit**

```bash
git add "app/[locale]/(website)/contact-us/page.tsx"
git commit -m "feat(website): convert contact-us page to Server Component"
```

---

### Task 12: Wire the contact form to the Server Action

**Files:**
- Modify: `components/website/home/contact-us.tsx`

The existing form has uncontrolled inputs. This task adds controlled state, calls the `submitContact` Server Action, and shows success/error feedback.

- [ ] **Step 1: Update imports at the top of `components/website/home/contact-us.tsx`**

Add these imports (keep all existing imports):

```tsx
import { submitContact } from "@/app/actions/contact";
import { useTransition, useState } from "react";
```

Note: `useState` is already imported via React — replace the existing React import block:

```tsx
import { useTransition, useState } from "react";
```

- [ ] **Step 2: Add form state inside the `ContactUs` component function**, before the `return` statement

Find the line `const defaultContactInfo` and insert this block just before the `return`:

```tsx
  const [isPending, startTransition] = useTransition();
  const [formState, setFormState] = useState({
    name: "", email: "", code: "+966", phone: "", message: "",
  });
  const [result, setResult] = useState<{ ok: boolean; message: string } | null>(null);

  const handleChange = (field: keyof typeof formState) =>
    (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) =>
      setFormState((prev) => ({ ...prev, [field]: e.target.value }));

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    setResult(null);
    startTransition(async () => {
      const res = await submitContact(formState);
      setResult(res);
      if (res.ok) {
        setFormState({ name: "", email: "", code: "+966", phone: "", message: "" });
      }
    });
  };
```

- [ ] **Step 3: Wire the form tag and inputs**

Replace the `<form className="space-y-6">` opening tag with:

```tsx
<form className="space-y-6" onSubmit={handleSubmit}>
```

Replace the name `Input`:
```tsx
<Input
  placeholder={t("namePlaceholder")}
  className="text-start rounded-[12px] border border-border h-[56px]"
  value={formState.name}
  onChange={handleChange("name")}
  required
/>
```

Replace the email `Input`:
```tsx
<Input
  type="email"
  placeholder={t("emailPlaceholder")}
  className="text-start rounded-[12px] border border-border h-[56px]"
  value={formState.email}
  onChange={handleChange("email")}
  required
/>
```

Replace the phone code `Select` `defaultValue` with `value` and add `onValueChange`:
```tsx
<Select
  value={formState.code}
  onValueChange={(v) => setFormState((prev) => ({ ...prev, code: v }))}
>
```

Replace the phone number `Input`:
```tsx
<Input
  placeholder={t("phonePlaceholder")}
  className="flex-1 text-start rounded-[12px] border border-[#e7e7e7] h-[56px]"
  value={formState.phone}
  onChange={handleChange("phone")}
/>
```

Replace the `Textarea`:
```tsx
<Textarea
  placeholder={t("messagePlaceholder")}
  className="min-h-[120px] text-start rounded-[12px] border border-border"
  value={formState.message}
  onChange={handleChange("message")}
  required
/>
```

- [ ] **Step 4: Add feedback message and loading state on the submit button**

Replace the submit `Button`:
```tsx
{result && (
  <p
    className={`text-sm text-center font-medium ${
      result.ok ? "text-green-600" : "text-destructive"
    }`}
  >
    {result.message}
  </p>
)}
<Button
  type="submit"
  disabled={isPending}
  className="w-full h-12 rounded-[12px] bg-[#1c75bc] text-white gap-2 hover:bg-[#1c75bc]/90 flex-row disabled:opacity-60"
>
  <span>{isPending ? t("sending") : t("send")}</span>
  <Send className="h-4 w-4" />
</Button>
```

- [ ] **Step 5: Add the `sending` translation key** to both locale files

`messages/ar.json` under `contact`:
```json
"sending": "جاري الإرسال..."
```

`messages/en.json` under `contact`:
```json
"sending": "Sending..."
```

- [ ] **Step 6: Verify build passes**

```bash
npm run build
```

Expected: `✓ Compiled successfully`

- [ ] **Step 7: Commit**

```bash
git add components/website/home/contact-us.tsx messages/ar.json messages/en.json
git commit -m "feat(website): wire contact form to submitContact Server Action"
```

---

## Self-Review

**Spec coverage check:**
- ✅ `lib/website-api.ts` — Task 1
- ✅ `app/actions/contact.ts` — Task 2
- ✅ Home page fetches 3 blogs + 4 team members — Task 3
- ✅ `OurBlogs` accepts `BlogItem[]`, strips HTML, uses fallback image — Task 4
- ✅ `OurTeam` accepts `TeamMember[]`, handles missing role/bio — Task 5
- ✅ Blogs page is Server Component with `searchParams` pagination — Task 6
- ✅ `BlogsContent` accepts server data, URL pagination, client search — Task 7
- ✅ Lifi page is Server Component with `searchParams` pagination — Task 8
- ✅ `LifiContent` + `LifiCard` wired to real data with PDF links — Task 9
- ✅ Our Team page is Server Component — Task 10
- ✅ Contact Us page is Server Component — Task 11
- ✅ Contact form wired to Server Action with loading + feedback — Task 12
- ✅ Blog detail page intentionally unchanged (no single-blog API)
- ✅ About Us, Our Services, Our Work intentionally unchanged
- ✅ ISR `revalidate: 60` applied in `lib/website-api.ts`
- ✅ `dev-lam.maaal.site` already in `next.config.ts` remotePatterns

**Placeholder scan:** No TBDs, all code is complete.

**Type consistency:** `BlogItem`, `LifiItem`, `TeamMember`, `Paginator`, `stripHtml` all defined in Task 1 and referenced by exact same names in all subsequent tasks.
