|
|
@@ -4,7 +4,8 @@ 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 } from "lucide-react";
|
|
|
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
|
+import { Loader2, ShoppingBag, ExternalLink, CheckCircle2, AlertCircle, Key } from "lucide-react";
|
|
|
import { API_URL } from "@/lib/config";
|
|
|
|
|
|
interface WooCommerceConnectProps {
|
|
|
@@ -13,11 +14,14 @@ interface WooCommerceConnectProps {
|
|
|
|
|
|
export function WooCommerceConnect({ onClose }: WooCommerceConnectProps) {
|
|
|
const [storeUrl, setStoreUrl] = useState("");
|
|
|
+ const [consumerKey, setConsumerKey] = useState("");
|
|
|
+ const [consumerSecret, setConsumerSecret] = useState("");
|
|
|
const [isConnecting, setIsConnecting] = useState(false);
|
|
|
const [error, setError] = useState("");
|
|
|
const [success, setSuccess] = useState(false);
|
|
|
+ const [successMessage, setSuccessMessage] = useState("");
|
|
|
|
|
|
- const handleConnect = async () => {
|
|
|
+ const handleOAuthConnect = async () => {
|
|
|
setError("");
|
|
|
setSuccess(false);
|
|
|
|
|
|
@@ -85,6 +89,7 @@ export function WooCommerceConnect({ onClose }: WooCommerceConnectProps) {
|
|
|
if (data.success && data.authUrl) {
|
|
|
// Redirect to WooCommerce for authorization
|
|
|
setSuccess(true);
|
|
|
+ setSuccessMessage("Redirecting to WooCommerce for authorization...");
|
|
|
setTimeout(() => {
|
|
|
window.location.href = data.authUrl;
|
|
|
}, 1000);
|
|
|
@@ -99,9 +104,105 @@ export function WooCommerceConnect({ onClose }: WooCommerceConnectProps) {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- const handleKeyPress = (e: React.KeyboardEvent) => {
|
|
|
- if (e.key === "Enter" && !isConnecting) {
|
|
|
- handleConnect();
|
|
|
+ const handleManualConnect = async () => {
|
|
|
+ setError("");
|
|
|
+ setSuccess(false);
|
|
|
+
|
|
|
+ // Validate inputs
|
|
|
+ if (!storeUrl.trim()) {
|
|
|
+ setError("Please enter your WooCommerce store URL");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!consumerKey.trim()) {
|
|
|
+ setError("Please enter your Consumer Key");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!consumerSecret.trim()) {
|
|
|
+ setError("Please enter your Consumer Secret");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Normalize URL
|
|
|
+ let normalizedUrl = storeUrl.trim();
|
|
|
+
|
|
|
+ // Remove trailing slash
|
|
|
+ normalizedUrl = normalizedUrl.replace(/\/$/, "");
|
|
|
+
|
|
|
+ // Add https:// if missing
|
|
|
+ if (!normalizedUrl.startsWith('http://') && !normalizedUrl.startsWith('https://')) {
|
|
|
+ normalizedUrl = `https://${normalizedUrl}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Validate URL format
|
|
|
+ try {
|
|
|
+ const url = new URL(normalizedUrl);
|
|
|
+ if (url.protocol !== 'https:') {
|
|
|
+ setError("Store URL must use HTTPS for security");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ setError("Please enter a valid store URL (e.g., https://yourstore.com)");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ setIsConnecting(true);
|
|
|
+
|
|
|
+ try {
|
|
|
+ // Get auth token
|
|
|
+ const sessionData = localStorage.getItem('session_data');
|
|
|
+ if (!sessionData) {
|
|
|
+ setError("Authentication required. Please log in again.");
|
|
|
+ setIsConnecting(false);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const session = JSON.parse(sessionData);
|
|
|
+
|
|
|
+ // Call the manual connection Edge Function
|
|
|
+ const response = await fetch(
|
|
|
+ `${API_URL}/oauth-woocommerce?action=connect_manual`,
|
|
|
+ {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${session.session.access_token}`,
|
|
|
+ 'Content-Type': 'application/json'
|
|
|
+ },
|
|
|
+ body: JSON.stringify({
|
|
|
+ storeUrl: normalizedUrl,
|
|
|
+ consumerKey: consumerKey.trim(),
|
|
|
+ consumerSecret: consumerSecret.trim()
|
|
|
+ })
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ const errorData = await response.json();
|
|
|
+ throw new Error(errorData.error || 'Failed to connect to WooCommerce');
|
|
|
+ }
|
|
|
+
|
|
|
+ const data = await response.json();
|
|
|
+
|
|
|
+ if (data.success) {
|
|
|
+ setSuccess(true);
|
|
|
+ setSuccessMessage(`Store "${data.storeName}" connected successfully!`);
|
|
|
+
|
|
|
+ // Redirect to webshops page after a short delay
|
|
|
+ setTimeout(() => {
|
|
|
+ if (onClose) {
|
|
|
+ onClose();
|
|
|
+ }
|
|
|
+ window.location.href = '/webshops?wc_connected=true';
|
|
|
+ }, 2000);
|
|
|
+ } else {
|
|
|
+ throw new Error('Invalid response from server');
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (err) {
|
|
|
+ console.error("Connection error:", err);
|
|
|
+ setError(err instanceof Error ? err.message : "Failed to connect to WooCommerce. Please try again.");
|
|
|
+ setIsConnecting(false);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
@@ -124,7 +225,7 @@ export function WooCommerceConnect({ onClose }: WooCommerceConnectProps) {
|
|
|
<Alert className="bg-green-500/10 border-green-500/50">
|
|
|
<CheckCircle2 className="h-4 w-4 text-green-500" />
|
|
|
<AlertDescription className="text-green-500">
|
|
|
- Redirecting to WooCommerce for authorization...
|
|
|
+ {successMessage}
|
|
|
</AlertDescription>
|
|
|
</Alert>
|
|
|
)}
|
|
|
@@ -139,71 +240,170 @@ export function WooCommerceConnect({ onClose }: WooCommerceConnectProps) {
|
|
|
</Alert>
|
|
|
)}
|
|
|
|
|
|
- {/* Connection Form */}
|
|
|
- <div className="space-y-4">
|
|
|
- <div className="space-y-2">
|
|
|
- <Label htmlFor="storeUrl" className="text-white">
|
|
|
- WooCommerce Store URL
|
|
|
- </Label>
|
|
|
- <Input
|
|
|
- id="storeUrl"
|
|
|
- type="text"
|
|
|
- placeholder="https://yourstore.com"
|
|
|
- value={storeUrl}
|
|
|
- onChange={(e) => setStoreUrl(e.target.value)}
|
|
|
- onKeyPress={handleKeyPress}
|
|
|
- className="bg-slate-700 border-slate-600 text-white placeholder:text-slate-400"
|
|
|
+ {/* Connection Methods */}
|
|
|
+ <Tabs defaultValue="oauth" className="w-full">
|
|
|
+ <TabsList className="grid w-full grid-cols-2 bg-slate-700">
|
|
|
+ <TabsTrigger value="oauth" className="data-[state=active]:bg-slate-600">
|
|
|
+ OAuth (Recommended)
|
|
|
+ </TabsTrigger>
|
|
|
+ <TabsTrigger value="manual" className="data-[state=active]:bg-slate-600">
|
|
|
+ <Key className="w-4 h-4 mr-2" />
|
|
|
+ API Keys
|
|
|
+ </TabsTrigger>
|
|
|
+ </TabsList>
|
|
|
+
|
|
|
+ {/* OAuth Method */}
|
|
|
+ <TabsContent value="oauth" className="space-y-4 mt-4">
|
|
|
+ <div className="space-y-2">
|
|
|
+ <Label htmlFor="storeUrl" className="text-white">
|
|
|
+ WooCommerce Store URL
|
|
|
+ </Label>
|
|
|
+ <Input
|
|
|
+ id="storeUrl"
|
|
|
+ type="text"
|
|
|
+ placeholder="https://yourstore.com"
|
|
|
+ value={storeUrl}
|
|
|
+ onChange={(e) => setStoreUrl(e.target.value)}
|
|
|
+ onKeyPress={(e) => e.key === "Enter" && !isConnecting && handleOAuthConnect()}
|
|
|
+ className="bg-slate-700 border-slate-600 text-white placeholder:text-slate-400"
|
|
|
+ disabled={isConnecting}
|
|
|
+ />
|
|
|
+ <p className="text-sm text-slate-400">
|
|
|
+ Enter your WooCommerce store URL (must use HTTPS)
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <Button
|
|
|
+ onClick={handleOAuthConnect}
|
|
|
disabled={isConnecting}
|
|
|
- />
|
|
|
- <p className="text-sm text-slate-400">
|
|
|
- Enter your WooCommerce store URL (must use HTTPS)
|
|
|
- </p>
|
|
|
- </div>
|
|
|
+ className="w-full bg-purple-500 hover:bg-purple-600 text-white"
|
|
|
+ >
|
|
|
+ {isConnecting ? (
|
|
|
+ <>
|
|
|
+ <Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
|
|
+ Connecting...
|
|
|
+ </>
|
|
|
+ ) : (
|
|
|
+ <>
|
|
|
+ <ShoppingBag className="w-4 h-4 mr-2" />
|
|
|
+ Connect via OAuth
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ </Button>
|
|
|
|
|
|
- <Button
|
|
|
- onClick={handleConnect}
|
|
|
- disabled={isConnecting}
|
|
|
- className="w-full bg-purple-500 hover:bg-purple-600 text-white"
|
|
|
- >
|
|
|
- {isConnecting ? (
|
|
|
- <>
|
|
|
- <Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
|
|
- Connecting...
|
|
|
- </>
|
|
|
- ) : (
|
|
|
- <>
|
|
|
- <ShoppingBag className="w-4 h-4 mr-2" />
|
|
|
- Connect to WooCommerce
|
|
|
- </>
|
|
|
- )}
|
|
|
- </Button>
|
|
|
- </div>
|
|
|
+ <div className="bg-slate-700/50 rounded-lg p-4 space-y-3">
|
|
|
+ <h4 className="text-white font-medium flex items-center gap-2">
|
|
|
+ <ExternalLink className="w-4 h-4 text-purple-500" />
|
|
|
+ What happens next?
|
|
|
+ </h4>
|
|
|
+ <ul className="space-y-2 text-sm text-slate-300">
|
|
|
+ <li className="flex items-start gap-2">
|
|
|
+ <span className="text-purple-500 mt-0.5">1.</span>
|
|
|
+ <span>You'll be redirected to your WooCommerce admin panel</span>
|
|
|
+ </li>
|
|
|
+ <li className="flex items-start gap-2">
|
|
|
+ <span className="text-purple-500 mt-0.5">2.</span>
|
|
|
+ <span>Review and approve the connection request</span>
|
|
|
+ </li>
|
|
|
+ <li className="flex items-start gap-2">
|
|
|
+ <span className="text-purple-500 mt-0.5">3.</span>
|
|
|
+ <span>WooCommerce will generate secure API credentials</span>
|
|
|
+ </li>
|
|
|
+ <li className="flex items-start gap-2">
|
|
|
+ <span className="text-purple-500 mt-0.5">4.</span>
|
|
|
+ <span>Return to ShopCall.ai to complete your setup</span>
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ </TabsContent>
|
|
|
|
|
|
- {/* Information Section */}
|
|
|
- <div className="bg-slate-700/50 rounded-lg p-4 space-y-3">
|
|
|
- <h4 className="text-white font-medium flex items-center gap-2">
|
|
|
- <ExternalLink className="w-4 h-4 text-purple-500" />
|
|
|
- What happens next?
|
|
|
- </h4>
|
|
|
- <ul className="space-y-2 text-sm text-slate-300">
|
|
|
- <li className="flex items-start gap-2">
|
|
|
- <span className="text-purple-500 mt-0.5">1.</span>
|
|
|
- <span>You'll be redirected to your WooCommerce admin panel</span>
|
|
|
- </li>
|
|
|
- <li className="flex items-start gap-2">
|
|
|
- <span className="text-purple-500 mt-0.5">2.</span>
|
|
|
- <span>Review and approve the connection request</span>
|
|
|
- </li>
|
|
|
- <li className="flex items-start gap-2">
|
|
|
- <span className="text-purple-500 mt-0.5">3.</span>
|
|
|
- <span>WooCommerce will generate secure API credentials</span>
|
|
|
- </li>
|
|
|
- <li className="flex items-start gap-2">
|
|
|
- <span className="text-purple-500 mt-0.5">4.</span>
|
|
|
- <span>Return to ShopCall.ai to complete your setup</span>
|
|
|
- </li>
|
|
|
- </ul>
|
|
|
- </div>
|
|
|
+ {/* Manual API Keys Method */}
|
|
|
+ <TabsContent value="manual" className="space-y-4 mt-4">
|
|
|
+ <div className="space-y-4">
|
|
|
+ <div className="space-y-2">
|
|
|
+ <Label htmlFor="manualStoreUrl" className="text-white">
|
|
|
+ WooCommerce Store URL
|
|
|
+ </Label>
|
|
|
+ <Input
|
|
|
+ id="manualStoreUrl"
|
|
|
+ type="text"
|
|
|
+ placeholder="https://yourstore.com"
|
|
|
+ value={storeUrl}
|
|
|
+ onChange={(e) => setStoreUrl(e.target.value)}
|
|
|
+ className="bg-slate-700 border-slate-600 text-white placeholder:text-slate-400"
|
|
|
+ disabled={isConnecting}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="space-y-2">
|
|
|
+ <Label htmlFor="consumerKey" className="text-white">
|
|
|
+ Consumer Key
|
|
|
+ </Label>
|
|
|
+ <Input
|
|
|
+ id="consumerKey"
|
|
|
+ type="text"
|
|
|
+ placeholder="ck_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
|
+ value={consumerKey}
|
|
|
+ onChange={(e) => setConsumerKey(e.target.value)}
|
|
|
+ className="bg-slate-700 border-slate-600 text-white placeholder:text-slate-400 font-mono text-sm"
|
|
|
+ disabled={isConnecting}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="space-y-2">
|
|
|
+ <Label htmlFor="consumerSecret" className="text-white">
|
|
|
+ Consumer Secret
|
|
|
+ </Label>
|
|
|
+ <Input
|
|
|
+ id="consumerSecret"
|
|
|
+ type="password"
|
|
|
+ placeholder="cs_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
|
+ value={consumerSecret}
|
|
|
+ onChange={(e) => setConsumerSecret(e.target.value)}
|
|
|
+ onKeyPress={(e) => e.key === "Enter" && !isConnecting && handleManualConnect()}
|
|
|
+ className="bg-slate-700 border-slate-600 text-white placeholder:text-slate-400 font-mono text-sm"
|
|
|
+ disabled={isConnecting}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <Button
|
|
|
+ onClick={handleManualConnect}
|
|
|
+ disabled={isConnecting}
|
|
|
+ className="w-full bg-purple-500 hover:bg-purple-600 text-white"
|
|
|
+ >
|
|
|
+ {isConnecting ? (
|
|
|
+ <>
|
|
|
+ <Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
|
|
+ Connecting...
|
|
|
+ </>
|
|
|
+ ) : (
|
|
|
+ <>
|
|
|
+ <Key className="w-4 h-4 mr-2" />
|
|
|
+ Connect with API Keys
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ </Button>
|
|
|
+
|
|
|
+ <div className="bg-blue-500/10 border border-blue-500/50 rounded-lg p-4 space-y-2">
|
|
|
+ <h4 className="text-white font-medium flex items-center gap-2">
|
|
|
+ <ExternalLink className="w-4 h-4 text-blue-500" />
|
|
|
+ How to generate API keys
|
|
|
+ </h4>
|
|
|
+ <ol className="space-y-2 text-sm text-slate-300 list-decimal list-inside">
|
|
|
+ <li>Go to WooCommerce → Settings → Advanced → REST API</li>
|
|
|
+ <li>Click "Add key"</li>
|
|
|
+ <li>Set description: "ShopCall.ai"</li>
|
|
|
+ <li>Set permissions: "Read"</li>
|
|
|
+ <li>Click "Generate API key"</li>
|
|
|
+ <li>Copy the Consumer Key and Consumer Secret</li>
|
|
|
+ </ol>
|
|
|
+ <p className="text-xs text-slate-400 mt-2">
|
|
|
+ Make sure to copy both keys immediately - WooCommerce shows the Consumer Secret only once!
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ </TabsContent>
|
|
|
+ </Tabs>
|
|
|
|
|
|
{/* Security Notice */}
|
|
|
<div className="bg-blue-500/10 border border-blue-500/50 rounded-lg p-4 space-y-2">
|