From f90e8cfc2823b98a88fc77e679797ba80465ad44 Mon Sep 17 00:00:00 2001 From: Alaguraj0361 Date: Fri, 3 Apr 2026 17:45:50 +0530 Subject: [PATCH] implement catering inquiry form with email notification service and interactive UI components --- package-lock.json | 21 ++ package.json | 2 + src/app/actions.ts | 76 +++++- .../CateringContent.tsx | 54 ++-- .../CateringPopup.module.css | 238 ++++++++++++++++++ .../CateringPopup.tsx | 133 ++++++++++ .../catering.module.css | 10 +- 7 files changed, 496 insertions(+), 38 deletions(-) create mode 100644 src/app/catering-services-ontario/CateringPopup.module.css create mode 100644 src/app/catering-services-ontario/CateringPopup.tsx diff --git a/package-lock.json b/package-lock.json index a528b1d..ec3d36c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "axios": "^1.13.2", "framer-motion": "^12.23.24", "next": "16.0.3", + "nodemailer": "^8.0.4", "react": "19.2.0", "react-dom": "19.2.0", "react-google-recaptcha": "^3.1.0", @@ -21,6 +22,7 @@ }, "devDependencies": { "@types/node": "^20", + "@types/nodemailer": "^7.0.11", "@types/react": "^19", "@types/react-dom": "^19", "@types/react-google-recaptcha": "^2.1.9", @@ -1340,6 +1342,16 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/nodemailer": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.11.tgz", + "integrity": "sha512-E+U4RzR2dKrx+u3N4DlsmLaDC6mMZOM/TPROxA0UAPiTgI0y4CEFBmZE+coGWTjakDriRsXG368lNk1u9Q0a2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/react": { "version": "19.2.7", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", @@ -7575,6 +7587,15 @@ "dev": true, "license": "MIT" }, + "node_modules/nodemailer": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.4.tgz", + "integrity": "sha512-k+jf6N8PfQJ0Fe8ZhJlgqU5qJU44Lpvp2yvidH3vp1lPnVQMgi4yEEMPXg5eJS1gFIJTVq1NHBk7Ia9ARdSBdQ==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/normalize-url": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", diff --git a/package.json b/package.json index d88f450..481810f 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "axios": "^1.13.2", "framer-motion": "^12.23.24", "next": "16.0.3", + "nodemailer": "^8.0.4", "react": "19.2.0", "react-dom": "19.2.0", "react-google-recaptcha": "^3.1.0", @@ -24,6 +25,7 @@ }, "devDependencies": { "@types/node": "^20", + "@types/nodemailer": "^7.0.11", "@types/react": "^19", "@types/react-dom": "^19", "@types/react-google-recaptcha": "^2.1.9", diff --git a/src/app/actions.ts b/src/app/actions.ts index 65dc3dd..ac397b9 100644 --- a/src/app/actions.ts +++ b/src/app/actions.ts @@ -1,18 +1,78 @@ 'use server' +import nodemailer from 'nodemailer'; + +// Configure SMTP transport using environment variables +const transporter = nodemailer.createTransport({ + host: process.env.EMAIL_HOST || 'smtp.gmail.com', // Replace with actual SMTP host if different + port: Number(process.env.EMAIL_PORT) || 587, + secure: (process.env.EMAIL_SECURE === 'true'), + auth: { + user: process.env.EMAIL_USER, + pass: process.env.EMAIL_PASS, + }, +}); + export async function submitReservation(formData: FormData) { + const name = formData.get('name'); + const phone = formData.get('phone'); + const date = formData.get('date'); + const message = formData.get('message'); + + // For reservations, we'll log it for now as per current logic + console.log('Reservation received:', { name, phone, date, message }); + + // Simulate delay + await new Promise(resolve => setTimeout(resolve, 1000)); + return { success: true, message: 'Reservation submitted successfully!' }; +} + +export async function submitCateringInquiry(formData: FormData) { const rawFormData = { name: formData.get('name'), + email: formData.get('email'), phone: formData.get('phone'), + eventType: formData.get('eventType'), date: formData.get('date'), + guests: formData.get('guests'), message: formData.get('message'), + }; + + try { + // Send email to hello@antalyarestaurant.ca + await transporter.sendMail({ + from: process.env.EMAIL_USER || '"Antalya Website" ', + to: 'hello@antalyarestaurant.ca', + subject: `Catering Inquiry: ${rawFormData.eventType} - ${rawFormData.name}`, + text: ` + Name: ${rawFormData.name} + Email: ${rawFormData.email} + Phone: ${rawFormData.phone} + Event Type: ${rawFormData.eventType} + Event Date: ${rawFormData.date} + Number of Guests: ${rawFormData.guests} + Message: ${rawFormData.message} + `, + html: ` +
+

New Catering Inquiry

+

Name: ${rawFormData.name}

+

Email: ${rawFormData.email}

+

Phone: ${rawFormData.phone}

+

Event Type: ${rawFormData.eventType}

+

Event Date: ${rawFormData.date}

+

Number of Guests: ${rawFormData.guests}

+
+

Message:

+

${rawFormData.message || 'No special requests provided.'}

+
+
+ `, + }); + + return { success: true, message: 'Your catering inquiry has been submitted successfully to hello@antalyarestaurant.ca!' }; + } catch (error) { + console.error('Failed to send catering inquiry email:', error); + return { success: false, message: 'There was an error sending your inquiry. Please try again or email us directly.' }; } - - // Simulate server-side processing - console.log('Reservation received:', rawFormData) - - // In a real app, you would save to DB or send email here - await new Promise(resolve => setTimeout(resolve, 1000)) - - return { success: true, message: 'Reservation submitted successfully!' } } diff --git a/src/app/catering-services-ontario/CateringContent.tsx b/src/app/catering-services-ontario/CateringContent.tsx index 6588e67..58221f4 100644 --- a/src/app/catering-services-ontario/CateringContent.tsx +++ b/src/app/catering-services-ontario/CateringContent.tsx @@ -9,11 +9,19 @@ import Image from 'next/image' import styles from './catering.module.css' import CateringPackages from './CateringPackages'; +import CateringPopup from './CateringPopup'; export default function CateringContent() { // Slider state for Visual Journey section const [currentSlide, setCurrentSlide] = useState(0); const [mounted, setMounted] = useState(false); + const [isPopupOpen, setIsPopupOpen] = useState(false); + const [selectedEventType, setSelectedEventType] = useState(''); + + const openPopup = (type: string) => { + setSelectedEventType(type); + setIsPopupOpen(true); + }; const sliderImages = [ '/images/catering/visual-slider/visual-journey-1.webp', @@ -114,7 +122,12 @@ export default function CateringContent() { return (
- + {/* Catering Popup Modal */} + setIsPopupOpen(false)} + initialEventType={selectedEventType} + /> {/* Page Hero */}
@@ -149,7 +162,11 @@ export default function CateringContent() {
{/* Card 1: Events */} - + openPopup('Corporate & Social Events')} + >

Corporate & Social Events

- {/* */}
{/* Card 2: Food & Drinks */} - + openPopup('Family Gatherings & Weddings')} + >

Family Gatherings & Weddings

- {/* */}
{/* Card 3: Venues */} - + openPopup('Birthday Party & Baby Shower')} + >

Birthday Party & Baby Shower

- {/* */}
@@ -274,13 +290,6 @@ export default function CateringContent() { Catering Visualization Decorative Cutlery Icon

A Visual Journey Through Antalya Catering

- {/*
- - - - - -
*/}

Experience the essence of Antalya brought to your event - from beautifully presented dishes to elegant setups that elevate every occasion. Our catering blends authentic Turkish flavours with refined presentation, creating a feast that delights both the eyes and the palate.

@@ -372,11 +381,6 @@ export default function CateringContent() { > hello@antalyarestaurant.ca - - {/* - - Visit site - */}
diff --git a/src/app/catering-services-ontario/CateringPopup.module.css b/src/app/catering-services-ontario/CateringPopup.module.css new file mode 100644 index 0000000..7112525 --- /dev/null +++ b/src/app/catering-services-ontario/CateringPopup.module.css @@ -0,0 +1,238 @@ +.overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.85); + backdrop-filter: blur(8px); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; + padding: 20px; + overflow-y: auto; /* Enable overlay-level scrolling */ +} + +.popup { + background-color: #f5e6d3; + padding: 3rem; + border-radius: 20px; + width: 95%; + max-width: 750px; + position: relative; + box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3); + border: 1px solid rgba(196, 156, 92, 0.4); + margin: auto; /* Center when scrollable */ + max-height: calc(100vh - 40px); + overflow-y: auto; /* Scrollable content inside the popup */ +} + +/* Custom scrollbar for the popup */ +.popup::-webkit-scrollbar { + width: 6px; +} + +.popup::-webkit-scrollbar-track { + background: transparent; +} + +.popup::-webkit-scrollbar-thumb { + background: #c49c5c; + border-radius: 10px; +} + +.closeButton { + position: absolute; + top: 1rem; + right: 1rem; + background: #fff; + border: none; + font-size: 1.5rem; + cursor: pointer; + color: #441109; + transition: transform 0.3s ease; + z-index: 10; + width: 40px; + height: 40px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 4px 10px rgba(0,0,0,0.1); +} + +.closeButton:hover { + transform: rotate(90deg) scale(1.1); +} + +.title { + font-family: var(--font-playfair); + font-size: 2.2rem; + color: #441109; + margin-bottom: 0.5rem; + text-align: center; +} + +.subtitle { + font-size: 0.95rem; + color: #5d4037; + margin-bottom: 2.5rem; + text-align: center; + max-width: 500px; + margin-left: auto; + margin-right: auto; +} + +.form { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.formGrid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1.2rem; +} + +.inputGroup { + display: flex; + flex-direction: column; + gap: 0.4rem; +} + +.fullWidth { + grid-column: span 2; +} + +.label { + font-size: 0.8rem; + font-weight: 700; + color: #441109; + text-transform: uppercase; + letter-spacing: 1px; +} + +.input, .textarea { + padding: 0.8rem 1rem; + border-radius: 10px; + border: 1px solid #d3cab3; + background-color: #fff; + color: #441109; + font-family: inherit; + font-size: 0.95rem; + outline: none; + transition: all 0.3s ease; +} + +.input:focus, .textarea:focus { + border-color: #c49c5c; + box-shadow: 0 0 0 4px rgba(196, 156, 92, 0.15); +} + +.textarea { + resize: none; + height: 100px; + grid-column: span 2; +} + +.submitButton { + background-color: #441109; + color: #f5e6d3; + padding: 1.1rem; + border-radius: 10px; + border: 1px solid #441109; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 2.5px; + cursor: pointer; + transition: all 0.3s ease; + margin-top: 0.5rem; + font-size: 0.95rem; + grid-column: span 2; +} + +.submitButton:hover { + background-color: #c49c5c; + border-color: #c49c5c; + color: #fff; +} + +.submitButton:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.successMessage { + background-color: rgba(76, 175, 80, 0.1); + color: #2e7d32; + padding: 1rem; + border-radius: 8px; + text-align: center; + font-size: 0.9rem; + font-weight: 600; +} + +@media (max-width: 768px) { + .formGrid { + grid-template-columns: 1fr; + gap: 1rem; + } + + .textarea, .submitButton, .fullWidth { + grid-column: span 1; + } + + .popup { + padding: 2.5rem 1.5rem 2rem; + max-height: calc(100vh - 40px); + } + + .title { + font-size: 1.8rem; + } + + .subtitle { + margin-bottom: 1.5rem; + font-size: 0.9rem; + } + + .input, .textarea { + padding: 0.7rem 0.9rem; + } +} + +@media (max-width: 480px) { + .popup { + padding: 2.5rem 1.2rem 1.5rem; + width: 100%; + border-radius: 15px; + } + + .title { + font-size: 1.5rem; + } + + .subtitle { + font-size: 0.85rem; + } + + .form { + gap: 0.8rem; + } + + .inputGroup { + gap: 0.3rem; + } + + .label { + font-size: 0.75rem; + } + + .submitButton { + padding: 0.9rem; + font-size: 0.9rem; + margin-top: 0.2rem; + } +} diff --git a/src/app/catering-services-ontario/CateringPopup.tsx b/src/app/catering-services-ontario/CateringPopup.tsx new file mode 100644 index 0000000..c1a6aeb --- /dev/null +++ b/src/app/catering-services-ontario/CateringPopup.tsx @@ -0,0 +1,133 @@ +'use client' + +import { useState, useTransition } from 'react' +import { motion, AnimatePresence } from 'framer-motion' +import { submitCateringInquiry } from '../actions' +import styles from './CateringPopup.module.css' + +interface CateringPopupProps { + isOpen: boolean; + onClose: () => void; + initialEventType?: string; +} + +export default function CateringPopup({ isOpen, onClose, initialEventType = '' }: CateringPopupProps) { + const [isPending, startTransition] = useTransition(); + const [isSuccess, setIsSuccess] = useState(false); + const [error, setError] = useState(null); + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + setError(null); + + const formData = new FormData(event.currentTarget); + + startTransition(async () => { + try { + const result = await submitCateringInquiry(formData); + if (result.success) { + setIsSuccess(true); + setTimeout(() => { + onClose(); + setIsSuccess(false); + }, 3000); + } else { + setError('Failed to submit inquiry. Please try again.'); + } + } catch (err) { + setError('Something went wrong. Please try again later.'); + } + }); + } + + return ( + + {isOpen && ( + e.target === e.currentTarget && onClose()} + > + + + +

Catering Inquiry

+

Let us make your event unforgettable with authentic Turkish flavors.

+ + {isSuccess ? ( + + Thank you! Your inquiry has been sent to hello@antalyarestaurant.ca. We will get back to you shortly. + + ) : ( +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ {error &&

{error}

} +
+ )} +
+
+ )} +
+ ) +} diff --git a/src/app/catering-services-ontario/catering.module.css b/src/app/catering-services-ontario/catering.module.css index 9503bff..4e33c32 100644 --- a/src/app/catering-services-ontario/catering.module.css +++ b/src/app/catering-services-ontario/catering.module.css @@ -169,10 +169,10 @@ cursor: pointer; transition: transform 0.4s ease, box-shadow 0.4s ease; border-radius: 30 30 30 30px; - /* Rounded only at Bottom-Left to emphasize L shape */ - border: none; - border-right: 8px solid #b07c4b; - border-top: 8px solid #b07c4b; +} + +.clickableCard { + cursor: pointer; } .topCard:hover { @@ -468,7 +468,7 @@ .mainHeading { font-family: var(--font-playfair); font-size: var(--main-heading-size); - color: #5d4037; + color: #c49c5c; line-height: 1.3; font-weight: 700; margin-bottom: 1.5rem;