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
File diff suppressed because it is too large Load Diff
+6 -2
View File
@@ -2,12 +2,13 @@ import { redirect } from "next/navigation";
import { getAdminUser } from "@/lib/admin-permissions";
import { getAIProviderSettings } from "@/actions/integrations/ai-providers";
import AIClient from "./AIClient";
import AdminAccessDenied from "@/components/admin/AdminAccessDenied";
export default async function AISettingsPage() {
const adminUser = await getAdminUser();
if (!adminUser) redirect("/login");
if (!adminUser) return <AdminAccessDenied />;
const brandId = adminUser.brand_id ?? "64294306-5f42-463d-a5e8-2ad6c81a96de";
const brandId = adminUser.brand_id ?? "";
const settings = await getAIProviderSettings(brandId);
const isConnected = !!settings.apiKey;
@@ -19,6 +20,9 @@ export default async function AISettingsPage() {
isConnected={isConnected}
brandId={brandId}
brandName={brandName}
provider={settings.provider}
model={settings.model}
customEndpoint={settings.customEndpoint}
/>
);
}
+72 -21
View File
@@ -2,14 +2,22 @@ import { redirect } from "next/navigation";
import { getAdminUser } from "@/lib/admin-permissions";
import { ADDON_CATALOG, isFeatureEnabled } from "@/lib/feature-flags";
import BrandFeatureCards from "@/components/admin/BrandFeatureCards";
import { AdminPageHeader } from "@/components/admin/design-system";
export default async function AppsSettingsPage() {
type Props = {
searchParams: Promise<{ reason?: string }>;
};
export default async function AppsSettingsPage({ searchParams }: Props) {
const adminUser = await getAdminUser();
if (!adminUser) redirect("/login");
if (!adminUser.can_manage_settings && adminUser.role !== "platform_admin") {
redirect("/admin");
}
const params = await searchParams;
const reason = params.reason;
const brandId = adminUser.brand_id ?? "";
const enabledFeatures: Record<string, boolean> = {};
@@ -17,30 +25,73 @@ export default async function AppsSettingsPage() {
enabledFeatures[key] = await isFeatureEnabled(brandId, key);
}
return (
<main className="min-h-screen bg-stone-100 px-6 py-10">
<div className="mx-auto max-w-4xl">
{/* Breadcrumb */}
<nav className="flex items-center gap-2 text-xs text-stone-500 mb-6">
<a href="/admin" className="hover:text-stone-800 transition-colors">Admin</a>
<span>/</span>
<span className="text-stone-800">Add-ons</span>
</nav>
const featureNames: Record<string, string> = {
route_trace: "Route Trace",
wholesale_portal: "Wholesale Portal",
harvest_reach: "Harvest Reach",
water_log: "Water Log",
ai_tools: "AI Tools",
sms_campaigns: "SMS Campaigns",
square_sync: "Square Sync",
};
<div className="mb-8">
<div className="flex items-center gap-3 mb-2">
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-stone-200 border border-stone-300">
<svg className="h-5 w-5 text-stone-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M14.857 17.082a23.848 23.848 0 005.337-1.219 23.75 23.75 0 00-5.337-1.219 23.848 23.848 0 005.337 1.219zM10.5 2.25a2.25 2.25 0 013.165 1.453 2.25 2.25 0 00-1.453 3.165m-6.197 6.197A3.5 3.5 0 017.5 12.75v1.5a3.5 3.5 0 001.5 2.625h1.5a3.5 3.5 0 001.5-2.625V13.5m-6.197 6.197a3.5 3.5 0 001.5 2.625m0 0v1.5a3.5 3.5 0 01-1.5 2.625H5.25m0 0H3.75a2.25 2.25 0 00-2.25 2.25v1.5a2.25 2.25 0 002.25 2.25h1.5m0 0A3.5 3.5 0 0112.75 19.5v1.5a3.5 3.5 0 01-3.5 3.5H8.25m0 0H6.75" />
</svg>
</div>
<h1 className="text-3xl font-bold text-stone-950">Add-ons</h1>
return (
<main className="min-h-screen admin-section px-6 py-10" style={{ backgroundColor: "var(--admin-bg)" }}>
<div className="mx-auto max-w-5xl">
{/* Header with icon */}
<div className="flex items-center gap-4 mb-6">
<div
className="flex h-12 w-12 items-center justify-center rounded-xl"
style={{
background: 'linear-gradient(135deg, rgba(16, 185, 129, 0.15) 0%, rgba(16, 185, 129, 0.08) 100%)',
border: '1px solid rgba(16, 185, 129, 0.2)',
}}
>
<svg className="h-6 w-6" style={{ color: '#059669' }} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M9.594 3.94c.09.542.56.94 1.11.94h2.64c.55 0 1.02-.398 1.11-.94l.213-1.999c.018-.158.04-.315.062-.472a.563.563 0 00-.122-.519l-.79-2.758A.562.562 0 0014.56 0H9.44a.563.563 0 00-.424.264l-.79 2.758a.563.563 0 00-.122.519c.022.157.044.314.062.472l.213 1.999z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M12 15.75a3.75 3.75 0 100-7.5 3.75 3.75 0 000 7.5z" />
</svg>
</div>
<p className="mt-2 text-stone-500">
Enable or disable add-on features for your brand. Changes take effect immediately.
</p>
<AdminPageHeader
breadcrumb={[{ label: "Admin", href: "/admin" }, { label: "Settings" }, { label: "Add-ons" }]}
title="Add-ons"
description="Enable features to extend your platform capabilities"
/>
</div>
{/* Reason banner */}
{reason && featureNames[reason] && (
<div
className="mb-6 rounded-xl p-4"
style={{
background: 'rgba(16, 185, 129, 0.08)',
border: '1px solid rgba(16, 185, 129, 0.2)',
}}
>
<div className="flex items-center gap-3">
<div
className="flex h-10 w-10 items-center justify-center rounded-lg"
style={{
background: 'rgba(16, 185, 129, 0.15)',
}}
>
<svg className="h-5 w-5" style={{ color: '#059669' }} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" />
</svg>
</div>
<div>
<h2 className="font-semibold text-stone-950">
{featureNames[reason]} is not enabled
</h2>
<p className="text-sm text-stone-500">
Enable the {featureNames[reason]} add-on below to access this feature.
</p>
</div>
</div>
</div>
)}
{/* Feature cards */}
<BrandFeatureCards brandId={brandId} initialEnabledFeatures={enabledFeatures} />
</div>
</main>
+15 -22
View File
@@ -3,6 +3,11 @@ import { getAdminUser } from "@/lib/admin-permissions";
import { getAdminUsers, getBrands } from "@/actions/admin/users";
import SettingsClient from "@/components/admin/SettingsClient";
export const metadata = {
title: "Settings - Route Commerce Admin",
description: "Manage your brand settings, workers, tasks, and user permissions",
};
export default async function AdminSettingsPage() {
const adminUser = await getAdminUser();
if (!adminUser) redirect("/login");
@@ -14,28 +19,16 @@ export default async function AdminSettingsPage() {
getBrands(),
]);
// Breadcrumb nav (for page context)
const breadcrumb = (
<nav className="flex items-center gap-2 text-xs text-stone-500 mb-3">
<a href="/admin" className="hover:text-stone-600 transition-colors">Admin</a>
<span>/</span>
<span className="text-stone-600">Settings</span>
</nav>
);
return (
<main>
{breadcrumb}
<SettingsClient
brandId={brandId}
users={error ? [] : users}
brands={brands}
currentUser={{
id: adminUser.id ?? adminUser.user_id,
role: adminUser.role,
can_manage_users: adminUser.can_manage_users,
}}
/>
</main>
<SettingsClient
brandId={brandId}
users={error ? [] : users}
brands={brands}
currentUser={{
id: adminUser.id ?? adminUser.user_id,
role: adminUser.role,
can_manage_users: adminUser.can_manage_users,
}}
/>
);
}