From fda28fd66c81856d335ce04d148c29fde52ad2b6 Mon Sep 17 00:00:00 2001 From: James Cross Date: Wed, 18 Feb 2026 18:59:38 +0000 Subject: [PATCH] feat: service card UX improvements - Add hover effects and cursor pointer to location buttons on organisation pages - Use brand colours for location button styles instead of default Tailwind - Show Open 24/7 pill for services open around the clock (flag or 00:00-23:59 slots) - Hide opening times list and Open Now pill for 24-hour services - Add loading animation on service card click - Update tests to match new 24-hour service display behaviour --- src/app/globals.css | 26 ++++++++ .../FindHelp/GroupedServiceCard.tsx | 7 ++- src/components/FindHelp/ServiceCard.tsx | 60 ++++++++++++------- .../OrganisationServicesAccordion.tsx | 8 +-- .../components/ServiceCard-24-7.test.tsx | 50 ++++++++++------ 5 files changed, 103 insertions(+), 48 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index 53d3763..c835955 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -263,6 +263,10 @@ @apply text-sm text-brand-l leading-relaxed; } +.card-loading { + @apply opacity-60 pointer-events-none animate-pulse; +} + /* Card Grid System */ .card-grid { @apply grid gap-6; @@ -980,6 +984,28 @@ select:disabled { } } +/* Location buttons within service listings */ +.location-btn { + background-color: var(--color-brand-q); + border-color: #e5e7eb; + color: var(--color-brand-l); + cursor: pointer; +} + +.location-btn:hover { + background-color: #e9e9e9; + border-color: var(--color-brand-f); +} + +.location-btn.selected { + border-color: var(--color-brand-a); + color: var(--color-brand-k); +} + +.location-btn.selected:hover { + background-color: #e6f5f1; +} + /* Statistics section */ .statistics-value { font-family: 'Museo Sans', Arial, sans-serif; diff --git a/src/components/FindHelp/GroupedServiceCard.tsx b/src/components/FindHelp/GroupedServiceCard.tsx index 70820a5..7d5556c 100644 --- a/src/components/FindHelp/GroupedServiceCard.tsx +++ b/src/components/FindHelp/GroupedServiceCard.tsx @@ -1,6 +1,6 @@ 'use client'; -import React from 'react'; +import React, { useState } from 'react'; import Link from 'next/link'; import { useSearchParams } from 'next/navigation'; import { useLocation } from '@/contexts/LocationContext'; @@ -90,10 +90,13 @@ const GroupedServiceCard = React.memo(function GroupedServiceCard({ const shouldTruncate = decodedDescription.length > 100; + const [isLoading, setIsLoading] = useState(false); + return ( setIsLoading(true)} + className={`card card-compact${isLoading ? ' card-loading' : ''}`} aria-label={`View details for ${decodedOrgName}`} >
diff --git a/src/components/FindHelp/ServiceCard.tsx b/src/components/FindHelp/ServiceCard.tsx index 615443e..fea05ff 100644 --- a/src/components/FindHelp/ServiceCard.tsx +++ b/src/components/FindHelp/ServiceCard.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; import Link from 'next/link'; import { useSearchParams } from 'next/navigation'; import { useLocation } from '@/contexts/LocationContext'; @@ -70,6 +70,12 @@ const ServiceCard = React.memo(function ServiceCard({ service, isOpen, onToggle const distanceText = formatDistance(service.distance); + const is24Hour = service.isOpen247 || (service.openTimes && service.openTimes.some((slot) => { + const startTime = Number(slot.start); + const endTime = Number(slot.end); + return startTime === 0 && endTime === 2359; + })); + return { destination, decodedDescription, @@ -81,7 +87,8 @@ const ServiceCard = React.memo(function ServiceCard({ service, isOpen, onToggle categoryName, subCategoryName, openingStatus, - distanceText + distanceText, + is24Hour }; }, [service, location, searchParams]); @@ -93,21 +100,24 @@ const ServiceCard = React.memo(function ServiceCard({ service, isOpen, onToggle categoryName, subCategoryName, openingStatus, - distanceText + distanceText, + is24Hour } = memoizedData; + const [isLoading, setIsLoading] = useState(false); + return ( { - // Track service card click for analytics + setIsLoading(true); trackServiceCardClick( service.id?.toString() || 'unknown', decodedOrgName, categoryName ); }} - className="card card-compact" + className={`card card-compact${isLoading ? ' card-loading' : ''}`} aria-label={`View details for ${decodedName}`} >
@@ -132,17 +142,23 @@ const ServiceCard = React.memo(function ServiceCard({ service, isOpen, onToggle )} - {openingStatus.isOpen && ( - - Open Now - - )} - - {/* Appointment Only indicator */} - {openingStatus.isAppointmentOnly && ( - - Appointment Only + {is24Hour ? ( + + Open 24/7 + ) : ( + <> + {openingStatus.isOpen && ( + + Open Now + + )} + {openingStatus.isAppointmentOnly && ( + + Appointment Only + + )} + )}
@@ -202,7 +218,7 @@ const ServiceCard = React.memo(function ServiceCard({ service, isOpen, onToggle
)} - {service.openTimes && service.openTimes.length > 0 && !service.isOpen247 ? ( + {is24Hour ? null : service.openTimes && service.openTimes.length > 0 ? (

Opening Times: