================================================================
Repopack Output File
================================================================

This file was generated by Repopack on: 2024-08-19T13:56:35.510Z

Purpose:
--------
This file contains a packed representation of the entire repository's contents.
It is designed to be easily consumable by AI systems for analysis, code review,
or other automated processes.

File Format:
------------
The content is organized as follows:
1. This header section
2. Repository structure
3. Multiple file entries, each consisting of:
  a. A separator line (================)
  b. The file path (File: path/to/file)
  c. Another separator line
  d. The full contents of the file
  e. A blank line

Usage Guidelines:
-----------------
1. This file should be treated as read-only. Any changes should be made to the
  original repository files, not this packed version.
2. When processing this file, use the separators and "File:" markers to
  distinguish between different files in the repository.
3. Be aware that this file may contain sensitive information. Handle it with
  the same level of security as you would the original repository.

Notes:
------
- Some files may have been excluded based on .gitignore rules and Repopack's
  configuration.
- Binary files are not included in this packed representation. Please refer to
  the Repository Structure section for a complete list of file paths, including
  binary files.



For more information about Repopack, visit: https://github.com/yamadashy/repopack

================================================================
Repository Structure
================================================================
components/
  ActionForm.tsx
  AuthButton.tsx
  CampaignActionFields.tsx
  ContextWrapper.tsx
  Dashboard.tsx
  DashboardSKeleton.tsx
  layout.tsx
  ManageSubscriberActionFields.tsx
  MinimalLayout.tsx
  ProtectedRoute.tsx
  Sidebar.tsx
  SonCreationForm.tsx
  SonDetailsForm.tsx
  SonList.tsx
  SonListSkeleton.tsx
  TransactionalEmailActionFields.tsx
  WebhookInfo.tsx
contexts/
  AuthContext.tsx
  ListContext.tsx
  SonContext.tsx
  TemplateContext.tsx
hooks/
  useCustomToast.ts
  useInitialData.ts
  useLists.ts
  useSon.ts
  useSons.ts
  useTemplates.ts
  useWebhook.ts
lib/
  api-client.ts
  schemas.ts
  types.ts
  utils.ts
pages/
  auth/
    verify.tsx
  sons/
    [id].tsx
    index.tsx
    new.tsx
  _app.tsx
  _document.tsx
  index.tsx
  login.tsx
  settings.tsx
public/
  fonts/
    inter-var-latin.woff2
  favicon.ico
  next.svg
  vercel.svg
styles/
  globals.css
types/
  next-auth.d.ts
.eslintrc.json
.gitignore
components.json
next.config.mjs
package.json
postcss.config.mjs
README.md
tailwind.config.ts
tsconfig.json

================================================================
Repository Files
================================================================

================
File: components/ActionForm.tsx
================
import React from "react";
import { UseFormReturn } from "react-hook-form";
import {
  FormField,
  FormItem,
  FormLabel,
  FormControl,
  FormMessage,
  FormDescription,
} from "@/components/ui/form";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Trash2 } from "lucide-react";
import { CampaignActionFields } from "./CampaignActionFields";
import { ManageSubscriberActionFields } from "./ManageSubscriberActionFields";
import { TransactionalEmailActionFields } from "./TransactionalEmailActionFields";
import { ListmonkList, ListmonkTemplate } from "@/lib/types";

interface ActionFormProps {
  form: UseFormReturn<any>;
  index: number;
  remove: (index: number) => void;
  lists: ListmonkList[];
  templates: ListmonkTemplate[];
}

export function ActionForm({
  form,
  index,
  remove,
  lists,
  templates,
}: ActionFormProps) {
  const actionType: string = form.watch(`actions.${index}.type`);
  const trigger: string = form.watch("trigger");

  return (
    <Card className="border border-gray-200">
      <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
        <CardTitle className="text-sm font-medium">
          Action {index + 1}
        </CardTitle>
        <Button
          type="button"
          onClick={() => remove(index)}
          variant="ghost"
          size="sm"
        >
          <Trash2 className="h-4 w-4" />
        </Button>
      </CardHeader>
      <CardContent className="space-y-4">
        <FormField
          control={form.control}
          name={`actions.${index}.type`}
          render={({ field }) => (
            <FormItem>
              <FormLabel>Action Type</FormLabel>
              <Select
                onValueChange={(value: string) => {
                  field.onChange(value);
                  form.setValue(`actions.${index}.parameters`, {});
                }}
                value={field.value}
              >
                <FormControl>
                  <SelectTrigger>
                    <SelectValue placeholder="Select an action type" />
                  </SelectTrigger>
                </FormControl>
                <SelectContent>
                  <SelectItem value="send_transactional_email">
                    Send Transactional Email
                  </SelectItem>
                  <SelectItem value="manage_subscriber">
                    Manage Subscriber
                  </SelectItem>
                  <SelectItem value="create_campaign">
                    Create Campaign
                  </SelectItem>
                </SelectContent>
              </Select>
              {actionType === "manage_subscriber" && (
                <FormDescription>
                  <p>
                    This action will automatically create the subscriber on
                    listmonk if he doesn't exist already.
                  </p>
                  Subscribers will be automatically added or removed to lists
                  with the same name as the newsletters on Ghost.
                </FormDescription>
              )}

              <FormMessage />
            </FormItem>
          )}
        />

        {actionType === "create_campaign" && (
          <CampaignActionFields
            form={form}
            index={index}
            lists={lists}
            templates={templates}
          />
        )}

        {actionType === "manage_subscriber" && trigger === "member_created" && (
          <ManageSubscriberActionFields
            form={form}
            index={index}
            lists={lists}
          />
        )}

        {actionType === "send_transactional_email" && (
          <TransactionalEmailActionFields
            form={form}
            index={index}
            templates={templates}
          />
        )}
      </CardContent>
    </Card>
  );
}

================
File: components/AuthButton.tsx
================
import { useAuthContext } from "@/contexts/AuthContext";
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { LogIn, LogOut } from "lucide-react";
import { useRouter } from "next/router";

export const AuthButton = () => {
  const { user, logout } = useAuthContext();
  const router = useRouter();

  const handleLogout = async () => {
    await logout();
    router.push("/login");
  };

  return (
    <>
      {user ? (
        <Button onClick={handleLogout}>
          <LogOut className="mr-2 h-4 w-4" />
          Logout
        </Button>
      ) : (
        <Link href="/login">
          <Button>
            <LogIn className="mr-2 h-4 w-4" />
            Login
          </Button>
        </Link>
      )}
    </>
  );
};

