mirror of
https://gitlab.com/davidels1986/ore-cart-checkout.git
synced 2026-10-30 02:59:13 +00:00
Changes
This commit is contained in:
parent
6a11507431
commit
57b2cf99f9
2 changed files with 158 additions and 35 deletions
|
|
@ -15,6 +15,7 @@ import {
|
|||
} from '@/components/ui/dialog';
|
||||
import { useCart } from '@/context/CartContext';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { sendQuoteWebhook } from '@/lib/webhookService';
|
||||
|
||||
export function CartDrawer() {
|
||||
const { items, isCartOpen, setIsCartOpen, removeItem, updateQuantity, updateNotes, clearCart } = useCart();
|
||||
|
|
@ -22,6 +23,7 @@ export function CartDrawer() {
|
|||
const navigate = useNavigate();
|
||||
const [quoteMessage, setQuoteMessage] = useState('');
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [hasSubmitted, setHasSubmitted] = useState(false);
|
||||
const [showQuoteForm, setShowQuoteForm] = useState(false);
|
||||
const [contactInfo, setContactInfo] = useState({
|
||||
name: '',
|
||||
|
|
@ -55,42 +57,18 @@ export function CartDrawer() {
|
|||
};
|
||||
|
||||
const handleSubmitQuote = async () => {
|
||||
if (!contactInfo.name || !contactInfo.email) {
|
||||
toast({
|
||||
title: 'Missing information',
|
||||
description: 'Please provide your name and email.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (hasSubmitted) return;
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
const quoteRequest = {
|
||||
items: items.map(item => ({
|
||||
productId: item.product.id,
|
||||
name: item.product.name,
|
||||
sku: item.product.sku,
|
||||
quantity: item.quantity,
|
||||
notes: item.notes,
|
||||
})),
|
||||
user: {
|
||||
email: contactInfo.email,
|
||||
name: contactInfo.name,
|
||||
phone: contactInfo.phone,
|
||||
company: contactInfo.company,
|
||||
},
|
||||
message: quoteMessage,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
try {
|
||||
console.log('Quote Request:', quoteRequest);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
const result = await sendQuoteWebhook(contactInfo, items);
|
||||
|
||||
if (result.success) {
|
||||
setHasSubmitted(true);
|
||||
toast({
|
||||
title: 'Quote Request Sent! ✓',
|
||||
description: 'Our team will contact you within 24 hours with pricing.',
|
||||
title: 'Quote Request Submitted ✓',
|
||||
description:
|
||||
'Your quote request has been submitted successfully. Our team will review your request and contact you soon.',
|
||||
});
|
||||
|
||||
clearCart();
|
||||
|
|
@ -98,15 +76,16 @@ export function CartDrawer() {
|
|||
setContactInfo({ name: '', email: '', phone: '', company: '' });
|
||||
setShowQuoteForm(false);
|
||||
setIsCartOpen(false);
|
||||
} catch {
|
||||
setHasSubmitted(false);
|
||||
} else {
|
||||
toast({
|
||||
title: 'Error sending quote',
|
||||
description: 'Please try again or contact us directly.',
|
||||
description: result.error || 'Please try again or contact us directly.',
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
|
||||
setIsSubmitting(false);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
144
src/lib/webhookService.ts
Normal file
144
src/lib/webhookService.ts
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
import { CartItem } from '@/types';
|
||||
|
||||
const WEBHOOK_URL = 'https://n8nmac.apphub.co.za/webhook/wfsales_quotes';
|
||||
const TIMEOUT_MS = 10_000;
|
||||
|
||||
interface CustomerInfo {
|
||||
name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
company: string;
|
||||
}
|
||||
|
||||
interface WebhookPayload {
|
||||
source: string;
|
||||
request_type: string;
|
||||
quote_request_id: string;
|
||||
customer: {
|
||||
name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
company: string;
|
||||
};
|
||||
cart: {
|
||||
cart_id: string;
|
||||
currency: string;
|
||||
subtotal: number;
|
||||
items: {
|
||||
sku: string;
|
||||
description: string;
|
||||
quantity: number;
|
||||
unit: string;
|
||||
}[];
|
||||
};
|
||||
metadata: {
|
||||
website: string;
|
||||
submitted_at: string;
|
||||
};
|
||||
}
|
||||
|
||||
function generateUUID(): string {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
|
||||
function isValidEmail(email: string): boolean {
|
||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
||||
}
|
||||
|
||||
export function validateQuoteRequest(
|
||||
customer: CustomerInfo,
|
||||
items: CartItem[]
|
||||
): { valid: boolean; error?: string } {
|
||||
if (!customer.name.trim()) {
|
||||
return { valid: false, error: 'Customer name is required.' };
|
||||
}
|
||||
if (!customer.email.trim() || !isValidEmail(customer.email.trim())) {
|
||||
return { valid: false, error: 'A valid email address is required.' };
|
||||
}
|
||||
if (items.length === 0) {
|
||||
return { valid: false, error: 'Cart is empty.' };
|
||||
}
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
export function buildPayload(
|
||||
customer: CustomerInfo,
|
||||
items: CartItem[]
|
||||
): WebhookPayload {
|
||||
return {
|
||||
source: 'online_shop',
|
||||
request_type: 'quote_request',
|
||||
quote_request_id: generateUUID(),
|
||||
customer: {
|
||||
name: customer.name.trim(),
|
||||
email: customer.email.trim(),
|
||||
phone: customer.phone?.trim() || '',
|
||||
company: customer.company?.trim() || '',
|
||||
},
|
||||
cart: {
|
||||
cart_id: generateUUID(),
|
||||
currency: 'ZAR',
|
||||
subtotal: 0,
|
||||
items: items.map((item) => ({
|
||||
sku: item.product.sku || '',
|
||||
description: item.product.name,
|
||||
quantity: item.quantity || 1,
|
||||
unit: 'each',
|
||||
})),
|
||||
},
|
||||
metadata: {
|
||||
website: 'Q - PORTAL',
|
||||
submitted_at: new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function sendQuoteWebhook(
|
||||
customer: CustomerInfo,
|
||||
items: CartItem[]
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
const validation = validateQuoteRequest(customer, items);
|
||||
if (!validation.valid) {
|
||||
return { success: false, error: validation.error };
|
||||
}
|
||||
|
||||
const payload = buildPayload(customer, items);
|
||||
|
||||
console.log('📤 Webhook request payload:', JSON.stringify(payload, null, 2));
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
||||
|
||||
try {
|
||||
const response = await fetch(WEBHOOK_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
const responseBody = await response.text();
|
||||
console.log('📥 Webhook response status:', response.status);
|
||||
console.log('📥 Webhook response body:', responseBody);
|
||||
|
||||
if (!response.ok) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Webhook returned status ${response.status}. Please try again.`,
|
||||
};
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (err: unknown) {
|
||||
const message =
|
||||
err instanceof DOMException && err.name === 'AbortError'
|
||||
? 'Request timed out after 10 seconds. Please try again.'
|
||||
: err instanceof Error
|
||||
? err.message
|
||||
: 'Unknown error';
|
||||
console.error('❌ Webhook error:', message);
|
||||
return { success: false, error: message };
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue