From b75be18a870243986724421f9106cb4b85144121 Mon Sep 17 00:00:00 2001 From: joshua Date: Fri, 21 Aug 2026 11:47:23 +0200 Subject: [PATCH] Add event/organisation location with Google Maps links Events and the organisation profile now have an address, with a "Directions" link and an embedded Google Maps view (no API key required) shown on event pages, event cards, and the Contact page. New events default their location to the org's configured address. --- CHANGELOG.md | 4 +++ .../migration.sql | 2 ++ backend/prisma/schema.prisma | 1 + backend/src/controllers/eventController.js | 6 ++-- frontend/src/app/contact/page.tsx | 7 ++++ .../src/app/dashboard/admin/settings/page.tsx | 9 ++++- .../app/dashboard/supervisor/events/page.tsx | 17 ++++++++-- frontend/src/app/events/[id]/page.tsx | 9 +++++ frontend/src/components/events/EventCard.tsx | 9 ++++- .../src/components/events/LocationMap.tsx | 33 +++++++++++++++++++ frontend/src/lib/maps.ts | 9 +++++ 11 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 backend/prisma/migrations/20260821090459_add_event_location/migration.sql create mode 100644 frontend/src/components/events/LocationMap.tsx create mode 100644 frontend/src/lib/maps.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index dacbfe1..3509576 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project follows [Semantic Versioning](https://semver.org/). ## [Unreleased] +### Added + +- Events now have an optional Location field (address), defaulting to the organisation's configured address when creating a new event. Wherever an address is shown — event admin form, public event page, event card listings, the Contact page, and Site Settings → Organisation — there's now a "Directions"/"View on map" link, and the event detail and Contact pages also show an embedded Google Maps view (no API key required). + ## [1.8.0] - 2026-08-21 ### Added diff --git a/backend/prisma/migrations/20260821090459_add_event_location/migration.sql b/backend/prisma/migrations/20260821090459_add_event_location/migration.sql new file mode 100644 index 0000000..8d4a4ed --- /dev/null +++ b/backend/prisma/migrations/20260821090459_add_event_location/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Event" ADD COLUMN "location" TEXT; diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 95248bd..9c79b88 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -105,6 +105,7 @@ model Event { contactName String? contactPhone String? contactEmail String? + location String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt createdById String? diff --git a/backend/src/controllers/eventController.js b/backend/src/controllers/eventController.js index af32a59..e0ba75a 100644 --- a/backend/src/controllers/eventController.js +++ b/backend/src/controllers/eventController.js @@ -41,7 +41,7 @@ function toAbsoluteUrl(req, url) { // @access Private/Admin const createEvent = async (req, res) => { try { - const { title, description, startDate, endDate, registrationDeadline, goLiveAt, price, picture, redirectUrl, isHidden, requiresAuth, requiresRegistration, contactName, contactPhone, contactEmail } = req.body; + const { title, description, startDate, endDate, registrationDeadline, goLiveAt, price, picture, redirectUrl, isHidden, requiresAuth, requiresRegistration, contactName, contactPhone, contactEmail, location } = req.body; const data = { id: uuidv4(), @@ -62,6 +62,7 @@ const createEvent = async (req, res) => { contactName: contactName || null, contactPhone: contactPhone || null, contactEmail: contactEmail || null, + location: location || null, }; try { @@ -459,7 +460,7 @@ const updateEvent = async (req, res) => { // totals — same rule already enforced for payments/costs. Admin can reopen first. await assertEventOpen(req.params.id, res); - const { title, description, startDate, endDate, registrationDeadline, goLiveAt, price, picture, isActive, redirectUrl, isHidden, requiresAuth, requiresRegistration, contactName, contactPhone, contactEmail } = req.body; + const { title, description, startDate, endDate, registrationDeadline, goLiveAt, price, picture, isActive, redirectUrl, isHidden, requiresAuth, requiresRegistration, contactName, contactPhone, contactEmail, location } = req.body; const data = { title: title || event.title, @@ -477,6 +478,7 @@ const updateEvent = async (req, res) => { contactName: contactName !== undefined ? (contactName || null) : event.contactName, contactPhone: contactPhone !== undefined ? (contactPhone || null) : event.contactPhone, contactEmail: contactEmail !== undefined ? (contactEmail || null) : event.contactEmail, + location: location !== undefined ? (location || null) : event.location, updatedAt: new Date(), redirectUrl: redirectUrl !== undefined ? redirectUrl : event.redirectUrl, }; diff --git a/frontend/src/app/contact/page.tsx b/frontend/src/app/contact/page.tsx index 575f17c..db5c7c1 100644 --- a/frontend/src/app/contact/page.tsx +++ b/frontend/src/app/contact/page.tsx @@ -5,6 +5,7 @@ import { Navbar } from "@/components/layout/Navbar"; import { Footer } from "@/components/layout/Footer"; import { useSiteSettings } from "@/contexts/SiteSettingsContext"; import { appName } from "@/lib/siteConfig"; +import { LocationMap } from "@/components/events/LocationMap"; export default function ContactPage() { const { settings, loading } = useSiteSettings(); @@ -68,6 +69,12 @@ export default function ContactPage() { )} + + {address && ( +
+ +
+ )}