diff --git a/prisma/migrations/20250528040308_/migration.sql b/prisma/migrations/20250528040308_/migration.sql new file mode 100644 index 0000000..8cec846 --- /dev/null +++ b/prisma/migrations/20250528040308_/migration.sql @@ -0,0 +1,14 @@ +-- CreateTable +CREATE TABLE "ContactSubmission" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "email" TEXT NOT NULL, + "message" TEXT NOT NULL, + "reason" TEXT NOT NULL, + "ipAddress" TEXT, + "userAgent" TEXT, + "referrer" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "ContactSubmission_pkey" PRIMARY KEY ("id") +); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 95d6224..080afa2 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -668,3 +668,20 @@ model NewsletterSubscriber { @@index([isActive]) @@index([subscribedAt]) } + + + +model ContactSubmission { + id String @id @default(uuid()) + name String + email String + message String + reason String + + // Tracking fields + ipAddress String? + userAgent String? + referrer String? + + createdAt DateTime @default(now()) +} diff --git a/src/routes/(admin)/admin/+layout.svelte b/src/routes/(admin)/+layout.svelte similarity index 87% rename from src/routes/(admin)/admin/+layout.svelte rename to src/routes/(admin)/+layout.svelte index 6505ab9..b58f13e 100644 --- a/src/routes/(admin)/admin/+layout.svelte +++ b/src/routes/(admin)/+layout.svelte @@ -1,8 +1,8 @@ + +
+
+

Contact Submissions

+
+ + {#if data.contacts && data.contacts.length > 0} +
+ + + + + + + + + + + + {#each data.contacts as contact} + + + + + + + + {/each} + +
+ Contact Info + + Reason + + Message + + Submitted + + Tracking +
+
+
{contact.name}
+
{contact.email}
+
+
+ + {contact.reason} + + +
+ {contact.message} +
+
+ {formatDate(contact.createdAt)} + +
+ {#if contact.ipAddress} +
IP: {contact.ipAddress}
+ {/if} + {#if contact.referrer} +
+ Ref: {contact.referrer} +
+ {/if} +
+
+
+ +
+ Total submissions: {data.contacts.length} +
+ {:else} +
+
+ + + +
+

No contact submissions

+

No contact form requests have been submitted yet.

+
+ {/if} +
\ No newline at end of file diff --git a/src/routes/(site)/contact/+page.server.js b/src/routes/(site)/contact/+page.server.js new file mode 100644 index 0000000..24e0735 --- /dev/null +++ b/src/routes/(site)/contact/+page.server.js @@ -0,0 +1,110 @@ +import prisma from '$lib/prisma'; +import { fail } from '@sveltejs/kit'; + +/** @type {import('./$types').PageServerLoad} */ +export async function load() { + return {}; +} + +/** @type {import('./$types').Actions} */ +export const actions = { + default: async ({ request }) => { + + + const data = await request.formData(); + const name = data.get('name'); + const email = data.get('email'); + const serviceType = data.get('serviceType'); + const message = data.get('message'); + + // Server-side validation + const errors = {}; + + if (!name || name.toString().trim() === '') { + errors.name = 'Name is required'; + } + + if (!email || email.toString().trim() === '') { + errors.email = 'Email is required'; + } else if (!/\S+@\S+\.\S+/.test(email.toString())) { + errors.email = 'Email is invalid'; + } + + if (!serviceType || serviceType.toString().trim() === '') { + errors.serviceType = 'Please select a service type'; + } + + if (!message || message.toString().trim() === '') { + errors.message = 'Message is required'; + } + + if (Object.keys(errors).length > 0) { + return fail(400, { + errors, + name: name?.toString() || '', + email: email?.toString() || '', + serviceType: serviceType?.toString() || '', + message: message?.toString() || '' + }); + } + + try { + // Get client information from headers + const userAgent = request.headers.get('user-agent'); + const forwarded = request.headers.get('x-forwarded-for'); + const realIp = request.headers.get('x-real-ip'); + const cfConnectingIp = request.headers.get('cf-connecting-ip'); + const referrer = request.headers.get('referer'); + + // Determine IP address (priority: CF > X-Real-IP > X-Forwarded-For) + let ipAddress = cfConnectingIp || realIp; + if (!ipAddress && forwarded) { + ipAddress = forwarded.split(',')[0].trim(); + } + + + // Store submission in database + const submission = await prisma.contactSubmission.create({ + data: { + name: name.toString().trim(), + email: email.toString().trim(), + reason: serviceType.toString().trim(), + message: message.toString().trim(), + ipAddress, + userAgent, + referrer + } + }); + + + return { + success: true, + message: 'Thank you for your message! We\'ll get back to you within 24 hours.' + }; + + } catch (error) { + console.error('Error saving contact submission:', error); + + // More specific error handling + if (error.code === 'P1001') { + return fail(500, { + error: 'Database connection failed. Please try again later.', + name: name?.toString() || '', + email: email?.toString() || '', + serviceType: serviceType?.toString() || '', + message: message?.toString() || '' + }); + } + + return fail(500, { + error: 'Sorry, there was an error submitting your message. Please try again later.', + name: name?.toString() || '', + email: email?.toString() || '', + serviceType: serviceType?.toString() || '', + message: message?.toString() || '' + }); + } finally { + + } + } +}; \ No newline at end of file diff --git a/src/routes/(site)/contact/+page.svelte b/src/routes/(site)/contact/+page.svelte index f0da489..795fcd7 100644 --- a/src/routes/(site)/contact/+page.svelte +++ b/src/routes/(site)/contact/+page.svelte @@ -2,6 +2,7 @@