Bernt Christian Egeland 7 месяцев назад
Родитель
Сommit
48a22ef58d

+ 32 - 2
src/app/(authenticated)/messages/page.tsx

@@ -1,17 +1,47 @@
 import { getAuthContext } from "@/lib/get-auth-context";
-import { getFeatures } from "@/lib/features";
+import { getFeatures, isCloudMode } from "@/lib/features";
 import { redirect } from "next/navigation";
 import { getRecentSmsThreads } from "@/features/sms/Actions/smsActions";
+import { getSmsSettings } from "@/features/sms/Actions/smsSettingsActions";
 import { MessagesPageClient } from "@/features/sms/Components/MessagesPageClient";
 import { PageHeader } from "@/components/page-header";
+import { FeatureLockedMessage } from "../settings/feature-locked-message";
+import { SmsNotConfiguredMessage } from "./sms-not-configured-message";
 
 export default async function MessagesPage() {
   const ctx = await getAuthContext();
   if (!ctx) redirect("/auth/sign-in");
 
   const features = await getFeatures(ctx.organizationId);
+
   if (!features.sms) {
-    redirect("/");
+    return (
+      <>
+        <PageHeader />
+        <div className="flex flex-1 flex-col p-4 pt-0">
+          <FeatureLockedMessage
+            feature="SMS Messaging"
+            description="Send and receive SMS messages with your customers directly from Torqvoice."
+            isCloud={isCloudMode()}
+          />
+        </div>
+      </>
+    );
+  }
+
+  const settingsResult = await getSmsSettings();
+  const settings = settingsResult.success && settingsResult.data ? settingsResult.data : {};
+  const hasProvider = !!settings["sms.provider"];
+
+  if (!hasProvider) {
+    return (
+      <>
+        <PageHeader />
+        <div className="flex flex-1 flex-col p-4 pt-0">
+          <SmsNotConfiguredMessage />
+        </div>
+      </>
+    );
   }
 
   const threadsResult = await getRecentSmsThreads();

+ 23 - 0
src/app/(authenticated)/messages/sms-not-configured-message.tsx

@@ -0,0 +1,23 @@
+import Link from "next/link";
+import { MessageSquare, ArrowRight } from "lucide-react";
+import { Button } from "@/components/ui/button";
+
+export function SmsNotConfiguredMessage() {
+  return (
+    <div className="flex flex-col items-center justify-center py-16 text-center">
+      <div className="flex h-12 w-12 items-center justify-center rounded-full bg-blue-500/10">
+        <MessageSquare className="h-6 w-6 text-blue-600" />
+      </div>
+      <h2 className="mt-4 text-lg font-semibold">SMS Not Configured</h2>
+      <p className="mt-1 max-w-sm text-sm text-muted-foreground">
+        Set up an SMS provider to send and receive messages with your customers. Supports Twilio, Vonage, and Telnyx.
+      </p>
+      <Button asChild className="mt-4" size="sm">
+        <Link href="/settings/sms">
+          Go to SMS Settings
+          <ArrowRight className="ml-1.5 h-3.5 w-3.5" />
+        </Link>
+      </Button>
+    </div>
+  );
+}

+ 85 - 35
src/components/app-sidebar.tsx

@@ -58,20 +58,6 @@ import type { PlanFeatures } from '@/lib/features'
 import { useTheme } from '@/components/theme-provider'
 import { NotificationBell, NotificationPanel } from '@/features/notifications/Components/NotificationPanel'
 
-const baseNavItems = [
-  { title: 'Dashboard', url: '/', icon: LayoutDashboard },
-  { title: 'Vehicles', url: '/vehicles', icon: Car },
-  { title: 'Customers', url: '/customers', icon: Users },
-  { title: 'Work Orders', url: '/work-orders', icon: ClipboardList },
-  { title: 'Messages', url: '/messages', icon: MessageSquare },
-  { title: 'Inspections', url: '/inspections', icon: ClipboardCheck },
-  { title: 'Calendar', url: '/calendar', icon: CalendarDays },
-  { title: 'Quotes', url: '/quotes', icon: FileText },
-  { title: 'Billing', url: '/billing', icon: Receipt },
-  { title: 'Inventory', url: '/inventory', icon: Package },
-  { title: 'Reports', url: '/reports', icon: BarChart3 },
-]
-
 const adminNavItems = [{ title: 'Admin Panel', url: '/admin', icon: ShieldCheck }]
 
 type OrgInfo = { id: string; name: string; role: string }
@@ -104,15 +90,42 @@ export function AppSidebar({
   const [newOrgName, setNewOrgName] = React.useState('')
   const [creatingOrg, setCreatingOrg] = React.useState(false)
 
-  const navItems = [
-    ...baseNavItems.filter((item) => {
-      if (item.title === 'Reports' && (features?.reports === false || !canAccessReports)) return false
-      if (item.title === 'Messages' && features?.sms === false) return false
-      return true
-    }),
-    ...(canAccessSettings ? [{ title: 'Settings', url: '/settings', icon: Settings }] : []),
+  const showReports = features?.reports !== false && canAccessReports
+
+  const clientItems = [
+    { title: 'Customers', url: '/customers', icon: Users },
+    { title: 'Messages', url: '/messages', icon: MessageSquare },
+  ]
+
+  const workshopItems = [
+    { title: 'Vehicles', url: '/vehicles', icon: Car },
+    { title: 'Work Orders', url: '/work-orders', icon: ClipboardList },
+    { title: 'Inspections', url: '/inspections', icon: ClipboardCheck },
+    { title: 'Calendar', url: '/calendar', icon: CalendarDays },
   ]
 
+  const businessItems = [
+    { title: 'Quotes', url: '/quotes', icon: FileText },
+    { title: 'Billing', url: '/billing', icon: Receipt },
+    { title: 'Inventory', url: '/inventory', icon: Package },
+    ...(showReports ? [{ title: 'Reports', url: '/reports', icon: BarChart3 }] : []),
+  ]
+
+  const renderNavGroup = (items: { title: string; url: string; icon: React.ComponentType<{ className?: string }> }[]) =>
+    items.map((item) => {
+      const isActive = pathname === item.url || (item.url !== '/' && pathname.startsWith(item.url))
+      return (
+        <SidebarMenuItem key={item.title}>
+          <SidebarMenuButton asChild isActive={isActive}>
+            <Link href={item.url} className="font-medium">
+              <item.icon className="size-4" />
+              {item.title}
+            </Link>
+          </SidebarMenuButton>
+        </SidebarMenuItem>
+      )
+    })
+
   const activeOrg = organizations.find((o) => o.id === activeOrgId) || organizations[0]
 
   const handleSwitchOrg = async (orgId: string) => {
@@ -146,6 +159,8 @@ export function AppSidebar({
     router.push('/auth/sign-in')
   }
 
+  const dashboardActive = pathname === '/'
+
   return (
     <Sidebar variant="floating" {...props}>
       <SidebarHeader>
@@ -207,25 +222,60 @@ export function AppSidebar({
         </SidebarMenu>
       </SidebarHeader>
       <SidebarContent>
+        {/* Dashboard */}
         <SidebarGroup>
           <SidebarMenu className="gap-2">
-            {navItems.map((item) => {
-              const isActive =
-                pathname === item.url || (item.url !== '/' && pathname.startsWith(item.url))
+            <SidebarMenuItem>
+              <SidebarMenuButton asChild isActive={dashboardActive}>
+                <Link href="/" className="font-medium">
+                  <LayoutDashboard className="size-4" />
+                  Dashboard
+                </Link>
+              </SidebarMenuButton>
+            </SidebarMenuItem>
+          </SidebarMenu>
+        </SidebarGroup>
 
-              return (
-                <SidebarMenuItem key={item.title}>
-                  <SidebarMenuButton asChild isActive={isActive}>
-                    <Link href={item.url} className="font-medium">
-                      <item.icon className="size-4" />
-                      {item.title}
-                    </Link>
-                  </SidebarMenuButton>
-                </SidebarMenuItem>
-              )
-            })}
+        {/* Clients */}
+        <SidebarGroup>
+          <SidebarGroupLabel>Clients</SidebarGroupLabel>
+          <SidebarMenu className="gap-2">
+            {renderNavGroup(clientItems)}
           </SidebarMenu>
         </SidebarGroup>
+
+        {/* Workshop */}
+        <SidebarGroup>
+          <SidebarGroupLabel>Workshop</SidebarGroupLabel>
+          <SidebarMenu className="gap-2">
+            {renderNavGroup(workshopItems)}
+          </SidebarMenu>
+        </SidebarGroup>
+
+        {/* Business */}
+        <SidebarGroup>
+          <SidebarGroupLabel>Business</SidebarGroupLabel>
+          <SidebarMenu className="gap-2">
+            {renderNavGroup(businessItems)}
+          </SidebarMenu>
+        </SidebarGroup>
+
+        {/* Settings */}
+        {canAccessSettings && (
+          <SidebarGroup>
+            <SidebarMenu className="gap-2">
+              <SidebarMenuItem>
+                <SidebarMenuButton asChild isActive={pathname.startsWith('/settings')}>
+                  <Link href="/settings" className="font-medium">
+                    <Settings className="size-4" />
+                    Settings
+                  </Link>
+                </SidebarMenuButton>
+              </SidebarMenuItem>
+            </SidebarMenu>
+          </SidebarGroup>
+        )}
+
         {isSuperAdmin && (
           <SidebarGroup>
             <SidebarGroupLabel>Super Admin</SidebarGroupLabel>