From 35908a0bd9f12a726a5b42efe1447e2b6ddfa594 Mon Sep 17 00:00:00 2001 From: ashwin Date: Wed, 28 May 2025 09:35:21 +0530 Subject: [PATCH 1/2] feat(contact): add ContactSubmission model and migration --- prisma/migrations/20250528040308_/migration.sql | 14 ++++++++++++++ prisma/schema.prisma | 17 +++++++++++++++++ src/routes/(site)/contact/+page.server.js | 4 ++++ 3 files changed, 35 insertions(+) create mode 100644 prisma/migrations/20250528040308_/migration.sql create mode 100644 src/routes/(site)/contact/+page.server.js 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/(site)/contact/+page.server.js b/src/routes/(site)/contact/+page.server.js new file mode 100644 index 0000000..c797d09 --- /dev/null +++ b/src/routes/(site)/contact/+page.server.js @@ -0,0 +1,4 @@ +/** @type {import('./$types').PageServerLoad} */ +export async function load() { + return {}; +}; \ No newline at end of file From 0f56111f66e49c96bd82cb86a4efbcf7e7f05700 Mon Sep 17 00:00:00 2001 From: ashwin Date: Wed, 28 May 2025 09:55:06 +0530 Subject: [PATCH 2/2] feat(contact): implement contact submission handling with server-side validation and enhanced UI --- src/routes/(admin)/{admin => }/+layout.svelte | 12 +- .../(admin)/admin/contacts/+page.server.js | 6 + .../(admin)/admin/contacts/+page.svelte | 97 +++++++ src/routes/(site)/contact/+page.server.js | 106 +++++++ src/routes/(site)/contact/+page.svelte | 261 ++++++++++-------- 5 files changed, 358 insertions(+), 124 deletions(-) rename src/routes/(admin)/{admin => }/+layout.svelte (87%) create mode 100644 src/routes/(admin)/admin/contacts/+page.server.js create mode 100644 src/routes/(admin)/admin/contacts/+page.svelte 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 index c797d09..24e0735 100644 --- a/src/routes/(site)/contact/+page.server.js +++ b/src/routes/(site)/contact/+page.server.js @@ -1,4 +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 @@