Admin AI Tools page: unified design system styling, provider selector with OpenAI turd emoji, free-text model input with exact ID warning

This commit is contained in:
2026-06-02 02:21:11 +00:00
parent 809e0061ca
commit 15e939ad7e
116 changed files with 14991 additions and 5326 deletions
@@ -1,26 +1,5 @@
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>
);
export default function AnalyticsPage() {
redirect("/admin/communications");
}
@@ -2,6 +2,7 @@ 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 { getHarvestReachSegments } from "@/actions/harvest-reach/segments";
import CommunicationsPage from "@/components/admin/CommunicationsPage";
export default async function CampaignEditPage({
@@ -10,36 +11,30 @@ export default async function CampaignEditPage({
params: Promise<{ id: string }>;
}) {
const adminUser = await getAdminUser();
if (!adminUser) redirect("/admin");
if (!adminUser.can_manage_messages) redirect("/admin/pickup");
if (!adminUser || !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([
const [campaignsResult, templatesResult, segmentsResult] = await Promise.all([
getCommunicationCampaigns(effectiveBrandId),
getCommunicationTemplates(effectiveBrandId),
getHarvestReachSegments(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>
<CommunicationsPage
campaigns={campaignsResult.success ? campaignsResult.campaigns : []}
templates={templatesResult.success ? templatesResult.templates : []}
brandId={effectiveBrandId}
initialSegments={segmentsResult.success ? segmentsResult.segments : []}
editCampaign={campaign}
editMode={isNew ? "new" : "edit"}
/>
);
}
+12 -18
View File
@@ -1,37 +1,31 @@
import { redirect } from "next/navigation";
import { getAdminUser } from "@/lib/admin-permissions";
import { getHarvestReachCampaigns } from "@/actions/harvest-reach/campaigns";
import { getCommunicationCampaigns } from "@/actions/communications/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");
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),
getCommunicationCampaigns(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>
<CommunicationsPage
campaigns={campaignsResult.success ? campaignsResult.campaigns : []}
templates={templatesResult.success ? templatesResult.templates : []}
brandId={effectiveBrandId}
initialSegments={segmentsResult.success ? segmentsResult.segments : []}
editMode="new"
/>
);
}
+3 -40
View File
@@ -1,42 +1,5 @@
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>
);
}
export default function ContactsPage() {
redirect("/admin/communications");
}
+2 -38
View File
@@ -1,41 +1,5 @@
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>
);
export default function LogsPage() {
redirect("/admin/communications");
}
+11 -28
View File
@@ -8,9 +8,11 @@ import CommunicationsPage from "@/components/admin/CommunicationsPage";
export default async function CommunicationsRootPage() {
const adminUser = await getAdminUser();
if (!adminUser || !adminUser.can_manage_messages) redirect("/admin/pickup");
if (!adminUser || !adminUser.can_manage_messages) {
redirect("/admin/pickup");
}
const effectiveBrandId = adminUser.brand_id ?? "64294306-5f42-463d-a5e8-2ad6c81a96de";
const effectiveBrandId = adminUser!.brand_id ?? "64294306-5f42-463d-a5e8-2ad6c81a96de";
const [campaignsResult, templatesResult, segmentsResult, analyticsResult] = await Promise.all([
getCommunicationCampaigns(effectiveBrandId),
@@ -20,31 +22,12 @@ export default async function CommunicationsRootPage() {
]);
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>
<CommunicationsPage
campaigns={campaignsResult.success ? campaignsResult.campaigns : []}
templates={templatesResult.success ? templatesResult.templates : []}
brandId={effectiveBrandId}
initialSegments={segmentsResult.success ? segmentsResult.segments : []}
initialAnalytics={analyticsResult}
/>
);
}
+2 -24
View File
@@ -1,27 +1,5 @@
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>
);
export default function SegmentsPage() {
redirect("/admin/communications");
}
+32 -24
View File
@@ -1,9 +1,7 @@
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";
import CommunicationSettingsForm from "@/components/admin/CommunicationSettingsForm";
export default async function SettingsPage() {
const adminUser = await getAdminUser();
@@ -11,31 +9,41 @@ export default async function SettingsPage() {
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),
]);
const settingsResult = await 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 className="min-h-screen bg-[var(--admin-bg)]">
<div className="px-4 sm:px-6 md:px-8 pt-4 sm:pt-6">
{/* Back button */}
<a
href="/admin/communications"
className="inline-flex items-center gap-2 text-sm text-stone-500 hover:text-stone-700 mb-4"
>
<svg className="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="m15 18-6-6 6-6"/>
</svg>
Back to Harvest Reach
</a>
{/* Header */}
<div className="flex items-center gap-3 mb-6">
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-emerald-600">
<svg className="h-5 w-5 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/>
<circle cx="12" cy="12" r="3"/>
</svg>
</div>
<div>
<h1 className="text-xl sm:text-2xl font-black text-[var(--admin-text-primary)] tracking-tight">Harvest Reach Settings</h1>
<p className="text-xs text-[var(--admin-text-muted)]">Configure email and SMS integration</p>
</div>
</div>
<CommunicationsPage
campaigns={campaignsResult.success ? campaignsResult.campaigns : []}
templates={templatesResult.success ? templatesResult.templates : []}
activeTab="settings"
brandId={brandId}
initialSettings={settingsResult}
/>
{/* Settings Form */}
<div className="rounded-2xl border border-[var(--admin-border)] bg-white p-4 sm:p-6">
<CommunicationSettingsForm settings={settingsResult} brandId={brandId} />
</div>
</div>
</main>
</div>
);
}
@@ -1,6 +1,8 @@
import { redirect } from "next/navigation";
import { getAdminUser } from "@/lib/admin-permissions";
import { getCommunicationTemplates, getTemplateById } from "@/actions/communications/templates";
import { getCommunicationCampaigns } from "@/actions/communications/campaigns";
import { getHarvestReachSegments } from "@/actions/harvest-reach/segments";
import CommunicationsPage from "@/components/admin/CommunicationsPage";
export default async function TemplateEditPage({
@@ -9,31 +11,30 @@ export default async function TemplateEditPage({
params: Promise<{ id: string }>;
}) {
const adminUser = await getAdminUser();
if (!adminUser) redirect("/admin");
if (!adminUser.can_manage_messages) redirect("/admin/pickup");
if (!adminUser || !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, segmentsResult] = await Promise.all([
getCommunicationCampaigns(effectiveBrandId),
getCommunicationTemplates(effectiveBrandId),
getHarvestReachSegments(effectiveBrandId),
]);
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>
<CommunicationsPage
campaigns={campaignsResult.success ? campaignsResult.campaigns : []}
templates={templatesResult.success ? templatesResult.templates : []}
brandId={effectiveBrandId}
initialSegments={segmentsResult.success ? segmentsResult.segments : []}
editTemplate={template}
editMode={isNew ? "new" : "edit"}
/>
);
}
@@ -1,36 +1,5 @@
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>
);
export default function TemplatesPage() {
redirect("/admin/communications");
}