Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7eed7a01df | ||
|
|
a7be2baab2 | ||
|
|
5a9137e252 | ||
|
|
4e63a78e8e | ||
|
|
2a067a3687 | ||
|
|
bdae0c6b08 | ||
|
|
c84cc257d0 | ||
|
|
7cbb147b00 |
@@ -7,6 +7,21 @@ and this project follows [Semantic Versioning](https://semver.org/).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [1.9.2] - 2026-08-22
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Contact-only events (no registration possible) were still getting the daily summary email — it now skips them since there's nothing to summarize.
|
||||||
|
|
||||||
|
## [1.9.1] - 2026-08-22
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- The browser tab title and homepage "Welcome to..." heading always showed the app's built-in default name instead of the organisation name configured in Site Settings → Organisation.
|
||||||
|
- The backend's status page (`/`) and API docs page (`/docs`) always showed "Cross Code Events" instead of the configured organisation name.
|
||||||
|
|
||||||
|
## [1.9.0] - 2026-08-21
|
||||||
|
|
||||||
### Added
|
### 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).
|
- 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,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "event-management-backend",
|
"name": "event-management-backend",
|
||||||
"version": "1.8.0",
|
"version": "1.9.2",
|
||||||
"description": "Event Management System Backend",
|
"description": "Event Management System Backend",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -141,7 +141,8 @@ app.use('/api', costRoutes);
|
|||||||
app.use('/api/cashups', cashupRoutes);
|
app.use('/api/cashups', cashupRoutes);
|
||||||
|
|
||||||
// Pre-warm the settings cache so synchronous helpers have DB values from startup
|
// Pre-warm the settings cache so synchronous helpers have DB values from startup
|
||||||
require('./utils/settingsCache').warmCache().catch(() => {});
|
const { getSettingSync, warmCache } = require('./utils/settingsCache');
|
||||||
|
warmCache().catch(() => {});
|
||||||
app.use('/uploads', express.static('public/uploads'));
|
app.use('/uploads', express.static('public/uploads'));
|
||||||
|
|
||||||
// ── Shared page helpers ────────────────────────────────────────────────────────
|
// ── Shared page helpers ────────────────────────────────────────────────────────
|
||||||
@@ -244,8 +245,9 @@ app.get('/', async (req, res) => {
|
|||||||
? `<span class="badge badge-warn">testing</span>`
|
? `<span class="badge badge-warn">testing</span>`
|
||||||
: `<span class="badge badge-warn">development</span>`;
|
: `<span class="badge badge-warn">development</span>`;
|
||||||
|
|
||||||
const html = pageShell('Cross Code Events API — Status', '#2563eb', `
|
const orgName = getSettingSync('org_name', process.env.ORG_NAME || 'Cross Code');
|
||||||
<h1>Cross Code Events API</h1>
|
const html = pageShell(`${orgName} Events API — Status`, '#2563eb', `
|
||||||
|
<h1>${orgName} Events API</h1>
|
||||||
<p class="subtitle">v${API_VERSION} — ${now}</p>
|
<p class="subtitle">v${API_VERSION} — ${now}</p>
|
||||||
|
|
||||||
<div class="stat-grid">
|
<div class="stat-grid">
|
||||||
@@ -1030,13 +1032,14 @@ app.get('/docs', async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const notificationsHtml = NOTIFICATIONS.map(renderNotificationCategory).join('');
|
const notificationsHtml = NOTIFICATIONS.map(renderNotificationCategory).join('');
|
||||||
|
const orgName = getSettingSync('org_name', process.env.ORG_NAME || 'Cross Code');
|
||||||
|
|
||||||
const html = `<!DOCTYPE html>
|
const html = `<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Cross Code Events — API Docs</title>
|
<title>${orgName} Events — API Docs</title>
|
||||||
<style>
|
<style>
|
||||||
*{box-sizing:border-box;margin:0;padding:0}
|
*{box-sizing:border-box;margin:0;padding:0}
|
||||||
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#f3f4f6;color:#1f2937;min-height:100vh;padding:24px 16px}
|
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#f3f4f6;color:#1f2937;min-height:100vh;padding:24px 16px}
|
||||||
@@ -1060,7 +1063,7 @@ app.get('/docs', async (req, res) => {
|
|||||||
<body>
|
<body>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<div style="display:flex;align-items:baseline;justify-content:space-between;flex-wrap:wrap;gap:8px;margin-bottom:6px">
|
<div style="display:flex;align-items:baseline;justify-content:space-between;flex-wrap:wrap;gap:8px;margin-bottom:6px">
|
||||||
<h1 style="font-size:1.4rem;font-weight:700;color:#111827">Cross Code Events — API Reference</h1>
|
<h1 style="font-size:1.4rem;font-weight:700;color:#111827">${orgName} Events — API Reference</h1>
|
||||||
<a href="/" style="font-size:.82rem;color:#6b7280">← Status page</a>
|
<a href="/" style="font-size:.82rem;color:#6b7280">← Status page</a>
|
||||||
</div>
|
</div>
|
||||||
<p style="font-size:.82rem;color:#6b7280;margin-bottom:20px">
|
<p style="font-size:.82rem;color:#6b7280;margin-bottom:20px">
|
||||||
@@ -1087,7 +1090,7 @@ app.get('/docs', async (req, res) => {
|
|||||||
${notificationsHtml}
|
${notificationsHtml}
|
||||||
|
|
||||||
<p style="font-size:.72rem;color:#9ca3af;margin-top:28px;text-align:center">
|
<p style="font-size:.72rem;color:#9ca3af;margin-top:28px;text-align:center">
|
||||||
Cross Code Events API v${API_VERSION} — ${new Date().toISOString()}
|
${orgName} Events API v${API_VERSION} — ${new Date().toISOString()}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -1370,14 +1370,14 @@ async function sendDailyEventSummaries(now = new Date()) {
|
|||||||
const today = new Date(now);
|
const today = new Date(now);
|
||||||
const notifyInclude = { createdBy: { select: { id: true, name: true, email: true } }, notifyRecipients: { select: { id: true, name: true, email: true } } };
|
const notifyInclude = { createdBy: { select: { id: true, name: true, email: true } }, notifyRecipients: { select: { id: true, name: true, email: true } } };
|
||||||
let events = await prisma.event.findMany({
|
let events = await prisma.event.findMany({
|
||||||
where: { isActive: true, startDate: { gte: today } },
|
where: { isActive: true, startDate: { gte: today }, requiresRegistration: true },
|
||||||
include: notifyInclude,
|
include: notifyInclude,
|
||||||
orderBy: { startDate: 'asc' },
|
orderBy: { startDate: 'asc' },
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
events = await prisma.event.findMany({
|
events = await prisma.event.findMany({
|
||||||
where: { isActive: true, startDate: { gte: today }, goLiveAt: { lte: today } },
|
where: { isActive: true, startDate: { gte: today }, goLiveAt: { lte: today }, requiresRegistration: true },
|
||||||
include: notifyInclude,
|
include: notifyInclude,
|
||||||
orderBy: { startDate: 'asc' },
|
orderBy: { startDate: 'asc' },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hope-events-frontend",
|
"name": "hope-events-frontend",
|
||||||
"version": "1.8.0",
|
"version": "1.9.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbopack",
|
"dev": "next dev --turbopack",
|
||||||
|
|||||||
@@ -44,10 +44,11 @@ async function getServerSettings(): Promise<SiteSettings> {
|
|||||||
export async function generateMetadata(): Promise<Metadata> {
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
const settings = await getServerSettings();
|
const settings = await getServerSettings();
|
||||||
const faviconUrl = settings.favicon_url ? resolveToApiOrigin(settings.favicon_url) : null;
|
const faviconUrl = settings.favicon_url ? resolveToApiOrigin(settings.favicon_url) : null;
|
||||||
|
const displayName = settings.org_name || appName;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: appName,
|
title: displayName,
|
||||||
description: `Manage and register for events with ${appName}`,
|
description: `Manage and register for events with ${displayName}`,
|
||||||
icons: {
|
icons: {
|
||||||
icon: faviconUrl || "/favicon.ico",
|
icon: faviconUrl || "/favicon.ico",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Navbar } from "@/components/layout/Navbar";
|
|||||||
import { Footer } from "@/components/layout/Footer";
|
import { Footer } from "@/components/layout/Footer";
|
||||||
import { JoinUsButton } from "@/components/home/JoinUsButton";
|
import { JoinUsButton } from "@/components/home/JoinUsButton";
|
||||||
import { appName } from "@/lib/siteConfig";
|
import { appName } from "@/lib/siteConfig";
|
||||||
|
import { API_BASE } from "@/lib/api";
|
||||||
import { Calendar, ArrowRight, CalendarCheck, Users, Heart, ShieldCheck } from "lucide-react";
|
import { Calendar, ArrowRight, CalendarCheck, Users, Heart, ShieldCheck } from "lucide-react";
|
||||||
|
|
||||||
type Event = {
|
type Event = {
|
||||||
@@ -26,8 +27,22 @@ const FEATURES = [
|
|||||||
{ icon: Heart, title: "Make an Impact", description: "Be part of what God is doing and make a difference together." },
|
{ icon: Heart, title: "Make an Impact", description: "Be part of what God is doing and make a difference together." },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
async function getOrgName(): Promise<string> {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_BASE}/api/settings`, { next: { revalidate: 60 } });
|
||||||
|
if (!res.ok) return appName;
|
||||||
|
const settings = await res.json();
|
||||||
|
return settings?.org_name || appName;
|
||||||
|
} catch {
|
||||||
|
return appName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default async function HomePage() {
|
export default async function HomePage() {
|
||||||
const events = await apiFetch<Event[]>("/api/events", { nextOptions: { next: { revalidate: 60 } } });
|
const [events, displayName] = await Promise.all([
|
||||||
|
apiFetch<Event[]>("/api/events", { nextOptions: { next: { revalidate: 60 } } }),
|
||||||
|
getOrgName(),
|
||||||
|
]);
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const upcoming = (events || []).filter(e => {
|
const upcoming = (events || []).filter(e => {
|
||||||
const t = new Date(e.startDate).getTime();
|
const t = new Date(e.startDate).getTime();
|
||||||
@@ -52,7 +67,7 @@ export default async function HomePage() {
|
|||||||
{sorted.length} upcoming event{sorted.length === 1 ? "" : "s"}
|
{sorted.length} upcoming event{sorted.length === 1 ? "" : "s"}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<h1 className="text-4xl sm:text-5xl font-bold mb-4 text-gray-900">Welcome to {appName}</h1>
|
<h1 className="text-4xl sm:text-5xl font-bold mb-4 text-gray-900">Welcome to {displayName}</h1>
|
||||||
<p className="text-gray-600 text-lg mb-8">Experience unforgettable moments. Powered by purpose.</p>
|
<p className="text-gray-600 text-lg mb-8">Experience unforgettable moments. Powered by purpose.</p>
|
||||||
<div className="flex flex-wrap items-center justify-center gap-3">
|
<div className="flex flex-wrap items-center justify-center gap-3">
|
||||||
<a href="/events" className="inline-flex items-center gap-2 px-6 py-2.5 bg-brand-600 text-white rounded-xl hover:bg-brand-700 shadow-sm font-medium transition-colors">
|
<a href="/events" className="inline-flex items-center gap-2 px-6 py-2.5 bg-brand-600 text-white rounded-xl hover:bg-brand-700 shadow-sm font-medium transition-colors">
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hope-events",
|
"name": "hope-events",
|
||||||
"version": "1.8.0",
|
"version": "1.9.2",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev:backend": "cd backend && npm run dev",
|
"dev:backend": "cd backend && npm run dev",
|
||||||
|
|||||||
Reference in New Issue
Block a user