|
|
@@ -621,200 +621,201 @@ export function IntegrationsContent() {
|
|
|
</div>
|
|
|
)}
|
|
|
|
|
|
- <Card className="bg-slate-800 border-slate-700">
|
|
|
- <CardHeader>
|
|
|
- <CardTitle className="text-white">Connected Webshops</CardTitle>
|
|
|
- <p className="text-slate-400">Overview of all your connected stores with their configurations</p>
|
|
|
- </CardHeader>
|
|
|
- <CardContent className="p-0">
|
|
|
- {loading ? (
|
|
|
- <div className="flex items-center justify-center p-12">
|
|
|
- <Loader2 className="w-8 h-8 text-slate-400 animate-spin" />
|
|
|
- </div>
|
|
|
- ) : connectedShops.length === 0 ? (
|
|
|
- <div className="text-center p-12">
|
|
|
- <Store className="w-16 h-16 text-slate-600 mx-auto mb-4" />
|
|
|
- <h3 className="text-lg font-semibold text-white mb-2">No Stores Connected</h3>
|
|
|
- <p className="text-slate-400 mb-6">
|
|
|
- Connect your first e-commerce store to get started with AI-powered customer calls
|
|
|
- </p>
|
|
|
- <Button
|
|
|
- className="bg-cyan-500 hover:bg-cyan-600 text-white"
|
|
|
- onClick={() => setShowConnectDialog(true)}
|
|
|
- >
|
|
|
- <Plus className="w-4 h-4 mr-2" />
|
|
|
- Connect Your First Store
|
|
|
- </Button>
|
|
|
- </div>
|
|
|
- ) : (
|
|
|
- <div className="overflow-x-auto">
|
|
|
- <table className="w-full">
|
|
|
- <thead className="border-b border-slate-700">
|
|
|
- <tr className="text-left">
|
|
|
- <th className="p-4 text-slate-300 font-medium">Store</th>
|
|
|
- <th className="p-4 text-slate-300 font-medium">Phone Number</th>
|
|
|
- <th className="p-4 text-slate-300 font-medium">Platform</th>
|
|
|
- <th className="p-4 text-slate-300 font-medium">Data Access</th>
|
|
|
- <th className="p-4 text-slate-300 font-medium">Sync Status</th>
|
|
|
- <th className="p-4 text-slate-300 font-medium">Background Sync</th>
|
|
|
- <th className="p-4 text-slate-300 font-medium">Created</th>
|
|
|
- <th className="p-4 text-slate-300 font-medium">Status</th>
|
|
|
- <th className="p-4 text-slate-300 font-medium">Actions</th>
|
|
|
- </tr>
|
|
|
- </thead>
|
|
|
- <tbody>
|
|
|
- {connectedShops.map((shop) => (
|
|
|
- <tr key={shop.id} className="border-b border-slate-700/50 hover:bg-slate-700/30">
|
|
|
- <td className="p-4">
|
|
|
- <div>
|
|
|
- <div className="flex items-center gap-2">
|
|
|
- <div className="text-white font-medium">{shop.store_name || 'Unnamed Store'}</div>
|
|
|
- {shop.platform_name === 'woocommerce' && shop.alt_data?.wcVersion && (
|
|
|
- <Badge variant="outline" className="text-xs border-purple-500 text-purple-400">
|
|
|
- WC {shop.alt_data.wcVersion}
|
|
|
- </Badge>
|
|
|
- )}
|
|
|
- </div>
|
|
|
- <div className="text-slate-500 text-xs">{shop.store_url || '-'}</div>
|
|
|
- {shop.platform_name === 'woocommerce' && shop.alt_data?.wpVersion && (
|
|
|
- <div className="text-slate-600 text-xs mt-1">WordPress {shop.alt_data.wpVersion}</div>
|
|
|
- )}
|
|
|
- </div>
|
|
|
- </td>
|
|
|
- <td className="p-4">
|
|
|
- <div className="flex items-center gap-2">
|
|
|
- <PhoneCall className="w-4 h-4 text-slate-400" />
|
|
|
- <div>
|
|
|
- <div className={`text-sm ${shop.phone_number ? "text-white font-mono" : "text-slate-500 italic"}`}>
|
|
|
- {shop.phone_number || "Not assigned"}
|
|
|
- </div>
|
|
|
- <Badge
|
|
|
- className={`text-xs ${
|
|
|
- shop.phone_number ? "bg-green-500" : "bg-slate-500"
|
|
|
- } text-white`}
|
|
|
- >
|
|
|
- {shop.phone_number ? "Active" : "Setup Required"}
|
|
|
- </Badge>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </td>
|
|
|
- <td className="p-4">
|
|
|
- <Badge className={`capitalize ${
|
|
|
- shop.platform_name === 'woocommerce' ? 'bg-purple-500' :
|
|
|
- shop.platform_name === 'shoprenter' ? 'bg-blue-500' :
|
|
|
- 'bg-green-500'
|
|
|
- } text-white`}>
|
|
|
- {shop.platform_name}
|
|
|
- </Badge>
|
|
|
- </td>
|
|
|
- <td className="p-4">
|
|
|
- <div className="flex flex-col gap-2">
|
|
|
- {getDataAccessBadges(shop)}
|
|
|
- <Button
|
|
|
- size="sm"
|
|
|
- variant="ghost"
|
|
|
- className="text-xs text-cyan-400 hover:text-cyan-300 hover:bg-cyan-500/10 h-6 px-2"
|
|
|
- onClick={() => handleOpenPermissions(shop)}
|
|
|
- >
|
|
|
- <Shield className="w-3 h-3 mr-1" />
|
|
|
- Configure
|
|
|
- </Button>
|
|
|
- </div>
|
|
|
- </td>
|
|
|
- <td className="p-4">
|
|
|
- <div className="flex flex-col gap-2">
|
|
|
- {getSyncStatusBadge(shop)}
|
|
|
- {shop.sync_error && (
|
|
|
- <div className="text-xs text-red-400 max-w-[200px] truncate" title={shop.sync_error}>
|
|
|
- {shop.sync_error}
|
|
|
- </div>
|
|
|
- )}
|
|
|
- {shop.sync_completed_at && shop.sync_status === 'completed' && (
|
|
|
- <div className="text-xs text-slate-500">
|
|
|
- {new Date(shop.sync_completed_at).toLocaleString()}
|
|
|
- </div>
|
|
|
- )}
|
|
|
- {getSyncStats(shop)}
|
|
|
- </div>
|
|
|
- </td>
|
|
|
- <td className="p-4">
|
|
|
- <div className="flex items-center gap-2">
|
|
|
- <Switch
|
|
|
- checked={shop.store_sync_config?.[0]?.enabled ?? true}
|
|
|
- onCheckedChange={() => handleToggleStoreEnabled(
|
|
|
- shop.id,
|
|
|
- shop.store_name || 'this store',
|
|
|
- shop.store_sync_config?.[0]?.enabled ?? true
|
|
|
- )}
|
|
|
- disabled={shop.sync_status === 'syncing'}
|
|
|
- title={shop.sync_status === 'syncing' ? 'Cannot change while syncing' : 'Toggle background sync'}
|
|
|
- />
|
|
|
- <span className="text-xs text-slate-400">
|
|
|
- {shop.store_sync_config?.[0]?.enabled ?? true ? 'Enabled' : 'Disabled'}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- </td>
|
|
|
- <td className="p-4">
|
|
|
- <div className="text-sm text-slate-400">
|
|
|
- {shop.created_at ? new Date(shop.created_at).toLocaleDateString() : '-'}
|
|
|
- </div>
|
|
|
- </td>
|
|
|
- <td className="p-4">
|
|
|
- <Badge className={`${shop.is_active ? "bg-green-500" : "bg-red-500"} text-white`}>
|
|
|
- {shop.is_active ? "Active" : "Inactive"}
|
|
|
- </Badge>
|
|
|
- </td>
|
|
|
- <td className="p-4">
|
|
|
- <div className="flex items-center gap-2">
|
|
|
- <Button
|
|
|
- size="sm"
|
|
|
- variant="outline"
|
|
|
- className="text-cyan-500 border-cyan-500 hover:bg-cyan-500/10"
|
|
|
- onClick={() => handleManualSync(shop.id, shop.store_name || 'this store')}
|
|
|
- disabled={shop.sync_status === 'syncing'}
|
|
|
- title={shop.sync_status === 'syncing' ? 'Sync already in progress' : 'Sync store data'}
|
|
|
- >
|
|
|
- {shop.sync_status === 'syncing' ? (
|
|
|
- <Loader2 className="w-4 h-4 animate-spin" />
|
|
|
- ) : (
|
|
|
- <Zap className="w-4 h-4" />
|
|
|
- )}
|
|
|
- </Button>
|
|
|
- <Button
|
|
|
- size="sm"
|
|
|
- className="bg-cyan-500 hover:bg-cyan-600 text-white"
|
|
|
- onClick={() => window.location.href = `/ai-config?shop=${shop.id}`}
|
|
|
- >
|
|
|
- <Bot className="w-4 h-4 mr-1" />
|
|
|
- AI Config
|
|
|
- </Button>
|
|
|
- <Button
|
|
|
- size="sm"
|
|
|
- variant="outline"
|
|
|
- className="text-cyan-500 border-cyan-500 hover:bg-cyan-500/10"
|
|
|
- onClick={() => window.location.href = `/phone-numbers?shop=${shop.id}`}
|
|
|
- >
|
|
|
- <PhoneCall className="w-4 h-4 mr-1" />
|
|
|
- Phone
|
|
|
- </Button>
|
|
|
- <Button
|
|
|
- size="sm"
|
|
|
- variant="ghost"
|
|
|
- className="text-red-400 hover:text-red-300 hover:bg-red-500/10"
|
|
|
- onClick={() => handleDisconnectStore(shop.id, shop.store_name || 'this store')}
|
|
|
- >
|
|
|
- <Trash2 className="w-4 h-4" />
|
|
|
- </Button>
|
|
|
- </div>
|
|
|
- </td>
|
|
|
- </tr>
|
|
|
- ))}
|
|
|
- </tbody>
|
|
|
- </table>
|
|
|
- </div>
|
|
|
- )}
|
|
|
- </CardContent>
|
|
|
- </Card>
|
|
|
+ <div>
|
|
|
+ <div className="flex items-center justify-between mb-4">
|
|
|
+ <div>
|
|
|
+ <h3 className="text-xl font-semibold text-white">Connected Webshops</h3>
|
|
|
+ <p className="text-slate-400 text-sm">Manage your connected stores</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {loading ? (
|
|
|
+ <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
|
+ {[...Array(3)].map((_, i) => (
|
|
|
+ <Card key={i} className="bg-slate-800 border-slate-700">
|
|
|
+ <CardContent className="p-6">
|
|
|
+ <div className="flex items-center justify-center h-32">
|
|
|
+ <Loader2 className="w-6 h-6 text-slate-400 animate-spin" />
|
|
|
+ </div>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ ) : connectedShops.length === 0 ? (
|
|
|
+ <Card className="bg-slate-800 border-slate-700">
|
|
|
+ <CardContent className="p-12">
|
|
|
+ <div className="text-center">
|
|
|
+ <Store className="w-16 h-16 text-slate-600 mx-auto mb-4" />
|
|
|
+ <h3 className="text-lg font-semibold text-white mb-2">No Stores Connected</h3>
|
|
|
+ <p className="text-slate-400 mb-6">
|
|
|
+ Connect your first e-commerce store to get started with AI-powered customer calls
|
|
|
+ </p>
|
|
|
+ <Button
|
|
|
+ className="bg-cyan-500 hover:bg-cyan-600 text-white"
|
|
|
+ onClick={() => setShowConnectDialog(true)}
|
|
|
+ >
|
|
|
+ <Plus className="w-4 h-4 mr-2" />
|
|
|
+ Connect Your First Store
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+ ) : (
|
|
|
+ <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
|
+ {connectedShops.map((shop) => (
|
|
|
+ <Card key={shop.id} className="bg-slate-800 border-slate-700 hover:border-slate-600 transition-all">
|
|
|
+ <CardContent className="p-5">
|
|
|
+ {/* Header Section */}
|
|
|
+ <div className="flex items-start justify-between mb-4">
|
|
|
+ <div className="flex-1 min-w-0">
|
|
|
+ <div className="flex items-center gap-2 mb-1">
|
|
|
+ <Store className={`w-4 h-4 flex-shrink-0 ${
|
|
|
+ shop.platform_name === 'woocommerce' ? 'text-purple-500' :
|
|
|
+ shop.platform_name === 'shoprenter' ? 'text-blue-500' :
|
|
|
+ 'text-green-500'
|
|
|
+ }`} />
|
|
|
+ <h4 className="text-white font-semibold truncate">{shop.store_name || 'Unnamed Store'}</h4>
|
|
|
+ </div>
|
|
|
+ <p className="text-xs text-slate-500 truncate">{shop.store_url || '-'}</p>
|
|
|
+ </div>
|
|
|
+ <Badge className={`${shop.is_active ? "bg-green-500" : "bg-red-500"} text-white text-xs flex-shrink-0 ml-2`}>
|
|
|
+ {shop.is_active ? "Active" : "Inactive"}
|
|
|
+ </Badge>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* Platform & Version Info */}
|
|
|
+ <div className="flex flex-wrap gap-2 mb-4">
|
|
|
+ <Badge className={`capitalize ${
|
|
|
+ shop.platform_name === 'woocommerce' ? 'bg-purple-500' :
|
|
|
+ shop.platform_name === 'shoprenter' ? 'bg-blue-500' :
|
|
|
+ 'bg-green-500'
|
|
|
+ } text-white text-xs`}>
|
|
|
+ {shop.platform_name}
|
|
|
+ </Badge>
|
|
|
+ {shop.platform_name === 'woocommerce' && shop.alt_data?.wcVersion && (
|
|
|
+ <Badge variant="outline" className="text-xs border-purple-400 text-purple-400">
|
|
|
+ WC {shop.alt_data.wcVersion}
|
|
|
+ </Badge>
|
|
|
+ )}
|
|
|
+ {shop.platform_name === 'woocommerce' && shop.alt_data?.wpVersion && (
|
|
|
+ <Badge variant="outline" className="text-xs border-slate-500 text-slate-400">
|
|
|
+ WP {shop.alt_data.wpVersion}
|
|
|
+ </Badge>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* Phone Number Section */}
|
|
|
+ <div className="bg-slate-700/50 rounded-lg p-3 mb-3">
|
|
|
+ <div className="flex items-center gap-2 mb-1">
|
|
|
+ <PhoneCall className="w-3 h-3 text-slate-400" />
|
|
|
+ <span className="text-xs text-slate-400">Phone Number</span>
|
|
|
+ </div>
|
|
|
+ <div className={`text-sm ${shop.phone_number ? "text-white font-mono" : "text-slate-500 italic"}`}>
|
|
|
+ {shop.phone_number || "Not assigned"}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* Sync Status Section */}
|
|
|
+ <div className="bg-slate-700/50 rounded-lg p-3 mb-3">
|
|
|
+ <div className="flex items-center justify-between mb-2">
|
|
|
+ <span className="text-xs text-slate-400">Sync Status</span>
|
|
|
+ {getSyncStatusBadge(shop)}
|
|
|
+ </div>
|
|
|
+ {shop.sync_completed_at && shop.sync_status === 'completed' && (
|
|
|
+ <div className="text-xs text-slate-500">
|
|
|
+ Last: {new Date(shop.sync_completed_at).toLocaleString()}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ {shop.sync_error && (
|
|
|
+ <div className="text-xs text-red-400 mt-1 truncate" title={shop.sync_error}>
|
|
|
+ Error: {shop.sync_error}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ {getSyncStats(shop)}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* Data Access & Background Sync */}
|
|
|
+ <div className="flex items-center justify-between mb-4 pb-3 border-b border-slate-700">
|
|
|
+ <div>
|
|
|
+ <div className="text-xs text-slate-400 mb-1">Data Access</div>
|
|
|
+ {getDataAccessBadges(shop)}
|
|
|
+ </div>
|
|
|
+ <div className="text-right">
|
|
|
+ <div className="text-xs text-slate-400 mb-1">Auto Sync</div>
|
|
|
+ <Switch
|
|
|
+ checked={shop.store_sync_config?.[0]?.enabled ?? true}
|
|
|
+ onCheckedChange={() => handleToggleStoreEnabled(
|
|
|
+ shop.id,
|
|
|
+ shop.store_name || 'this store',
|
|
|
+ shop.store_sync_config?.[0]?.enabled ?? true
|
|
|
+ )}
|
|
|
+ disabled={shop.sync_status === 'syncing'}
|
|
|
+ title={shop.sync_status === 'syncing' ? 'Cannot change while syncing' : 'Toggle background sync'}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* Action Buttons */}
|
|
|
+ <div className="grid grid-cols-2 gap-2">
|
|
|
+ <Button
|
|
|
+ size="sm"
|
|
|
+ variant="outline"
|
|
|
+ className="text-cyan-500 border-cyan-500 hover:bg-cyan-500/10 text-xs"
|
|
|
+ onClick={() => handleManualSync(shop.id, shop.store_name || 'this store')}
|
|
|
+ disabled={shop.sync_status === 'syncing'}
|
|
|
+ title={shop.sync_status === 'syncing' ? 'Sync in progress' : 'Sync now'}
|
|
|
+ >
|
|
|
+ {shop.sync_status === 'syncing' ? (
|
|
|
+ <Loader2 className="w-3 h-3 mr-1 animate-spin" />
|
|
|
+ ) : (
|
|
|
+ <Zap className="w-3 h-3 mr-1" />
|
|
|
+ )}
|
|
|
+ Sync
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ size="sm"
|
|
|
+ className="bg-cyan-500 hover:bg-cyan-600 text-white text-xs"
|
|
|
+ onClick={() => window.location.href = `/ai-config?shop=${shop.id}`}
|
|
|
+ >
|
|
|
+ <Bot className="w-3 h-3 mr-1" />
|
|
|
+ AI Config
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ size="sm"
|
|
|
+ variant="outline"
|
|
|
+ className="text-purple-500 border-purple-500 hover:bg-purple-500/10 text-xs"
|
|
|
+ onClick={() => window.location.href = `/phone-numbers?shop=${shop.id}`}
|
|
|
+ >
|
|
|
+ <PhoneCall className="w-3 h-3 mr-1" />
|
|
|
+ Phone
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ size="sm"
|
|
|
+ variant="outline"
|
|
|
+ className="text-blue-500 border-blue-500 hover:bg-blue-500/10 text-xs"
|
|
|
+ onClick={() => handleOpenPermissions(shop)}
|
|
|
+ >
|
|
|
+ <Shield className="w-3 h-3 mr-1" />
|
|
|
+ Access
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* Delete Button */}
|
|
|
+ <Button
|
|
|
+ size="sm"
|
|
|
+ variant="ghost"
|
|
|
+ className="w-full mt-2 text-red-400 hover:text-red-300 hover:bg-red-500/10 text-xs"
|
|
|
+ onClick={() => handleDisconnectStore(shop.id, shop.store_name || 'this store')}
|
|
|
+ >
|
|
|
+ <Trash2 className="w-3 h-3 mr-1" />
|
|
|
+ Disconnect Store
|
|
|
+ </Button>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
|
|
|
<Card className="bg-slate-800 border-slate-700">
|
|
|
<CardHeader>
|