From 14ec2f9c717233f09e26706962770bb1d78fdb6b Mon Sep 17 00:00:00 2001 From: default Date: Tue, 2 Jun 2026 04:42:32 +0000 Subject: [PATCH] fix: settings page redesign - tabbed interface, no sidebar dropdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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. --- src/app/admin/advanced/page.tsx | 46 +-- src/app/admin/settings/ai/page.tsx | 23 +- src/app/admin/settings/apps/page.tsx | 95 +---- src/app/admin/settings/page.tsx | 25 +- src/components/admin/AdminSidebar.tsx | 91 +---- src/components/admin/SettingsClient.tsx | 463 +++++++----------------- 6 files changed, 149 insertions(+), 594 deletions(-) diff --git a/src/app/admin/advanced/page.tsx b/src/app/admin/advanced/page.tsx index 211444d..219c68d 100644 --- a/src/app/admin/advanced/page.tsx +++ b/src/app/admin/advanced/page.tsx @@ -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 ( -
-
- - - - } - /> - -
-
- ); + redirect("/admin/settings#advanced"); } \ No newline at end of file diff --git a/src/app/admin/settings/ai/page.tsx b/src/app/admin/settings/ai/page.tsx index 0e65a09..0fbb74c 100644 --- a/src/app/admin/settings/ai/page.tsx +++ b/src/app/admin/settings/ai/page.tsx @@ -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 ; + 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 ( - - ); + redirect("/admin/settings#ai"); } \ No newline at end of file diff --git a/src/app/admin/settings/apps/page.tsx b/src/app/admin/settings/apps/page.tsx index 3f30133..431d899 100644 --- a/src/app/admin/settings/apps/page.tsx +++ b/src/app/admin/settings/apps/page.tsx @@ -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 = {}; - for (const key of Object.keys(ADDON_CATALOG) as (keyof typeof ADDON_CATALOG)[]) { - enabledFeatures[key] = await isFeatureEnabled(brandId, key); - } - - const featureNames: Record = { - 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 ( -
-
- {/* Header with icon */} -
-
- - - - -
- -
- - {/* Reason banner */} - {reason && featureNames[reason] && ( -
-
-
- - - -
-
-

- {featureNames[reason]} is not enabled -

-

- Enable the {featureNames[reason]} add-on below to access this feature. -

-
-
-
- )} - - {/* Feature cards */} - -
-
- ); + // Redirect to unified settings page with Add-ons tab + redirect("/admin/settings#addons"); } \ No newline at end of file diff --git a/src/app/admin/settings/page.tsx b/src/app/admin/settings/page.tsx index 0910b8f..2a6d79d 100644 --- a/src/app/admin/settings/page.tsx +++ b/src/app/admin/settings/page.tsx @@ -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 ( - - ); + return ; } \ No newline at end of file diff --git a/src/components/admin/AdminSidebar.tsx b/src/components/admin/AdminSidebar.tsx index 516a304..ca96cd8 100644 --- a/src/components/admin/AdminSidebar.tsx +++ b/src/components/admin/AdminSidebar.tsx @@ -21,21 +21,6 @@ const TOP_LINKS = [ { href: "/admin/advanced", label: "Advanced", icon: "advanced" }, ]; -type SettingsItem = { - href: string; - label: string; - description: string; -}; - -const SETTINGS_SUB_LINKS: SettingsItem[] = [ - { href: "/admin/settings", label: "General", description: "Brand name, logo, timezone" }, - { href: "/admin/settings/apps", label: "Add-ons", description: "Enable and manage feature add-ons" }, - { href: "/admin/settings/billing", label: "Billing & Plans", description: "Subscription, invoices, usage" }, - { href: "/admin/settings/payments", label: "Payments", description: "Stripe, Square, payment methods" }, - { href: "/admin/communications/abandoned-carts", label: "Abandoned Cart Recovery", description: "3-email recovery sequence" }, - { href: "/admin/communications/welcome-sequence", label: "Welcome Sequence", description: "4-email onboarding for new subscribers" }, -]; - function GridIcon({ className }: { className?: string }) { return ( @@ -145,7 +130,6 @@ export default function AdminSidebar({ userRole }: SidebarProps) { const pathname = usePathname(); const router = useRouter(); const [mobileOpen, setMobileOpen] = useState(false); - const [settingsOpen, setSettingsOpen] = useState(false); const roleLabel = userRole === "platform_admin" ? "Platform Admin" : userRole === "brand_admin" ? "Brand Admin" @@ -154,23 +138,9 @@ export default function AdminSidebar({ userRole }: SidebarProps) { const isActive = (href: string) => { if (href === "/admin") return pathname === "/admin"; - if (href === "/admin/settings") return pathname === "/admin/settings" || pathname.startsWith("/admin/settings"); return pathname.startsWith(href); }; - // Check if we're on a settings-related page - const isSettingsPage = pathname.startsWith("/admin/settings") || - pathname.startsWith("/admin/users") || - pathname.startsWith("/admin/communications/abandoned-carts") || - pathname.startsWith("/admin/communications/welcome-sequence"); - - // Auto-expand settings menu when navigating to a settings page - useEffect(() => { - if (isSettingsPage) { - setSettingsOpen(true); - } - }, [isSettingsPage]); - async function handleLogout() { document.cookie = "dev_session=;path=/;max-age=0"; await supabase.auth.signOut(); @@ -234,74 +204,17 @@ export default function AdminSidebar({ userRole }: SidebarProps) { {/* Nav links */} -
- - - {/* Settings sub-links */} - {settingsOpen && ( -
- {SETTINGS_SUB_LINKS.map((subItem) => { - const subActive = pathname === subItem.href || pathname.startsWith(subItem.href + "/"); - return ( - setMobileOpen(false)} - className={[ - "block px-3 py-2 rounded-lg text-xs font-medium transition-all duration-200", - subActive - ? "bg-emerald-500/10 text-emerald-400" - : "text-[var(--admin-sidebar-text)] hover:bg-white/5 opacity-70 hover:opacity-100", - ].join(" ")} - > - {subItem.label} - - ); - })} -
- )} -
- ); - } - return ( setMobileOpen(false)} className={[ - "group flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium transition-all duration-200", + "group flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium transition-all duration-200 mb-1", active ? "border-l-2" : "border-l-2 border-transparent hover:border-l-2", diff --git a/src/components/admin/SettingsClient.tsx b/src/components/admin/SettingsClient.tsx index 1d7676c..b34bd6a 100644 --- a/src/components/admin/SettingsClient.tsx +++ b/src/components/admin/SettingsClient.tsx @@ -1,150 +1,154 @@ "use client"; import { useState, useEffect } from "react"; -import SettingsSections from "@/components/admin/SettingsSections"; -import UsersPage from "@/components/admin/UsersPage"; -import BrandSettingsForm from "@/components/admin/BrandSettingsForm"; -import PaymentSettingsForm from "@/components/admin/PaymentSettingsForm"; -import { PageHeader, AdminButton } from "@/components/admin/design-system"; -import type { PaymentProvider } from "@/actions/payments"; +import { PageHeader, AdminFilterTabs } from "@/components/admin/design-system"; -type Tab = "general" | "brand" | "workers" | "tasks" | "users"; +type Tab = "general" | "addons" | "billing" | "integrations" | "ai" | "advanced"; -const TABS: { id: Tab; label: string; icon: string; hash: string }[] = [ - { id: "general", label: "General", icon: "settings", hash: "general" }, - { id: "brand", label: "Brand", icon: "brand", hash: "brand" }, - { id: "workers", label: "Workers", icon: "users", hash: "workers" }, - { id: "tasks", label: "Tasks", icon: "list", hash: "tasks" }, - { id: "users", label: "Users", icon: "user-check", hash: "users" }, +const TABS: { id: Tab; label: string }[] = [ + { id: "general", label: "General" }, + { id: "addons", label: "Add-ons" }, + { id: "billing", label: "Billing" }, + { id: "integrations", label: "Integrations" }, + { id: "ai", label: "AI" }, + { id: "advanced", label: "Advanced" }, ]; -// Icon components -const Icons = { - settings: (className: string) => ( - - - - - ), - brand: (className: string) => ( - - - - ), - users: (className: string) => ( - - - - ), - "user-check": (className: string) => ( - - - - - - - ), - plug: (className: string) => ( - - - - ), - list: (className: string) => ( - - - - ), -}; - -type Brand = { id: string; name: string }; - -// Brand Selector Component -function BrandSelector({ - brands, - selectedBrandId, - onSelect, -}: { - brands: Brand[]; - selectedBrandId: string; - onSelect: (id: string) => void; -}) { - return ( -
-

Select which brand's settings to manage:

-
- {brands.map((brand) => ( - - ))} -
-
- ); -} - type Props = { brandId: string; - users: import("@/actions/admin/users").AdminUserRow[]; - brands: Brand[]; - paymentSettings?: { - provider?: PaymentProvider | null; - stripe_publishable_key?: string | null; - stripe_secret_key?: string | null; - square_access_token?: string | null; - square_location_id?: string | null; - square_sync_enabled?: boolean; - square_inventory_mode?: "none" | "rc_to_square" | "square_to_rc" | "bidirectional"; - square_last_sync_at?: string | null; - square_last_sync_error?: string | null; - } | null; - currentUser: { - id: string; - role: string; - can_manage_users: boolean; - }; }; -export default function SettingsClient({ - brandId, - users, - brands, - paymentSettings, - currentUser, -}: Props) { +export default function SettingsClient({ brandId }: Props) { const [activeTab, setActiveTab] = useState("general"); - // Handle URL hash for sidebar navigation + // Handle URL hash for navigation useEffect(() => { const hash = window.location.hash.slice(1); - const matchingTab = TABS.find(t => t.hash === hash); + const matchingTab = TABS.find(t => t.id === hash); if (matchingTab) { setActiveTab(matchingTab.id); } }, []); + // Content for each tab - links to full pages + const renderContent = () => { + switch (activeTab) { + case "general": + return ( +
+
+
+ + + +
+

Workers, Tasks & Users

+

Manage time tracking workers, tasks, and user permissions

+ +
+
+ ); + + case "addons": + return ( +
+
+
+ + + +
+

Feature Add-ons

+

Enable features to extend your platform capabilities

+ + Open Add-ons + +
+
+ ); + + case "billing": + return ( +
+
+
+ + + +
+

Billing & Plans

+

Manage your subscription, invoices, and usage

+ + Open Billing + +
+
+ ); + + case "integrations": + return ( +
+
+
+ + + +
+

Integrations

+

Configure Stripe, Square, shipping, and other integrations

+ + Open Integrations + +
+
+ ); + + case "ai": + return ( +
+
+
+ + + +
+

AI Settings

+

Configure AI provider settings for intelligent automation

+ + Open AI Settings + +
+
+ ); + + case "advanced": + return ( +
+
+
+ + + + +
+

Advanced Settings

+

Developer settings, APIs, webhooks, and integrations

+ + Open Advanced + +
+
+ ); + + default: + return null; + } + }; + return (
{/* Page Header */} @@ -158,209 +162,22 @@ export default function SettingsClient({ } - actions={ - window.location.href = "/admin/advanced"} - > - Advanced Settings - - } /> {/* Tab navigation */} - +
+ setActiveTab(tab as Tab)} + tabs={TABS.map(tab => ({ value: tab.id, label: tab.label }))} + size="md" + /> +
{/* Content */}
- {activeTab === "general" && ( -
- -
- )} - - {activeTab === "brand" && ( -
- {currentUser.role === "platform_admin" && brands.length > 0 && ( -
-
-
-
- {Icons.brand("w-4 h-4 text-white")} -
-
-

Select Brand

-

Choose a brand to view and edit its settings

-
-
-
-
- { - window.location.href = `/admin/settings?brand=${id}#brand`; - }} - /> -
-
- )} - - {/* Selected Brand Settings */} -
-
-
-
- {Icons.brand("w-4 h-4 text-white")} -
-
-

Brand Settings

-

Company information, logos, and default signatures

-
-
-
-
- -
-
- - {/* Payment Settings */} -
-
-
-
- - - -
-
-

Payment Settings

-

Configure your payment provider for checkout processing

-
-
-
-
- -
-
-
- )} - - {activeTab === "workers" && ( -
-
-
-
- {Icons.users("w-4 h-4 text-white")} -
-
-

Workers & PINs

-

Manage time tracking workers and PIN codes

-
-
-
-
- -
-
- )} - - {activeTab === "tasks" && ( -
-
-
-
- {Icons.list("w-4 h-4 text-white")} -
-
-

Tasks

-

Define tasks workers can clock into

-
-
-
-
- -
-
- )} - - {activeTab === "users" && ( -
-
-
-
- {Icons["user-check"]("w-4 h-4 text-[var(--admin-bg)]")} -
-
-

Users & Permissions

-

Manage team access and roles

-
-
-
-
- -
-
- )} - -
-
-
-
- - - -
-
-

Advanced Settings

-

AI, integrations, Square sync, shipping, and webhooks

-
-
- window.location.href = "/admin/advanced"} - > - Open Advanced → - -
-
+ {renderContent()}
);