|
|
@@ -1,58 +1,31 @@
|
|
|
+import { useState, useEffect } from "react";
|
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
|
import { Button } from "@/components/ui/button";
|
|
|
import { Badge } from "@/components/ui/badge";
|
|
|
-import { Input } from "@/components/ui/input";
|
|
|
-import { Label } from "@/components/ui/label";
|
|
|
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
|
-import { PhoneCall, Plus, Settings, AlertTriangle, CheckCircle, Clock, Link, Shield } from "lucide-react";
|
|
|
+import { PhoneCall, Plus, Settings, AlertTriangle, CheckCircle, Clock, Link, Shield, Loader2 } from "lucide-react";
|
|
|
+import { API_URL } from "@/lib/config";
|
|
|
+import { Alert, AlertDescription } from "@/components/ui/alert";
|
|
|
|
|
|
-const connectedShops = [
|
|
|
- {
|
|
|
- name: "Fashion Forward",
|
|
|
- platform: "Shopify",
|
|
|
- phoneNumber: "+1 (555) 123-4567",
|
|
|
- status: "Active",
|
|
|
- type: "Free",
|
|
|
- country: "United States",
|
|
|
- statusColor: "bg-green-500"
|
|
|
- },
|
|
|
- {
|
|
|
- name: "Tech Gadgets Pro",
|
|
|
- platform: "WooCommerce",
|
|
|
- phoneNumber: "+1 (555) 987-6543",
|
|
|
- status: "Active",
|
|
|
- type: "Premium",
|
|
|
- country: "United States",
|
|
|
- statusColor: "bg-green-500"
|
|
|
- },
|
|
|
- {
|
|
|
- name: "Home & Garden",
|
|
|
- platform: "Shopify",
|
|
|
- phoneNumber: "+44 20 7946 0958",
|
|
|
- status: "Pending KYC",
|
|
|
- type: "Premium",
|
|
|
- country: "United Kingdom",
|
|
|
- statusColor: "bg-yellow-500"
|
|
|
- },
|
|
|
- {
|
|
|
- name: "Sports Equipment",
|
|
|
- platform: "WooCommerce",
|
|
|
- phoneNumber: "+1 (555) 456-7890",
|
|
|
- status: "Active",
|
|
|
- type: "Free",
|
|
|
- country: "United States",
|
|
|
- statusColor: "bg-green-500"
|
|
|
- },
|
|
|
- {
|
|
|
- name: "Beauty Essentials",
|
|
|
- platform: "Shopify",
|
|
|
- phoneNumber: "Not assigned",
|
|
|
- status: "Setup Required",
|
|
|
- type: "-",
|
|
|
- country: "-",
|
|
|
- statusColor: "bg-red-500"
|
|
|
- }
|
|
|
-];
|
|
|
+interface PhoneNumber {
|
|
|
+ id: string;
|
|
|
+ phone_number: string;
|
|
|
+ country_code: string;
|
|
|
+ country_name: string;
|
|
|
+ location: string;
|
|
|
+ phone_type: string;
|
|
|
+ price: number;
|
|
|
+ is_available: boolean;
|
|
|
+}
|
|
|
+
|
|
|
+interface Store {
|
|
|
+ id: string;
|
|
|
+ store_name: string;
|
|
|
+ platform_name: string;
|
|
|
+ store_url: string;
|
|
|
+ is_active: boolean;
|
|
|
+ connected_at: string;
|
|
|
+ phone_numbers?: PhoneNumber;
|
|
|
+}
|
|
|
|
|
|
const availableCountries = [
|
|
|
{
|
|
|
@@ -66,7 +39,7 @@ const availableCountries = [
|
|
|
availability: "Instant"
|
|
|
},
|
|
|
{
|
|
|
- name: "United Kingdom",
|
|
|
+ name: "United Kingdom",
|
|
|
flag: "🇬🇧",
|
|
|
pricing: {
|
|
|
setup: "$0",
|
|
|
@@ -129,6 +102,70 @@ const carriers = [
|
|
|
];
|
|
|
|
|
|
export function PhoneNumbersContent() {
|
|
|
+ const [stores, setStores] = useState<Store[]>([]);
|
|
|
+ const [loading, setLoading] = useState(true);
|
|
|
+ const [error, setError] = useState<string>("");
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ fetchStores();
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const fetchStores = async () => {
|
|
|
+ try {
|
|
|
+ const sessionData = localStorage.getItem('session_data');
|
|
|
+ if (!sessionData) {
|
|
|
+ setError('Authentication required. Please log in again.');
|
|
|
+ setLoading(false);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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) {
|
|
|
+ setStores(data.stores || []);
|
|
|
+ } else {
|
|
|
+ setError('Failed to load stores');
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.error('Error fetching stores:', err);
|
|
|
+ setError('Failed to load stores. Please try again.');
|
|
|
+ } finally {
|
|
|
+ setLoading(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const getPlatformDisplayName = (platform: string) => {
|
|
|
+ const platformMap: { [key: string]: string } = {
|
|
|
+ 'shopify': 'Shopify',
|
|
|
+ 'woocommerce': 'WooCommerce',
|
|
|
+ 'shoprenter': 'ShopRenter'
|
|
|
+ };
|
|
|
+ return platformMap[platform.toLowerCase()] || platform;
|
|
|
+ };
|
|
|
+
|
|
|
+ const getStatusBadge = (store: Store) => {
|
|
|
+ if (!store.is_active) {
|
|
|
+ return { text: "Inactive", color: "bg-red-500" };
|
|
|
+ }
|
|
|
+ if (!store.phone_numbers) {
|
|
|
+ return { text: "No Phone Number", color: "bg-yellow-500" };
|
|
|
+ }
|
|
|
+ return { text: "Active", color: "bg-green-500" };
|
|
|
+ };
|
|
|
+
|
|
|
+ const activeStoresCount = stores.filter(s => s.is_active && s.phone_numbers).length;
|
|
|
+
|
|
|
return (
|
|
|
<div className="flex-1 space-y-6 p-8 bg-slate-900">
|
|
|
<div className="flex items-center justify-between">
|
|
|
@@ -136,7 +173,10 @@ export function PhoneNumbersContent() {
|
|
|
<h2 className="text-3xl font-bold tracking-tight text-white">Phone Numbers</h2>
|
|
|
<p className="text-slate-400">Manage phone numbers for your webshops</p>
|
|
|
</div>
|
|
|
- <Button className="bg-cyan-500 hover:bg-cyan-600 text-white">
|
|
|
+ <Button
|
|
|
+ className="bg-slate-600 hover:bg-slate-600 text-slate-400 cursor-not-allowed"
|
|
|
+ disabled
|
|
|
+ >
|
|
|
<Plus className="w-4 h-4 mr-2" />
|
|
|
Add Phone Number
|
|
|
</Button>
|
|
|
@@ -145,72 +185,130 @@ export function PhoneNumbersContent() {
|
|
|
<div className="space-y-6">
|
|
|
<div className="flex items-center justify-between">
|
|
|
<h3 className="text-xl font-semibold text-white">Webshop Phone Numbers</h3>
|
|
|
- <p className="text-slate-400">5 webshops • 4 active numbers</p>
|
|
|
+ <p className="text-slate-400">
|
|
|
+ {loading ? "Loading..." : `${stores.length} webshop${stores.length !== 1 ? 's' : ''} • ${activeStoresCount} active number${activeStoresCount !== 1 ? 's' : ''}`}
|
|
|
+ </p>
|
|
|
</div>
|
|
|
|
|
|
- <Card className="bg-slate-800 border-slate-700">
|
|
|
- <CardContent className="p-0">
|
|
|
- <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">Webshop</th>
|
|
|
- <th className="p-4 text-slate-300 font-medium">Phone Number</th>
|
|
|
- <th className="p-4 text-slate-300 font-medium">Country</th>
|
|
|
- <th className="p-4 text-slate-300 font-medium">Type</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, index) => (
|
|
|
- <tr key={index} className="border-b border-slate-700/50 hover:bg-slate-700/30">
|
|
|
- <td className="p-4">
|
|
|
- <div>
|
|
|
- <div className="text-white font-medium">{shop.name}</div>
|
|
|
- <div className="text-slate-400 text-sm">{shop.platform}</div>
|
|
|
- </div>
|
|
|
- </td>
|
|
|
- <td className="p-4">
|
|
|
- <div className="flex items-center gap-2">
|
|
|
- <PhoneCall className="w-4 h-4 text-slate-400" />
|
|
|
- <span className={`text-sm ${shop.phoneNumber === "Not assigned" ? "text-slate-500 italic" : "text-white font-mono"}`}>
|
|
|
- {shop.phoneNumber}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- </td>
|
|
|
- <td className="p-4 text-slate-300">{shop.country}</td>
|
|
|
- <td className="p-4">
|
|
|
- <Badge variant={shop.type === "Free" ? "default" : "secondary"} className={shop.type === "Free" ? "bg-green-600 text-white" : "bg-purple-600 text-white"}>
|
|
|
- {shop.type}
|
|
|
- </Badge>
|
|
|
- </td>
|
|
|
- <td className="p-4">
|
|
|
- <Badge className={`${shop.statusColor} text-white`}>
|
|
|
- {shop.status}
|
|
|
- </Badge>
|
|
|
- </td>
|
|
|
- <td className="p-4">
|
|
|
- <div className="flex items-center gap-2">
|
|
|
- {shop.phoneNumber === "Not assigned" ? (
|
|
|
- <Button size="sm" className="bg-cyan-500 hover:bg-cyan-600 text-white">
|
|
|
- <Plus className="w-4 h-4 mr-1" />
|
|
|
- Assign
|
|
|
- </Button>
|
|
|
- ) : (
|
|
|
- <Button variant="ghost" size="sm" className="text-slate-400 hover:text-white">
|
|
|
- <Settings className="w-4 h-4" />
|
|
|
- </Button>
|
|
|
- )}
|
|
|
- </div>
|
|
|
- </td>
|
|
|
+ {error && (
|
|
|
+ <Alert className="bg-red-500/10 border-red-500/50">
|
|
|
+ <AlertTriangle className="h-4 w-4 text-red-500" />
|
|
|
+ <AlertDescription className="text-red-500">
|
|
|
+ {error}
|
|
|
+ </AlertDescription>
|
|
|
+ </Alert>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {loading ? (
|
|
|
+ <Card className="bg-slate-800 border-slate-700">
|
|
|
+ <CardContent className="p-8">
|
|
|
+ <div className="flex items-center justify-center gap-3">
|
|
|
+ <Loader2 className="w-6 h-6 text-cyan-500 animate-spin" />
|
|
|
+ <span className="text-slate-400">Loading stores and phone numbers...</span>
|
|
|
+ </div>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+ ) : stores.length === 0 ? (
|
|
|
+ <Card className="bg-slate-800 border-slate-700">
|
|
|
+ <CardContent className="p-8">
|
|
|
+ <div className="text-center">
|
|
|
+ <PhoneCall className="w-12 h-12 text-slate-600 mx-auto mb-4" />
|
|
|
+ <h3 className="text-white text-lg font-semibold mb-2">No webshops connected</h3>
|
|
|
+ <p className="text-slate-400 mb-4">Connect a webshop first to see phone number assignments</p>
|
|
|
+ <Button className="bg-cyan-500 hover:bg-cyan-600 text-white" onClick={() => window.location.href = '/webshops'}>
|
|
|
+ Go to Webshops
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+ ) : (
|
|
|
+ <Card className="bg-slate-800 border-slate-700">
|
|
|
+ <CardContent className="p-0">
|
|
|
+ <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">Webshop</th>
|
|
|
+ <th className="p-4 text-slate-300 font-medium">Phone Number</th>
|
|
|
+ <th className="p-4 text-slate-300 font-medium">Country</th>
|
|
|
+ <th className="p-4 text-slate-300 font-medium">Type</th>
|
|
|
+ <th className="p-4 text-slate-300 font-medium">Status</th>
|
|
|
+ <th className="p-4 text-slate-300 font-medium">Actions</th>
|
|
|
</tr>
|
|
|
- ))}
|
|
|
- </tbody>
|
|
|
- </table>
|
|
|
- </div>
|
|
|
- </CardContent>
|
|
|
- </Card>
|
|
|
+ </thead>
|
|
|
+ <tbody>
|
|
|
+ {stores.map((store) => {
|
|
|
+ const statusBadge = getStatusBadge(store);
|
|
|
+ const phoneNumber = store.phone_numbers;
|
|
|
+
|
|
|
+ return (
|
|
|
+ <tr key={store.id} className="border-b border-slate-700/50 hover:bg-slate-700/30">
|
|
|
+ <td className="p-4">
|
|
|
+ <div>
|
|
|
+ <div className="text-white font-medium">{store.store_name || store.store_url}</div>
|
|
|
+ <div className="text-slate-400 text-sm">{getPlatformDisplayName(store.platform_name)}</div>
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ <td className="p-4">
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
+ <PhoneCall className="w-4 h-4 text-slate-400" />
|
|
|
+ <span className={`text-sm ${phoneNumber ? "text-white font-mono" : "text-slate-500 italic"}`}>
|
|
|
+ {phoneNumber ? phoneNumber.phone_number : "Not assigned"}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ <td className="p-4 text-slate-300">
|
|
|
+ {phoneNumber ? `${phoneNumber.country_name} (${phoneNumber.location})` : "-"}
|
|
|
+ </td>
|
|
|
+ <td className="p-4">
|
|
|
+ {phoneNumber ? (
|
|
|
+ <Badge
|
|
|
+ variant={phoneNumber.price === 0 ? "default" : "secondary"}
|
|
|
+ className={phoneNumber.price === 0 ? "bg-green-600 text-white" : "bg-purple-600 text-white"}
|
|
|
+ >
|
|
|
+ {phoneNumber.price === 0 ? "Free" : "Premium"}
|
|
|
+ </Badge>
|
|
|
+ ) : (
|
|
|
+ <span className="text-slate-500">-</span>
|
|
|
+ )}
|
|
|
+ </td>
|
|
|
+ <td className="p-4">
|
|
|
+ <Badge className={`${statusBadge.color} text-white`}>
|
|
|
+ {statusBadge.text}
|
|
|
+ </Badge>
|
|
|
+ </td>
|
|
|
+ <td className="p-4">
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
+ {phoneNumber ? (
|
|
|
+ <Button
|
|
|
+ variant="ghost"
|
|
|
+ size="sm"
|
|
|
+ className="text-slate-400 hover:text-slate-400 cursor-not-allowed"
|
|
|
+ disabled
|
|
|
+ >
|
|
|
+ <Settings className="w-4 h-4" />
|
|
|
+ </Button>
|
|
|
+ ) : (
|
|
|
+ <Button
|
|
|
+ size="sm"
|
|
|
+ className="bg-slate-600 hover:bg-slate-600 text-slate-400 cursor-not-allowed"
|
|
|
+ disabled
|
|
|
+ >
|
|
|
+ <Plus className="w-4 h-4 mr-1" />
|
|
|
+ Assign
|
|
|
+ </Button>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+ )}
|
|
|
|
|
|
<div className="space-y-4 opacity-60">
|
|
|
<div className="flex items-center justify-between">
|