Initial commit - Route Commerce platform

This commit is contained in:
2026-06-01 19:40:55 +00:00
commit 53a9671461
617 changed files with 106132 additions and 0 deletions
@@ -0,0 +1,53 @@
import { getAdminUser } from "@/lib/admin-permissions";
import { getBrandSettings } from "@/actions/brand-settings";
import AbandonedCartDashboard from "@/components/admin/AbandonedCartDashboard";
const TUXEDO_BRAND_ID = "64294306-5f42-463d-a5e8-2ad6c81a96de";
const IRD_BRAND_ID = "b1cb7a96-d82b-40b1-80b1-d6dd26c56e28";
export default async function AbandonedCartsPage() {
const adminUser = await getAdminUser();
const brandId = adminUser?.brand_id ?? TUXEDO_BRAND_ID;
const settingsResult = await getBrandSettings(brandId);
const brandName = settingsResult?.success ? (settingsResult.settings?.brand_name ?? "Farm") : "Farm";
return (
<main className="min-h-screen bg-stone-100 px-6 py-10">
<div className="mx-auto max-w-5xl space-y-6">
{/* Header */}
<div>
<nav className="flex items-center gap-2 text-xs text-stone-500 mb-4">
<a href="/admin" className="hover:text-stone-600 transition-colors">Admin</a>
<span>/</span>
<a href="/admin/communications" className="hover:text-stone-600 transition-colors">Communications</a>
<span>/</span>
<span className="text-stone-600">Abandoned Cart Recovery</span>
</nav>
<div className="flex items-end justify-between">
<div>
<h1 className="text-2xl font-bold text-stone-950">Abandoned Cart Recovery</h1>
<p className="mt-1 text-sm text-stone-500">{brandName} 3-email sequence (1h, 24h, 48h)</p>
</div>
<div className="flex items-center gap-3 text-xs text-stone-500">
<span className="flex items-center gap-1.5">
<span className="w-2 h-2 rounded-full bg-blue-600" />
Active
</span>
<span className="flex items-center gap-1.5">
<span className="w-2 h-2 rounded-full bg-emerald-600" />
Recovered
</span>
<span className="flex items-center gap-1.5">
<span className="w-2 h-2 rounded-full bg-stone-400" />
Expired
</span>
</div>
</div>
</div>
<AbandonedCartDashboard brandId={brandId} />
</div>
</main>
);
}
@@ -0,0 +1,26 @@
import { redirect } from "next/navigation";
import { getAdminUser } from "@/lib/admin-permissions";
import { getCampaignAnalytics } from "@/actions/harvest-reach/campaigns";
import CommunicationsPage from "@/components/admin/CommunicationsPage";
export default async function AnalyticsPage() {
const adminUser = await getAdminUser();
if (!adminUser || !adminUser.can_manage_messages) redirect("/admin/pickup");
const effectiveBrandId = adminUser.brand_id ?? "64294306-5f42-463d-a5e8-2ad6c81a96de";
const analytics = await getCampaignAnalytics(effectiveBrandId);
return (
<div className="min-h-screen bg-stone-100 px-6 py-10">
<div className="mx-auto max-w-6xl">
<CommunicationsPage
campaigns={[]}
templates={[]}
activeTab="analytics"
brandId={effectiveBrandId}
initialAnalytics={analytics}
/>
</div>
</div>
);
}
@@ -0,0 +1,45 @@
import { redirect } from "next/navigation";
import { getAdminUser } from "@/lib/admin-permissions";
import { getCommunicationCampaigns, getCampaignById } from "@/actions/communications/campaigns";
import { getCommunicationTemplates } from "@/actions/communications/templates";
import CommunicationsPage from "@/components/admin/CommunicationsPage";
export default async function CampaignEditPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const adminUser = await getAdminUser();
if (!adminUser) redirect("/admin");
if (!adminUser.can_manage_messages) redirect("/admin/pickup");
const { id } = await params;
const isNew = id === "new";
const effectiveBrandId = adminUser.brand_id ?? "64294306-5f42-463d-a5e8-2ad6c81a96de";
const [campaignsResult, templatesResult] = await Promise.all([
getCommunicationCampaigns(effectiveBrandId),
getCommunicationTemplates(effectiveBrandId),
]);
const campaign = isNew ? undefined : id ? await getCampaignById(id) : undefined;
return (
<main className="min-h-screen bg-stone-100 px-6 py-10">
<div className="mx-auto max-w-6xl">
<div className="mb-8">
<h1 className="text-3xl font-bold text-stone-950">Harvest Reach</h1>
</div>
<CommunicationsPage
campaigns={campaignsResult.success ? campaignsResult.campaigns : []}
templates={templatesResult.success ? templatesResult.templates : []}
activeTab="campaigns"
brandId={adminUser.brand_id ?? "64294306-5f42-463d-a5e8-2ad6c81a96de"}
editCampaign={campaign}
editMode={isNew ? "new" : "edit"}
/>
</div>
</main>
);
}
@@ -0,0 +1,37 @@
import { redirect } from "next/navigation";
import { getAdminUser } from "@/lib/admin-permissions";
import { getHarvestReachCampaigns } from "@/actions/harvest-reach/campaigns";
import { getCommunicationTemplates } from "@/actions/communications/templates";
import { getHarvestReachSegments } from "@/actions/harvest-reach/segments";
import CommunicationsPage from "@/components/admin/CommunicationsPage";
export default async function ComposePage() {
const adminUser = await getAdminUser();
if (!adminUser || !adminUser.can_manage_messages) redirect("/admin/pickup");
const effectiveBrandId = adminUser.brand_id ?? "64294306-5f42-463d-a5e8-2ad6c81a96de";
const [campaignsResult, templatesResult, segmentsResult] = await Promise.all([
getHarvestReachCampaigns(effectiveBrandId),
getCommunicationTemplates(effectiveBrandId),
getHarvestReachSegments(effectiveBrandId),
]);
const campaigns = campaignsResult.success ? campaignsResult.campaigns : [];
const templates = templatesResult.success ? templatesResult.templates : [];
const segments = segmentsResult.success ? segmentsResult.segments : [];
return (
<div className="min-h-screen bg-stone-100 px-6 py-10">
<div className="mx-auto max-w-6xl">
<CommunicationsPage
campaigns={campaigns}
templates={templates}
activeTab="compose"
brandId={effectiveBrandId}
initialSegments={segments}
/>
</div>
</div>
);
}
@@ -0,0 +1,42 @@
import { redirect } from "next/navigation";
import { getAdminUser } from "@/lib/admin-permissions";
import { getCommunicationCampaigns } from "@/actions/communications/campaigns";
import { getCommunicationTemplates } from "@/actions/communications/templates";
import { getContacts } from "@/actions/communications/contacts";
import CommunicationsPage from "@/components/admin/CommunicationsPage";
export default async function ContactsPage() {
const adminUser = await getAdminUser();
if (!adminUser) redirect("/admin");
if (!adminUser.can_manage_messages) redirect("/admin/pickup");
const brandId = adminUser.brand_id ?? "64294306-5f42-463d-a5e8-2ad6c81a96de";
const [campaignsResult, templatesResult, contactsResult] = await Promise.all([
getCommunicationCampaigns(brandId),
getCommunicationTemplates(brandId),
getContacts({ brandId, limit: 50 }),
]);
return (
<main className="min-h-screen bg-stone-100 px-6 py-10">
<div className="mx-auto max-w-6xl">
<div className="mb-8">
<h1 className="text-3xl font-bold text-stone-950">Harvest Reach</h1>
<p className="mt-2 text-stone-500">
Manage email campaigns, templates, contacts, and message history.
</p>
</div>
<CommunicationsPage
campaigns={campaignsResult.success ? campaignsResult.campaigns : []}
templates={templatesResult.success ? templatesResult.templates : []}
activeTab="contacts"
brandId={brandId}
initialContacts={contactsResult.success ? contactsResult.contacts : []}
initialContactTotal={contactsResult.success ? contactsResult.total : 0}
/>
</div>
</main>
);
}
@@ -0,0 +1,41 @@
import { redirect } from "next/navigation";
import { getAdminUser } from "@/lib/admin-permissions";
import { getCommunicationCampaigns } from "@/actions/communications/campaigns";
import { getCommunicationTemplates } from "@/actions/communications/templates";
import { getMessageLogs } from "@/actions/communications/send";
import CommunicationsPage from "@/components/admin/CommunicationsPage";
export default async function LogsPage() {
const adminUser = await getAdminUser();
if (!adminUser) redirect("/admin");
if (!adminUser.can_manage_messages) redirect("/admin/pickup");
const brandId = adminUser.brand_id ?? "64294306-5f42-463d-a5e8-2ad6c81a96de";
const [campaignsResult, templatesResult, logsResult] = await Promise.all([
getCommunicationCampaigns(brandId),
getCommunicationTemplates(brandId),
getMessageLogs({ brandId, limit: 200 }),
]);
return (
<main className="min-h-screen bg-stone-100 px-6 py-10">
<div className="mx-auto max-w-6xl">
<div className="mb-8">
<h1 className="text-3xl font-bold text-stone-950">Harvest Reach</h1>
<p className="mt-2 text-stone-500">
Manage email campaigns, templates, and message history.
</p>
</div>
<CommunicationsPage
campaigns={campaignsResult.success ? campaignsResult.campaigns : []}
templates={templatesResult.success ? templatesResult.templates : []}
activeTab="logs"
brandId={brandId}
initialLogs={logsResult.success ? logsResult.logs : []}
/>
</div>
</main>
);
}
+50
View File
@@ -0,0 +1,50 @@
import { redirect } from "next/navigation";
import { getAdminUser } from "@/lib/admin-permissions";
import { getCommunicationCampaigns } from "@/actions/communications/campaigns";
import { getCommunicationTemplates } from "@/actions/communications/templates";
import { getHarvestReachSegments } from "@/actions/harvest-reach/segments";
import { getCampaignAnalytics } from "@/actions/harvest-reach/campaigns";
import CommunicationsPage from "@/components/admin/CommunicationsPage";
export default async function CommunicationsRootPage() {
const adminUser = await getAdminUser();
if (!adminUser || !adminUser.can_manage_messages) redirect("/admin/pickup");
const effectiveBrandId = adminUser.brand_id ?? "64294306-5f42-463d-a5e8-2ad6c81a96de";
const [campaignsResult, templatesResult, segmentsResult, analyticsResult] = await Promise.all([
getCommunicationCampaigns(effectiveBrandId),
getCommunicationTemplates(effectiveBrandId),
getHarvestReachSegments(effectiveBrandId),
getCampaignAnalytics(effectiveBrandId),
]);
return (
<main className="min-h-screen px-6 py-10">
<div className="mx-auto max-w-6xl">
{/* Breadcrumb */}
<nav className="flex items-center gap-2 text-xs text-stone-500 mb-6">
<a href="/admin" className="hover:text-stone-600 transition-colors">Admin</a>
<span>/</span>
<span className="text-stone-600">Harvest Reach</span>
</nav>
<div className="mb-8">
<h1 className="text-3xl font-bold text-stone-950">Harvest Reach</h1>
<p className="mt-2 text-stone-500">
Manage email campaigns, templates, and message history.
</p>
</div>
<CommunicationsPage
campaigns={campaignsResult.success ? campaignsResult.campaigns : []}
templates={templatesResult.success ? templatesResult.templates : []}
activeTab="campaigns"
brandId={effectiveBrandId}
initialSegments={segmentsResult.success ? segmentsResult.segments : []}
initialAnalytics={analyticsResult}
/>
</div>
</main>
);
}
@@ -0,0 +1,27 @@
import { redirect } from "next/navigation";
import { getAdminUser } from "@/lib/admin-permissions";
import { getHarvestReachSegments } from "@/actions/harvest-reach/segments";
import CommunicationsPage from "@/components/admin/CommunicationsPage";
export default async function SegmentsPage() {
const adminUser = await getAdminUser();
if (!adminUser || !adminUser.can_manage_messages) redirect("/admin/pickup");
const effectiveBrandId = adminUser.brand_id ?? "64294306-5f42-463d-a5e8-2ad6c81a96de";
const segmentsResult = await getHarvestReachSegments(effectiveBrandId);
const segments = segmentsResult.success ? segmentsResult.segments : [];
return (
<div className="min-h-screen bg-stone-100 px-6 py-10">
<div className="mx-auto max-w-6xl">
<CommunicationsPage
campaigns={[]}
templates={[]}
activeTab="segments"
brandId={effectiveBrandId}
initialSegments={segments}
/>
</div>
</div>
);
}
@@ -0,0 +1,41 @@
import { redirect } from "next/navigation";
import { getAdminUser } from "@/lib/admin-permissions";
import { getCommunicationCampaigns } from "@/actions/communications/campaigns";
import { getCommunicationTemplates } from "@/actions/communications/templates";
import { getCommunicationSettings } from "@/actions/communications/settings";
import CommunicationsPage from "@/components/admin/CommunicationsPage";
export default async function SettingsPage() {
const adminUser = await getAdminUser();
if (!adminUser) redirect("/admin");
if (!adminUser.can_manage_messages) redirect("/admin/pickup");
const brandId = adminUser.brand_id ?? "64294306-5f42-463d-a5e8-2ad6c81a96de";
const [campaignsResult, templatesResult, settingsResult] = await Promise.all([
getCommunicationCampaigns(brandId),
getCommunicationTemplates(brandId),
getCommunicationSettings(brandId),
]);
return (
<main className="min-h-screen bg-stone-100 px-6 py-10">
<div className="mx-auto max-w-6xl">
<div className="mb-8">
<h1 className="text-3xl font-bold text-stone-950">Harvest Reach</h1>
<p className="mt-2 text-stone-500">
Manage email campaigns, templates, and message history.
</p>
</div>
<CommunicationsPage
campaigns={campaignsResult.success ? campaignsResult.campaigns : []}
templates={templatesResult.success ? templatesResult.templates : []}
activeTab="settings"
brandId={brandId}
initialSettings={settingsResult}
/>
</div>
</main>
);
}
@@ -0,0 +1,39 @@
import { redirect } from "next/navigation";
import { getAdminUser } from "@/lib/admin-permissions";
import { getCommunicationTemplates, getTemplateById } from "@/actions/communications/templates";
import CommunicationsPage from "@/components/admin/CommunicationsPage";
export default async function TemplateEditPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const adminUser = await getAdminUser();
if (!adminUser) redirect("/admin");
if (!adminUser.can_manage_messages) redirect("/admin/pickup");
const { id } = await params;
const isNew = id === "new";
const templatesResult = await getCommunicationTemplates();
const template = isNew ? undefined : id ? await getTemplateById(id) : undefined;
return (
<main className="min-h-screen bg-stone-100 px-6 py-10">
<div className="mx-auto max-w-6xl">
<div className="mb-8">
<h1 className="text-3xl font-bold text-stone-950">Harvest Reach</h1>
</div>
<CommunicationsPage
campaigns={[]}
templates={templatesResult.success ? templatesResult.templates : []}
activeTab="templates"
brandId={adminUser.brand_id ?? "64294306-5f42-463d-a5e8-2ad6c81a96de"}
editTemplate={template}
editMode={isNew ? "new" : "edit"}
/>
</div>
</main>
);
}
@@ -0,0 +1,36 @@
import { redirect } from "next/navigation";
import { getAdminUser } from "@/lib/admin-permissions";
import { getCommunicationCampaigns } from "@/actions/communications/campaigns";
import { getCommunicationTemplates } from "@/actions/communications/templates";
import CommunicationsPage from "@/components/admin/CommunicationsPage";
export default async function TemplatesPage() {
const adminUser = await getAdminUser();
if (!adminUser) redirect("/admin");
if (!adminUser.can_manage_messages) redirect("/admin/pickup");
const [campaignsResult, templatesResult] = await Promise.all([
getCommunicationCampaigns(adminUser.brand_id ?? undefined),
getCommunicationTemplates(adminUser.brand_id ?? undefined),
]);
return (
<main className="min-h-screen bg-stone-100 px-6 py-10">
<div className="mx-auto max-w-6xl">
<div className="mb-8">
<h1 className="text-3xl font-bold text-stone-950">Harvest Reach</h1>
<p className="mt-2 text-stone-500">
Manage email campaigns, templates, and message history.
</p>
</div>
<CommunicationsPage
campaigns={campaignsResult.success ? campaignsResult.campaigns : []}
templates={templatesResult.success ? templatesResult.templates : []}
activeTab="templates"
brandId={adminUser.brand_id ?? "64294306-5f42-463d-a5e8-2ad6c81a96de"}
/>
</div>
</main>
);
}
@@ -0,0 +1,41 @@
import { getAdminUser } from "@/lib/admin-permissions";
import { getBrandSettings } from "@/actions/brand-settings";
import WelcomeSequenceDashboard from "@/components/admin/WelcomeSequenceDashboard";
const TUXEDO_BRAND_ID = "64294306-5f42-463d-a5e8-2ad6c81a96de";
export default async function WelcomeSequencePage() {
const adminUser = await getAdminUser();
const brandId = adminUser?.brand_id ?? TUXEDO_BRAND_ID;
const settingsResult = await getBrandSettings(brandId);
const brandName = settingsResult?.success ? (settingsResult.settings?.brand_name ?? "Farm") : "Farm";
return (
<main className="min-h-screen bg-stone-100 px-6 py-10">
<div className="mx-auto max-w-5xl space-y-6">
{/* Header */}
<div>
<nav className="flex items-center gap-2 text-xs text-stone-500 mb-4">
<a href="/admin" className="hover:text-stone-600 transition-colors">Admin</a>
<span>/</span>
<a href="/admin/communications" className="hover:text-stone-600 transition-colors">Communications</a>
<span>/</span>
<span className="text-stone-600">Welcome Sequence</span>
</nav>
<div className="flex items-end justify-between">
<div>
<h1 className="text-2xl font-bold text-stone-950">Welcome Email Sequence</h1>
<p className="mt-1 text-sm text-stone-500">{brandName} 4-email onboarding series</p>
</div>
<div className="text-xs text-stone-400">
Auto-enrolls new subscribers (email opt-in)
</div>
</div>
</div>
<WelcomeSequenceDashboard brandId={brandId} />
</div>
</main>
);
}