Browse Source

feat: redesign /webshops UI with compact card-based layout #62

- Replace wide table with responsive card grid layout
- Each store displayed as compact card with organized sections
- Improved information density with better visual hierarchy
- Added hover effects and better spacing
- Maintains all functionality (sync, disconnect, configure, etc.)
- Uses consistent design patterns from rest of the app
- Responsive grid: 1 column mobile, 2 columns tablet, 3 columns desktop
- Better visual grouping of related information
- Smaller, more compact action buttons
- Enhanced readability and user experience
Claude 5 months ago
parent
commit
ba31785a54
1 changed files with 195 additions and 194 deletions
  1. 195 194
      shopcall.ai-main/src/components/IntegrationsContent.tsx

+ 195 - 194
shopcall.ai-main/src/components/IntegrationsContent.tsx

@@ -621,200 +621,201 @@ export function IntegrationsContent() {
           </div>
           </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">
         <Card className="bg-slate-800 border-slate-700">
           <CardHeader>
           <CardHeader>