Skip to content

Commit 3d768cb

Browse files
authored
Merge pull request #266 from StreetSupport/fix/csp-stats-breadcrumb-fixes
fix: CSP errors, homepage stats performance, and breadcrumb accessibility
2 parents 926f276 + 3bbc907 commit 3d768cb

File tree

5 files changed

+49
-60
lines changed

5 files changed

+49
-60
lines changed

next.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ const cspDirectives = [
66
"default-src 'self'",
77
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://maps.googleapis.com https://www.googletagmanager.com https://www.google-analytics.com https://*.sentry-cdn.com",
88
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
9-
"img-src 'self' data: blob: https://streetsupportstorageprod.blob.core.windows.net https://maps.googleapis.com https://maps.gstatic.com https://*.ggpht.com https://www.google-analytics.com https://www.googletagmanager.com",
9+
"img-src 'self' data: blob: https://streetsupportstorageprod.blob.core.windows.net https://maps.googleapis.com https://maps.gstatic.com https://*.ggpht.com https://www.google-analytics.com https://www.googletagmanager.com https://www.google.co.uk",
1010
"font-src 'self' https://fonts.gstatic.com",
11-
"connect-src 'self' https://maps.googleapis.com https://www.google-analytics.com https://www.googletagmanager.com https://*.ingest.sentry.io https://*.sentry.io",
11+
"connect-src 'self' https://maps.googleapis.com https://www.google-analytics.com https://www.googletagmanager.com https://*.analytics.google.com https://*.ingest.sentry.io https://*.sentry.io",
1212
"frame-src https://www.youtube.com https://www.youtube-nocookie.com",
1313
"frame-ancestors 'none'",
1414
"object-src 'none'",

src/app/globals.css

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,10 +1010,15 @@ select:disabled {
10101010
.statistics-value {
10111011
font-family: 'Museo Sans', Arial, sans-serif;
10121012
font-weight: 700;
1013-
text-shadow: 0 4px 16px rgba(0,0,0,0.22);
10141013
}
10151014

10161015
.statistics-title {
1017-
font-family: 'Museo Sans', Arial, sans-serif;
1018-
font-weight: 500;
1016+
font-family: 'Museo Sans', Arial, sans-serif;
1017+
font-weight: 500;
1018+
}
1019+
1020+
.statistics-subtitle {
1021+
font-family: 'Museo Sans', Arial, sans-serif;
1022+
font-weight: 300;
1023+
opacity: 0.7;
10191024
}

src/app/page.tsx

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,15 @@ import HomepageMap from '@/components/Homepage/HomepageMap';
66
import LocationDropdown from '@/components/Homepage/LocationDropdown';
77
import Hero from '@/components/ui/Hero';
88
import { generateSEOMetadata } from '@/utils/seo';
9+
import { statsRepository } from '@/repositories/stats';
10+
11+
export const revalidate = 60;
912

1013
async function getStatistics() {
1114
try {
12-
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000';
13-
const response = await fetch(`${baseUrl}/api/stats`, {
14-
next: { revalidate: 60 } // Revalidate every 60 seconds
15-
});
16-
17-
if (!response.ok) {
18-
throw new Error('Failed to fetch statistics');
19-
}
20-
21-
return await response.json();
15+
return await statsRepository.getStats();
2216
} catch (error) {
2317
console.error('Error fetching statistics:', error);
24-
// Return default values if fetch fails
2518
return {
2619
organisations: 0,
2720
services: 0,
@@ -140,25 +133,25 @@ export default async function Home() {
140133
</section>
141134

142135
{/* Statistics Section */}
143-
<section className="section-spacing px-4 bg-brand-a text-white">
136+
<section className="section-spacing px-4 bg-brand-a text-white" aria-label="Key statistics">
144137
<div className="max-w-4xl mx-auto">
145-
<dl className="grid grid-cols-1 md:grid-cols-3 gap-8 text-center">
138+
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 text-center">
146139
<div>
147-
<dd className="statistics-value text-5xl md:text-6xl font-bold mb-2 text-brand-d">{stats.organisations}</dd>
148-
<dt className="statistics-title text-xl md:text-2xl font-light">Organisations</dt>
149-
<dd className="statistics-title text-lg md:text-xl font-light text-brand-k">Listed</dd>
140+
<div className="statistics-value text-5xl md:text-6xl font-bold mb-2 text-brand-d">{stats.organisations}</div>
141+
<div className="statistics-title text-xl md:text-2xl font-medium">Organisations</div>
142+
<div className="statistics-subtitle text-lg md:text-xl">Listed</div>
150143
</div>
151144
<div>
152-
<dd className="statistics-value text-5xl md:text-6xl font-bold mb-2 text-brand-d">{stats.services}</dd>
153-
<dt className="statistics-title text-xl md:text-2xl font-light">Services</dt>
154-
<dd className="statistics-title text-lg md:text-xl font-light text-brand-k">Provided</dd>
145+
<div className="statistics-value text-5xl md:text-6xl font-bold mb-2 text-brand-d">{stats.services}</div>
146+
<div className="statistics-title text-xl md:text-2xl font-medium">Services</div>
147+
<div className="statistics-subtitle text-lg md:text-xl">Provided</div>
155148
</div>
156149
<div>
157-
<dd className="statistics-value text-5xl md:text-6xl font-bold mb-2 text-brand-d">{stats.partnerships}</dd>
158-
<dt className="statistics-title text-xl md:text-2xl font-light">Homelessness Partnerships</dt>
159-
<dd className="statistics-title text-lg md:text-xl font-light text-brand-k">Supported</dd>
150+
<div className="statistics-value text-5xl md:text-6xl font-bold mb-2 text-brand-d">{stats.partnerships}</div>
151+
<div className="statistics-title text-xl md:text-2xl font-medium">Partnerships</div>
152+
<div className="statistics-subtitle text-lg md:text-xl">Supported</div>
160153
</div>
161-
</dl>
154+
</div>
162155
</div>
163156
</section>
164157

src/components/ui/Breadcrumbs.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ interface BreadcrumbsProps {
1515

1616
export default function Breadcrumbs({ items }: BreadcrumbsProps) {
1717
return (
18-
<div className="bg-brand-n py-4">
18+
<div className="bg-brand-h py-4">
1919
<div className="content-container px-6">
2020
<nav className="flex" aria-label="Breadcrumb">
2121
<ol className="inline-flex items-center space-x-1 md:space-x-3">
@@ -26,7 +26,7 @@ export default function Breadcrumbs({ items }: BreadcrumbsProps) {
2626
{item.href && !item.current ? (
2727
<Link
2828
href={item.href}
29-
className="text-white hover:text-white underline"
29+
className="!text-brand-e hover:!text-white underline"
3030
>
3131
{item.label}
3232
</Link>

src/repositories/stats/stats.mongo.ts

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,26 @@ export const createMongoStatsRepository = (): StatsRepository => ({
1212
const client = await getClientPromise();
1313
const db = client.db(DB_NAME);
1414

15-
// Count published organisations
16-
const organisationsCount = await db
17-
.collection('ServiceProviders')
18-
.countDocuments({ IsPublished: true });
15+
const [organisationsCount, uniqueServices, locationsCount] = await Promise.all([
16+
db.collection('ServiceProviders').countDocuments({ IsPublished: true }),
1917

20-
// Get all published organisations to count their services
21-
const publishedOrgs = await db
22-
.collection('ServiceProviders')
23-
.find({ IsPublished: true })
24-
.project({ Key: 1 })
25-
.toArray();
26-
27-
const orgKeys = publishedOrgs.map((org) => org.Key);
28-
29-
// Count unique services grouped by organisation + category + subcategory
30-
const uniqueServices = await db
31-
.collection('ProvidedServices')
32-
.aggregate([
18+
db.collection('ProvidedServices').aggregate([
19+
{
20+
$lookup: {
21+
from: 'ServiceProviders',
22+
localField: 'ServiceProviderKey',
23+
foreignField: 'Key',
24+
as: 'provider',
25+
pipeline: [
26+
{ $match: { IsPublished: true } },
27+
{ $project: { _id: 1 } },
28+
],
29+
},
30+
},
3331
{
3432
$match: {
35-
ServiceProviderKey: { $in: orgKeys },
3633
IsPublished: true,
34+
'provider.0': { $exists: true },
3735
},
3836
},
3937
{
@@ -45,22 +43,15 @@ export const createMongoStatsRepository = (): StatsRepository => ({
4543
},
4644
},
4745
},
48-
{
49-
$count: 'total',
50-
},
51-
])
52-
.toArray();
53-
54-
const servicesCount = uniqueServices[0]?.total || 0;
46+
{ $count: 'total' },
47+
]).toArray(),
5548

56-
// Count public cities/locations (partnerships)
57-
const locationsCount = await db
58-
.collection('Cities')
59-
.countDocuments({ IsPublic: true });
49+
db.collection('Cities').countDocuments({ IsPublic: true }),
50+
]);
6051

6152
return {
6253
organisations: organisationsCount,
63-
services: servicesCount,
54+
services: uniqueServices[0]?.total || 0,
6455
partnerships: locationsCount,
6556
};
6657
},

0 commit comments

Comments
 (0)