|
@@ -4,7 +4,6 @@ import { Button } from "@/components/ui/button";
|
|
|
import { Input } from "@/components/ui/input";
|
|
import { Input } from "@/components/ui/input";
|
|
|
import { Label } from "@/components/ui/label";
|
|
import { Label } from "@/components/ui/label";
|
|
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
|
|
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
|
|
|
import { Loader2, ShoppingBag, ExternalLink, CheckCircle2, AlertCircle, Key } from "lucide-react";
|
|
import { Loader2, ShoppingBag, ExternalLink, CheckCircle2, AlertCircle, Key } from "lucide-react";
|
|
|
import { API_URL } from "@/lib/config";
|
|
import { API_URL } from "@/lib/config";
|
|
|
|
|
|
|
@@ -21,89 +20,6 @@ export function WooCommerceConnect({ onClose }: WooCommerceConnectProps) {
|
|
|
const [success, setSuccess] = useState(false);
|
|
const [success, setSuccess] = useState(false);
|
|
|
const [successMessage, setSuccessMessage] = useState("");
|
|
const [successMessage, setSuccessMessage] = useState("");
|
|
|
|
|
|
|
|
- const handleOAuthConnect = async () => {
|
|
|
|
|
- setError("");
|
|
|
|
|
- setSuccess(false);
|
|
|
|
|
-
|
|
|
|
|
- // Validate store URL
|
|
|
|
|
- if (!storeUrl.trim()) {
|
|
|
|
|
- setError("Please enter your WooCommerce store URL");
|
|
|
|
|
- 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 OAuth initiation Edge Function
|
|
|
|
|
- const response = await fetch(
|
|
|
|
|
- `${API_URL}/oauth-woocommerce?action=init&store_url=${encodeURIComponent(normalizedUrl)}`,
|
|
|
|
|
- {
|
|
|
|
|
- method: 'GET',
|
|
|
|
|
- headers: {
|
|
|
|
|
- 'Authorization': `Bearer ${session.session.access_token}`,
|
|
|
|
|
- 'Content-Type': 'application/json'
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- if (!response.ok) {
|
|
|
|
|
- const errorData = await response.json();
|
|
|
|
|
- throw new Error(errorData.error || 'Failed to initiate OAuth flow');
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const data = await response.json();
|
|
|
|
|
-
|
|
|
|
|
- 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);
|
|
|
|
|
- } 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);
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
const handleManualConnect = async () => {
|
|
const handleManualConnect = async () => {
|
|
|
setError("");
|
|
setError("");
|
|
|
setSuccess(false);
|
|
setSuccess(false);
|
|
@@ -240,31 +156,19 @@ export function WooCommerceConnect({ onClose }: WooCommerceConnectProps) {
|
|
|
</Alert>
|
|
</Alert>
|
|
|
)}
|
|
)}
|
|
|
|
|
|
|
|
- {/* 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">
|
|
|
|
|
|
|
+ {/* Manual API Keys Connection Form */}
|
|
|
|
|
+ <div className="space-y-4">
|
|
|
|
|
+ <div className="space-y-4">
|
|
|
<div className="space-y-2">
|
|
<div className="space-y-2">
|
|
|
- <Label htmlFor="storeUrl" className="text-white">
|
|
|
|
|
|
|
+ <Label htmlFor="manualStoreUrl" className="text-white">
|
|
|
WooCommerce Store URL
|
|
WooCommerce Store URL
|
|
|
</Label>
|
|
</Label>
|
|
|
<Input
|
|
<Input
|
|
|
- id="storeUrl"
|
|
|
|
|
|
|
+ id="manualStoreUrl"
|
|
|
type="text"
|
|
type="text"
|
|
|
placeholder="https://yourstore.com"
|
|
placeholder="https://yourstore.com"
|
|
|
value={storeUrl}
|
|
value={storeUrl}
|
|
|
onChange={(e) => setStoreUrl(e.target.value)}
|
|
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"
|
|
className="bg-slate-700 border-slate-600 text-white placeholder:text-slate-400"
|
|
|
disabled={isConnecting}
|
|
disabled={isConnecting}
|
|
|
/>
|
|
/>
|
|
@@ -273,137 +177,101 @@ export function WooCommerceConnect({ onClose }: WooCommerceConnectProps) {
|
|
|
</p>
|
|
</p>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <Button
|
|
|
|
|
- onClick={handleOAuthConnect}
|
|
|
|
|
- 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 via OAuth
|
|
|
|
|
- </>
|
|
|
|
|
- )}
|
|
|
|
|
- </Button>
|
|
|
|
|
-
|
|
|
|
|
- <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 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>
|
|
|
- </TabsContent>
|
|
|
|
|
-
|
|
|
|
|
- {/* 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 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>
|
|
|
|
|
+ </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>
|
|
|
|
|
|
|
+ <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 WooCommerce Store
|
|
|
|
|
+ </>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </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>
|
|
|
|
|
|
|
+ <div className="bg-blue-500/10 border border-blue-500/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-blue-500" />
|
|
|
|
|
+ How to generate WooCommerce API keys
|
|
|
|
|
+ </h4>
|
|
|
|
|
+ <ol className="space-y-2 text-sm text-slate-300">
|
|
|
|
|
+ <li className="flex items-start gap-2">
|
|
|
|
|
+ <span className="text-blue-500 font-semibold min-w-[20px]">1.</span>
|
|
|
|
|
+ <span>Log into your WooCommerce admin panel</span>
|
|
|
|
|
+ </li>
|
|
|
|
|
+ <li className="flex items-start gap-2">
|
|
|
|
|
+ <span className="text-blue-500 font-semibold min-w-[20px]">2.</span>
|
|
|
|
|
+ <span>Navigate to <strong>WooCommerce → Settings → Advanced → REST API</strong></span>
|
|
|
|
|
+ </li>
|
|
|
|
|
+ <li className="flex items-start gap-2">
|
|
|
|
|
+ <span className="text-blue-500 font-semibold min-w-[20px]">3.</span>
|
|
|
|
|
+ <span>Click <strong>"Add key"</strong> button</span>
|
|
|
|
|
+ </li>
|
|
|
|
|
+ <li className="flex items-start gap-2">
|
|
|
|
|
+ <span className="text-blue-500 font-semibold min-w-[20px]">4.</span>
|
|
|
|
|
+ <span>Fill in the form:
|
|
|
|
|
+ <ul className="ml-4 mt-1 space-y-1">
|
|
|
|
|
+ <li>• Description: <code className="text-purple-400">ShopCall.ai</code></li>
|
|
|
|
|
+ <li>• User: <em>(select your admin user)</em></li>
|
|
|
|
|
+ <li>• Permissions: <code className="text-purple-400">Read</code></li>
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </li>
|
|
|
|
|
+ <li className="flex items-start gap-2">
|
|
|
|
|
+ <span className="text-blue-500 font-semibold min-w-[20px]">5.</span>
|
|
|
|
|
+ <span>Click <strong>"Generate API key"</strong></span>
|
|
|
|
|
+ </li>
|
|
|
|
|
+ <li className="flex items-start gap-2">
|
|
|
|
|
+ <span className="text-blue-500 font-semibold min-w-[20px]">6.</span>
|
|
|
|
|
+ <span>Copy both the <strong>Consumer Key</strong> and <strong>Consumer Secret</strong></span>
|
|
|
|
|
+ </li>
|
|
|
|
|
+ </ol>
|
|
|
|
|
+ <Alert className="bg-yellow-500/10 border-yellow-500/50 mt-3">
|
|
|
|
|
+ <AlertCircle className="h-4 w-4 text-yellow-500" />
|
|
|
|
|
+ <AlertDescription className="text-yellow-500 text-xs">
|
|
|
|
|
+ Important: WooCommerce shows the Consumer Secret only once! Make sure to copy it before closing the page.
|
|
|
|
|
+ </AlertDescription>
|
|
|
|
|
+ </Alert>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
{/* Security Notice */}
|
|
{/* Security Notice */}
|
|
|
<div className="bg-blue-500/10 border border-blue-500/50 rounded-lg p-4 space-y-2">
|
|
<div className="bg-blue-500/10 border border-blue-500/50 rounded-lg p-4 space-y-2">
|
|
@@ -412,9 +280,9 @@ export function WooCommerceConnect({ onClose }: WooCommerceConnectProps) {
|
|
|
<div className="space-y-1">
|
|
<div className="space-y-1">
|
|
|
<h4 className="text-white font-medium">Secure Connection</h4>
|
|
<h4 className="text-white font-medium">Secure Connection</h4>
|
|
|
<p className="text-sm text-slate-300">
|
|
<p className="text-sm text-slate-300">
|
|
|
- ShopCall.ai uses OAuth 1.0a authentication with read-only access to your store data.
|
|
|
|
|
- Your credentials are encrypted and stored securely. We never have access to your
|
|
|
|
|
- WordPress admin password.
|
|
|
|
|
|
|
+ ShopCall.ai uses WooCommerce REST API with read-only access to your store data.
|
|
|
|
|
+ Your API credentials are encrypted and stored securely. We never have access to your
|
|
|
|
|
+ WordPress admin password. All connections use HTTPS for maximum security.
|
|
|
</p>
|
|
</p>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|