Browse Source

fix: improve shop selector behavior and UI consistency

- Update shop selector to properly sync URL parameters when changing shops
- Remove duplicate shop selector from AI Config page, use centralized sidebar selector
- Fix Website Content page to properly clear stale data when switching shops
- Redeploy shoprenter-sync Edge Function to fix NULL product names and categories

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Fszontagh 4 months ago
parent
commit
cece7f08ec

+ 14 - 140
shopcall.ai-main/src/components/AIConfigContent.tsx

@@ -1,29 +1,16 @@
 
 import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
 import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
 import { Label } from "@/components/ui/label";
 import { Textarea } from "@/components/ui/textarea";
 import { Switch } from "@/components/ui/switch";
 import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
-import { Badge } from "@/components/ui/badge";
 import { Mic, MessageSquare, Store, Loader2 } from "lucide-react";
 import { useState, useEffect } from "react";
 import { API_URL } from "@/lib/config";
 import { useToast } from "@/hooks/use-toast";
 import { useTranslation } from "react-i18next";
-
-interface StoreData {
-  id: string;
-  store_name: string | null;
-  platform_name: string;
-  store_url: string | null;
-  phone_number: string | null;
-  is_active: boolean | null;
-  alt_data?: {
-    [key: string]: unknown;
-  };
-}
+import { useShop } from "@/components/context/ShopContext";
 
 interface AIConfig {
   voice_type: string;
@@ -38,9 +25,7 @@ interface AIConfig {
 export function AIConfigContent() {
   const { t } = useTranslation();
   const { toast } = useToast();
-  const [stores, setStores] = useState<StoreData[]>([]);
-  const [selectedStore, setSelectedStore] = useState<StoreData | null>(null);
-  const [loading, setLoading] = useState(true);
+  const { selectedShop, stores, isLoading } = useShop();
   const [saving, setSaving] = useState(false);
   const [aiConfig, setAiConfig] = useState<AIConfig>({
     voice_type: "sarah",
@@ -52,66 +37,14 @@ export function AIConfigContent() {
     escalation_policy: "medium"
   });
 
-  // Get shop parameter from URL
+  // Load AI config when selected shop changes
   useEffect(() => {
-    const params = new URLSearchParams(window.location.search);
-    const shopId = params.get('shop');
-
-    const fetchStores = async () => {
-      try {
-        const sessionData = localStorage.getItem('session_data');
-        if (!sessionData) {
-          throw new Error('No session data found');
-        }
-
-        const session = JSON.parse(sessionData);
-        const response = await fetch(`${API_URL}/api/stores`, {
-          headers: {
-            'Authorization': `Bearer ${session.session.access_token}`,
-            'Content-Type': 'application/json'
-          }
-        });
-
-        if (!response.ok) {
-          throw new Error('Failed to fetch stores');
-        }
-
-        const data = await response.json();
-        if (data.success) {
-          const storesList = data.stores || [];
-          setStores(storesList);
-
-          // Select the store from URL parameter or first store
-          if (shopId) {
-            const store = storesList.find((s: StoreData) => s.id === shopId);
-            if (store) {
-              setSelectedStore(store);
-              loadAIConfig(store);
-            } else if (storesList.length > 0) {
-              setSelectedStore(storesList[0]);
-              loadAIConfig(storesList[0]);
-            }
-          } else if (storesList.length > 0) {
-            setSelectedStore(storesList[0]);
-            loadAIConfig(storesList[0]);
-          }
-        }
-      } catch (error) {
-        console.error('Error fetching stores:', error);
-        toast({
-          title: t('common.error'),
-          description: t('webshops.error'),
-          variant: "destructive"
-        });
-      } finally {
-        setLoading(false);
-      }
-    };
-
-    fetchStores();
-  }, [toast]);
+    if (selectedShop) {
+      loadAIConfig(selectedShop);
+    }
+  }, [selectedShop]);
 
-  const loadAIConfig = (store: StoreData) => {
+  const loadAIConfig = (store: any) => {
     // Load AI config from store's alt_data or use defaults
     const config = store.alt_data?.ai_config || {};
 
@@ -126,18 +59,8 @@ export function AIConfigContent() {
     });
   };
 
-  const handleStoreChange = (storeId: string) => {
-    const store = stores.find(s => s.id === storeId);
-    if (store) {
-      setSelectedStore(store);
-      loadAIConfig(store);
-      // Update URL
-      window.history.replaceState({}, '', `/ai-config?shop=${storeId}`);
-    }
-  };
-
   const handleSaveConfig = async () => {
-    if (!selectedStore) return;
+    if (!selectedShop) return;
 
     setSaving(true);
     try {
@@ -149,7 +72,7 @@ export function AIConfigContent() {
       const session = JSON.parse(sessionData);
 
       // Update store's alt_data with AI config
-      const response = await fetch(`${API_URL}/api/stores/${selectedStore.id}/ai-config`, {
+      const response = await fetch(`${API_URL}/api/stores/${selectedShop.id}/ai-config`, {
         method: 'PUT',
         headers: {
           'Authorization': `Bearer ${session.session.access_token}`,
@@ -178,7 +101,7 @@ export function AIConfigContent() {
     }
   };
 
-  if (loading) {
+  if (isLoading) {
     return (
       <div className="flex-1 flex items-center justify-center min-h-screen bg-slate-900">
         <Loader2 className="w-8 h-8 text-cyan-500 animate-spin" />
@@ -208,7 +131,7 @@ export function AIConfigContent() {
     );
   }
 
-  if (!selectedStore) {
+  if (!selectedShop) {
     return (
       <div className="flex-1 flex items-center justify-center min-h-screen bg-slate-900">
         <p className="text-slate-400">No store selected</p>
@@ -241,55 +164,6 @@ export function AIConfigContent() {
         </div>
       </div>
 
-      {/* Webshop Selector */}
-      <Card className="bg-slate-800 border-slate-700">
-        <CardHeader>
-          <div className="flex items-center gap-3">
-            <Store className="w-6 h-6 text-cyan-500" />
-            <CardTitle className="text-white">{t('aiConfig.selectWebshop.title')}</CardTitle>
-          </div>
-          <p className="text-slate-400">{t('aiConfig.selectWebshop.subtitle')}</p>
-        </CardHeader>
-        <CardContent>
-          <Select value={selectedStore.id} onValueChange={handleStoreChange}>
-            <SelectTrigger className="bg-slate-700 border-slate-600 text-white">
-              <SelectValue />
-            </SelectTrigger>
-            <SelectContent className="bg-slate-700 border-slate-600">
-              {stores.map((store) => (
-                <SelectItem key={store.id} value={store.id}>
-                  <div className="flex items-center gap-3">
-                    <div>
-                      <div className="text-white font-medium">{store.store_name || 'Unnamed Store'}</div>
-                      <div className="text-slate-400 text-sm capitalize">{store.platform_name}</div>
-                    </div>
-                  </div>
-                </SelectItem>
-              ))}
-            </SelectContent>
-          </Select>
-
-          {/* Current Shop Info */}
-          <div className="mt-4 p-4 bg-slate-700/50 rounded-lg">
-            <div className="flex items-center justify-between">
-              <div>
-                <h4 className="text-white font-medium">{selectedStore.store_name || 'Unnamed Store'}</h4>
-                <p className="text-slate-400 text-sm capitalize">{selectedStore.platform_name}</p>
-                {selectedStore.store_url && (
-                  <p className="text-slate-500 text-xs mt-1">{selectedStore.store_url}</p>
-                )}
-                {selectedStore.phone_number && (
-                  <p className="text-slate-300 text-sm font-mono mt-1">{selectedStore.phone_number}</p>
-                )}
-              </div>
-              <Badge className={`${selectedStore.is_active ? "bg-green-500" : "bg-red-500"} text-white`}>
-                {selectedStore.is_active ? t('webshops.active') : t('webshops.inactive')}
-              </Badge>
-            </div>
-          </div>
-        </CardContent>
-      </Card>
-
       <div className="grid gap-6">
         <Card className="bg-slate-800 border-slate-700">
           <CardHeader>
@@ -297,7 +171,7 @@ export function AIConfigContent() {
               <Mic className="w-6 h-6 text-cyan-500" />
               <CardTitle className="text-white">{t('aiConfig.voiceSettings.title')}</CardTitle>
             </div>
-            <p className="text-slate-400">{t('aiConfig.voiceSettings.subtitle')} {selectedStore.store_name || 'your store'}</p>
+            <p className="text-slate-400">{t('aiConfig.voiceSettings.subtitle')} {selectedShop.store_name || 'your store'}</p>
           </CardHeader>
           <CardContent className="space-y-6">
             <div className="grid gap-6 md:grid-cols-2">
@@ -350,7 +224,7 @@ export function AIConfigContent() {
               <MessageSquare className="w-6 h-6 text-cyan-500" />
               <CardTitle className="text-white">{t('aiConfig.conversationBehavior.title')}</CardTitle>
             </div>
-            <p className="text-slate-400">{t('aiConfig.conversationBehavior.subtitle')} {selectedStore.store_name || 'your store'} customers</p>
+            <p className="text-slate-400">{t('aiConfig.conversationBehavior.subtitle')} {selectedShop.store_name || 'your store'} customers</p>
           </CardHeader>
           <CardContent className="space-y-6">
             <div className="space-y-2">

+ 9 - 1
shopcall.ai-main/src/components/AppSidebar.tsx

@@ -14,7 +14,7 @@ import {
   SidebarHeader,
   SidebarFooter,
 } from "@/components/ui/sidebar";
-import { useNavigate } from "react-router-dom";
+import { useNavigate, useSearchParams } from "react-router-dom";
 import { LayoutDashboard, Phone, BarChart3, Settings, CreditCard, Layers3, PhoneCall, LogOut, Brain, Database, Store, ChevronDown, Package, Globe } from "lucide-react";
 import { useAuth } from "@/components/context/AuthContext";
 import { useShop } from "@/components/context/ShopContext";
@@ -27,6 +27,7 @@ import { API_URL } from "@/lib/config";
 export function AppSidebar() {
   const { t } = useTranslation();
   const navigate = useNavigate();
+  const [searchParams, setSearchParams] = useSearchParams();
   const currentPath = window.location.pathname;
   const { logout } = useAuth();
   const { selectedShop, setSelectedShop, stores, setStores } = useShop();
@@ -110,6 +111,13 @@ export function AppSidebar() {
     const shop = stores.find(s => s.id === shopId);
     if (shop) {
       setSelectedShop(shop);
+
+      // Update URL query parameter if the current page uses the shop parameter
+      if (searchParams.has('shop')) {
+        const newSearchParams = new URLSearchParams(searchParams);
+        newSearchParams.set('shop', shopId);
+        setSearchParams(newSearchParams);
+      }
     }
   };
 

+ 26 - 5
shopcall.ai-main/src/components/ManageStoreDataContent.tsx

@@ -206,10 +206,9 @@ export function ManageStoreDataContent({ defaultTab }: ManageStoreDataContentPro
           const storesList = result.stores || [];
           setStores(storesList);
 
-          // For dedicated pages (Products/Website Content), use global shop context
-          if (isDedicatedPage && selectedShop) {
-            setSelectedStore(selectedShop);
-          } else {
+          // For dedicated pages (Products/Website Content), DON'T set selectedStore here
+          // It will be set by the useEffect that watches selectedShop
+          if (!isDedicatedPage) {
             // For non-dedicated pages, select store from URL parameter or first store
             const shopId = searchParams.get('shop');
             if (shopId) {
@@ -237,7 +236,7 @@ export function ManageStoreDataContent({ defaultTab }: ManageStoreDataContentPro
     };
 
     fetchStores();
-  }, [searchParams, toast, isDedicatedPage, selectedShop]);
+  }, [searchParams, toast, isDedicatedPage]);
 
   // Update selected store when global shop context changes (for dedicated pages)
   useEffect(() => {
@@ -250,6 +249,19 @@ export function ManageStoreDataContent({ defaultTab }: ManageStoreDataContentPro
       setCategoryFilter("all");
       setSelectedItems(new Set());
       setSelectAll(false);
+
+      // Clear data from previous shop
+      setData([]);
+      setCategories([]);
+      setTotalCount(0);
+      setEnabledCount(0);
+      setDisabledCount(0);
+
+      // Clear website content data from previous shop
+      setScraperStatus(null);
+      setScraperContent([]);
+      setCustomUrls([]);
+      setContentTypeFilter("all");
     }
   }, [selectedShop, isDedicatedPage]);
 
@@ -949,6 +961,15 @@ export function ManageStoreDataContent({ defaultTab }: ManageStoreDataContentPro
     );
   }
 
+  // For dedicated pages, wait for selectedShop from context
+  if (isDedicatedPage && !selectedStore) {
+    return (
+      <div className="flex-1 flex items-center justify-center min-h-screen bg-slate-900">
+        <Loader2 className="w-8 h-8 text-cyan-500 animate-spin" />
+      </div>
+    );
+  }
+
   if (stores.length === 0) {
     return (
       <div className="flex-1 space-y-6 p-8 bg-slate-900">