Skip to content

Commit 55d3503

Browse files
Merge pull request #158 from StreetSupport/staging
Merge staging into main
2 parents fe02dc8 + dfb318f commit 55d3503

File tree

13 files changed

+178
-124
lines changed

13 files changed

+178
-124
lines changed

src/app/[slug]/page.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import LocationStatistics from '@/components/Location/LocationStatistics';
1111
import LocationNews from '@/components/Location/LocationNews';
1212
import EmergencyContactSection from '@/components/Location/EmergencyContactSection';
1313
import SupporterLogos from '@/components/Location/SupporterLogos';
14+
import WatsonXChat from '@/components/ui/WatsonXChat';
1415
import { generateLocationSEOMetadata } from '@/utils/seo';
1516

1617
export const dynamic = 'force-dynamic';
@@ -250,6 +251,9 @@ export default async function LocationPage(props) {
250251

251252
{/* Supporter Logos Section */}
252253
<SupporterLogos locationSlug={slug} />
254+
255+
{/* WatsonX Chat - handles West Midlands locations internally */}
256+
<WatsonXChat locationSlug={slug} />
253257
</main>
254258
);
255259
}

src/app/api/banners/route.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ export async function GET(req: NextRequest) {
7979
layoutStyle: banner.LayoutStyle || 'full-width',
8080

8181
// Scheduling
82-
showDates: banner.ShowDates,
8382
startDate: banner.StartDate ? new Date(banner.StartDate).toISOString() : undefined,
8483
endDate: banner.EndDate ? new Date(banner.EndDate).toISOString() : undefined,
8584
badgeText: banner.BadgeText,

src/app/west-midlands/page.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import NewsGrid from '@/components/News/NewsGrid';
33
import Link from 'next/link';
44
import Image from 'next/image';
55
import { generateSEOMetadata } from '@/utils/seo';
6+
import WatsonXChat from '@/components/ui/WatsonXChat';
67

78
export const metadata = generateSEOMetadata({
89
title: 'Street Support West Midlands',
@@ -252,6 +253,10 @@ export default function WestMidlandsPage() {
252253
</div>
253254
</div>
254255
</div>
256+
257+
{/* WatsonX Chat for West Midlands */}
258+
<WatsonXChat />
259+
255260
</>
256261
);
257262
}

src/components/Banners/GivingCampaignBanner.tsx

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ const GivingCampaignBanner: React.FC<GivingCampaignBannerProps> = ({
3232
textColour,
3333
layoutStyle,
3434
accentGraphic,
35-
showDates,
3635
startDate,
3736
endDate,
3837
badgeText,
@@ -240,19 +239,11 @@ const GivingCampaignBanner: React.FC<GivingCampaignBannerProps> = ({
240239
</div>
241240

242241
{/* Date range */}
243-
{showDates && (startDate || endDate) && (
242+
{startDate && endDate && (
244243
<div className="mt-6 text-sm opacity-70">
245-
{startDate && endDate && (
246-
<p>
247-
{new Date(startDate).toLocaleDateString('en-GB')} - {new Date(endDate).toLocaleDateString('en-GB')}
248-
</p>
249-
)}
250-
{startDate && !endDate && (
251-
<p>From {new Date(startDate).toLocaleDateString('en-GB')}</p>
252-
)}
253-
{!startDate && endDate && (
254-
<p>Until {new Date(endDate).toLocaleDateString('en-GB')}</p>
255-
)}
244+
<p>
245+
{new Date(startDate).toLocaleDateString('en-GB')} - {new Date(endDate).toLocaleDateString('en-GB')}
246+
</p>
256247
</div>
257248
)}
258249
</div>

src/components/Banners/PartnershipCharterBanner.tsx

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ const PartnershipCharterBanner: React.FC<PartnershipCharterBannerProps> = ({
2929
textColour,
3030
layoutStyle,
3131
accentGraphic,
32-
showDates,
3332
startDate,
3433
endDate,
3534
badgeText,
@@ -291,19 +290,11 @@ const PartnershipCharterBanner: React.FC<PartnershipCharterBannerProps> = ({
291290
)}
292291

293292
{/* Date range */}
294-
{showDates && (startDate || endDate) && (
293+
{startDate && endDate && (
295294
<div className="mt-6 text-sm opacity-70">
296-
{startDate && endDate && (
297-
<p>
298-
{new Date(startDate).toLocaleDateString('en-GB')} - {new Date(endDate).toLocaleDateString('en-GB')}
299-
</p>
300-
)}
301-
{startDate && !endDate && (
302-
<p>From {new Date(startDate).toLocaleDateString('en-GB')}</p>
303-
)}
304-
{!startDate && endDate && (
305-
<p>Until {new Date(endDate).toLocaleDateString('en-GB')}</p>
306-
)}
295+
<p>
296+
{new Date(startDate).toLocaleDateString('en-GB')} - {new Date(endDate).toLocaleDateString('en-GB')}
297+
</p>
307298
</div>
308299
)}
309300
</div>

src/components/Banners/ResourceProjectBanner.tsx

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ const ResourceProjectBanner: React.FC<ResourceProjectBannerProps> = ({
2929
textColour,
3030
layoutStyle,
3131
accentGraphic,
32-
showDates,
3332
startDate,
3433
endDate,
3534
badgeText,
@@ -417,19 +416,11 @@ const ResourceProjectBanner: React.FC<ResourceProjectBannerProps> = ({
417416
)}
418417

419418
{/* Date range */}
420-
{showDates && (startDate || endDate) && (
419+
{startDate && endDate && (
421420
<div className="mt-6 text-sm opacity-70">
422-
{startDate && endDate && (
423-
<p>
424-
Available: {new Date(startDate).toLocaleDateString('en-GB')} - {new Date(endDate).toLocaleDateString('en-GB')}
425-
</p>
426-
)}
427-
{startDate && !endDate && (
428-
<p>Available from {new Date(startDate).toLocaleDateString('en-GB')}</p>
429-
)}
430-
{!startDate && endDate && (
431-
<p>Available until {new Date(endDate).toLocaleDateString('en-GB')}</p>
432-
)}
421+
<p>
422+
Available: {new Date(startDate).toLocaleDateString('en-GB')} - {new Date(endDate).toLocaleDateString('en-GB')}
423+
</p>
433424
</div>
434425
)}
435426
</div>

src/components/SwepPageContent.tsx

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
'use client';
22

33
import React, { useState, useEffect } from 'react';
4+
import Link from 'next/link';
45
import Breadcrumbs from '@/components/ui/Breadcrumbs';
56
import { SwepData } from '@/types';
6-
import { formatSwepActivePeriod, parseSwepBody } from '@/utils/swep';
77

88
interface SwepPageContentProps {
99
locationSlug: string;
@@ -76,9 +76,6 @@ export default function SwepPageContent({ locationSlug, locationName }: SwepPage
7676
);
7777
}
7878

79-
const activePeriodText = formatSwepActivePeriod(swepData);
80-
const parsedBody = parseSwepBody(swepData.body);
81-
8279
return (
8380
<main>
8481
<Breadcrumbs
@@ -89,25 +86,33 @@ export default function SwepPageContent({ locationSlug, locationName }: SwepPage
8986
]}
9087
/>
9188

92-
{/* Header */}
93-
<div className="bg-red-50 border-b-4 border-brand-g py-12">
94-
<div className="max-w-6xl mx-auto px-6">
95-
<div className="flex items-center gap-3 mb-4">
96-
<div className="w-6 h-6 bg-brand-g rounded-full flex items-center justify-center">
97-
<div className="w-3 h-3 bg-white rounded-full"></div>
89+
{!swepData.isActive && (
90+
<div className="bg-brand-d py-12">
91+
<div className="max-w-6xl mx-auto px-6 text-center">
92+
<h2 className="text-2xl font-bold text-brand-l mb-4">
93+
Severe Weather Emergency Accommodation is not currently active in {locationName}
94+
</h2>
95+
<p className="text-brand-k mb-4">
96+
This page provides information about emergency accommodation during severe weather. The service is activated when temperatures drop below freezing.
97+
</p>
98+
<p className="text-brand-k mb-6">If you need help right now, please visit:</p>
99+
<div className="flex flex-col sm:flex-row gap-4 justify-center">
100+
<Link
101+
href={`/${locationSlug}/advice/`}
102+
className="inline-flex items-center justify-center px-6 py-3 bg-brand-g text-white font-semibold rounded-md hover:bg-opacity-90 transition-colors"
103+
>
104+
See emergency advice
105+
</Link>
106+
<Link
107+
href="/find-help/"
108+
className="inline-flex items-center justify-center px-6 py-3 bg-brand-e text-brand-l font-semibold rounded-md hover:bg-opacity-90 transition-colors"
109+
>
110+
Find Help
111+
</Link>
98112
</div>
99-
<h1 className="text-3xl font-bold text-red-800 mb-0">
100-
{swepData.title}
101-
</h1>
102-
</div>
103-
<p className="text-lg text-red-700 mb-4">
104-
{activePeriodText}
105-
</p>
106-
<div className="bg-brand-g text-white px-4 py-2 rounded-md inline-block">
107-
<strong>Emergency Support Available</strong>
108113
</div>
109114
</div>
110-
</div>
115+
)}
111116

112117
{/* Content */}
113118
<section className="py-12">
@@ -124,7 +129,7 @@ export default function SwepPageContent({ locationSlug, locationName }: SwepPage
124129

125130
<div
126131
className="prose prose-lg max-w-none"
127-
dangerouslySetInnerHTML={{ __html: parsedBody }}
132+
dangerouslySetInnerHTML={{ __html: swepData.body }}
128133
/>
129134

130135
<div className="mt-12 p-6 bg-blue-50 rounded-lg border-l-4 border-blue-600">

src/components/ui/WatsonXChat.tsx

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
'use client';
2+
3+
import { useEffect } from 'react';
4+
5+
type WatsonAssistantInstance = {
6+
render: () => Promise<void>;
7+
destroy: () => void;
8+
};
9+
10+
// Extend Window interface for WatsonX
11+
declare global {
12+
interface Window {
13+
watsonAssistantChatOptions?: {
14+
integrationID: string;
15+
region: string;
16+
serviceInstanceID: string;
17+
clientVersion?: string;
18+
onLoad: (instance: WatsonAssistantInstance) => Promise<void>;
19+
};
20+
watsonAssistantInstance?: {
21+
// The web chat instance exposes a destroy() method that removes all UI
22+
destroy: () => void;
23+
} | null;
24+
}
25+
}
26+
27+
// West Midlands locations that should have WatsonX chat
28+
const WATSON_X_LOCATIONS = [
29+
'birmingham',
30+
'sandwell',
31+
'walsall',
32+
'wolverhampton',
33+
'coventry',
34+
'dudley',
35+
'solihull'
36+
];
37+
38+
interface WatsonXChatProps {
39+
locationSlug?: string;
40+
}
41+
42+
/**
43+
* WatsonX Chat component for West Midlands locations
44+
* Injects the IBM WatsonX Assistant chat widget
45+
*
46+
* @param locationSlug - Optional location slug to determine if chat should be shown
47+
* If not provided, chat is always shown (for west-midlands hub page)
48+
*/
49+
export default function WatsonXChat({ locationSlug }: WatsonXChatProps) {
50+
// Determine if we should show the chat widget
51+
const shouldShowChat = !locationSlug || WATSON_X_LOCATIONS.includes(locationSlug);
52+
53+
useEffect(() => {
54+
// Only run on client side
55+
if (typeof window === 'undefined') return;
56+
57+
// Don't initialize if this location shouldn't have the chat
58+
if (!shouldShowChat) {
59+
// Clean up any existing instance when navigating away from WM locations
60+
if (window.watsonAssistantInstance) {
61+
try {
62+
window.watsonAssistantInstance.destroy();
63+
} catch (error) {
64+
console.error('Error destroying Watson Assistant instance', error);
65+
}
66+
window.watsonAssistantInstance = null;
67+
}
68+
const existingScript = document.querySelector(
69+
'script[src*="WatsonAssistantChatEntry.js"]'
70+
);
71+
if (existingScript) {
72+
existingScript.remove();
73+
}
74+
delete window.watsonAssistantChatOptions;
75+
return;
76+
}
77+
78+
// Check if already initialized
79+
if (window.watsonAssistantChatOptions) return;
80+
81+
// Set up Watson Assistant options
82+
window.watsonAssistantChatOptions = {
83+
integrationID: '83b099b7-08a1-4118-bba3-341fbec366d1',
84+
region: 'eu-gb',
85+
serviceInstanceID: 'a3a6beaa-5967-4039-8390-d48ace365d86',
86+
onLoad: async (instance) => {
87+
// Keep a reference so we can destroy it when the component unmounts
88+
window.watsonAssistantInstance = instance;
89+
await instance.render();
90+
}
91+
};
92+
93+
// Load the Watson Assistant script
94+
const script = document.createElement('script');
95+
script.src = `https://web-chat.global.assistant.watson.appdomain.cloud/versions/${
96+
window.watsonAssistantChatOptions.clientVersion || 'latest'
97+
}/WatsonAssistantChatEntry.js`;
98+
script.async = true;
99+
document.head.appendChild(script);
100+
101+
// Cleanup on unmount
102+
return () => {
103+
// Destroy the existing chat instance so the widget disappears
104+
if (window.watsonAssistantInstance) {
105+
try {
106+
window.watsonAssistantInstance.destroy();
107+
} catch (error) {
108+
console.error('Error destroying Watson Assistant instance', error);
109+
}
110+
window.watsonAssistantInstance = null;
111+
}
112+
113+
// Remove the script if component unmounts
114+
const existingScript = document.querySelector(
115+
'script[src*="WatsonAssistantChatEntry.js"]'
116+
);
117+
if (existingScript) {
118+
existingScript.remove();
119+
}
120+
121+
// Clean up the global options
122+
delete window.watsonAssistantChatOptions;
123+
};
124+
}, [shouldShowChat, locationSlug]);
125+
126+
// This component doesn't render anything visible
127+
return null;
128+
}

src/types/banners.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ export interface BannerProps {
6767
accentGraphic?: AccentGraphic;
6868

6969
// Optional features
70-
showDates?: boolean;
7170
startDate?: string;
7271
endDate?: string;
7372
badgeText?: string;

0 commit comments

Comments
 (0)