|
@@ -3,11 +3,13 @@ import { useNavigate, useSearchParams } from "react-router-dom";
|
|
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
|
|
import { Button } from "@/components/ui/button";
|
|
import { Button } from "@/components/ui/button";
|
|
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
|
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
|
|
|
-import { Store, LogIn, UserPlus, Loader2, CheckCircle, AlertCircle, ArrowRight } from "lucide-react";
|
|
|
|
|
|
|
+import { Store, LogIn, UserPlus, Loader2, CheckCircle, AlertCircle, ArrowRight, Phone } from "lucide-react";
|
|
|
import { useAuth } from "@/components/context/AuthContext";
|
|
import { useAuth } from "@/components/context/AuthContext";
|
|
|
import { API_URL } from "@/lib/config";
|
|
import { API_URL } from "@/lib/config";
|
|
|
import { useToast } from "@/hooks/use-toast";
|
|
import { useToast } from "@/hooks/use-toast";
|
|
|
import { useTranslation } from "react-i18next";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
|
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
|
|
|
+import { Label } from "@/components/ui/label";
|
|
|
|
|
|
|
|
interface PendingInstallation {
|
|
interface PendingInstallation {
|
|
|
installation_id: string;
|
|
installation_id: string;
|
|
@@ -15,11 +17,19 @@ interface PendingInstallation {
|
|
|
platform: 'shoprenter' | 'shopify' | 'woocommerce';
|
|
platform: 'shoprenter' | 'shopify' | 'woocommerce';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+interface PhoneNumber {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ phone_number: string;
|
|
|
|
|
+ country_code: string;
|
|
|
|
|
+ monthly_price: number;
|
|
|
|
|
+ currency: string;
|
|
|
|
|
+ is_available: boolean;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
export default function IntegrationsRedirect() {
|
|
export default function IntegrationsRedirect() {
|
|
|
const { t } = useTranslation();
|
|
const { t } = useTranslation();
|
|
|
const navigate = useNavigate();
|
|
const navigate = useNavigate();
|
|
|
const [searchParams] = useSearchParams();
|
|
const [searchParams] = useSearchParams();
|
|
|
- const { isAuthenticated, loading: authLoading } = useAuth();
|
|
|
|
|
const { toast } = useToast();
|
|
const { toast } = useToast();
|
|
|
|
|
|
|
|
const [pendingInstall, setPendingInstall] = useState<PendingInstallation | null>(null);
|
|
const [pendingInstall, setPendingInstall] = useState<PendingInstallation | null>(null);
|
|
@@ -28,6 +38,55 @@ export default function IntegrationsRedirect() {
|
|
|
const [completing, setCompleting] = useState(false);
|
|
const [completing, setCompleting] = useState(false);
|
|
|
const [validating, setValidating] = useState(false);
|
|
const [validating, setValidating] = useState(false);
|
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
|
+ const [checkingAuth, setCheckingAuth] = useState(true);
|
|
|
|
|
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
|
|
|
+
|
|
|
|
|
+ // Phone number selection state
|
|
|
|
|
+ const [phoneNumbers, setPhoneNumbers] = useState<PhoneNumber[]>([]);
|
|
|
|
|
+ const [selectedPhoneNumber, setSelectedPhoneNumber] = useState<string>('');
|
|
|
|
|
+ const [loadingPhoneNumbers, setLoadingPhoneNumbers] = useState(false);
|
|
|
|
|
+
|
|
|
|
|
+ // Check authentication status on mount
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ const checkAuthStatus = async () => {
|
|
|
|
|
+ setCheckingAuth(true);
|
|
|
|
|
+ const sessionData = localStorage.getItem('session_data');
|
|
|
|
|
+
|
|
|
|
|
+ if (!sessionData) {
|
|
|
|
|
+ setIsAuthenticated(false);
|
|
|
|
|
+ setCheckingAuth(false);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const session = JSON.parse(sessionData);
|
|
|
|
|
+ if (!session.success || !session.session?.access_token) {
|
|
|
|
|
+ setIsAuthenticated(false);
|
|
|
|
|
+ setCheckingAuth(false);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Validate token with backend
|
|
|
|
|
+ const response = await fetch(`${API_URL}/auth/check`, {
|
|
|
|
|
+ method: 'GET',
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
|
|
+ 'Authorization': `Bearer ${session.session.access_token}`,
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const data = await response.json();
|
|
|
|
|
+ setIsAuthenticated(data.success === true);
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error('Auth check failed:', err);
|
|
|
|
|
+ setIsAuthenticated(false);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setCheckingAuth(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ checkAuthStatus();
|
|
|
|
|
+ }, []);
|
|
|
|
|
|
|
|
// Detect platform from URL parameters and validate HMAC
|
|
// Detect platform from URL parameters and validate HMAC
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
@@ -110,9 +169,42 @@ export default function IntegrationsRedirect() {
|
|
|
validateAndSetup();
|
|
validateAndSetup();
|
|
|
}, [searchParams, t]);
|
|
}, [searchParams, t]);
|
|
|
|
|
|
|
|
|
|
+ // Fetch available phone numbers when user is authenticated
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ const fetchPhoneNumbers = async () => {
|
|
|
|
|
+ if (!isAuthenticated || !pendingInstall) return;
|
|
|
|
|
+
|
|
|
|
|
+ setLoadingPhoneNumbers(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?available=true`, {
|
|
|
|
|
+ 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 || []);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error('Error fetching phone numbers:', err);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setLoadingPhoneNumbers(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ fetchPhoneNumbers();
|
|
|
|
|
+ }, [isAuthenticated, pendingInstall]);
|
|
|
|
|
+
|
|
|
// Handle auth state changes
|
|
// Handle auth state changes
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
|
- if (authLoading) return;
|
|
|
|
|
|
|
+ if (checkingAuth) return;
|
|
|
|
|
|
|
|
if (pendingInstall) {
|
|
if (pendingInstall) {
|
|
|
if (isAuthenticated) {
|
|
if (isAuthenticated) {
|
|
@@ -123,7 +215,7 @@ export default function IntegrationsRedirect() {
|
|
|
setShowAuthDialog(true);
|
|
setShowAuthDialog(true);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- }, [isAuthenticated, authLoading, pendingInstall]);
|
|
|
|
|
|
|
+ }, [isAuthenticated, checkingAuth, pendingInstall]);
|
|
|
|
|
|
|
|
// Complete the installation
|
|
// Complete the installation
|
|
|
const completeInstallation = async () => {
|
|
const completeInstallation = async () => {
|
|
@@ -144,7 +236,8 @@ export default function IntegrationsRedirect() {
|
|
|
'Content-Type': 'application/json'
|
|
'Content-Type': 'application/json'
|
|
|
},
|
|
},
|
|
|
body: JSON.stringify({
|
|
body: JSON.stringify({
|
|
|
- installation_id: pendingInstall.installation_id
|
|
|
|
|
|
|
+ installation_id: pendingInstall.installation_id,
|
|
|
|
|
+ phone_number_id: selectedPhoneNumber || null
|
|
|
})
|
|
})
|
|
|
});
|
|
});
|
|
|
|
|
|
|
@@ -233,7 +326,7 @@ export default function IntegrationsRedirect() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Show loading state
|
|
// Show loading state
|
|
|
- if (authLoading || validating || (!pendingInstall && !error)) {
|
|
|
|
|
|
|
+ if (checkingAuth || validating || (!pendingInstall && !error)) {
|
|
|
return (
|
|
return (
|
|
|
<div className="min-h-screen flex items-center justify-center bg-slate-900">
|
|
<div className="min-h-screen flex items-center justify-center bg-slate-900">
|
|
|
<div className="text-center">
|
|
<div className="text-center">
|
|
@@ -241,6 +334,8 @@ export default function IntegrationsRedirect() {
|
|
|
<p className="text-slate-400">
|
|
<p className="text-slate-400">
|
|
|
{validating
|
|
{validating
|
|
|
? t('integrations.oauth.validating', 'Validating security parameters...')
|
|
? t('integrations.oauth.validating', 'Validating security parameters...')
|
|
|
|
|
+ : checkingAuth
|
|
|
|
|
+ ? t('integrations.oauth.checkingAuth', 'Checking authentication...')
|
|
|
: t('integrations.oauth.processing', 'Processing integration...')}
|
|
: t('integrations.oauth.processing', 'Processing integration...')}
|
|
|
</p>
|
|
</p>
|
|
|
</div>
|
|
</div>
|
|
@@ -335,6 +430,49 @@ export default function IntegrationsRedirect() {
|
|
|
</DialogDescription>
|
|
</DialogDescription>
|
|
|
</DialogHeader>
|
|
</DialogHeader>
|
|
|
|
|
|
|
|
|
|
+ {/* Phone Number Selection */}
|
|
|
|
|
+ <div className="mt-4 space-y-3">
|
|
|
|
|
+ <div className="space-y-2">
|
|
|
|
|
+ <Label className="text-slate-300 flex items-center gap-2">
|
|
|
|
|
+ <Phone className="w-4 h-4" />
|
|
|
|
|
+ {t('integrations.oauth.selectPhoneNumber', 'Select Phone Number')}
|
|
|
|
|
+ </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">
|
|
|
|
|
+ {t('integrations.oauth.loadingPhoneNumbers', 'Loading available phone numbers...')}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ) : phoneNumbers.length > 0 ? (
|
|
|
|
|
+ <Select value={selectedPhoneNumber} onValueChange={setSelectedPhoneNumber}>
|
|
|
|
|
+ <SelectTrigger className="bg-slate-700 border-slate-600 text-white">
|
|
|
|
|
+ <SelectValue placeholder={t('integrations.oauth.selectPhonePlaceholder', '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.monthly_price > 0 ? `${phone.monthly_price} ${phone.currency}/mo` : t('common.free', 'Free')}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </SelectItem>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </SelectContent>
|
|
|
|
|
+ </Select>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <p className="text-sm text-slate-400 py-2">
|
|
|
|
|
+ {t('integrations.oauth.noPhoneNumbersAvailable', 'No phone numbers available. You can add one later.')}
|
|
|
|
|
+ </p>
|
|
|
|
|
+ )}
|
|
|
|
|
+ <p className="text-xs text-slate-500">
|
|
|
|
|
+ {t('integrations.oauth.phoneNumberOptional', 'Optional - You can also assign a phone number later in settings.')}
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
<div className="space-y-3 mt-4">
|
|
<div className="space-y-3 mt-4">
|
|
|
<Button
|
|
<Button
|
|
|
className="w-full bg-cyan-500 hover:bg-cyan-600 text-white"
|
|
className="w-full bg-cyan-500 hover:bg-cyan-600 text-white"
|