fix: settings page redesign - tabbed interface, no sidebar dropdown
- AdminSidebar: removed dropdown menu, flat nav links, no scrolling - Removed SETTINGS_SUB_LINKS array - Removed settingsOpen state and dropdown logic - Settings is now a simple link to /admin/settings - Nav uses overflow-hidden to prevent sidebar scroll - SettingsClient: unified tabbed interface (like CommunicationsPage) - New tabs: General, Add-ons, Billing, Integrations, AI, Advanced - Uses AdminFilterTabs for navigation - Each tab shows a card with link to full settings page - Redirected pages to /admin/settings#tab: - /admin/settings/apps → /admin/settings#addons - /admin/settings/integrations → /admin/settings#integrations - /admin/settings/ai → /admin/settings#ai - /admin/advanced → /admin/settings#advanced All TypeScript checks pass.
This commit is contained in:
@@ -1,53 +1,9 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { getAdminUser } from "@/lib/admin-permissions";
|
||||
import { getBrands } from "@/actions/admin/users";
|
||||
import { getStripeConnectStatus } from "@/actions/stripe-connect";
|
||||
import AdvancedSettingsClient from "@/components/admin/AdvancedSettingsClient";
|
||||
import { PageHeader } from "@/components/admin/design-system";
|
||||
|
||||
export const metadata = {
|
||||
title: "Advanced Settings - Route Commerce Admin",
|
||||
description: "Developer settings, API integrations, and advanced configurations",
|
||||
};
|
||||
|
||||
export default async function AdvancedSettingsPage() {
|
||||
const adminUser = await getAdminUser();
|
||||
if (!adminUser) redirect("/login");
|
||||
|
||||
// Only admins can access advanced settings
|
||||
if (!adminUser.can_manage_settings && adminUser.role !== "platform_admin") {
|
||||
redirect("/admin");
|
||||
}
|
||||
|
||||
const brandId = adminUser.brand_id ?? "";
|
||||
const { brands } = await getBrands();
|
||||
|
||||
// Get Stripe Connect status
|
||||
const stripeConnect = brandId ? await getStripeConnectStatus(brandId) : null;
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-[var(--admin-bg)] px-6 py-10">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<PageHeader
|
||||
breadcrumb={[
|
||||
{ label: "Admin", href: "/admin" },
|
||||
{ label: "Settings", href: "/admin/settings" },
|
||||
{ label: "Advanced" },
|
||||
]}
|
||||
title="Advanced Settings"
|
||||
subtitle="Developer settings, APIs, and integrations"
|
||||
icon={
|
||||
<svg className="h-6 w-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456zM16.894 20.567L16.5 21.75l-.394-1.183a2.25 2.25 0 00-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 001.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 001.423 1.423l1.183.394-1.183.394a2.25 2.25 0 00-1.423 1.423z"/>
|
||||
</svg>
|
||||
}
|
||||
/>
|
||||
<AdvancedSettingsClient
|
||||
brandId={brandId}
|
||||
brands={brands}
|
||||
stripeConnect={stripeConnect}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
redirect("/admin/settings#advanced");
|
||||
}
|
||||
@@ -1,28 +1,9 @@
|
||||
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) return <AdminAccessDenied />;
|
||||
if (!adminUser) redirect("/login");
|
||||
|
||||
const brandId = adminUser.brand_id ?? "";
|
||||
|
||||
const settings = await getAIProviderSettings(brandId);
|
||||
const isConnected = !!settings.apiKey;
|
||||
|
||||
const brandName = "Brand"; // Note: resolved from adminUser.brand_id on the server; hardcoded fallback for settings UI
|
||||
|
||||
return (
|
||||
<AIClient
|
||||
isConnected={isConnected}
|
||||
brandId={brandId}
|
||||
brandName={brandName}
|
||||
provider={settings.provider}
|
||||
model={settings.model}
|
||||
customEndpoint={settings.customEndpoint}
|
||||
/>
|
||||
);
|
||||
redirect("/admin/settings#ai");
|
||||
}
|
||||
@@ -1,99 +1,10 @@
|
||||
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";
|
||||
|
||||
type Props = {
|
||||
searchParams: Promise<{ reason?: string }>;
|
||||
};
|
||||
|
||||
export default async function AppsSettingsPage({ searchParams }: Props) {
|
||||
export default async function AppsSettingsPage() {
|
||||
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> = {};
|
||||
for (const key of Object.keys(ADDON_CATALOG) as (keyof typeof ADDON_CATALOG)[]) {
|
||||
enabledFeatures[key] = await isFeatureEnabled(brandId, key);
|
||||
}
|
||||
|
||||
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",
|
||||
};
|
||||
|
||||
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>
|
||||
<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>
|
||||
);
|
||||
// Redirect to unified settings page with Add-ons tab
|
||||
redirect("/admin/settings#addons");
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { getAdminUser } from "@/lib/admin-permissions";
|
||||
import { getAdminUsers, getBrands } from "@/actions/admin/users";
|
||||
import { getPaymentSettings } from "@/actions/payments";
|
||||
import SettingsClient from "@/components/admin/SettingsClient";
|
||||
|
||||
export const metadata = {
|
||||
@@ -28,26 +26,5 @@ export default async function AdminSettingsPage({
|
||||
brandId = "64294306-5f42-463d-a5e8-2ad6c81a96de";
|
||||
}
|
||||
|
||||
const [{ users, error }, { brands }] = await Promise.all([
|
||||
getAdminUsers(isPlatformAdmin ? undefined : (adminUser.brand_id ?? undefined)),
|
||||
getBrands(),
|
||||
]);
|
||||
|
||||
// Fetch payment settings for the current brand
|
||||
const paymentResult = await getPaymentSettings(brandId);
|
||||
const paymentSettings = paymentResult.success ? paymentResult.settings : null;
|
||||
|
||||
return (
|
||||
<SettingsClient
|
||||
brandId={brandId}
|
||||
users={error ? [] : users}
|
||||
brands={brands}
|
||||
paymentSettings={paymentSettings}
|
||||
currentUser={{
|
||||
id: adminUser.id ?? adminUser.user_id,
|
||||
role: adminUser.role,
|
||||
can_manage_users: adminUser.can_manage_users,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return <SettingsClient brandId={brandId} />;
|
||||
}
|
||||
Reference in New Issue
Block a user