diff --git a/.env.example b/.env.example index 1fdd801..9e97a2c 100644 --- a/.env.example +++ b/.env.example @@ -9,4 +9,14 @@ DB_NAME=your-db-name # Admin credentials ADMIN_USER=your-admin-username -ADMIN_PASSWORD=your-admin-password \ No newline at end of file +ADMIN_PASSWORD=your-admin-password + +# SMTP (Office365) settings for sending contact/booking emails +# Use the Office365 SMTP endpoint and a mailbox account that can send (e.g. christian@warme.dk) +SMTP_HOST=smtp.office365.com +SMTP_PORT=587 +SMTP_USER=your-smtp-user@example.com +SMTP_PASS=your-smtp-password + +# The recipient for booking messages (defaults to christian@warme.dk) +BOOKING_EMAIL=christian@warme.dk \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d60adfc..6591c54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "lucide-react": "^0.292.0", "mysql2": "^3.15.3", "next": "^14.2.33", + "nodemailer": "^7.0.10", "openai": "^6.7.0", "react": "18.2.0", "react-day-picker": "^8.9.1", @@ -4323,6 +4324,15 @@ "dev": true, "license": "MIT" }, + "node_modules/nodemailer": { + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.10.tgz", + "integrity": "sha512-Us/Se1WtT0ylXgNFfyFSx4LElllVLJXQjWi2Xz17xWw7amDKO2MLtFnVp1WACy7GkVGs+oBlRopVNUzlrGSw1w==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", diff --git a/package.json b/package.json index e2012ea..1c1f039 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "lucide-react": "^0.292.0", "mysql2": "^3.15.3", "next": "^14.2.33", + "nodemailer": "^7.0.10", "openai": "^6.7.0", "react": "18.2.0", "react-day-picker": "^8.9.1", diff --git a/src/app/api/contact/route.ts b/src/app/api/contact/route.ts new file mode 100644 index 0000000..2067be0 --- /dev/null +++ b/src/app/api/contact/route.ts @@ -0,0 +1,64 @@ +import { NextResponse } from 'next/server' +import nodemailer from 'nodemailer' + +// Simple validation helper +function validate(body: any) { + if (!body) return 'No data' + const { name, email, phone, message } = body + if (!name || !email || !message) return 'Missing required fields' + return null +} + +export async function POST(req: Request) { + try { + const body = await req.json() + const err = validate(body) + if (err) return NextResponse.json({ error: err }, { status: 400 }) + + // Read SMTP config from environment + const host = process.env.SMTP_HOST || 'smtp.office365.com' + const port = parseInt(process.env.SMTP_PORT || '587', 10) + const user = process.env.SMTP_USER + const pass = process.env.SMTP_PASS + const to = process.env.BOOKING_EMAIL || 'christian@warme.dk' + + if (!user || !pass) { + console.error('SMTP credentials not configured') + return NextResponse.json({ error: 'Email service not configured' }, { status: 500 }) + } + + // Create transporter + const transporter = nodemailer.createTransport({ + host, + port, + secure: port === 465, // true for 465, false for other ports + auth: { + user, + pass + } + }) + + const { name, email, phone, message } = body + + const subject = `Kontaktformular: ${name}` + const html = ` +

Navn: ${name}

+

Email: ${email}

+

Telefon: ${phone || '-'}

+

Besked:

+
${message.replace(/\n/g, '
')}
+ ` + + await transporter.sendMail({ + from: `${name} <${email}>`, + to, + subject, + html + }) + + return NextResponse.json({ ok: true }) + } catch (error) { + console.error('Error in contact POST:', error) + return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }) + } +} diff --git a/src/app/kontakt/page.tsx b/src/app/kontakt/page.tsx index 249eaf6..6a399a9 100644 --- a/src/app/kontakt/page.tsx +++ b/src/app/kontakt/page.tsx @@ -1,6 +1,6 @@ import { sampleData } from '@/config/sample-data' -import { Input } from '@/components/ui/input' import { Mail, Phone, Instagram, Facebook } from 'lucide-react' +import ContactForm from '@/components/contact-form' export default function ContactPage() { const { contact } = sampleData.chef @@ -53,41 +53,7 @@ export default function ContactPage() { {/* Contact Form */}

Send besked

-
-
- - -
-
- - -
-
- - -
-
- - -
- -
+
diff --git a/src/components/contact-form.tsx b/src/components/contact-form.tsx new file mode 100644 index 0000000..1e7b474 --- /dev/null +++ b/src/components/contact-form.tsx @@ -0,0 +1,86 @@ +'use client' + +import { useState } from 'react' +import { Input } from '@/components/ui/input' + +export default function ContactForm() { + const [name, setName] = useState('') + const [email, setEmail] = useState('') + const [phone, setPhone] = useState('') + const [message, setMessage] = useState('') + const [loading, setLoading] = useState(false) + const [success, setSuccess] = useState(null) + const [error, setError] = useState(null) + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + setLoading(true) + setError(null) + setSuccess(null) + + try { + const res = await fetch('/api/contact', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name, email, phone, message }) + }) + + const data = await res.json() + if (!res.ok) throw new Error(data?.error || 'Unknown error') + setSuccess('Besked sendt — tak!') + setName('') + setEmail('') + setPhone('') + setMessage('') + } catch (err: any) { + setError(err.message || 'Der skete en fejl') + } finally { + setLoading(false) + } + } + + return ( +
+ {success && ( +
{success}
+ )} + {error && ( +
{error}
+ )} + +
+ + setName(e.target.value)} className="w-full" required /> +
+ +
+ + setEmail(e.target.value)} className="w-full" required /> +
+ +
+ + setPhone(e.target.value)} className="w-full" /> +
+ +
+ +