================
File: components/CampaignActionFields.tsx
================
import React from "react";
import {
  FormField,
  FormItem,
  FormLabel,
  FormControl,
  FormMessage,
  FormDescription,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { X } from "lucide-react";
import { CampaignActionFieldsProps } from "@/lib/types";

export function CampaignActionFields({
  form,
  index,
  lists,
  templates,
}: CampaignActionFieldsProps) {
  return (
    <div className="space-y-4">
      <FormField
        control={form.control}
        name={`actions.${index}.parameters.subject`}
        render={({ field }) => (
          <FormItem>
            <FormLabel>Subject</FormLabel>
            <FormControl>
              <Input {...field} placeholder="Enter campaign subject" />
            </FormControl>
            <FormDescription>
              The subject line for your campaign email.
            </FormDescription>
            <FormMessage />
          </FormItem>
        )}
      />

      <FormField
        control={form.control}
        name={`actions.${index}.parameters.type`}
        render={({ field }) => (
          <FormItem>
            <FormLabel>Campaign Type</FormLabel>
            <Select onValueChange={field.onChange} value={field.value}>
              <FormControl>
                <SelectTrigger>
                  <SelectValue placeholder="Select campaign type" />
                </SelectTrigger>
              </FormControl>
              <SelectContent>
                <SelectItem value="regular">Regular</SelectItem>
                <SelectItem value="optin">Opt-in</SelectItem>
              </SelectContent>
            </Select>
            <FormDescription>
              Choose the type of campaign you want to create.
            </FormDescription>
            <FormMessage />
          </FormItem>
        )}
      />

      <FormField
        control={form.control}
        name={`actions.${index}.parameters.content_type`}
        render={({ field }) => (
          <FormItem>
            <FormLabel>Content Type</FormLabel>
            <Select onValueChange={field.onChange} value={field.value}>
              <FormControl>
                <SelectTrigger>
                  <SelectValue placeholder="Select content type" />
                </SelectTrigger>
              </FormControl>
              <SelectContent>
                <SelectItem value="richtext">Rich Text</SelectItem>
                <SelectItem value="html">HTML</SelectItem>
                <SelectItem value="markdown">Markdown</SelectItem>
                <SelectItem value="plain">Plain Text</SelectItem>
              </SelectContent>
            </Select>
            <FormDescription>
              Choose the content type for your campaign.
            </FormDescription>
            <FormMessage />
          </FormItem>
        )}
      />

      <FormField
        control={form.control}
        name={`actions.${index}.parameters.body`}
        render={({ field }) => (
          <FormItem>
            <FormLabel>Campaign Body</FormLabel>
            <FormControl>
              <Textarea
                {...field}
                placeholder="Enter your campaign content here"
                rows={10}
              />
            </FormControl>
            <FormDescription>
              The main content of your campaign. Use the format specified in the
              Content Type field.
            </FormDescription>
            <FormMessage />
          </FormItem>
        )}
      />

      <FormField
        control={form.control}
        name={`actions.${index}.parameters.lists`}
        render={({ field }) => (
          <FormItem>
            <FormLabel>Lists</FormLabel>
            <FormControl>
              <Select
                onValueChange={(value) => {
                  const newLists = [...(field.value || []), parseInt(value)];
                  field.onChange(newLists);
                }}
              >
                <SelectTrigger>
                  <SelectValue placeholder="Select lists" />
                </SelectTrigger>
                <SelectContent>
                  {lists.map((list) => (
                    <SelectItem key={list.id} value={list.id.toString()}>
                      {list.name}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </FormControl>
            <div className="mt-2 flex flex-wrap gap-2">
              {field.value?.map((listId: number) => {
                const list = lists.find((l) => l.id === listId);
                return (
                  <Badge key={listId} variant="secondary" className="px-2 py-1">
                    {list ? list.name : `List ${listId}`}
                    <Button
                      variant="ghost"
                      size="sm"
                      className="ml-1 h-4 w-4 p-0"
                      onClick={() => {
                        const newLists = field.value.filter(
                          (id: number) => id !== listId
                        );
                        field.onChange(newLists);
                      }}
                    >
                      <X className="h-3 w-3" />
                    </Button>
                  </Badge>
                );
              })}
            </div>
            <FormDescription>
              Select the lists to send this campaign to.
            </FormDescription>
            <FormMessage />
          </FormItem>
        )}
      />

      <FormField
        control={form.control}
        name={`actions.${index}.parameters.template_id`}
        render={({ field }) => (
          <FormItem>
            <FormLabel>Template</FormLabel>
            <FormControl>
              <Select
                onValueChange={(value) => field.onChange(parseInt(value))}
                value={field.value?.toString()}
              >
                <SelectTrigger>
                  <SelectValue placeholder="Select template" />
                </SelectTrigger>
                <SelectContent>
                  {templates.map((template) => (
                    <SelectItem
                      key={template.id}
                      value={template.id.toString()}
                    >
                      {template.name}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </FormControl>
            <FormDescription>
              Choose the template for your campaign email.
            </FormDescription>
            <FormMessage />
          </FormItem>
        )}
      />

      <FormField
        control={form.control}
        name={`actions.${index}.parameters.tags`}
        render={({ field }) => (
          <FormItem>
            <FormLabel>Tags</FormLabel>
            <FormControl>
              <Input {...field} placeholder="Enter comma-separated tags" />
            </FormControl>
            <FormDescription>
              Add tags to categorize your campaign (comma-separated).
            </FormDescription>
            <FormMessage />
          </FormItem>
        )}
      />

      <FormField
        control={form.control}
        name={`actions.${index}.parameters.send_now`}
        render={({ field }) => (
          <FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
            <div className="space-y-0.5">
              <FormLabel className="text-base">Send Now</FormLabel>
              <FormDescription>
                Toggle to send the campaign immediately upon creation.
              </FormDescription>
            </div>
            <FormControl>
              <Switch checked={field.value} onCheckedChange={field.onChange} />
            </FormControl>
          </FormItem>
        )}
      />
    </div>
  );
}

================
File: components/ContextWrapper.tsx
================
import { SonProvider } from "@/contexts/SonContext";
import { ListProvider } from "@/contexts/ListContext";
import { TemplateProvider } from "@/contexts/TemplateContext";

export default function ContextWrapper({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <SonProvider>
      <ListProvider>
        <TemplateProvider>{children}</TemplateProvider>
      </ListProvider>
    </SonProvider>
  );
}

================
File: components/Dashboard.tsx
================
import { useEffect, useState } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import {
  BarChart,
  Bar,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  Legend,
} from "recharts";
import { ChartContainer, ChartConfig } from "@/components/ui/chart";
import { DashboardSkeleton } from "./DashboardSKeleton";

// Mock data - replace with actual API calls in production
const recentActivity = [
  {
    id: 1,
    action: 'Son "Welcome Email" triggered',
    timestamp: "2024-08-10T10:30:00Z",
  },
  {
    id: 2,
    action: 'New Son "Survey Request" created',
    timestamp: "2024-08-10T09:15:00Z",
  },
  {
    id: 3,
    action: 'Son "Monthly Newsletter" modified',
    timestamp: "2024-08-09T16:45:00Z",
  },
];

const sonStats = [
  { name: "Welcome Email", executions: 120, success: 115, failure: 5 },
  { name: "Survey Request", executions: 80, success: 78, failure: 2 },
  { name: "Monthly Newsletter", executions: 50, success: 50, failure: 0 },
];

const chartConfig = {
  executions: {
    label: "Executions",
    color: "#2563eb",
  },
  success: {
    label: "Success",
    color: "#F97316",
  },
  failure: {
    label: "Failure",
    color: "#d1d5db",
  },
} satisfies ChartConfig;

export default function Dashboard() {
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    // Simulate API call
    setTimeout(() => setIsLoading(false), 1000);
  }, []);

  if (isLoading) {
    return <DashboardSkeleton />;
  }

  return (
    <div className="flex-1 space-y-4">
      <h1 className="text-2xl font-bold">Dashboard</h1>

      <div className="grid gap-4 md:grid-cols-2">
        <Card>
          <CardHeader>
            <CardTitle>Recent Activity</CardTitle>
          </CardHeader>
          <CardContent>
            {recentActivity.map((activity) => (
              <Alert key={activity.id} className="mb-2">
                <AlertTitle>{activity.action}</AlertTitle>
                <AlertDescription>
                  {new Date(activity.timestamp).toLocaleString()}
                </AlertDescription>
              </Alert>
            ))}
          </CardContent>
        </Card>

        <Card>
          <CardHeader>
            <CardTitle>Son Performance</CardTitle>
          </CardHeader>
          <CardContent>
            <ChartContainer
              config={chartConfig}
              className="min-h-[200px] w-full"
            >
              <BarChart data={sonStats}>
                <CartesianGrid strokeDasharray="3 3" />
                <XAxis dataKey="name" />
                <YAxis />
                <Tooltip />
                <Legend />
                <Bar dataKey="success" fill="var(--color-success)" />
                <Bar dataKey="failure" fill="var(--color-failure)" />
              </BarChart>
            </ChartContainer>
          </CardContent>
        </Card>
      </div>
    </div>
  );
}

================
File: components/DashboardSKeleton.tsx
================
import { Card, CardContent, CardHeader } from "@/components/ui/card";

export function DashboardSkeleton() {
  return (
    <div className="flex-1 space-y-4">
      <div className="h-8 w-1/4 bg-gray-200 rounded animate-pulse"></div>

      <div className="grid gap-4 md:grid-cols-2">
        <Card>
          <CardHeader>
            <div className="h-6 w-1/3 bg-gray-200 rounded animate-pulse"></div>
          </CardHeader>
          <CardContent>
            <div className="space-y-2">
              {[1, 2, 3].map((i) => (
                <div
                  key={i}
                  className="h-12 bg-gray-200 rounded animate-pulse"
                ></div>
              ))}
            </div>
          </CardContent>
        </Card>

        <Card>
          <CardHeader>
            <div className="h-6 w-1/3 bg-gray-200 rounded animate-pulse"></div>
          </CardHeader>
          <CardContent>
            <div className="h-40 bg-gray-200 rounded animate-pulse"></div>
          </CardContent>
        </Card>
      </div>
    </div>
  );
}

================
File: components/layout.tsx
================
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Menu } from "lucide-react";
import ContextWrapper from "@/components/ContextWrapper";
import { Sidebar } from "@/components/Sidebar";
import { AuthButton } from "./AuthButton";

export default function Layout({ children }: { children: React.ReactNode }) {
  const [isSidebarOpen, setIsSidebarOpen] = useState(false);

  return (
    <div className="flex">
      <Sidebar isOpen={isSidebarOpen} />
      <div className="flex-1 flex flex-col">
        <header className="bg-white shadow p-4 flex justify-between items-center">
          <Button
            variant="ghost"
            className="md:hidden"
            onClick={() => setIsSidebarOpen(!isSidebarOpen)}
          >
            <Menu className="h-6 w-6" />
          </Button>
          <h1 className="text-xl font-bold">Ghost-Listmonk Connector</h1>
          <AuthButton />
        </header>
        <main className="flex-1 bg-gray-100 p-4 overflow-y-auto">
          <ContextWrapper>{children}</ContextWrapper>
        </main>
      </div>
    </div>
  );
}

================
File: components/ManageSubscriberActionFields.tsx
================
import React from "react";
import {
  FormField,
  FormItem,
  FormLabel,
  FormControl,
  FormMessage,
  FormDescription,
} from "@/components/ui/form";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { X } from "lucide-react";
import { ManageSubscriberActionFieldsProps } from "@/lib/types";

export function ManageSubscriberActionFields({
  form,
  index,
  lists,
}: ManageSubscriberActionFieldsProps) {
  return (
    <div className="space-y-4">
      <FormField
        control={form.control}
        name={`actions.${index}.parameters.lists`}
        render={({ field }) => (
          <FormItem>
            <FormLabel>Lists</FormLabel>
            <FormControl>
              <Select
                onValueChange={(value) => {
                  const newLists = [...(field.value || []), parseInt(value)];
                  field.onChange(newLists);
                }}
              >
                <SelectTrigger>
                  <SelectValue placeholder="Select lists" />
                </SelectTrigger>
                <SelectContent>
                  {lists.map((list) => (
                    <SelectItem key={list.id} value={list.id.toString()}>
                      {list.name}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </FormControl>
            <div className="mt-2 flex flex-wrap gap-2">
              {field.value?.map((listId: number) => {
                const list = lists.find((l) => l.id === listId);
                return (
                  <Badge key={listId} variant="secondary" className="px-2 py-1">
                    {list ? list.name : `List ${listId}`}
                    <Button
                      variant="ghost"
                      size="sm"
                      className="ml-1 h-4 w-4 p-0"
                      onClick={() => {
                        const newLists = field.value.filter(
                          (id: number) => id !== listId
                        );
                        field.onChange(newLists);
                      }}
                    >
                      <X className="h-3 w-3" />
                    </Button>
                  </Badge>
                );
              })}
            </div>
            <FormDescription>
              Default lists to add this user on creation. (Apart from the lists
              for the ghost blog newsletters) p.e. &quot;New Subscribers&quot;
            </FormDescription>
            <FormMessage />
          </FormItem>
        )}
      />
      {/* Add more fields specific to manage_subscriber action if needed */}
    </div>
  );
}

================
File: components/MinimalLayout.tsx
================
import ContextWrapper from "@/components/ContextWrapper";

export default function MinimalLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return <main className="flex min-h-screen p-4">{children}</main>;
}

================
File: components/ProtectedRoute.tsx
================
import { useEffect } from "react";
import { useRouter } from "next/router";
import { useAuthContext } from "@/contexts/AuthContext";

export function ProtectedRoute({ children }: { children: React.ReactNode }) {
  const { user, loading } = useAuthContext();
  const router = useRouter();

  useEffect(() => {
    if (!loading && !user) {
      router.push("/login");
    }
  }, [user, loading, router]);

  if (loading) {
    return <div>Loading...</div>;
  }

  return user ? <>{children}</> : null;
}

================
File: components/Sidebar.tsx
================
// components/Sidebar.tsx


import { usePathname } from "next/navigation";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import {
  LayoutDashboard,
  ListPlus,
  ListTree,
  Settings,
} from "lucide-react";

export const Sidebar = ({ isOpen }: { isOpen: boolean }) => {
  const pathname = usePathname();
  

  const menuItems = [
    { href: "/", label: "Dashboard", icon: LayoutDashboard },
    { href: "/sons/new", label: "Create Son", icon: ListPlus },
    { href: "/sons", label: "Manage Sons", icon: ListTree },
    { href: "/settings", label: "Settings", icon: Settings },
  ];

  

  return (
    <aside
      className={`bg-primary text-primary-foreground w-64 min-h-screen p-4 ${
        isOpen ? "" : "hidden"
      } md:block`}
    >
      <nav>
        <ul>
          {menuItems.map((item) => (
            <li key={item.href} className="mb-2">
              <Link href={item.href}>
                <Button
                  variant={pathname === item.href ? "secondary" : "ghost"}
                  className="w-full justify-start"
                >
                  <item.icon className="mr-2 h-4 w-4" />
                  {item.label}
                </Button>
              </Link>
            </li>
          ))}
        </ul>
      </nav>
    </aside>
  );
};

================
File: components/SonCreationForm.tsx
================
import React from "react";
import { useForm, useFieldArray } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import { Form } from "@/components/ui/form";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { PlusCircle } from "lucide-react";
import { useCustomToast } from "@/hooks/useCustomToast";
import { SonDetailsForm } from "./SonDetailsForm";
import { ActionForm } from "./ActionForm";

// Import or define your schema here
import { sonSchema } from "@/lib/schemas";
import { useSonContext } from "@/contexts/SonContext";
import { useListContext } from "@/contexts/ListContext";
import { useTemplateContext } from "@/contexts/TemplateContext";

type SonFormValues = z.infer<typeof sonSchema>;

export default function SonCreationForm() {
  const { createSon } = useSonContext();
  const { showToast } = useCustomToast();
  const router = useRouter();
  const { lists, loading: listsLoading, error: listsError } = useListContext();
  const {
    templates,
    loading: templatesLoading,
    error: templatesError,
  } = useTemplateContext();

  const form = useForm<SonFormValues>({
    resolver: zodResolver(sonSchema),
    defaultValues: {
      name: "",
      trigger: "member_created",
      delay: "0s",
      enabled: true,
      actions: [
        {
          type: "send_transactional_email",
          parameters: {},
        },
      ],
    },
  });

  const { fields, append, remove } = useFieldArray({
    control: form.control,
    name: "actions",
  });

  const onSubmit = async (data: SonFormValues) => {
    try {
      await createSon(data);
      showToast("Success", "Son created successfully");
      router.push("/sons");
    } catch (error) {
      showToast("Error", "Failed to create Son", "destructive");
      console.error("Failed to create Son:", error);
    }
  };

  if (listsLoading || templatesLoading) {
    return (
      <div className="flex justify-center items-center h-full">Loading...</div>
    );
  }

  if (listsError || templatesError) {
    return (
      <div className="text-red-500">
        Error: {listsError?.message || templatesError?.message}
      </div>
    );
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
        <SonDetailsForm form={form} />

        <Card>
          <CardHeader>
            <CardTitle>Actions</CardTitle>
          </CardHeader>
          <CardContent className="space-y-6">
            {fields.map((field, index) => (
              <ActionForm
                key={field.id}
                form={form}
                index={index}
                remove={remove}
                lists={lists}
                templates={templates}
              />
            ))}

            <Button
              type="button"
              onClick={() =>
                append({ type: "send_transactional_email", parameters: {} })
              }
              variant="outline"
              className="w-full"
            >
              <PlusCircle className="mr-2 h-4 w-4" /> Add Action
            </Button>
          </CardContent>
        </Card>

        <Button type="submit" className="w-full">
          Create Son
        </Button>
      </form>
    </Form>
  );
}

================
File: components/SonDetailsForm.tsx
================
import React from "react";
import {
  FormField,
  FormItem,
  FormLabel,
  FormControl,
  FormMessage,
  FormDescription,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { SonDetailsFormProps } from "@/lib/types";
import { Switch } from "@/components/ui/switch";

export function SonDetailsForm({ form }: SonDetailsFormProps) {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Son Details</CardTitle>
      </CardHeader>
      <CardContent className="space-y-4">
        <FormField
          control={form.control}
          name="name"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Son Name</FormLabel>
              <FormControl>
                <Input placeholder="Enter Son name" {...field} />
              </FormControl>
              <FormDescription>
                Give your Son a unique and descriptive name.
              </FormDescription>
              <FormMessage />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="trigger"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Trigger</FormLabel>
              <Select onValueChange={field.onChange} defaultValue={field.value}>
                <FormControl>
                  <SelectTrigger>
                    <SelectValue placeholder="Select a trigger" />
                  </SelectTrigger>
                </FormControl>
                <SelectContent>
                  <SelectItem value="member_created">Member Created</SelectItem>
                  <SelectItem value="member_deleted">Member Deleted</SelectItem>
                  <SelectItem value="member_updated">Member Updated</SelectItem>
                  <SelectItem value="post_published">Post Published</SelectItem>
                  <SelectItem value="post_scheduled">Post Scheduled</SelectItem>
                </SelectContent>
              </Select>
              <FormDescription>
                Choose the event that will trigger this Son.
              </FormDescription>
              <FormMessage />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="delay"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Delay</FormLabel>
              <FormControl>
                <Input
                  {...field}
                  placeholder="e.g., 30m, 2h, 1d, 1w"
                />
              </FormControl>
              <FormDescription>
                Set a delay before the Son executes its actions. Use format like "30m" for 30 minutes, "2h" for 2 hours, "1d" for 1 day, or "1w" for 1 week.
              </FormDescription>
              <FormMessage />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="enabled"
          render={({ field }) => (
            <FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
              <div className="space-y-0.5">
                <FormLabel className="text-base">Enabled</FormLabel>
                <FormDescription>Enable or disable this Son.</FormDescription>
              </div>
              <FormControl>
                <Switch
                  checked={field.value}
                  onCheckedChange={field.onChange}
                />
              </FormControl>
            </FormItem>
          )}
        />
      </CardContent>
    </Card>
  );
}

================
File: components/SonList.tsx
================
import React from "react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import { Pencil, Trash2, ToggleLeft, ToggleRight } from "lucide-react";
import { useSonContext } from "@/contexts/SonContext";
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { SonListSkeleton } from "./SonListSkeleton";
import { useToast } from "@/components/ui/use-toast";
import { Son } from "@/lib/types";
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip";

function formatDuration(duration: string): string {
  console.log("duration", duration);
  const match = duration.match(/^(\d+)([smhdw])$/);
  if (!match) return duration;

  const [, value, unit] = match;
  const num = parseInt(value, 10);

  switch (unit) {
    case "s":
      return `${num} second${num !== 1 ? "s" : ""}`;
    case "m":
      return `${num} minute${num !== 1 ? "s" : ""}`;
    case "h":
      return `${num} hour${num !== 1 ? "s" : ""}`;
    case "d":
      return `${num} day${num !== 1 ? "s" : ""}`;
    case "w":
      return `${num} week${num !== 1 ? "s" : ""}`;
    default:
      return duration;
  }
}

export function SonList() {
  const { sons, loading, error, deleteSon, updateSon } = useSonContext();
  const { toast } = useToast();

  if (loading) {
    return <SonListSkeleton />;
  }

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  if (sons.length === 0) {
    return <div>No sons found. Create your first son!</div>;
  }

  const handleToggleEnabled = async (son: Son) => {
    try {
      await updateSon(son.id, { ...son, enabled: !son.enabled });
      toast({
        title: "Son Updated",
        description: `${son.name} has been ${
          !son.enabled ? "enabled" : "disabled"
        }.`,
        variant: "default",
      });
    } catch (error) {
      toast({
        title: "Error",
        description: "Failed to update Son status.",
        variant: "destructive",
      });
    }
  };

  return (
    <Card>
      <CardHeader>
        <CardTitle>Your Sons</CardTitle>
      </CardHeader>
      <CardContent>
        <Table>
          <TableHeader>
            <TableRow>
              <TableHead>Name</TableHead>
              <TableHead>Trigger</TableHead>
              <TableHead>Status</TableHead>
              <TableHead>Delay</TableHead>
              <TableHead>Actions</TableHead>
            </TableRow>
          </TableHeader>
          <TableBody>
            {sons.map((son) => (
              <TableRow key={son.id}>
                <TableCell>{son.name}</TableCell>
                <TableCell>{son.trigger}</TableCell>
                <TableCell>
                  <TooltipProvider>
                    <Tooltip>
                      <TooltipTrigger>
                        <Button
                          variant="ghost"
                          size="sm"
                          onClick={() => handleToggleEnabled(son)}
                        >
                          {son.enabled ? (
                            <ToggleRight className="h-4 w-4 text-green-500" />
                          ) : (
                            <ToggleLeft className="h-4 w-4 text-red-500" />
                          )}
                        </Button>
                      </TooltipTrigger>
                      <TooltipContent>
                        <p>{son.enabled ? "Enabled" : "Disabled"}</p>
                      </TooltipContent>
                    </Tooltip>
                  </TooltipProvider>
                </TableCell>
                <TableCell>{formatDuration(son.delay)}</TableCell>
                <TableCell>
                  <div className="flex space-x-2">
                    <Link href={`/sons/${son.id}`}>
                      <Button variant="outline" size="icon">
                        <Pencil className="h-4 w-4" />
                      </Button>
                    </Link>
                    <AlertDialog>
                      <AlertDialogTrigger asChild>
                        <Button variant="outline" size="icon">
                          <Trash2 className="h-4 w-4" />
                        </Button>
                      </AlertDialogTrigger>
                      <AlertDialogContent>
                        <AlertDialogHeader>
                          <AlertDialogTitle>Are you sure?</AlertDialogTitle>
                          <AlertDialogDescription>
                            This action cannot be undone. This will permanently
                            delete the Son.
                          </AlertDialogDescription>
                        </AlertDialogHeader>
                        <AlertDialogFooter>
                          <AlertDialogCancel>Cancel</AlertDialogCancel>
                          <AlertDialogAction onClick={() => deleteSon(son.id)}>
                            Delete
                          </AlertDialogAction>
                        </AlertDialogFooter>
                      </AlertDialogContent>
                    </AlertDialog>
                  </div>
                </TableCell>
              </TableRow>
            ))}
          </TableBody>
        </Table>
      </CardContent>
    </Card>
  );
}

================
File: components/SonListSkeleton.tsx
================
import React from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";

export function SonListSkeleton() {
  return (
    <Card className="flex-1">
      <CardHeader>
        <CardTitle>Your Sons</CardTitle>
      </CardHeader>
      <CardContent>
        <Table>
          <TableHeader>
            <TableRow>
              <TableHead>Name</TableHead>
              <TableHead>Trigger</TableHead>
              <TableHead>Actions</TableHead>
            </TableRow>
          </TableHeader>
          <TableBody>
            {[...Array(5)].map((_, index) => (
              <TableRow key={index}>
                <TableCell>
                  <div className="h-4 bg-gray-300 rounded w-3/4 animate-pulse"></div>
                </TableCell>
                <TableCell>
                  <div className="h-4 bg-gray-300 rounded w-1/2 animate-pulse"></div>
                </TableCell>
                <TableCell>
                  <div className="flex space-x-2">
                    <div className="h-8 w-8 bg-gray-300 rounded-full animate-pulse"></div>
                    <div className="h-8 w-8 bg-gray-300 rounded-full animate-pulse"></div>
                  </div>
                </TableCell>
              </TableRow>
            ))}
          </TableBody>
        </Table>
      </CardContent>
    </Card>
  );
}

================
File: components/TransactionalEmailActionFields.tsx
================
import React, { useState, useEffect } from "react";
import { UseFormReturn } from "react-hook-form";
import {
  FormField,
  FormItem,
  FormLabel,
  FormControl,
  FormMessage,
  FormDescription,
} from "@/components/ui/form";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Trash2, Plus } from "lucide-react";
import { ListmonkTemplate } from "@/lib/types";
import Link from "next/link";

interface TransactionalEmailActionFieldsProps {
  form: UseFormReturn<any>;
  index: number;
  templates: ListmonkTemplate[];
}

interface Header {
  key: string;
  value: string;
}

interface EditingField {
  key: string;
  newKey: string;
  value: string;
}

export function TransactionalEmailActionFields({
  form,
  index,
  templates,
}: TransactionalEmailActionFieldsProps) {
  const [localData, setLocalData] = useState<Record<string, string>>({});
  const [editingField, setEditingField] = useState<EditingField | null>(null);

  useEffect(() => {
    const formData = form.getValues(`actions.${index}.parameters.data`);
    if (formData && Object.keys(formData).length > 0) {
      setLocalData(formData);
    }
  }, [form, index]);

  const addDataField = () => {
    const newKey = `newField${Object.keys(localData).length}`;
    setLocalData((prev) => ({ ...prev, [newKey]: "" }));
    setEditingField({ key: newKey, newKey: newKey, value: "" });
  };

  const removeDataField = (keyToRemove: string) => {
    setLocalData((prev) => {
      const newData = { ...prev };
      delete newData[keyToRemove];
      return newData;
    });
    setEditingField(null);
  };

  const handleKeyChange = (oldKey: string, newKey: string) => {
    setEditingField((prev) => (prev ? { ...prev, newKey } : null));
  };

  const handleValueChange = (key: string, value: string) => {
    setLocalData((prev) => ({ ...prev, [key]: value }));
  };

  const handleKeyBlur = () => {
    if (editingField && editingField.key !== editingField.newKey) {
      setLocalData((prev) => {
        const newData = { ...prev };
        delete newData[editingField.key];
        newData[editingField.newKey] = editingField.value;
        return newData;
      });
    }
    setEditingField(null);
  };

  useEffect(() => {
    form.setValue(`actions.${index}.parameters.data`, localData);
  }, [localData, form, index]);
  return (
    <>
      {/* Template selection field remains unchanged */}
      <FormField
        control={form.control}
        name={`actions.${index}.parameters.template_id`}
        render={({ field }) => (
          <FormItem>
            <FormLabel>Template</FormLabel>
            <Select
              onValueChange={(value: string) => field.onChange(parseInt(value))}
              value={field.value?.toString()}
            >
              <FormControl>
                <SelectTrigger>
                  <SelectValue placeholder="Select a template" />
                </SelectTrigger>
              </FormControl>
              <SelectContent>
                {templates.map((template) => (
                  <SelectItem key={template.id} value={template.id.toString()}>
                    {template.name}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
            <FormDescription>
              Check the available template expressions in this link -{" "}
              <Link href="https://listmonk.app/docs/templating" target="_blank">
                https://listmonk.app/docs/templating
              </Link>
            </FormDescription>
            <FormMessage />
          </FormItem>
        )}
      />

      <FormField
        control={form.control}
        name={`actions.${index}.parameters.headers`}
        render={({ field }) => (
          <FormItem>
            <FormLabel>Headers</FormLabel>
            <div className="space-y-2">
              {((field.value as Header[]) || []).map((header, headerIndex) => (
                <div key={headerIndex} className="flex items-center space-x-2">
                  <Input
                    placeholder="Key"
                    value={header.key}
                    onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                      const newHeaders = [...(field.value as Header[])];
                      newHeaders[headerIndex].key = e.target.value;
                      field.onChange(newHeaders);
                    }}
                  />
                  <Input
                    placeholder="Value"
                    value={header.value}
                    onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                      const newHeaders = [...(field.value as Header[])];
                      newHeaders[headerIndex].value = e.target.value;
                      field.onChange(newHeaders);
                    }}
                  />
                  <Button
                    type="button"
                    variant="ghost"
                    size="sm"
                    onClick={() => {
                      const newHeaders = (field.value as Header[]).filter(
                        (_, i) => i !== headerIndex
                      );
                      field.onChange(newHeaders);
                    }}
                  >
                    <Trash2 className="h-4 w-4" />
                  </Button>
                </div>
              ))}
              <Button
                type="button"
                variant="outline"
                size="sm"
                onClick={() => {
                  const newHeaders = [
                    ...((field.value as Header[]) || []),
                    { key: "", value: "" },
                  ];
                  field.onChange(newHeaders);
                }}
              >
                <Plus className="mr-2 h-4 w-4" /> Add Header
              </Button>
            </div>
            <FormMessage />
            <FormDescription>
              Headers will be added on the email, you can use this to track
              campaigns.
            </FormDescription>
          </FormItem>
        )}
      />

      <FormField
        control={form.control}
        name={`actions.${index}.parameters.data`}
        render={() => (
          <FormItem>
            <FormLabel>Additional Data</FormLabel>
            <div className="space-y-2">
              {Object.entries(localData).map(([key, value]) => (
                <div key={key} className="flex items-center space-x-2">
                  <Input
                    placeholder="Key"
                    value={
                      editingField?.key === key ? editingField.newKey : key
                    }
                    onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
                      handleKeyChange(key, e.target.value)
                    }
                    onFocus={() => setEditingField({ key, newKey: key, value })}
                    onBlur={handleKeyBlur}
                  />
                  <Input
                    placeholder="Value"
                    value={value}
                    onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
                      handleValueChange(key, e.target.value)
                    }
                  />
                  <Button
                    type="button"
                    variant="ghost"
                    size="sm"
                    onClick={() => removeDataField(key)}
                  >
                    <Trash2 className="h-4 w-4" />
                  </Button>
                </div>
              ))}
              <Button
                type="button"
                variant="outline"
                size="sm"
                onClick={addDataField}
              >
                <Plus className="mr-2 h-4 w-4" /> Add Data Field
              </Button>
            </div>
            <FormMessage />
            <FormDescription>
              <p>Enter any additional data for the transactional email.</p>
              <p>
                Available in the template as{" "}
                <code className="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold">
                  &#123;&#123; .Tx.Data.* &#125;&#125;
                </code>
              </p>
            </FormDescription>
          </FormItem>
        )}
      />
    </>
  );
}

================
File: components/WebhookInfo.tsx
================
// Create a new file: ui/components/WebhookInfo.tsx
import React, { useState } from "react";
import { useSonContext } from "@/contexts/SonContext";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Eye, EyeOff } from "lucide-react";

export function WebhookInfo() {
  const { webhook, webhookLoading, webhookError } = useSonContext();
  const [showSecret, setShowSecret] = useState(false);

  if (webhookLoading) {
    return <div>Loading webhook information...</div>;
  }

  if (webhookError) {
    return <div>Error loading webhook information: {webhookError.message}</div>;
  }

  if (!webhook) {
    return <div>No webhook information available.</div>;
  }

  return (
    <Card>
      <CardHeader>
        <CardTitle>Webhook Information</CardTitle>
      </CardHeader>
      <CardContent className="space-y-4">
        <div>
          <label className="text-sm font-medium">Endpoint:</label>
          <Input value={webhook.endpoint} readOnly />
        </div>
        <div>
          <label className="text-sm font-medium">Secret:</label>
          <div className="flex items-center space-x-2">
            <Input
              type={showSecret ? "text" : "password"}
              value={webhook.secret}
              readOnly
            />
            <Button
              variant="outline"
              size="icon"
              onClick={() => setShowSecret(!showSecret)}
            >
              {showSecret ? (
                <EyeOff className="h-4 w-4" />
              ) : (
                <Eye className="h-4 w-4" />
              )}
            </Button>
          </div>
        </div>
      </CardContent>
    </Card>
  );
}

================
File: contexts/AuthContext.tsx
================
import { createContext, useContext, useState, useEffect } from "react";
import { apiClient } from "@/lib/api-client";

interface User {
  id: string;
  email: string;
}

interface AuthContextType {
  user: User | null;
  login: (token: string, user: User) => Promise<void>;
  logout: () => Promise<void>;
  loading: boolean;
}

const AuthContext = createContext<AuthContextType | null>(null);

export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
  children,
}) => {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const token = localStorage.getItem("authToken");
    const storedUser = localStorage.getItem("user");
    if (token && storedUser) {
      setUser(JSON.parse(storedUser));
      apiClient.defaults.headers.common["Authorization"] = `Bearer ${token}`;
    }
    setLoading(false);
  }, []);

  const login = async (token: string, user: User) => {
    localStorage.setItem("authToken", token);
    localStorage.setItem("user", JSON.stringify(user));
    apiClient.defaults.headers.common["Authorization"] = `Bearer ${token}`;
    setUser(user);
  };

  const logout = async () => {
    try {
      await apiClient.post("/auth/logout");
    } catch (error) {
      console.error("Logout failed:", error);
    } finally {
      localStorage.removeItem("authToken");
      localStorage.removeItem("user");
      delete apiClient.defaults.headers.common["Authorization"];
      setUser(null);
    }
  };

  return (
    <AuthContext.Provider value={{ user, login, logout, loading }}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuthContext = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error("useAuth must be used within an AuthProvider");
  }
  return context;
};

================
File: contexts/ListContext.tsx
================
import React, { createContext, useContext } from "react";
import { ListmonkList } from "@/lib/types";
import { useLists } from "@/hooks/useLists";

interface ListContextProps {
  lists: ListmonkList[];
  loading: boolean;
  error: Error | null;
  fetchLists: () => void;
}

const ListContext = createContext<ListContextProps | undefined>(undefined);

export const ListProvider: React.FC<{ children: React.ReactNode }> = ({
  children,
}) => {
  const { lists, loading, error, fetchLists } = useLists();

  return (
    <ListContext.Provider
      value={{
        lists,
        loading,
        error,
        fetchLists,
      }}
    >
      {children}
    </ListContext.Provider>
  );
};

export const useListContext = () => {
  const context = useContext(ListContext);
  if (!context) {
    throw new Error("useListContext must be used within a ListProvider");
  }
  return context;
};

================
File: contexts/SonContext.tsx
================
// ui/contexts/SonContext.tsx
import React, { createContext, useContext } from "react";
import { Son } from "@/lib/types";
import { useSons } from "@/hooks/useSons";
import { Dispatch, SetStateAction } from "react";
import { useWebhook } from "@/hooks/useWebhook";
import { Webhook } from "@/lib/types";

interface SonContextProps {
  sons: Son[];
  setSons: Dispatch<SetStateAction<Son[]>>;
  loading: boolean;
  error: Error | null;
  fetchSons: () => void;
  createSon: (
    sonData: Omit<Son, "id" | "created_at" | "updated_at">
  ) => Promise<Son>;
  updateSon: (id: string, sonData: Partial<Son>) => Promise<Son>;
  deleteSon: (id: string) => Promise<void>;
  webhook: Webhook | null;
  webhookLoading: boolean;
  webhookError: Error | null;
  fetchWebhook: () => Promise<void>;
}

const SonContext = createContext<SonContextProps | undefined>(undefined);

export const SonProvider: React.FC<{ children: React.ReactNode }> = ({
  children,
}) => {
  const {
    sons,
    setSons,
    loading,
    error,
    fetchSons,
    createSon,
    updateSon,
    deleteSon,
  } = useSons();
  const {
    webhook,
    loading: webhookLoading,
    error: webhookError,
    fetchWebhook,
  } = useWebhook();

  return (
    <SonContext.Provider
      value={{
        sons,
        setSons,
        loading,
        error,
        fetchSons,
        createSon,
        updateSon,
        deleteSon,
        webhook,
        webhookLoading,
        webhookError,
        fetchWebhook,
      }}
    >
      {children}
    </SonContext.Provider>
  );
};

export const useSonContext = () => {
  const context = useContext(SonContext);
  if (!context) {
    throw new Error("useSonContext must be used within a SonProvider");
  }
  return context;
};

================
File: contexts/TemplateContext.tsx
================
import React, { createContext, useContext } from "react";
import { ListmonkTemplate } from "@/lib/types";
import { useTemplates } from "@/hooks/useTemplates";

interface TemplateContextProps {
  templates: ListmonkTemplate[];
  loading: boolean;
  error: Error | null;
  fetchTemplates: () => void;
}

const TemplateContext = createContext<TemplateContextProps | undefined>(
  undefined
);

export const TemplateProvider: React.FC<{ children: React.ReactNode }> = ({
  children,
}) => {
  const { templates, loading, error, fetchTemplates } = useTemplates();

  return (
    <TemplateContext.Provider
      value={{
        templates,
        loading,
        error,
        fetchTemplates,
      }}
    >
      {children}
    </TemplateContext.Provider>
  );
};

export const useTemplateContext = () => {
  const context = useContext(TemplateContext);
  if (!context) {
    throw new Error(
      "useTemplateContext must be used within a TemplateProvider"
    );
  }
  return context;
};

================
File: hooks/useCustomToast.ts
================
import { useToast } from "@/components/ui/use-toast"

export function useCustomToast() {
    const { toast } = useToast()

    const showToast = (title: string, description: string, variant: "default" | "destructive" = "default") => {
        toast({
            title,
            description,
            variant,
        })
    }

    return { showToast }
}

================
File: hooks/useInitialData.ts
================
import { useState, useEffect, useCallback } from 'react';
import { apiClient } from '@/lib/api-client';
import { Son, Webhook, ListmonkList, ListmonkTemplate } from '@/lib/types';

interface InitialData {
    sons: Son[];
    webhook: Webhook;
    lists: ListmonkList[];
    templates: ListmonkTemplate[];
}

export function useInitialData() {
    const [data, setData] = useState<InitialData | null>(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState<Error | null>(null);

    const fetchInitialData = useCallback(async () => {
        setLoading(true);
        try {
            const response = await apiClient.get<InitialData>('/initial-data');
            setData(response.data);
            setError(null);
        } catch (err) {
            setError(err instanceof Error ? err : new Error('An error occurred'));
        } finally {
            setLoading(false);
        }
    }, []);

    useEffect(() => {
        fetchInitialData();
    }, [fetchInitialData]);

    return { data, loading, error, fetchInitialData };
}

================
File: hooks/useLists.ts
================
import { useState, useEffect, useCallback } from 'react';
import { apiClient } from '@/lib/api-client';
import { ListmonkList } from '@/lib/types';


export function useLists() {
    const [lists, setLists] = useState<ListmonkList[]>([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState<Error | null>(null);

    const fetchLists = useCallback(async () => {
        setLoading(true);
        try {
            const response = await apiClient.get<{ data: ListmonkList[] }>('/lists');
            setLists(response.data.data);
            setError(null);
        } catch (err) {
            setError(err instanceof Error ? err : new Error('An error occurred'));
        } finally {
            setLoading(false);
        }
    }, []);

    useEffect(() => {
        fetchLists();
    }, [fetchLists]);

    return { lists, loading, error, fetchLists };
}

================
File: hooks/useSon.ts
================
import { useState, useEffect } from "react";
import { useSonContext } from "@/contexts/SonContext";
import { Son } from "@/lib/types";
import { apiClient } from "@/lib/api-client";

export function useSon(id: string) {
    const { sons, setSons } = useSonContext();  // Assuming you expose setSons in the context
    const [son, setSon] = useState<Son | null>(null);
    const [loading, setLoading] = useState<boolean>(true);
    const [error, setError] = useState<Error | null>(null);

    useEffect(() => {
        // Check if the Son is already in the context
        const existingSon = sons.find((s) => s.id === id);
        if (existingSon) {
            setSon(existingSon);
            setLoading(false);
        } else {
            // If not, fetch from the server
            const fetchSon = async () => {
                setLoading(true);
                try {
                    const response = await apiClient.get<Son>(`/sons/${id}`);
                    setSon(response.data);
                    setError(null);

                    // Optimistically update the context with the new Son
                    setSons((prevSons) => [...prevSons, response.data]);
                } catch (err) {
                    setError(err instanceof Error ? err : new Error("An error occurred"));
                } finally {
                    setLoading(false);
                }
            };

            fetchSon();
        }
    }, [id, sons, setSons]);

    return { son, loading, error };
}

================
File: hooks/useSons.ts
================
// src/hooks/useSons.ts
import { useState, useEffect, useCallback } from 'react';
import { apiClient } from '@/lib/api-client';
import { Son } from '@/lib/types';


export function useSons() {
    const [sons, setSons] = useState<Son[]>([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState<Error | null>(null);

    const fetchSons = useCallback(async () => {
        setLoading(true);
        try {
            const response = await apiClient.get<Son[]>('/sons');
            setSons(response.data);
            setError(null);
        } catch (err) {
            setError(err instanceof Error ? err : new Error('An error occurred'));
        } finally {
            setLoading(false);
        }
    }, []);

    const createSon = useCallback(async (sonData: Omit<Son, 'id' | 'created_at' | 'updated_at'>) => {
        setLoading(true);
        try {
            const response = await apiClient.post<Son>('/sons', sonData);
            setSons(prevSons => [...prevSons, response.data]);
            setError(null);
            return response.data;
        } catch (err) {
            setError(err instanceof Error ? err : new Error('An error occurred'));
            throw err;
        } finally {
            setLoading(false);
        }
    }, []);

    const updateSon = useCallback(async (id: string, sonData: Partial<Son>) => {
        setLoading(true);
        try {
            const response = await apiClient.put<Son>(`/sons/${id}`, sonData);
            setSons(prevSons => prevSons.map(son => son.id === id ? response.data : son));
            setError(null);
            return response.data;
        } catch (err) {
            setError(err instanceof Error ? err : new Error('An error occurred'));
            throw err;
        } finally {
            setLoading(false);
        }
    }, []);

    const deleteSon = useCallback(async (id: string) => {
        setLoading(true);
        try {
            await apiClient.delete(`/sons/${id}`);
            setSons(prevSons => prevSons.filter(son => son.id !== id));
            setError(null);
        } catch (err) {
            setError(err instanceof Error ? err : new Error('An error occurred'));
            throw err;
        } finally {
            setLoading(false);
        }
    }, []);

    useEffect(() => {
        fetchSons(); // Initial data fetch
    }, [fetchSons]);


    return { sons, setSons, loading, error, fetchSons, createSon, updateSon, deleteSon };
}

================
File: hooks/useTemplates.ts
================
import { useState, useEffect, useCallback } from 'react';
import { apiClient } from '@/lib/api-client';
import { ListmonkTemplate } from '@/lib/types';



export function useTemplates() {
    const [templates, setTemplates] = useState<ListmonkTemplate[]>([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState<Error | null>(null);

    const fetchTemplates = useCallback(async () => {
        setLoading(true);
        try {
            const response = await apiClient.get<{ data: ListmonkTemplate[] }>('/templates');
            setTemplates(response.data.data);
            setError(null);
        } catch (err) {
            setError(err instanceof Error ? err : new Error('An error occurred'));
        } finally {
            setLoading(false);
        }
    }, []);

    useEffect(() => {
        fetchTemplates();
    }, [fetchTemplates]);

    return { templates, loading, error, fetchTemplates };
}

================
File: hooks/useWebhook.ts
================
import { useState, useEffect, useCallback } from 'react';
import { apiClient } from '@/lib/api-client';
import { Webhook } from '@/lib/types';


export function useWebhook() {
    const [webhook, setWebhook] = useState<Webhook | null>(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState<Error | null>(null);

    const fetchWebhook = useCallback(async () => {
        setLoading(true);
        try {
            const response = await apiClient.get<{ data: Webhook }>('/webhook-info');
            setWebhook(response.data.data);
            setError(null);
        } catch (err) {
            setError(err instanceof Error ? err : new Error('An error occurred'));
        } finally {
            setLoading(false);
        }
    }, []);

    useEffect(() => {
        fetchWebhook();
    }, [fetchWebhook]);

    return { webhook, loading, error, fetchWebhook };
}

================
File: lib/api-client.ts
================
import axios from 'axios';

const API_BASE_URL = '/api';

export const apiClient = axios.create({
    baseURL: API_BASE_URL,
    withCredentials: true,
});

apiClient.interceptors.request.use(
    (config) => {
        const token = localStorage.getItem('authToken');
        if (token) {
            config.headers['Authorization'] = `Bearer ${token}`;
        }
        return config;
    },
    (error) => Promise.reject(error)
);

apiClient.interceptors.response.use(
    (response) => response,
    async (error) => {
        if (error.response && error.response.status === 401) {
            localStorage.removeItem('authToken');
            localStorage.removeItem('user');
            delete apiClient.defaults.headers.common['Authorization'];
            window.dispatchEvent(new CustomEvent('unauthorized'));
            window.location.href = '/login';
        }
        return Promise.reject(error);
    }
);

export default apiClient;

================
File: lib/schemas.ts
================
import * as z from 'zod';

// Define the schema for action parameters
const actionParametersSchema = z.object({
    subject: z.string().optional(),
    lists: z.array(z.number()).optional(),
    template_id: z.number().optional(),
    tags: z.string().optional(),
    send_now: z.boolean().optional(),
}).strict().or(z.record(z.any())); // Allow any other properties for flexibility



// Define the schema for a single action
const actionSchema = z.object({
    type: z.enum(['send_transactional_email', 'manage_subscriber', 'create_campaign']),
    parameters: actionParametersSchema,
});

// Define the schema for the entire Son object
export const sonSchema = z.object({
    id: z.string().optional(), // Optional because it might not be present when creating a new Son
    name: z.string().min(1, 'Name is required'),
    trigger: z.enum([
        'member_created',
        'member_deleted',
        'member_updated',
        'post_published',
        'post_scheduled',
    ]),
    delay: z.string().refine((val) => {
        const durationRegex = /^(\d+)\s*(s|m|h|d|w)$/;
        return durationRegex.test(val);
    }, {
        message: "Invalid duration format. Use format like '30m', '2h', '1d', or '1w'.",
    }).default('0s'),
    actions: z.array(actionSchema).min(1, 'At least one action is required'),
    enabled: z.boolean().default(true),
    created_at: z.date().optional(),
    updated_at: z.date().optional(),
});

export const editableSonSchema = z.object({
    name: z.string().min(1, 'Name is required'),
    trigger: z.enum([
        'member_created',
        'member_deleted',
        'member_updated',
        'post_published',
        'post_scheduled',
    ]),
    delay: z.string().min(2).default('0s').refine((val) => {
        const durationRegex = /^(\d+)\s*(s|m|h|d|w)$/;
        return durationRegex.test(val);
    }, {
        message: "Invalid duration format. Use format like '30m', '2h', '1d', or '1w'.",
    }),
    actions: z.array(z.object({
        type: z.enum(['send_transactional_email', 'manage_subscriber', 'create_campaign']),
        parameters: z.record(z.any()),
    })),
    enabled: z.boolean().default(true),
});


// Define a schema for creating a new Son (without id, createdAt, and updatedAt)
export const createSonSchema = sonSchema.omit({ id: true, created_at: true, updated_at: true });

// Define a schema for updating an existing Son
export const updateSonSchema = sonSchema.partial().extend({
    id: z.string(),
});

// Define types for create and update operations
export type CreateSonInput = z.infer<typeof createSonSchema>;
export type UpdateSonInput = z.infer<typeof updateSonSchema>;

================
File: lib/types.ts
================
import { UseFormReturn, FieldValues } from 'react-hook-form';
import { editableSonSchema } from '@/lib/schemas';  // Assuming this is where your Son type is defined
import { z } from 'zod';
// Define a type for the Son object based on the schema
export interface Son extends z.infer<typeof editableSonSchema> {
    id: string;
    created_at: string;
    updated_at: string;
}

// Type for editable Son fields
export type EditableSon = z.infer<typeof editableSonSchema>;

export interface ListmonkList {
    id: number;
    name: string;
    type: string;
    optin: string;
    tags: string[];
}

export interface ListmonkTemplate {
    id: number;
    name: string;
    type: string;
    is_default: boolean;
}
// Props for CampaignActionFields
export interface CampaignActionFieldsProps {
    form: UseFormReturn<EditableSon>;
    index: number;
    lists: ListmonkList[];
    templates: ListmonkTemplate[];
}

// Props for ManageSubscriberActionFields
export interface ManageSubscriberActionFieldsProps {
    form: UseFormReturn<EditableSon>;
    index: number;
    lists: ListmonkList[];
}

// Props for SonDetailsForm
export interface SonDetailsFormProps {
    form: UseFormReturn<EditableSon>;
}

export interface ActionFormProps {
    form: UseFormReturn<EditableSon>;
    index: number;
    remove: (index: number) => void;
    lists: ListmonkList[];
    templates: ListmonkTemplate[];
}

export interface Webhook {
    id: string;
    endpoint: string;
    secret: string;
}

================
File: lib/utils.ts
================
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

================
File: pages/auth/verify.tsx
================
import { useEffect } from "react";
import { useRouter } from "next/router";
import { useAuthContext } from "@/contexts/AuthContext";
import { useCustomToast } from "@/hooks/useCustomToast";
import { apiClient } from "@/lib/api-client";

const VerifyPage = () => {
  const router = useRouter();
  const { login } = useAuthContext();
  const { showToast } = useCustomToast();

  useEffect(() => {
    const verifyToken = async () => {
      const { token } = router.query;
      if (token) {
        try {
          const response = await apiClient.get(`/auth/verify?token=${token}`);
          await login(response.data.token, response.data.user);
          showToast("Success", "Logged in successfully");
          router.push("/");
        } catch (error) {
          console.error("Verification failed:", error);
          showToast("Error", "Verification failed", "destructive");
          router.push("/login");
        }
      }
    };

    verifyToken();
  }, [router.query]);

  return (
    <div className="flex items-center justify-center h-screen">
      <p>Verifying your magic link...</p>
    </div>
  );
};

VerifyPage.layout = "minimal";

export default VerifyPage;

================
File: pages/sons/[id].tsx
================
import { NextPageWithExtras } from "next";

import React, { useEffect } from "react";
import { useRouter } from "next/router";
import { useForm, useFieldArray } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Button } from "@/components/ui/button";
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Form } from "@/components/ui/form";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { PlusCircle } from "lucide-react";
import { useSon } from "@/hooks/useSon";
import { useCustomToast } from "@/hooks/useCustomToast";
import { SonDetailsForm } from "@/components/SonDetailsForm";
import { ActionForm } from "@/components/ActionForm";
import { editableSonSchema } from "@/lib/schemas";
import { EditableSon } from "@/lib/types";
import { useSonContext } from "@/contexts/SonContext";
import { useListContext } from "@/contexts/ListContext";
import { useTemplateContext } from "@/contexts/TemplateContext";

function isValidTrigger(trigger: string): trigger is EditableSon["trigger"] {
  return [
    "member_created",
    "member_deleted",
    "member_updated",
    "post_published",
    "post_scheduled",
  ].includes(trigger);
}

const SonDetailPage: NextPageWithExtras = () => {
  const router = useRouter();
  const { id } = router.query;
  const { son, loading: sonLoading, error: sonError } = useSon(id as string);
  const { updateSon, deleteSon } = useSonContext();
  const { showToast } = useCustomToast();
  const { lists, loading: listsLoading, error: listsError } = useListContext();
  const {
    templates,
    loading: templatesLoading,
    error: templatesError,
  } = useTemplateContext();

  const form = useForm<EditableSon>({
    resolver: zodResolver(editableSonSchema),
    defaultValues: {
      name: "",
      trigger: "member_created",
      delay: 0,
      actions: [],
    },
  });

  const { fields, append, remove } = useFieldArray({
    control: form.control,
    name: "actions",
  });

  useEffect(() => {
    if (son) {
      const { name, trigger, delay, actions } = son;

      if (isValidTrigger(trigger)) {
        form.reset({
          name,
          trigger,
          delay,
          actions: actions.map((action) => ({
            type: action.type,
            parameters: action.parameters,
          })),
        });
      } else {
        console.error(`Invalid trigger value: ${trigger}`);
        form.reset({
          name,
          trigger: "member_created",
          delay,
          actions: actions.map((action) => ({
            type: action.type,
            parameters: action.parameters,
          })),
        });
      }
    }
  }, [son, form]);

  if (sonLoading || listsLoading || templatesLoading) {
    return (
      <div className="flex justify-center items-center h-full">Loading...</div>
    );
  }

  if (sonError || listsError || templatesError) {
    return (
      <div className="text-red-500">
        Error:{" "}
        {sonError?.message || listsError?.message || templatesError?.message}
      </div>
    );
  }

  if (!son) return <div>Son not found</div>;

  const onSubmit = async (data: EditableSon) => {
    try {
      await updateSon(son.id, data);
      showToast("Success", "Son updated successfully");
      router.push("/sons");
    } catch (error) {
      showToast("Error", "Failed to update Son", "destructive");
      console.error("Failed to update Son:", error);
    }
  };

  const handleDelete = async () => {
    try {
      await deleteSon(son.id);
      showToast("Success", "Son deleted successfully");
      router.push("/sons");
    } catch (error) {
      showToast("Error", "Failed to delete Son", "destructive");
      console.error("Failed to delete Son:", error);
    }
  };

  return (
    <div className="flex-1 max-w-3xl mx-auto space-y-4">
      <h1 className="text-2xl font-bold">Edit Son: {son.name}</h1>
      <p className="text-sm text-gray-500">
        Created at: {new Date(son.created_at).toLocaleString()}
      </p>
      <p className="text-sm text-gray-500">
        Last updated: {new Date(son.updated_at).toLocaleString()}
      </p>

      <Form {...form}>
        <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
          <SonDetailsForm form={form} />

          <Card>
            <CardHeader>
              <CardTitle>Actions</CardTitle>
            </CardHeader>
            <CardContent className="space-y-6">
              {fields.map((field, index) => (
                <ActionForm
                  key={field.id}
                  form={form}
                  index={index}
                  remove={remove}
                  lists={lists}
                  templates={templates}
                />
              ))}

              <Button
                type="button"
                onClick={() =>
                  append({ type: "send_transactional_email", parameters: {} })
                }
                variant="outline"
                className="w-full"
              >
                <PlusCircle className="mr-2 h-4 w-4" /> Add Action
              </Button>
            </CardContent>
          </Card>

          <Button type="submit" className="w-full">
            Save Changes
          </Button>
        </form>
      </Form>
      <AlertDialog>
        <AlertDialogTrigger asChild>
          <Button variant="destructive">Delete Son</Button>
        </AlertDialogTrigger>
        <AlertDialogContent>
          <AlertDialogHeader>
            <AlertDialogTitle>Are you sure?</AlertDialogTitle>
            <AlertDialogDescription>
              This action cannot be undone. This will permanently delete the
              Son.
            </AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <AlertDialogCancel>Cancel</AlertDialogCancel>
            <AlertDialogAction onClick={handleDelete}>Delete</AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>
    </div>
  );
};

SonDetailPage.auth = true;

export default SonDetailPage;

================
File: pages/sons/index.tsx
================
import type { NextPageWithExtras } from "next";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { SonList } from "@/components/SonList";

const SonListPage: NextPageWithExtras = () => {
  return (
    <div className="flex-1 space-y-4 max-w-3xl mx-auto">
      <div className="flex justify-between items-center">
        <h1 className="text-2xl font-bold">Manage Sons</h1>
        <Link href="/sons/new">
          <Button>Create New Son</Button>
        </Link>
      </div>

      <SonList />
    </div>
  );
};

SonListPage.auth = true;

export default SonListPage;

================
File: pages/sons/new.tsx
================
import { NextPageWithExtras } from "next";
import SonCreationForm from "@/components/SonCreationForm";

const NewSonPage: NextPageWithExtras = () => {
  return (
    <div className="flex-1 max-w-3xl mx-auto">
      <h1 className="text-2xl font-bold mb-4">Create New Son</h1>
      <SonCreationForm />
    </div>
  );
};

NewSonPage.auth = true;

export default NewSonPage;

================
File: pages/_app.tsx
================
// ui/pages/_app.tsx
"use client";
import { AppProps } from "next/app";
import type { NextPageWithExtras } from "next";
import localFont from "next/font/local";
import { cn } from "@/lib/utils";
import DefaultLayout from "@/components/layout";
import MinimalLayout from "@/components/MinimalLayout";
import { Toaster } from "@/components/ui/toaster";
import "@/styles/globals.css";
import { ProtectedRoute } from "@/components/ProtectedRoute";
import { AuthProvider } from "@/contexts/AuthContext";
import { AnimatePresence, motion } from "framer-motion";
import { useRouter } from "next/router";
import { useToast } from "@/components/ui/use-toast";
import { useEffect } from "react";

const fontSans = localFont({
  src: "../public/fonts/inter-var-latin.woff2",
  variable: "--font-sans",
});

type AppPropsWithAuth = AppProps & {
  Component: NextPageWithExtras;
};

function MyApp({ Component, pageProps }: AppPropsWithAuth) {
  const router = useRouter();
  const Layout = Component.layout === "minimal" ? MinimalLayout : DefaultLayout;
  const { toast } = useToast();

  useEffect(() => {
    const handleUnauthorized = (event: Event) => {
      if (event instanceof CustomEvent && event.detail === "unauthorized") {
        toast({
          title: "Session Expired",
          description:
            "You've been logged out due to inactivity. Please log in again.",
          variant: "destructive",
        });
      }
    };

    window.addEventListener("unauthorized", handleUnauthorized);

    return () => {
      window.removeEventListener("unauthorized", handleUnauthorized);
    };
  }, [toast]);

  return (
    <div
      className={cn(
        "min-h-screen max-h-screen bg-background font-sans antialiased",
        fontSans.variable
      )}
    >
      <AuthProvider>
        <AnimatePresence mode="wait">
          <Layout>
            <motion.div
              key={router.route}
              initial={{ opacity: 0 }}
              animate={{ opacity: 1 }}
              exit={{ opacity: 0 }}
              transition={{ duration: 0.2 }}
              className="flex w-full"
            >
              {Component.auth ? (
                <ProtectedRoute>
                  <Component {...pageProps} />
                </ProtectedRoute>
              ) : (
                <Component {...pageProps} />
              )}
            </motion.div>
          </Layout>
        </AnimatePresence>
      </AuthProvider>
      <Toaster />
    </div>
  );
}

export default MyApp;

================
File: pages/_document.tsx
================
import { Html, Head, Main, NextScript } from "next/document";

export default function Document() {
  return (
    <Html lang="en">
      <Head />
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

================
File: pages/index.tsx
================
import { NextPageWithExtras } from "next";
import Dashboard from "@/components/Dashboard";

const HomePage: NextPageWithExtras = () => {
  return <Dashboard />;
};

HomePage.auth = true;

export default HomePage;

================
File: pages/login.tsx
================
import { useState } from "react";
import { useRouter } from "next/router";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
  Card,
  CardHeader,
  CardTitle,
  CardContent,
  CardFooter,
} from "@/components/ui/card";
import { useCustomToast } from "@/hooks/useCustomToast";
import { apiClient } from "@/lib/api-client";

const LoginPage = () => {
  const [email, setEmail] = useState("");
  const router = useRouter();
  const { showToast } = useCustomToast();

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    try {
      await apiClient.post("/auth/magic-link", { email });
      showToast("Success", "Magic link sent to your email");
    } catch (error) {
      console.error("Failed to send magic link:", error);
      showToast("Error", "Failed to send magic link", "destructive");
    }
  };

  return (
    <div className="flex flex-col w-full items-center justify-center bg-gray-100">
      <h1>
        <span className="font-bold text-2xl">Ghost-Listmonk Connector</span>
      </h1>
      <Card className="w-full max-w-md mt-12">
        <CardHeader>
          <CardTitle>Login</CardTitle>
        </CardHeader>
        <form onSubmit={handleSubmit}>
          <CardContent className="space-y-4">
            <Input
              type="email"
              placeholder="Email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              required
            />
          </CardContent>
          <CardFooter>
            <Button type="submit" className="w-full">
              Send Magic Link
            </Button>
          </CardFooter>
        </form>
      </Card>
    </div>
  );
};

LoginPage.layout = "minimal";

export default LoginPage;

================
File: pages/settings.tsx
================
import { NextPageWithExtras } from "next";
import { WebhookInfo } from "@/components/WebhookInfo";

const SettingsPage: NextPageWithExtras = () => {
  return (
    <div className="flex-1 space-y-4 max-w-3xl mx-auto">
      <h1 className="text-2xl font-bold">Settings</h1>
      <WebhookInfo />
    </div>
  );
};

SettingsPage.auth = true;

export default SettingsPage;

================
File: public/next.svg
================
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

================
File: public/vercel.svg
================
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>

================
File: styles/globals.css
================
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  @layer base {
    :root {
      --background: 0 0% 100%;
      --foreground: 20 14.3% 4.1%;
      --card: 0 0% 100%;
      --card-foreground: 20 14.3% 4.1%;
      --popover: 0 0% 100%;
      --popover-foreground: 20 14.3% 4.1%;
      --primary: 24.6 95% 53.1%;
      --primary-foreground: 60 9.1% 97.8%;
      --secondary: 60 4.8% 95.9%;
      --secondary-foreground: 24 9.8% 10%;
      --muted: 60 4.8% 95.9%;
      --muted-foreground: 25 5.3% 44.7%;
      --accent: 60 4.8% 95.9%;
      --accent-foreground: 24 9.8% 10%;
      --destructive: 0 84.2% 60.2%;
      --destructive-foreground: 60 9.1% 97.8%;
      --border: 20 5.9% 90%;
      --input: 20 5.9% 90%;
      --ring: 24.6 95% 53.1%;
      --radius: 1rem;
      --chart-1: 12 76% 61%;
      --chart-2: 173 58% 39%;
      --chart-3: 197 37% 24%;
      --chart-4: 43 74% 66%;
      --chart-5: 27 87% 67%;
    }

    .dark {
      --background: 20 14.3% 4.1%;
      --foreground: 60 9.1% 97.8%;
      --card: 20 14.3% 4.1%;
      --card-foreground: 60 9.1% 97.8%;
      --popover: 20 14.3% 4.1%;
      --popover-foreground: 60 9.1% 97.8%;
      --primary: 20.5 90.2% 48.2%;
      --primary-foreground: 60 9.1% 97.8%;
      --secondary: 12 6.5% 15.1%;
      --secondary-foreground: 60 9.1% 97.8%;
      --muted: 12 6.5% 15.1%;
      --muted-foreground: 24 5.4% 63.9%;
      --accent: 12 6.5% 15.1%;
      --accent-foreground: 60 9.1% 97.8%;
      --destructive: 0 72.2% 50.6%;
      --destructive-foreground: 60 9.1% 97.8%;
      --border: 12 6.5% 15.1%;
      --input: 12 6.5% 15.1%;
      --ring: 20.5 90.2% 48.2%;
      --chart-1: 220 70% 50%;
      --chart-2: 160 60% 45%;
      --chart-3: 30 80% 55%;
      --chart-4: 280 65% 60%;
      --chart-5: 340 75% 55%;
    }
  }
}

@layer base {
  * {
    @apply border-border;
  }
  body {
    @apply bg-background text-foreground;
  }
}

================
File: types/next-auth.d.ts
================
import type { NextComponentType, NextPageContext } from 'next'

declare module 'next' {
    export type NextPageWithExtras<P = {}, IP = P> = NextComponentType<NextPageContext, IP, P> & {
        auth?: boolean,
        layout?: 'default' | 'admin' | 'minimal' | (string & {})
        permissions?: string[]
        dataFetching?: 'ssg' | 'ssr' | 'csr'
    }
}

================
File: .eslintrc.json
================
{
  "extends": "next/core-web-vitals"
}

================
File: .gitignore
================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

================
File: components.json
================
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "new-york",
  "rsc": false,
  "tsx": true,
  "tailwind": {
    "config": "tailwind.config.ts",
    "css": "styles/globals.css",
    "baseColor": "zinc",
    "cssVariables": true,
    "prefix": ""
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils"
  }
}

================
File: next.config.mjs
================
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  output: "export",
  // assetPrefix: ".",
  images: {
    unoptimized: true,
  },
};

export default nextConfig;

================
File: package.json
================
{
  "name": "ui2",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@hookform/resolvers": "^3.9.0",
    "@radix-ui/react-alert-dialog": "^1.1.1",
    "@radix-ui/react-icons": "^1.3.0",
    "@radix-ui/react-label": "^2.1.0",
    "@radix-ui/react-select": "^2.1.1",
    "@radix-ui/react-separator": "^1.1.0",
    "@radix-ui/react-slot": "^1.1.0",
    "@radix-ui/react-switch": "^1.1.0",
    "@radix-ui/react-toast": "^1.2.1",
    "@radix-ui/react-tooltip": "^1.1.2",
    "axios": "^1.7.3",
    "class-variance-authority": "^0.7.0",
    "clsx": "^2.1.1",
    "framer-motion": "^11.3.24",
    "lucide-react": "^0.427.0",
    "next": "14.2.5",
    "react": "^18",
    "react-dom": "^18",
    "react-hook-form": "^7.52.2",
    "recharts": "^2.12.7",
    "tailwind-merge": "^2.5.1",
    "tailwindcss-animate": "^1.0.7",
    "zod": "^3.23.8"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "eslint": "^8",
    "eslint-config-next": "14.2.5",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "typescript": "^5"
  }
}

================
File: postcss.config.mjs
================
/** @type {import('postcss-load-config').Config} */
const config = {
  plugins: {
    tailwindcss: {},
  },
};

export default config;

================
File: README.md
================
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.

[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.

The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

================
File: tailwind.config.ts
================
import type { Config } from "tailwindcss"
import { fontFamily } from "tailwindcss/defaultTheme"

const config = {
  darkMode: ["class"],
  content: [
    './pages/**/*.{ts,tsx}',
    './components/**/*.{ts,tsx}',
    './app/**/*.{ts,tsx}',
    './src/**/*.{ts,tsx}',
  ],
  prefix: "",
  theme: {
    container: {
      center: true,
      padding: "2rem",
      screens: {
        "2xl": "1400px",
      },
    },
    extend: {
      fontFamily: {
        sans: ["var(--font-sans)", ...fontFamily.sans],
      },
      colors: {
        border: "hsl(var(--border))",
        input: "hsl(var(--input))",
        ring: "hsl(var(--ring))",
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
        primary: {
          DEFAULT: "hsl(var(--primary))",
          foreground: "hsl(var(--primary-foreground))",
        },
        secondary: {
          DEFAULT: "hsl(var(--secondary))",
          foreground: "hsl(var(--secondary-foreground))",
        },
        destructive: {
          DEFAULT: "hsl(var(--destructive))",
          foreground: "hsl(var(--destructive-foreground))",
        },
        muted: {
          DEFAULT: "hsl(var(--muted))",
          foreground: "hsl(var(--muted-foreground))",
        },
        accent: {
          DEFAULT: "hsl(var(--accent))",
          foreground: "hsl(var(--accent-foreground))",
        },
        popover: {
          DEFAULT: "hsl(var(--popover))",
          foreground: "hsl(var(--popover-foreground))",
        },
        card: {
          DEFAULT: "hsl(var(--card))",
          foreground: "hsl(var(--card-foreground))",
        },
      },
      borderRadius: {
        lg: "var(--radius)",
        md: "calc(var(--radius) - 2px)",
        sm: "calc(var(--radius) - 4px)",
      },
      keyframes: {
        "accordion-down": {
          from: { height: "0" },
          to: { height: "var(--radix-accordion-content-height)" },
        },
        "accordion-up": {
          from: { height: "var(--radix-accordion-content-height)" },
          to: { height: "0" },
        },
      },
      animation: {
        "accordion-down": "accordion-down 0.2s ease-out",
        "accordion-up": "accordion-up 0.2s ease-out",
      },
    },
  },
  plugins: [require("tailwindcss-animate")],
} satisfies Config

export default config

================
File: tsconfig.json
================
{
  "compilerOptions": {
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "typeRoots": ["./node_modules/@types", "./types"],
    "paths": {
      "@/*": ["./*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}
