|
@@ -0,0 +1,197 @@
|
|
|
|
|
+import { useState, useEffect } from "react";
|
|
|
|
|
+import { Label } from "@/components/ui/label";
|
|
|
|
|
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
|
|
|
+import { Alert, AlertDescription } from "@/components/ui/alert";
|
|
|
|
|
+import { PhoneCall, AlertCircle, Loader2, DollarSign } from "lucide-react";
|
|
|
|
|
+import { API_URL } from "@/lib/config";
|
|
|
|
|
+
|
|
|
|
|
+interface PhoneNumber {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ phone_number: string;
|
|
|
|
|
+ country_code: string;
|
|
|
|
|
+ country_name: string;
|
|
|
|
|
+ location: string;
|
|
|
|
|
+ phone_type: string;
|
|
|
|
|
+ price: number;
|
|
|
|
|
+ is_available: boolean;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface PhoneNumberSelectorProps {
|
|
|
|
|
+ value: string;
|
|
|
|
|
+ onChange: (phoneNumberId: string) => void;
|
|
|
|
|
+ disabled?: boolean;
|
|
|
|
|
+ error?: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function PhoneNumberSelector({ value, onChange, disabled, error }: PhoneNumberSelectorProps) {
|
|
|
|
|
+ const [phoneNumbers, setPhoneNumbers] = useState<PhoneNumber[]>([]);
|
|
|
|
|
+ const [loading, setLoading] = useState(true);
|
|
|
|
|
+ const [fetchError, setFetchError] = useState<string>("");
|
|
|
|
|
+ const [userCountry, setUserCountry] = useState<string | null>(null);
|
|
|
|
|
+
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ fetchPhoneNumbers();
|
|
|
|
|
+ }, []);
|
|
|
|
|
+
|
|
|
|
|
+ const fetchPhoneNumbers = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const sessionData = localStorage.getItem('session_data');
|
|
|
|
|
+ if (!sessionData) {
|
|
|
|
|
+ setFetchError('Authentication required. Please log in again.');
|
|
|
|
|
+ setLoading(false);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const session = JSON.parse(sessionData);
|
|
|
|
|
+ const response = await fetch(`${API_URL}/api/phone-numbers`, {
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ 'Authorization': `Bearer ${session.session.access_token}`,
|
|
|
|
|
+ 'Content-Type': 'application/json'
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (!response.ok) {
|
|
|
|
|
+ throw new Error('Failed to fetch phone numbers');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const data = await response.json();
|
|
|
|
|
+ if (data.success) {
|
|
|
|
|
+ setPhoneNumbers(data.phone_numbers || []);
|
|
|
|
|
+ setUserCountry(data.user_country);
|
|
|
|
|
+
|
|
|
|
|
+ // Check if there are no available phone numbers
|
|
|
|
|
+ const availableNumbers = data.phone_numbers.filter((pn: PhoneNumber) => pn.is_available);
|
|
|
|
|
+ if (availableNumbers.length === 0) {
|
|
|
|
|
+ setFetchError(
|
|
|
|
|
+ data.user_country
|
|
|
|
|
+ ? `No available phone numbers for your country (${data.user_country}). Please contact support.`
|
|
|
|
|
+ : 'No available phone numbers. Please contact support.'
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error('Error fetching phone numbers:', err);
|
|
|
|
|
+ setFetchError('Failed to load phone numbers. Please try again.');
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setLoading(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const formatPhoneNumber = (pn: PhoneNumber) => {
|
|
|
|
|
+ const price = pn.price > 0 ? ` ($${pn.price}/mo)` : ' (Free)';
|
|
|
|
|
+ return `${pn.phone_number} - ${pn.location}${price}`;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const selectedPhone = phoneNumbers.find(pn => pn.id === value);
|
|
|
|
|
+
|
|
|
|
|
+ if (loading) {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div className="space-y-2">
|
|
|
|
|
+ <Label className="text-white">Phone Number</Label>
|
|
|
|
|
+ <div className="flex items-center gap-2 p-3 bg-slate-700 border border-slate-600 rounded-md">
|
|
|
|
|
+ <Loader2 className="w-4 h-4 text-slate-400 animate-spin" />
|
|
|
|
|
+ <span className="text-slate-400">Loading available phone numbers...</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (fetchError) {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div className="space-y-2">
|
|
|
|
|
+ <Label className="text-white">Phone Number</Label>
|
|
|
|
|
+ <Alert className="bg-red-500/10 border-red-500/50">
|
|
|
|
|
+ <AlertCircle className="h-4 w-4 text-red-500" />
|
|
|
|
|
+ <AlertDescription className="text-red-500">
|
|
|
|
|
+ {fetchError}
|
|
|
|
|
+ </AlertDescription>
|
|
|
|
|
+ </Alert>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const availableNumbers = phoneNumbers.filter(pn => pn.is_available);
|
|
|
|
|
+
|
|
|
|
|
+ if (availableNumbers.length === 0) {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div className="space-y-2">
|
|
|
|
|
+ <Label className="text-white">Phone Number</Label>
|
|
|
|
|
+ <Alert className="bg-red-500/10 border-red-500/50">
|
|
|
|
|
+ <AlertCircle className="h-4 w-4 text-red-500" />
|
|
|
|
|
+ <AlertDescription className="text-red-500">
|
|
|
|
|
+ No available phone numbers for your country{userCountry ? ` (${userCountry})` : ''}. Please contact support to add more phone numbers.
|
|
|
|
|
+ </AlertDescription>
|
|
|
|
|
+ </Alert>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div className="space-y-2">
|
|
|
|
|
+ <Label htmlFor="phoneNumber" className="text-white">
|
|
|
|
|
+ Phone Number <span className="text-red-500">*</span>
|
|
|
|
|
+ </Label>
|
|
|
|
|
+ <Select value={value} onValueChange={onChange} disabled={disabled}>
|
|
|
|
|
+ <SelectTrigger
|
|
|
|
|
+ id="phoneNumber"
|
|
|
|
|
+ className={`bg-slate-700 border-slate-600 text-white ${error ? 'border-red-500' : ''}`}
|
|
|
|
|
+ >
|
|
|
|
|
+ <SelectValue placeholder="Select a phone number for this store">
|
|
|
|
|
+ {selectedPhone ? (
|
|
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
|
|
+ <PhoneCall className="w-4 h-4 text-cyan-500" />
|
|
|
|
|
+ <span className="font-mono">{selectedPhone.phone_number}</span>
|
|
|
|
|
+ <span className="text-slate-400">- {selectedPhone.location}</span>
|
|
|
|
|
+ {selectedPhone.price > 0 ? (
|
|
|
|
|
+ <span className="text-purple-400 flex items-center gap-1">
|
|
|
|
|
+ <DollarSign className="w-3 h-3" />
|
|
|
|
|
+ {selectedPhone.price}/mo
|
|
|
|
|
+ </span>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <span className="text-green-400">(Free)</span>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ) : null}
|
|
|
|
|
+ </SelectValue>
|
|
|
|
|
+ </SelectTrigger>
|
|
|
|
|
+ <SelectContent className="bg-slate-800 border-slate-700">
|
|
|
|
|
+ {availableNumbers.map((pn) => (
|
|
|
|
|
+ <SelectItem
|
|
|
|
|
+ key={pn.id}
|
|
|
|
|
+ value={pn.id}
|
|
|
|
|
+ className="text-white hover:bg-slate-700 focus:bg-slate-700"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
|
|
+ <PhoneCall className="w-4 h-4 text-cyan-500" />
|
|
|
|
|
+ <span className="font-mono">{pn.phone_number}</span>
|
|
|
|
|
+ <span className="text-slate-400">- {pn.location}</span>
|
|
|
|
|
+ {pn.price > 0 ? (
|
|
|
|
|
+ <span className="text-purple-400 flex items-center gap-1">
|
|
|
|
|
+ <DollarSign className="w-3 h-3" />
|
|
|
|
|
+ {pn.price}/mo
|
|
|
|
|
+ </span>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <span className="text-green-400">(Free)</span>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </SelectItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SelectContent>
|
|
|
|
|
+ </Select>
|
|
|
|
|
+ {error && (
|
|
|
|
|
+ <p className="text-sm text-red-500">{error}</p>
|
|
|
|
|
+ )}
|
|
|
|
|
+ <p className="text-sm text-slate-400">
|
|
|
|
|
+ Select a phone number for AI-powered calls to this store's customers
|
|
|
|
|
+ {userCountry && ` (showing ${userCountry} numbers)`}
|
|
|
|
|
+ </p>
|
|
|
|
|
+ {selectedPhone && selectedPhone.price === 0 && (
|
|
|
|
|
+ <Alert className="bg-green-500/10 border-green-500/50">
|
|
|
|
|
+ <AlertDescription className="text-green-500 text-sm">
|
|
|
|
|
+ This is a free phone number with no monthly fees.
|
|
|
|
|
+ </AlertDescription>
|
|
|
|
|
+ </Alert>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
|
|
+}
|