|
|
@@ -1,12 +1,28 @@
|
|
|
-import { useState } from "react";
|
|
|
+import { useState, useEffect } from "react";
|
|
|
import { Card, CardContent, CardDescription, 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 { Alert, AlertDescription } from "@/components/ui/alert";
|
|
|
-import { Loader2, ShoppingBag, ExternalLink, CheckCircle2, AlertCircle, Key } from "lucide-react";
|
|
|
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
|
+import { Loader2, ShoppingBag, ExternalLink, CheckCircle2, AlertCircle, Key, Phone } from "lucide-react";
|
|
|
import { API_URL } from "@/lib/config";
|
|
|
-import { PhoneNumberSelector } from "./PhoneNumberSelector";
|
|
|
+
|
|
|
+interface PhoneNumber {
|
|
|
+ id: string;
|
|
|
+ phone_number: string;
|
|
|
+ country_code: string;
|
|
|
+ country_name: string;
|
|
|
+ location: string;
|
|
|
+ price: number;
|
|
|
+ currency: string;
|
|
|
+ is_available: boolean;
|
|
|
+}
|
|
|
+
|
|
|
+interface Country {
|
|
|
+ country_code: string;
|
|
|
+ country_name: string;
|
|
|
+}
|
|
|
|
|
|
interface WooCommerceConnectProps {
|
|
|
onClose?: () => void;
|
|
|
@@ -16,13 +32,208 @@ export function WooCommerceConnect({ onClose }: WooCommerceConnectProps) {
|
|
|
const [storeUrl, setStoreUrl] = useState("");
|
|
|
const [consumerKey, setConsumerKey] = useState("");
|
|
|
const [consumerSecret, setConsumerSecret] = useState("");
|
|
|
- const [phoneNumberId, setPhoneNumberId] = useState("");
|
|
|
const [isConnecting, setIsConnecting] = useState(false);
|
|
|
const [error, setError] = useState("");
|
|
|
const [phoneNumberError, setPhoneNumberError] = useState("");
|
|
|
const [success, setSuccess] = useState(false);
|
|
|
const [successMessage, setSuccessMessage] = useState("");
|
|
|
|
|
|
+ // Phone number selection state with cascading selectors
|
|
|
+ const [countries, setCountries] = useState<Country[]>([]);
|
|
|
+ const [cities, setCities] = useState<string[]>([]);
|
|
|
+ const [phoneNumbers, setPhoneNumbers] = useState<PhoneNumber[]>([]);
|
|
|
+ const [selectedCountry, setSelectedCountry] = useState<string>('');
|
|
|
+ const [selectedCity, setSelectedCity] = useState<string>('');
|
|
|
+ const [selectedPhoneNumber, setSelectedPhoneNumber] = useState<string>('');
|
|
|
+ const [loadingCountries, setLoadingCountries] = useState(false);
|
|
|
+ const [loadingCities, setLoadingCities] = useState(false);
|
|
|
+ const [loadingPhoneNumbers, setLoadingPhoneNumbers] = useState(false);
|
|
|
+
|
|
|
+ // Detect browser/user country using various methods
|
|
|
+ const detectBrowserCountry = (): string | null => {
|
|
|
+ try {
|
|
|
+ // Method 1: Try to get from browser's navigator.language
|
|
|
+ const browserLang = navigator.language || (navigator as any).userLanguage;
|
|
|
+ if (browserLang) {
|
|
|
+ const parts = browserLang.split('-');
|
|
|
+ if (parts.length >= 2) {
|
|
|
+ return parts[1].toUpperCase();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Method 2: Try Intl.DateTimeFormat
|
|
|
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
|
+ const timezoneCountryMap: { [key: string]: string } = {
|
|
|
+ 'Europe/Budapest': 'HU',
|
|
|
+ 'Europe/London': 'GB',
|
|
|
+ 'America/New_York': 'US',
|
|
|
+ 'America/Los_Angeles': 'US',
|
|
|
+ 'America/Chicago': 'US',
|
|
|
+ 'Europe/Berlin': 'DE',
|
|
|
+ 'Europe/Paris': 'FR',
|
|
|
+ 'Europe/Rome': 'IT',
|
|
|
+ 'Europe/Madrid': 'ES',
|
|
|
+ 'Europe/Vienna': 'AT',
|
|
|
+ 'Europe/Brussels': 'BE',
|
|
|
+ 'Europe/Amsterdam': 'NL',
|
|
|
+ 'Europe/Warsaw': 'PL',
|
|
|
+ 'America/Toronto': 'CA',
|
|
|
+ 'America/Vancouver': 'CA'
|
|
|
+ };
|
|
|
+
|
|
|
+ if (timezone && timezoneCountryMap[timezone]) {
|
|
|
+ return timezoneCountryMap[timezone];
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.error('Error detecting browser country:', err);
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ };
|
|
|
+
|
|
|
+ // Fetch available countries on mount
|
|
|
+ useEffect(() => {
|
|
|
+ const fetchCountries = async () => {
|
|
|
+ setLoadingCountries(true);
|
|
|
+ try {
|
|
|
+ const sessionData = localStorage.getItem('session_data');
|
|
|
+ if (!sessionData) return;
|
|
|
+
|
|
|
+ const session = JSON.parse(sessionData);
|
|
|
+ const response = await fetch(`${API_URL}/api/phone-numbers?group_by=countries`, {
|
|
|
+ method: 'GET',
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${session.session.access_token}`,
|
|
|
+ 'Content-Type': 'application/json'
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (response.ok) {
|
|
|
+ const data = await response.json();
|
|
|
+ setCountries(data.countries || []);
|
|
|
+
|
|
|
+ // Determine which country to auto-select
|
|
|
+ let countryToSelect = data.user_country;
|
|
|
+
|
|
|
+ // If user doesn't have a country set, try browser detection
|
|
|
+ if (!countryToSelect) {
|
|
|
+ countryToSelect = detectBrowserCountry();
|
|
|
+ console.log('[WooCommerce] Browser detected country:', countryToSelect);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (countryToSelect) {
|
|
|
+ // Check if the country is in the available countries list
|
|
|
+ const hasCountry = (data.countries || []).some(
|
|
|
+ (c: Country) => c.country_code === countryToSelect
|
|
|
+ );
|
|
|
+ if (hasCountry) {
|
|
|
+ setSelectedCountry(countryToSelect);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ console.error('Failed to fetch countries:', response.status);
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.error('Error fetching countries:', err);
|
|
|
+ } finally {
|
|
|
+ setLoadingCountries(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ fetchCountries();
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ // Fetch cities when country is selected
|
|
|
+ useEffect(() => {
|
|
|
+ const fetchCities = async () => {
|
|
|
+ if (!selectedCountry) {
|
|
|
+ setCities([]);
|
|
|
+ setSelectedCity('');
|
|
|
+ setPhoneNumbers([]);
|
|
|
+ setSelectedPhoneNumber('');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ setLoadingCities(true);
|
|
|
+ setCities([]);
|
|
|
+ setSelectedCity('');
|
|
|
+ setPhoneNumbers([]);
|
|
|
+ setSelectedPhoneNumber('');
|
|
|
+
|
|
|
+ try {
|
|
|
+ const sessionData = localStorage.getItem('session_data');
|
|
|
+ if (!sessionData) return;
|
|
|
+
|
|
|
+ const session = JSON.parse(sessionData);
|
|
|
+ const response = await fetch(`${API_URL}/api/phone-numbers?group_by=cities&country=${selectedCountry}`, {
|
|
|
+ method: 'GET',
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${session.session.access_token}`,
|
|
|
+ 'Content-Type': 'application/json'
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (response.ok) {
|
|
|
+ const data = await response.json();
|
|
|
+ setCities(data.cities || []);
|
|
|
+ } else {
|
|
|
+ console.error('Failed to fetch cities:', response.status);
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.error('Error fetching cities:', err);
|
|
|
+ } finally {
|
|
|
+ setLoadingCities(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ fetchCities();
|
|
|
+ }, [selectedCountry]);
|
|
|
+
|
|
|
+ // Fetch phone numbers when city is selected
|
|
|
+ useEffect(() => {
|
|
|
+ const fetchPhoneNumbers = async () => {
|
|
|
+ if (!selectedCountry || !selectedCity) {
|
|
|
+ setPhoneNumbers([]);
|
|
|
+ setSelectedPhoneNumber('');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ setLoadingPhoneNumbers(true);
|
|
|
+ setPhoneNumbers([]);
|
|
|
+ setSelectedPhoneNumber('');
|
|
|
+
|
|
|
+ try {
|
|
|
+ const sessionData = localStorage.getItem('session_data');
|
|
|
+ if (!sessionData) return;
|
|
|
+
|
|
|
+ const session = JSON.parse(sessionData);
|
|
|
+ const response = await fetch(
|
|
|
+ `${API_URL}/api/phone-numbers?available=true&country=${selectedCountry}&city=${encodeURIComponent(selectedCity)}`,
|
|
|
+ {
|
|
|
+ method: 'GET',
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${session.session.access_token}`,
|
|
|
+ 'Content-Type': 'application/json'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ if (response.ok) {
|
|
|
+ const data = await response.json();
|
|
|
+ setPhoneNumbers(data.phone_numbers || []);
|
|
|
+ } else {
|
|
|
+ console.error('Failed to fetch phone numbers:', response.status);
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.error('Error fetching phone numbers:', err);
|
|
|
+ } finally {
|
|
|
+ setLoadingPhoneNumbers(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ fetchPhoneNumbers();
|
|
|
+ }, [selectedCountry, selectedCity]);
|
|
|
+
|
|
|
const handleManualConnect = async () => {
|
|
|
setError("");
|
|
|
setPhoneNumberError("");
|
|
|
@@ -44,9 +255,9 @@ export function WooCommerceConnect({ onClose }: WooCommerceConnectProps) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- if (!phoneNumberId) {
|
|
|
+ if (!selectedPhoneNumber) {
|
|
|
setPhoneNumberError("Please select a phone number for this store");
|
|
|
- setError("Please select a phone number before connecting");
|
|
|
+ setError("Please select a country, city, and phone number before connecting");
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -99,7 +310,7 @@ export function WooCommerceConnect({ onClose }: WooCommerceConnectProps) {
|
|
|
storeUrl: normalizedUrl,
|
|
|
consumerKey: consumerKey.trim(),
|
|
|
consumerSecret: consumerSecret.trim(),
|
|
|
- phoneNumberId: phoneNumberId
|
|
|
+ phoneNumberId: selectedPhoneNumber
|
|
|
})
|
|
|
}
|
|
|
);
|
|
|
@@ -219,12 +430,130 @@ export function WooCommerceConnect({ onClose }: WooCommerceConnectProps) {
|
|
|
/>
|
|
|
</div>
|
|
|
|
|
|
- <PhoneNumberSelector
|
|
|
- value={phoneNumberId}
|
|
|
- onChange={setPhoneNumberId}
|
|
|
- disabled={isConnecting}
|
|
|
- error={phoneNumberError}
|
|
|
- />
|
|
|
+ {/* Phone Number Selection - Cascading: Country → City → Phone */}
|
|
|
+ <div className="space-y-4 border border-slate-600 rounded-lg p-4 bg-slate-700/30">
|
|
|
+ <h4 className="text-white font-medium flex items-center gap-2">
|
|
|
+ <Phone className="w-4 h-4 text-cyan-500" />
|
|
|
+ Select Phone Number
|
|
|
+ </h4>
|
|
|
+
|
|
|
+ {/* Country Selector */}
|
|
|
+ <div className="space-y-2">
|
|
|
+ <Label className="text-slate-300">
|
|
|
+ Country <span className="text-red-400">*</span>
|
|
|
+ </Label>
|
|
|
+ {loadingCountries ? (
|
|
|
+ <div className="flex items-center justify-center py-3">
|
|
|
+ <Loader2 className="w-5 h-5 text-cyan-500 animate-spin" />
|
|
|
+ <span className="ml-2 text-sm text-slate-400">Loading countries...</span>
|
|
|
+ </div>
|
|
|
+ ) : countries.length > 0 ? (
|
|
|
+ <Select value={selectedCountry} onValueChange={setSelectedCountry} disabled={isConnecting}>
|
|
|
+ <SelectTrigger className={`bg-slate-700 border-slate-600 text-white ${!selectedCountry ? 'border-red-500/50' : ''}`}>
|
|
|
+ <SelectValue placeholder="Select a country" />
|
|
|
+ </SelectTrigger>
|
|
|
+ <SelectContent className="bg-slate-700 border-slate-600">
|
|
|
+ {countries.map((country) => (
|
|
|
+ <SelectItem key={country.country_code} value={country.country_code} className="text-white hover:bg-slate-600">
|
|
|
+ {country.country_name}
|
|
|
+ </SelectItem>
|
|
|
+ ))}
|
|
|
+ </SelectContent>
|
|
|
+ </Select>
|
|
|
+ ) : (
|
|
|
+ <div className="bg-red-500/10 border border-red-500/30 rounded-md p-3">
|
|
|
+ <p className="text-sm text-red-400">
|
|
|
+ No countries with available phone numbers.
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* City Selector - shown after country selection */}
|
|
|
+ {selectedCountry && (
|
|
|
+ <div className="space-y-2">
|
|
|
+ <Label className="text-slate-300">
|
|
|
+ City <span className="text-red-400">*</span>
|
|
|
+ </Label>
|
|
|
+ {loadingCities ? (
|
|
|
+ <div className="flex items-center justify-center py-3">
|
|
|
+ <Loader2 className="w-5 h-5 text-cyan-500 animate-spin" />
|
|
|
+ <span className="ml-2 text-sm text-slate-400">Loading cities...</span>
|
|
|
+ </div>
|
|
|
+ ) : cities.length > 0 ? (
|
|
|
+ <Select value={selectedCity} onValueChange={setSelectedCity} disabled={isConnecting}>
|
|
|
+ <SelectTrigger className={`bg-slate-700 border-slate-600 text-white ${!selectedCity ? 'border-red-500/50' : ''}`}>
|
|
|
+ <SelectValue placeholder="Select a city" />
|
|
|
+ </SelectTrigger>
|
|
|
+ <SelectContent className="bg-slate-700 border-slate-600">
|
|
|
+ {cities.map((city) => (
|
|
|
+ <SelectItem key={city} value={city} className="text-white hover:bg-slate-600">
|
|
|
+ {city}
|
|
|
+ </SelectItem>
|
|
|
+ ))}
|
|
|
+ </SelectContent>
|
|
|
+ </Select>
|
|
|
+ ) : (
|
|
|
+ <div className="bg-yellow-500/10 border border-yellow-500/30 rounded-md p-3">
|
|
|
+ <p className="text-sm text-yellow-400">
|
|
|
+ No cities with available phone numbers in this country.
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {/* Phone Number Selector - shown after city selection */}
|
|
|
+ {selectedCity && (
|
|
|
+ <div className="space-y-2">
|
|
|
+ <Label className="text-slate-300">
|
|
|
+ Phone Number <span className="text-red-400">*</span>
|
|
|
+ </Label>
|
|
|
+ {loadingPhoneNumbers ? (
|
|
|
+ <div className="flex items-center justify-center py-3">
|
|
|
+ <Loader2 className="w-5 h-5 text-cyan-500 animate-spin" />
|
|
|
+ <span className="ml-2 text-sm text-slate-400">Loading phone numbers...</span>
|
|
|
+ </div>
|
|
|
+ ) : phoneNumbers.length > 0 ? (
|
|
|
+ <>
|
|
|
+ <Select value={selectedPhoneNumber} onValueChange={setSelectedPhoneNumber} disabled={isConnecting}>
|
|
|
+ <SelectTrigger className={`bg-slate-700 border-slate-600 text-white ${!selectedPhoneNumber ? 'border-red-500/50' : ''} ${phoneNumberError ? 'border-red-500' : ''}`}>
|
|
|
+ <SelectValue placeholder="Select a phone number" />
|
|
|
+ </SelectTrigger>
|
|
|
+ <SelectContent className="bg-slate-700 border-slate-600">
|
|
|
+ {phoneNumbers.map((phone) => (
|
|
|
+ <SelectItem key={phone.id} value={phone.id} className="text-white hover:bg-slate-600">
|
|
|
+ <div className="flex items-center justify-between w-full">
|
|
|
+ <span>{phone.phone_number}</span>
|
|
|
+ <span className="ml-2 text-sm text-slate-400">
|
|
|
+ {phone.price > 0 ? `$${phone.price}/mo` : 'Free'}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </SelectItem>
|
|
|
+ ))}
|
|
|
+ </SelectContent>
|
|
|
+ </Select>
|
|
|
+ {phoneNumberError && (
|
|
|
+ <p className="text-xs text-red-400">{phoneNumberError}</p>
|
|
|
+ )}
|
|
|
+ </>
|
|
|
+ ) : (
|
|
|
+ <div className="bg-red-500/10 border border-red-500/30 rounded-md p-3">
|
|
|
+ <p className="text-sm text-red-400">
|
|
|
+ No phone numbers available in this city.
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {/* Show message when not all selections made */}
|
|
|
+ {!selectedPhoneNumber && countries.length > 0 && (
|
|
|
+ <p className="text-xs text-slate-400">
|
|
|
+ Please select a country, city, and phone number to continue.
|
|
|
+ </p>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
<Button
|