From 3e203de911c29529ae36ebb15b39e810e8d8908a Mon Sep 17 00:00:00 2001 From: Michael Olund Date: Tue, 6 Aug 2024 14:42:52 -0700 Subject: [PATCH 01/35] WIP CMS-33: New camping section layout (#1397) * CMS-33: New camping section layout * CMS-33: Turn h3 into h4 and turn h4 into h5 * CMS-33: Add pluralName * CMS-33: Fix build error * CMS-33: Update camping accordion styling (#1393) * CMS-33: Remove

 

from content * CMS-33: Add fallback for missing icons (cherry picked from commit ecb87ff6eb449f5c85cf7f5b82bd4ae4738dd167) * CMS-33: Change Group campgrounds to Groupsites * CMS-33: Remove single accordion toggles from parkdates --------- Co-authored-by: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> --- .../2024.07.29T00.00.18.remove-h3.js | 35 ++ .../2024.07.29T00.00.19.plural-units.js | 21 ++ .../content-types/camping-type/schema.json | 3 + .../src/components/park/campingDetails.js | 180 +-------- .../src/components/park/parkActivity.js | 2 +- src/gatsby/src/components/park/parkDates.js | 351 ++++-------------- .../src/components/park/parkFacility.js | 12 +- src/gatsby/src/components/park/subArea.js | 117 ++++++ src/gatsby/src/styles/global.scss | 16 + src/gatsby/src/styles/parks.scss | 44 +++ src/gatsby/src/templates/park.js | 162 +++----- src/gatsby/src/templates/site.js | 143 ++++--- src/gatsby/src/utils/subAreaHelper.js | 106 ++++++ 13 files changed, 592 insertions(+), 600 deletions(-) create mode 100644 src/cms/database/migrations/2024.07.29T00.00.18.remove-h3.js create mode 100644 src/cms/database/migrations/2024.07.29T00.00.19.plural-units.js create mode 100644 src/gatsby/src/components/park/subArea.js create mode 100644 src/gatsby/src/utils/subAreaHelper.js diff --git a/src/cms/database/migrations/2024.07.29T00.00.18.remove-h3.js b/src/cms/database/migrations/2024.07.29T00.00.18.remove-h3.js new file mode 100644 index 000000000..cb8225650 --- /dev/null +++ b/src/cms/database/migrations/2024.07.29T00.00.18.remove-h3.js @@ -0,0 +1,35 @@ +'use strict' + +async function up(knex) { + + // turn h3 into h4 and turn h4 into h5 + if (await knex.schema.hasTable('park_camping_types')) { + await knex.raw(`update park_camping_types set description = replace(description, ' 

','') where description like '%

 

%'`); + await knex.raw(`update park_facilities set description = replace(description,'

 

','') where description like '%

 

%'`); + await knex.raw(`update park_activities set description = replace(description,'

 

','') where description like '%

 

%'`); + } +} + +module.exports = { up }; diff --git a/src/cms/database/migrations/2024.07.29T00.00.19.plural-units.js b/src/cms/database/migrations/2024.07.29T00.00.19.plural-units.js new file mode 100644 index 000000000..e875acad5 --- /dev/null +++ b/src/cms/database/migrations/2024.07.29T00.00.19.plural-units.js @@ -0,0 +1,21 @@ +'use strict' + +async function up(knex) { + + if (await knex.schema.hasColumn('camping_types', 'plural_name')) { + await knex.raw(`update camping_types set plural_name = 'Yurts' where camping_type_code = 'yurt'`); + await knex.raw(`update camping_types set plural_name = 'Winter campgrounds' where camping_type_code = 'winter-camping'`); + await knex.raw(`update camping_types set plural_name = 'Backcountry areas' where camping_type_code = 'backcountry-camping'`); + await knex.raw(`update camping_types set plural_name = 'Boat-accessible campgrounds' where camping_type_code = 'boat-camping'`); + await knex.raw(`update camping_types set plural_name = 'Cabins and huts' where camping_type_code = 'cabins-huts'`); + await knex.raw(`update camping_types set plural_name = 'Frontcountry campgrounds' where camping_type_code = 'frontcountry-camping'`); + await knex.raw(`update camping_types set plural_name = 'Groupsites' where camping_type_code = 'group-camping'`); + await knex.raw(`update camping_types set plural_name = 'Huts' where camping_type_code = 'hut'`); + await knex.raw(`update camping_types set plural_name = 'Marine-accessible campgrounds' where camping_type_code = 'marine-accessible-camping'`); + await knex.raw(`update camping_types set plural_name = 'RV-accessible campgrounds' where camping_type_code = 'rv'`); + await knex.raw(`update camping_types set plural_name = 'Walk-in campgrounds' where camping_type_code = 'walk-in-camping'`); + await knex.raw(`update camping_types set plural_name = 'Wilderness areas' where camping_type_code = 'wilderness-camping'`); + } +} + +module.exports = { up }; diff --git a/src/cms/src/api/camping-type/content-types/camping-type/schema.json b/src/cms/src/api/camping-type/content-types/camping-type/schema.json index 29b9c15d3..49439d7d4 100644 --- a/src/cms/src/api/camping-type/content-types/camping-type/schema.json +++ b/src/cms/src/api/camping-type/content-types/camping-type/schema.json @@ -31,6 +31,9 @@ "note": { "type": "string" }, + "pluralName": { + "type": "string" + }, "isActive": { "type": "boolean" }, diff --git a/src/gatsby/src/components/park/campingDetails.js b/src/gatsby/src/components/park/campingDetails.js index 775eef1b1..68160af8a 100644 --- a/src/gatsby/src/components/park/campingDetails.js +++ b/src/gatsby/src/components/park/campingDetails.js @@ -1,130 +1,41 @@ -import React, { useState, useEffect } from "react" +import React from "react" import { navigate } from "gatsby" -import Accordion from "react-bootstrap/Accordion" import Row from "react-bootstrap/Row" import Col from "react-bootstrap/Col" -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" -import { faChevronUp, faChevronDown } from "@fortawesome/free-solid-svg-icons" +import ParkDates from "./parkDates" import HtmlContent from "./htmlContent" import StaticIcon from "./staticIcon" -import { countsList } from "../../utils/constants" import { isNullOrWhiteSpace } from "../../utils/helpers" import "../../styles/cmsSnippets/parkInfoPage.scss" -export const AccordionList = ({ eventKey, camping, open, hasReservation, reservations }) => { - const [isShow, setIsShow] = useState(false) - - useEffect(() => { - setIsShow(open) - }, [open]) - +export const CampingType = ({ camping }) => { return ( - hasReservation ? ( - - setIsShow(!isShow)} - > -
-
- -
- Reservations -
-
-
- {isShow ? - : - } -
-
-
- -
- {reservations} -
-
-
- ) : ( - - setIsShow(!isShow)} - > -
-
- - - {camping?.campingType?.campingTypeName} - -
-
- {isShow ? - : - } -
-
-
- -
- - {!isNullOrWhiteSpace(camping.description.data) ? - camping.description.data : (camping?.campingType?.defaultDescription.data) - } - -
-
-
- ) + <> +
+ +

+ {camping?.campingType?.campingTypeName} +

+
+ + {!isNullOrWhiteSpace(camping.description?.data) ? + camping.description.data : (camping?.campingType?.defaultDescription.data) + } + + + ) } export default function CampingDetails({ data }) { const activeCampings = data.activeCampings const parkOperation = data.parkOperation - const reservations = data.reservations.data.reservations const subAreas = data.subAreas || [] subAreas.sort((a, b) => (a.parkSubArea >= b.parkSubArea ? 1 : -1)) - const [open, setOpen] = useState(false) - - useEffect(() => { - if (activeCampings.length === 1) { - setOpen(true) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [activeCampings.length]) if (activeCampings.length === 0) return null - const isShown = (count, countGroup) => { - return countGroup[count.countVar] && - countGroup[count.countVar] !== "0" && - countGroup[count.countVar] !== "*" && - count.isActive; - } - - const checkCountDisplay = (text) => { - const newText = text.replace("rv-accessible", "RV-accessible") - return newText - } - const toFrontCountryReservations = () => { const reservationsURL = "https://camping.bcparks.ca" const parkReservationsURL = parkOperation?.reservationUrl || reservationsURL @@ -152,68 +63,13 @@ export default function CampingDetails({ data }) { )} - {parkOperation && - - -
- {countsList - .filter( - count => - isShown(count, parkOperation)).length > 0 - && subAreas - .filter(subArea => subArea.isActive).length !== 1 - && (<> -
Total number of campsites
- {countsList - .filter(count => isShown(count, parkOperation)) - .map((count, index) => ( -
- Total {checkCountDisplay(count.display.toLowerCase())}:{" "} - {parkOperation[count.countVar]} -
- ))} - - )} -
- -
- } - {activeCampings.length > 1 && ( - - )} - {activeCampings.length > 0 && - !isNullOrWhiteSpace(reservations) && ( - - ) - } {activeCampings.map((camping, index) => ( - ))} diff --git a/src/gatsby/src/components/park/parkActivity.js b/src/gatsby/src/components/park/parkActivity.js index 4f4004d99..69fbaa401 100644 --- a/src/gatsby/src/components/park/parkActivity.js +++ b/src/gatsby/src/components/park/parkActivity.js @@ -34,7 +34,7 @@ export const AccordionList = ({ eventKey, activity, open }) => { className="d-flex justify-content-between accordion-toggle" >
- + {activity.activityType.activityName} diff --git a/src/gatsby/src/components/park/parkDates.js b/src/gatsby/src/components/park/parkDates.js index cd59af774..f723f3ca6 100644 --- a/src/gatsby/src/components/park/parkDates.js +++ b/src/gatsby/src/components/park/parkDates.js @@ -1,5 +1,4 @@ import React, { useState, useEffect } from "react" -import { Link as GatsbyLink } from "gatsby" import Accordion from "react-bootstrap/Accordion" import Row from "react-bootstrap/Row" import Col from "react-bootstrap/Col" @@ -7,12 +6,9 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" import { faChevronUp, faChevronDown } from "@fortawesome/free-solid-svg-icons" import HtmlContent from "./htmlContent" -import HTMLArea from "../HTMLArea" -import StaticIcon from "./staticIcon" -import { countsList } from "../../utils/constants" -import { datePhrase, processDateRanges, groupSubAreaDates } from "../../utils/parkDatesHelper" +import SubArea from "./subArea" -export const AccordionList = ({ eventKey, subArea, open, isShown, subAreasNotesList }) => { +export const AccordionList = ({ eventKey, subArea, open, itemCount }) => { const [isShow, setIsShow] = useState(false) useEffect(() => { @@ -22,205 +18,53 @@ export const AccordionList = ({ eventKey, subArea, open, isShown, subAreasNotesL return ( - setIsShow(!isShow)} - > -
-
- + {itemCount > 1 ? + ( setIsShow(!isShow)} + > +
+
+ + {subArea.parkSubArea} + +
+
+ {isShow ? + : + } +
+
+
) : + ( +
{subArea.parkSubArea}
-
- {isShow ? - : - } -
-
- + ) + } -
-
- {subArea.typeName && ( - <> -
Facility type
-
{subArea.typeName}
- - )} - {countsList - .filter(count => isShown(count, subArea)).length > 0 && ( - <> -
Number of campsites
-
-
    - {countsList - .filter(count => isShown(count, subArea)) - .map((count, index) => ( -
  • - {count.display}:{" "} - {subArea[count.countVar]} -
  • - ))} -
-
- - )} - {subArea.serviceDates.length > 0 && ( - <> -
- Main operating season -
-
-
    - {subArea.serviceDates.map((dateRange, index) => -
  • {dateRange}
  • - )} -
-
- - )} - <> -
Winter season
-
- {subArea.offSeasonDates.length > 0 ? ( -
    - {subArea.offSeasonDates.map((dateRange, index) => -
  • {dateRange}
  • - )} -
- ) : ( - subArea.operationDates.length > 0 ? ( - <> - {subArea.operationDates[0].includes("Year-round") ? "Limited services" : "No services"} - - ) : ( - <>Not known - ) - )} -
- - {subArea.resDates.length > 0 && ( - <> -
Booking required
-
-
    - {subArea.resDates.map((dateRange, index) => -
  • {dateRange}
  • - )} -
-
- - )} - {subAreasNotesList - .filter(note => subArea[note.noteVar]) - .map((note, index) => ( -
- {note.display && ( -
- {note.display} -
- )} -
- - {subArea[note.noteVar]} - -
-
- ))} -
-
+
) } export default function ParkDates({ data }) { - const dataCopy = JSON.parse(JSON.stringify(data)) // deep copy - const parkOperation = dataCopy.parkOperation || {} - const parkType = dataCopy.parkType - const subAreas = dataCopy.subAreas || [] - subAreas.sort((a, b) => (a.parkSubArea >= b.parkSubArea ? 1 : -1)) - + const subAreas = data.subAreas.sort((a, b) => (a.parkSubArea >= b.parkSubArea ? 1 : -1)) const [open, setOpen] = useState(false) - // Operations record is required, even if subarea records are present - // If no operations record, show "not available" message - const hasOperations = parkOperation.isActive // either false, or whole record missing - - // -------- Operating Dates -------- - - const thisYear = new Date().getFullYear() - - // Overall operating dates for parks, to display above subareas - let fmt = "MMMM D, yyyy" // date format for overall operating dates - const yr = "year-round" // lowercase for overall operating dates - const parkOperationDates = dataCopy.parkOperationDates.find(d => d.operatingYear === +thisYear) || {} - let parkDates = datePhrase(parkOperationDates.gateOpenDate, parkOperationDates.gateCloseDate, fmt, yr, " to ", "") - - // make sure the parkDates is valid - if (parkDates !== yr && !parkDates.includes(thisYear)) { - parkDates = ""; - } - - // ---- Subarea Dates ----- - fmt = "MMMM D" - - for (let idx in subAreas) { - let subArea = subAreas[idx] - - if (subArea.isActive) { - - const facilityType = subArea.parkSubAreaType?.facilityType || {} - const campingType = subArea.parkSubAreaType?.campingType || {} - subArea.typeIcon = facilityType.icon || campingType.icon || ""; - subArea.typeName = facilityType.facilityName || campingType.campingTypeName || "" - subArea = groupSubAreaDates(subArea); - - // get distinct date ranges sorted chronologically - subArea.operationDates = processDateRanges(subArea.operationDates, fmt, yr, " to ") - subArea.serviceDates = processDateRanges(subArea.serviceDates, fmt, yr, " to ") - subArea.resDates = processDateRanges(subArea.resDates, fmt, yr, " to ") - subArea.offSeasonDates = processDateRanges(subArea.offSeasonDates, fmt, yr, " to ") - - // add a placeholder if no dates are available for the current year - if (subArea.serviceDates.length === 0 - && subArea.resDates.length === 0 - && subArea.offSeasonDates.length === 0) { - subArea.serviceDates.push(`${new Date().getFullYear()}: Dates unavailable`) - } + useEffect(() => { + if (subAreas.length === 1) { + setOpen(true) } - } - - // -------- Operating Notes ---------- - - // Use this to configure which notes show below the subareas, and within each subarea - // and in what order. Note that "openNote" appears separately above subareas - const parkOperationsNotesList = [ - { noteVar: "generalNote", display: "Note" }, - { noteVar: "serviceNote", display: "Service note" }, - { noteVar: "reservationsNote", display: "Booking note" }, - { noteVar: "offSeasonNote", display: "Winter season note" }, - ] - - const subAreasNotesList = [ - { noteVar: "generalNote", display: "Note" }, - { noteVar: "serviceNote", display: "Service note" }, - { noteVar: "reservationNote", display: "Booking note" }, - { noteVar: "offSeasonNote", display: "Winter season note" }, - ] - - const isShown = (count, countGroup) => { - return countGroup[count.countVar] && - countGroup[count.countVar] !== "0" && - countGroup[count.countVar] !== "*" && - count.isActive; - } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [subAreas.length]) useEffect(() => { if (subAreas.length === 1) { @@ -230,90 +74,49 @@ export default function ParkDates({ data }) { }, [subAreas.length]) return ( -
-

Dates of operation

- - - {!hasOperations && ( -
- No dates available -
- )} - {hasOperations && ( - <> -
-
- Check advisories before visiting. - Dates may change without notice. -
- {parkDates && ( -

- The {parkType} {parkOperation.hasParkGate !== false && "gate"} is open {parkDates} -

- )} - {!parkDates && ( -

Operating dates are unavailable

- )} - {parkOperation.openNote && ( -
- - {parkOperation.openNote} - -
- )} -
- {subAreas.length > 1 && ( - - )} - {subAreas - .filter(subArea => subArea.isActive) - .map((subArea, index) => ( - - ))} - - )} -
- {parkOperationsNotesList - .filter(note => parkOperation[note.noteVar]) - .map((note, index) => ( -
- {note.display && ( -
- {note.display} -
- )} -
- - {parkOperation[note.noteVar]} - -
-
- ))} -
- -
-
+ + )} + {subAreas + .filter(subArea => subArea.isActive) + .map((subArea, index) => ( + + ))} + + + + + )} + ) } diff --git a/src/gatsby/src/components/park/parkFacility.js b/src/gatsby/src/components/park/parkFacility.js index 30b4294cd..acc2b9e70 100644 --- a/src/gatsby/src/components/park/parkFacility.js +++ b/src/gatsby/src/components/park/parkFacility.js @@ -9,6 +9,7 @@ import HtmlContent from "./htmlContent" import StaticIcon from "./staticIcon" import { isNullOrWhiteSpace } from "../../utils/helpers" import "../../styles/cmsSnippets/parkInfoPage.scss" +import SubArea from "./subArea" export const AccordionList = ({ eventKey, facility, open }) => { const [isShow, setIsShow] = useState(false); @@ -33,7 +34,7 @@ export const AccordionList = ({ eventKey, facility, open }) => { className="d-flex justify-content-between accordion-toggle" >
- + {facility.facilityType.facilityName} @@ -46,14 +47,18 @@ export const AccordionList = ({ eventKey, facility, open }) => {
+ <> + {facility.subAreas.map((subArea) => ( + + ))}
- {!isNullOrWhiteSpace(facility.description.data) ? + {!isNullOrWhiteSpace(facility.description?.data) ? facility.description.data : facility.facilityType.defaultDescription.data } {!facility.hideStandardCallout && - !isNullOrWhiteSpace(facility.facilityType.appendStandardCalloutText.data) && ( + !isNullOrWhiteSpace(facility.facilityType?.appendStandardCalloutText?.data) && (
{facility.facilityType.appendStandardCalloutText.data} @@ -61,6 +66,7 @@ export const AccordionList = ({ eventKey, facility, open }) => {
)}
+
) diff --git a/src/gatsby/src/components/park/subArea.js b/src/gatsby/src/components/park/subArea.js new file mode 100644 index 000000000..070d0b607 --- /dev/null +++ b/src/gatsby/src/components/park/subArea.js @@ -0,0 +1,117 @@ +import React from "react" +import Row from "react-bootstrap/Row" +import Col from "react-bootstrap/Col" +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" +import { faCalendar } from "@fortawesome/free-regular-svg-icons" + +import HTMLArea from "../HTMLArea" +import { countsList } from "../../utils/constants" + +export default function SubArea({ data, showHeading }) { + + const subAreasNotesList = [ + { noteVar: "generalNote", display: "Note" }, + { noteVar: "serviceNote", display: "Service note" }, + { noteVar: "reservationNote", display: "Booking note" }, + { noteVar: "offSeasonNote", display: "Winter season note" }, + ] + + const isShown = (count, countGroup) => { + return countGroup[count.countVar] && + countGroup[count.countVar] !== "0" && + countGroup[count.countVar] !== "*" && + count.isActive; + } + + return ( +
+ {showHeading && (

{data.parkSubArea}

)} + + +
+ +
+
+ {data.serviceDates.length > 0 && ( +
+

Operating season

+
    + {data.serviceDates.map((dateRange, index) => +
  • {dateRange}
  • + )} +
+
+ )} + {data.resDates.length > 0 && ( +
+

Booking required

+
    + {data.resDates.map((dateRange, index) => +
  • {dateRange}
  • + )} +
+
+ )} +
+

Winter season

+ {data.offSeasonDates.length > 0 ? ( +
    + {data.offSeasonDates.map((dateRange, index) => +
  • {dateRange}
  • + )} +
+ ) : ( + data.operationDates.length > 0 ? ( + <> + {data.operationDates[0].includes("Year-round") ? "Limited services" : "No services"} + + ) : ( + <>Not known + ) + )} +
+
+ + {countsList + .filter(count => isShown(count, data)).length > 0 && ( + +
+ +
+
+

Number of campsites

+
    + {countsList + .filter(count => isShown(count, data)) + .map((count, index) => ( +
  • + {count.display}:{" "} + {data[count.countVar]} +
  • + ))} +
+
+ + )} +
+ {subAreasNotesList + .filter(note => data[note.noteVar]) + .map((note, index) => ( + + +
+ {note.display && ( +

+ {note.display} +

+ )} + + {data[note.noteVar]} + +
+ +
+ ))} +
+ ) +} \ No newline at end of file diff --git a/src/gatsby/src/styles/global.scss b/src/gatsby/src/styles/global.scss index a45ada7bb..e50ebbb0b 100644 --- a/src/gatsby/src/styles/global.scss +++ b/src/gatsby/src/styles/global.scss @@ -625,4 +625,20 @@ nav.breadcrumbs { height: 24px; } } + &.dates-accordion { + padding: 14px 16px; + &.is-open { + &--true { + padding: 8px 16px; + @media screen and (min-width: $smBreakpoint) { + padding: 14px 16px; + } + } + } + .accordion-toggle { + .accordion-header { + margin-left: 0; + } + } + } } \ No newline at end of file diff --git a/src/gatsby/src/styles/parks.scss b/src/gatsby/src/styles/parks.scss index 86bf6728b..eef614593 100644 --- a/src/gatsby/src/styles/parks.scss +++ b/src/gatsby/src/styles/parks.scss @@ -496,3 +496,47 @@ h2.section-heading { } } } + +.park-camping-type-description { + h3 { + font-size: 1rem; + } +} + +// camping (subarea) +.subarea-container { + &--left, &--right { + display: flex; + .subarea-icon { + padding-right: 16px; + svg { + width: 16px; + height: 16px; + } + } + } + &--left { + .subarea-list { + ul { + list-style: none; + padding: 0; + } + } + } + &--right { + margin-top: 32px; + @media (min-width: $smBreakpoint) { + margin-top: 0; + } + .subarea-list { + ul { + padding-left: 24px; + } + } + } + .subarea-list { + h4 { + margin-bottom: 4px; + } + } +} \ No newline at end of file diff --git a/src/gatsby/src/templates/park.js b/src/gatsby/src/templates/park.js index 72a889abe..1762e523e 100644 --- a/src/gatsby/src/templates/park.js +++ b/src/gatsby/src/templates/park.js @@ -7,6 +7,7 @@ import useScrollSpy from "react-use-scrollspy" import { isNullOrWhiteSpace } from "../utils/helpers"; import { loadAdvisories, WINTER_FULL_PARK_ADVISORY, WINTER_SUB_AREA_ADVISORY } from '../utils/advisoryHelper'; +import { preProcessSubAreas, combineCampingTypes, combineFacilities } from '../utils/subAreaHelper'; import Breadcrumbs from "../components/breadcrumbs" import Footer from "../components/footer" @@ -18,7 +19,6 @@ import CampingDetails from "../components/park/campingDetails" import About from "../components/park/about" import Reconciliation from "../components/park/reconciliation" import ParkActivity from "../components/park/parkActivity" -import ParkDates from "../components/park/parkDates" import ParkFacility from "../components/park/parkFacility" import ParkHeader from "../components/park/parkHeader" import ParkOverview from "../components/park/parkOverview" @@ -66,27 +66,10 @@ export default function ParkTemplate({ data }) { ["activityType.rank", "activityType.activityName"], ["asc"] ) - const activeFacilities = sortBy( - park.parkFacilities.filter( - facility => facility.isActive && facility.facilityType?.isActive - ), - ["facilityType.rank", "facilityType.facilityName"], - ["asc"] - ) - const activeCampings = sortBy( - park.parkCampingTypes.filter( - campingType => campingType.isActive && campingType.campingType?.isActive - ), - ["campingType.rank", "campingType.campingTypeName"], - ["asc"] - ) - const nonCampingActivities = - activeActivities - .sort((a, b) => a.activityType.activityName.localeCompare(b.activityType.activityName)) - const nonCampingFacilities = - activeFacilities - .sort((a, b) => a.facilityType.facilityName.localeCompare(b.facilityType.facilityName)) + const subAreas = preProcessSubAreas(park.parkOperationSubAreas) + const activeFacilities = combineFacilities(park.parkFacilities, data.allStrapiFacilityType.nodes, subAreas); + const activeCampings = combineCampingTypes(park.parkCampingTypes, data.allStrapiCampingType.nodes, subAreas); const hasReservations = operations.hasReservations const hasDayUsePass = operations.hasDayUsePass @@ -198,43 +181,36 @@ export default function ParkTemplate({ data }) { }, { sectionIndex: 3, - display: "Dates of operation", - // leave this link as is since the section won't exist in the future - link: "#park-dates-container", - visible: park.parkOperation, - }, - { - sectionIndex: 4, display: "Camping", link: "#camping", visible: activeCampings.length > 0, }, { - sectionIndex: 5, + sectionIndex: 4, display: "Things to do", link: "#things-to-do", - visible: nonCampingActivities.length > 0, + visible: activeActivities.length > 0, }, { - sectionIndex: 6, + sectionIndex: 5, display: "Facilities", link: "#facilities", - visible: nonCampingFacilities.length > 0, + visible: activeFacilities.length > 0, }, { - sectionIndex: 7, + sectionIndex: 6, display: `About this ${parkType}`, link: "#about-this-park", visible: !isNullOrWhiteSpace(natureAndCulture), }, { - sectionIndex: 8, + sectionIndex: 7, display: "Reconciliation with Indigenous Peoples", link: "#reconciliation", visible: !isNullOrWhiteSpace(reconciliationNotes), }, { - sectionIndex: 9, + sectionIndex: 8, display: `Contact`, link: "#contact", visible: !isNullOrWhiteSpace(contact) @@ -303,7 +279,7 @@ export default function ParkTemplate({ data }) { latitude={park.latitude} longitude={park.longitude} campings={activeCampings} - facilities={nonCampingFacilities} + facilities={activeFacilities} hasCampfireBan={hasCampfireBan} hasDayUsePass={hasDayUsePass} hasReservations={hasReservations} @@ -398,7 +374,7 @@ export default function ParkTemplate({ data }) { @@ -415,20 +391,6 @@ export default function ParkTemplate({ data }) {
)} {menuItems[3].visible && ( -
- -
- )} - {menuItems[4].visible && (
)} - {menuItems[5].visible && ( + {menuItems[4].visible && (
)} - {menuItems[6].visible && ( + {menuItems[5].visible && (
- +
)} - {menuItems[7].visible && ( + {menuItems[6].visible && (
)} - {menuItems[8].visible && ( + {menuItems[7].visible && (
)} - {menuItems[9].visible && ( + {menuItems[8].visible && (
@@ -608,18 +570,7 @@ export const query = graphql` data } facilityType { - facilityName facilityCode - isActive - icon - iconNA - rank - defaultDescription { - data - } - appendStandardCalloutText { - data - } } } parkCampingTypes { @@ -630,18 +581,7 @@ export const query = graphql` data } campingType { - campingTypeName campingTypeCode - isActive - icon - iconNA - rank - defaultDescription { - data - } - appendStandardCalloutText { - data - } } } parkGuidelines { @@ -676,31 +616,6 @@ export const query = graphql` dayUsePassUrl hasParkGate offSeasonUse - totalCapacity - frontcountrySites - reservableSites - nonReservableSites - vehicleSites - vehicleSitesReservable - doubleSites - pullThroughSites - rvSites - rvSitesReservable - electrifiedSites - longStaySites - walkInSites - walkInSitesReservable - groupSites - groupSitesReservable - backcountrySites - wildernessSites - boatAccessSites - horseSites - cabins - huts - yurts - shelters - boatLaunches openNote serviceNote reservationsNote @@ -784,12 +699,10 @@ export const query = graphql` subAreaTypeCode closureAffectsAccessStatus facilityType { - facilityName - icon + facilityCode } campingType { - campingTypeName - icon + campingTypeCode } } } @@ -895,6 +808,37 @@ export const query = graphql` } } } + allStrapiCampingType { + nodes { + appendStandardCalloutText { + data + } + defaultDescription { + data + } + campingTypeCode + campingTypeName + icon + isActive + rank + pluralName + } + } + allStrapiFacilityType { + nodes { + appendStandardCalloutText { + data + } + defaultDescription { + data + } + facilityCode + facilityName + icon + isActive + rank + } + } allStrapiMenu( sort: {order: ASC}, filter: {show: {eq: true}} diff --git a/src/gatsby/src/templates/site.js b/src/gatsby/src/templates/site.js index 25b599430..e5de5af20 100644 --- a/src/gatsby/src/templates/site.js +++ b/src/gatsby/src/templates/site.js @@ -7,6 +7,7 @@ import useScrollSpy from "react-use-scrollspy" import { isNullOrWhiteSpace } from "../utils/helpers"; import { loadAdvisories } from '../utils/advisoryHelper'; +import { preProcessSubAreas, combineCampingTypes, combineFacilities } from '../utils/subAreaHelper'; import Breadcrumbs from "../components/breadcrumbs" import Footer from "../components/footer" @@ -32,9 +33,6 @@ export default function SiteTemplate({ data }) { const site = data.strapiSite const park = site.protectedArea - const activities = site.parkActivities - const facilities = site.parkFacilities - const campingTypes = site.parkCampingTypes const operations = site.parkOperation || {} const photos = [...data.featuredPhotos.nodes, ...data.regularPhotos.nodes] @@ -45,33 +43,16 @@ export default function SiteTemplate({ data }) { const searchArea = managementAreas[0]?.searchArea || {} const activeActivities = sortBy( - activities.filter( + site.parkActivities.filter( activity => activity.isActive && activity.activityType?.isActive ), ["activityType.rank", "activityType.activityName"], ["asc"] ) - const activeFacilities = sortBy( - facilities.filter( - facility => facility.isActive && facility.facilityType?.isActive - ), - ["facilityType.rank", "facilityType.facilityName"], - ["asc"] - ) - const activeCampings = sortBy( - campingTypes.filter( - campingType => campingType.isActive && campingType.campingType?.isActive - ), - ["campingType.rank", "campingType.campingTypeName"], - ["asc"] - ) - const nonCampingActivities = - activeActivities - .sort((a, b) => a.activityType.activityName.localeCompare(b.activityType.activityName)) - const nonCampingFacilities = - activeFacilities - .sort((a, b) => a.facilityType.facilityName.localeCompare(b.facilityType.facilityName)) + const subAreas = preProcessSubAreas(site.protectedArea.parkOperationSubAreas.filter(sa => sa.orcsSiteNumber === site.orcsSiteNumber)) + const activeFacilities = combineFacilities(site.parkFacilities, data.allStrapiFacilityType.nodes, subAreas); + const activeCampings = combineCampingTypes(site.parkCampingTypes, data.allStrapiCampingType.nodes, subAreas); const hasReservations = operations.hasReservations const hasDayUsePass = operations.hasDayUsePass @@ -177,13 +158,13 @@ export default function SiteTemplate({ data }) { sectionIndex: 4, display: "Things to do", link: "#things-to-do", - visible: nonCampingActivities.length > 0, + visible: activeActivities.length > 0, }, { sectionIndex: 5, display: "Facilities", link: "#facilities", - visible: nonCampingFacilities.length > 0, + visible: activeFacilities.length > 0, }, ] @@ -232,7 +213,7 @@ export default function SiteTemplate({ data }) { latitude={site.latitude} longitude={site.longitude} campings={activeCampings} - facilities={nonCampingFacilities} + facilities={activeFacilities} hasCampfireBan={hasCampfireBan} hasDayUsePass={hasDayUsePass} hasReservations={hasReservations} @@ -325,12 +306,12 @@ export default function SiteTemplate({ data }) { )} {menuItems[4].visible && (
- +
)} {menuItems[5].visible && (
- +
)}
@@ -404,18 +385,69 @@ export const query = graphql` gateCloseDate } parkOperationSubAreas { + parkSubArea orcsSiteNumber isActive isOpen + hasReservations + hasBackcountryReservations + hasBackcountryPermits + hasFirstComeFirstServed + parkAccessUnitId + isCleanAirSite + totalCapacity + frontcountrySites + reservableSites + nonReservableSites + vehicleSites + vehicleSitesReservable + doubleSites + pullThroughSites + rvSites + rvSitesReservable + electrifiedSites + longStaySites + walkInSites + walkInSitesReservable + groupSites + groupSitesReservable + backcountrySites + wildernessSites + boatAccessSites + horseSites + cabins + huts + yurts + shelters + boatLaunches + openNote + serviceNote + reservationNote + offSeasonNote + adminNote closureAffectsAccessStatus parkOperationSubAreaDates { isActive operatingYear openDate closeDate + serviceStartDate + serviceEndDate + reservationStartDate + reservationEndDate + offSeasonStartDate + offSeasonEndDate } parkSubAreaType { + subAreaType + subAreaTypeCode closureAffectsAccessStatus + facilityType { + facilityCode + } + campingType { + campingTypeCode + } } } managementAreas { @@ -454,18 +486,7 @@ export const query = graphql` data } facilityType { - facilityName facilityCode - isActive - icon - iconNA - rank - defaultDescription { - data - } - appendStandardCalloutText { - data - } } } parkCampingTypes { @@ -476,18 +497,7 @@ export const query = graphql` data } campingType { - campingTypeName campingTypeCode - isActive - icon - iconNA - rank - defaultDescription { - data - } - appendStandardCalloutText { - data - } } } parkOperation { @@ -537,6 +547,37 @@ export const query = graphql` } } } + allStrapiCampingType { + nodes { + appendStandardCalloutText { + data + } + defaultDescription { + data + } + campingTypeCode + campingTypeName + icon + isActive + rank + pluralName + } + } + allStrapiFacilityType { + nodes { + appendStandardCalloutText { + data + } + defaultDescription { + data + } + facilityCode + facilityName + icon + isActive + rank + } + } allStrapiMenu( sort: {order: ASC}, filter: {show: {eq: true}} diff --git a/src/gatsby/src/utils/subAreaHelper.js b/src/gatsby/src/utils/subAreaHelper.js new file mode 100644 index 000000000..3abbacf24 --- /dev/null +++ b/src/gatsby/src/utils/subAreaHelper.js @@ -0,0 +1,106 @@ +import { processDateRanges, groupSubAreaDates } from "./parkDatesHelper" + + +const preProcessSubAreas = (subAreas) => { + const fmt = "MMMM D" // date format for overall operating dates + const yr = "year-round" // lowercase for overall operating dates + for (let idx in subAreas) { + let subArea = subAreas[idx] + + if (subArea.isActive) { + + const facilityType = subArea.parkSubAreaType?.facilityType || {} + const campingType = subArea.parkSubAreaType?.campingType || {} + subArea.typeCode = facilityType.facilityCode || campingType.campingTypeCode || "" + subArea = groupSubAreaDates(subArea); + + // get distinct date ranges sorted chronologically + subArea.operationDates = processDateRanges(subArea.operationDates, fmt, yr, " to ") + subArea.serviceDates = processDateRanges(subArea.serviceDates, fmt, yr, " to ") + subArea.resDates = processDateRanges(subArea.resDates, fmt, yr, " to ") + subArea.offSeasonDates = processDateRanges(subArea.offSeasonDates, fmt, yr, " to ") + + // add a placeholder if no dates are available for the current year + if (subArea.serviceDates.length === 0 + && subArea.resDates.length === 0 + && subArea.offSeasonDates.length === 0) { + subArea.serviceDates.push(`${new Date().getFullYear()}: Dates unavailable`) + } + } + } + + // add the subareas to a common object + let result = {}; + for (const subArea of subAreas) { + const campingTypeCode = subArea.typeCode; + if (!result[campingTypeCode]) { + result[campingTypeCode] = { subAreas: [] }; + } + result[campingTypeCode].subAreas.push(subArea); + } + return result; +} + +const combineCampingTypes = (parkCampingTypes, campingTypes, subAreas) => { + let arr = []; + let obj = subAreas; + + // add the parkCampingTypes to the common object + for (const parkCampingType of parkCampingTypes) { + const campingTypeCode = parkCampingType.campingType?.campingTypeCode; + if (!obj[campingTypeCode]) { + obj[campingTypeCode] = { subAreas: [] }; + } + obj[campingTypeCode] = { ...parkCampingType, ...obj[campingTypeCode] }; + } + + // add the campingTypes to the common object and convert it to an array + for (const campingTypeCode in obj) { + const parkCampingType = obj[campingTypeCode]; + parkCampingType.campingType = campingTypes.find(ct => ct.campingTypeCode === campingTypeCode); + // only include camping, not facilities + if (parkCampingType.campingType) { + // the camping type should be active, but we will include it anyway if it has subareas + if (parkCampingType.campingType.isActive || parkCampingType.subAreas.length > 0) { + arr.push(parkCampingType); + } + } + } + + return arr.sort((a, b) => a.campingType.campingTypeName.localeCompare(b.campingType.campingTypeName)) +} + +const combineFacilities = (parkFacilities, facilityTypes, subAreas) => { + let arr = []; + let obj = subAreas; + + // add the parkFacilities to the common object + for (const parkFacility of parkFacilities) { + const facilityCode = parkFacility.facilityType?.facilityCode; + if (!obj[facilityCode]) { + obj[facilityCode] = { subAreas: [] }; + } + obj[facilityCode] = { ...parkFacility, ...obj[facilityCode] }; + } + + // add the facilityTypes to the common object and convert it to an array + for (const facilityCode in obj) { + const parkFacility = obj[facilityCode]; + parkFacility.facilityType = facilityTypes.find(f => f.facilityCode === facilityCode); + // only include facilities, not camping + if (parkFacility.facilityType) { + // the facility type should be active, but we will include it anyway if it has subareas + if (parkFacility.facilityType.isActive || parkFacility.subAreas.length > 0) { + arr.push(parkFacility); + } + } + } + + return arr.sort((a, b) => a.facilityType.facilityName.localeCompare(b.facilityType.facilityName)) +} + +export { + preProcessSubAreas, + combineCampingTypes, + combineFacilities +} \ No newline at end of file From a971040d4b3c75a66da6a7dd824ff8d4f5301e34 Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Fri, 9 Aug 2024 09:05:31 -0700 Subject: [PATCH 02/35] CMS-37: Add park contacts collection (#1375) * CMS-37: Add park contacts collection * CMS-37: Update contact component * CMS-37: Add default contact information * CMS-37: Add facility-operator-contact collection * CMS-37: Update facility-operator-contact collection * CMS-37: Change the fields name * CMS-37: Change the contact link field name * CMS-260: Add a default contact * CMS-37: Update icons in contact section --- .../content-types/park-contact/lifecycles.js | 36 +++++ .../content-types/park-contact/schema.json | 56 +++++++ .../park-contact/controllers/park-contact.js | 9 ++ .../api/park-contact/routes/park-contact.js | 9 ++ .../api/park-contact/services/park-contact.js | 9 ++ .../park-operator-contact/schema.json | 35 +++++ .../controllers/park-operator-contact.js | 9 ++ .../routes/park-operator-contact.js | 9 ++ .../services/park-operator-contact.js | 9 ++ .../content-types/protected-area/schema.json | 6 + src/cms/src/components/contact/link.json | 32 ++++ src/gatsby/gatsby-config.js | 7 +- src/gatsby/gatsby-node.js | 38 +++++ src/gatsby/src/components/park/contact.js | 144 +++++++++++++++++- src/gatsby/src/styles/global.scss | 23 ++- src/gatsby/src/styles/parks.scss | 9 ++ src/gatsby/src/templates/park.js | 37 ++++- .../static/fontAwesomeIcons/envelope.svg | 2 +- .../static/fontAwesomeIcons/facebook.svg | 2 +- .../static/fontAwesomeIcons/instagram.svg | 2 +- src/gatsby/static/fontAwesomeIcons/laptop.svg | 2 +- .../static/fontAwesomeIcons/messages.svg | 2 +- src/gatsby/static/fontAwesomeIcons/phone.svg | 2 +- .../static/fontAwesomeIcons/x-twitter.svg | 2 +- 24 files changed, 474 insertions(+), 17 deletions(-) create mode 100644 src/cms/src/api/park-contact/content-types/park-contact/lifecycles.js create mode 100644 src/cms/src/api/park-contact/content-types/park-contact/schema.json create mode 100644 src/cms/src/api/park-contact/controllers/park-contact.js create mode 100644 src/cms/src/api/park-contact/routes/park-contact.js create mode 100644 src/cms/src/api/park-contact/services/park-contact.js create mode 100644 src/cms/src/api/park-operator-contact/content-types/park-operator-contact/schema.json create mode 100644 src/cms/src/api/park-operator-contact/controllers/park-operator-contact.js create mode 100644 src/cms/src/api/park-operator-contact/routes/park-operator-contact.js create mode 100644 src/cms/src/api/park-operator-contact/services/park-operator-contact.js create mode 100644 src/cms/src/components/contact/link.json diff --git a/src/cms/src/api/park-contact/content-types/park-contact/lifecycles.js b/src/cms/src/api/park-contact/content-types/park-contact/lifecycles.js new file mode 100644 index 000000000..ff60e86b8 --- /dev/null +++ b/src/cms/src/api/park-contact/content-types/park-contact/lifecycles.js @@ -0,0 +1,36 @@ +"use strict"; + +const updateName = async (data, where) => { + if (where) { + const id = where.id + const parkContact = await strapi.entityService.findOne( + "api::park-contact.park-contact", id, { populate: '*' } + ) + const protectedArea = parkContact.protectedArea + const parkOperatorContact = parkContact.parkOperatorContact + + data.name = "" + if (protectedArea) { + data.name = protectedArea.orcs + } + if (parkOperatorContact) { + data.name += ":" + data.name += parkOperatorContact.defaultTitle + } else { + data.name += ":" + data.name += data.title + } + } + return data +}; + +module.exports = { + async beforeCreate(event) { + let { data, where } = event.params; + data = await updateName(data, where); + }, + async beforeUpdate(event) { + let { data, where } = event.params; + data = await updateName(data, where); + }, +}; diff --git a/src/cms/src/api/park-contact/content-types/park-contact/schema.json b/src/cms/src/api/park-contact/content-types/park-contact/schema.json new file mode 100644 index 000000000..9c43107b6 --- /dev/null +++ b/src/cms/src/api/park-contact/content-types/park-contact/schema.json @@ -0,0 +1,56 @@ +{ + "kind": "collectionType", + "collectionName": "park_contacts", + "info": { + "singularName": "park-contact", + "pluralName": "park-contacts", + "displayName": "Park-contact", + "description": "" + }, + "options": { + "draftAndPublish": true + }, + "attributes": { + "rank": { + "type": "integer", + "required": true, + "default": 1, + "min": 1 + }, + "isActive": { + "type": "boolean", + "default": true, + "required": true + }, + "name": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "customField", + "options": { + "preset": "toolbar" + }, + "customField": "plugin::ckeditor5.CKEditor" + }, + "contactInformation": { + "displayName": "Link", + "type": "component", + "repeatable": true, + "component": "contact.link" + }, + "protectedArea": { + "type": "relation", + "relation": "manyToOne", + "target": "api::protected-area.protected-area", + "inversedBy": "parkContacts" + }, + "parkOperatorContact": { + "type": "relation", + "relation": "oneToOne", + "target": "api::park-operator-contact.park-operator-contact" + } + } +} \ No newline at end of file diff --git a/src/cms/src/api/park-contact/controllers/park-contact.js b/src/cms/src/api/park-contact/controllers/park-contact.js new file mode 100644 index 000000000..380a0b4b4 --- /dev/null +++ b/src/cms/src/api/park-contact/controllers/park-contact.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * park-contact controller + */ + +const { createCoreController } = require('@strapi/strapi').factories; + +module.exports = createCoreController('api::park-contact.park-contact'); diff --git a/src/cms/src/api/park-contact/routes/park-contact.js b/src/cms/src/api/park-contact/routes/park-contact.js new file mode 100644 index 000000000..0398e0ff0 --- /dev/null +++ b/src/cms/src/api/park-contact/routes/park-contact.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * park-contact router + */ + +const { createCoreRouter } = require('@strapi/strapi').factories; + +module.exports = createCoreRouter('api::park-contact.park-contact'); diff --git a/src/cms/src/api/park-contact/services/park-contact.js b/src/cms/src/api/park-contact/services/park-contact.js new file mode 100644 index 000000000..bd8da0bd7 --- /dev/null +++ b/src/cms/src/api/park-contact/services/park-contact.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * park-contact service + */ + +const { createCoreService } = require('@strapi/strapi').factories; + +module.exports = createCoreService('api::park-contact.park-contact'); diff --git a/src/cms/src/api/park-operator-contact/content-types/park-operator-contact/schema.json b/src/cms/src/api/park-operator-contact/content-types/park-operator-contact/schema.json new file mode 100644 index 000000000..24e1cb99e --- /dev/null +++ b/src/cms/src/api/park-operator-contact/content-types/park-operator-contact/schema.json @@ -0,0 +1,35 @@ +{ + "kind": "collectionType", + "collectionName": "park_operator_contact", + "info": { + "singularName": "park-operator-contact", + "pluralName": "park-operator-contacts", + "displayName": "Park-operator-contact", + "description": "" + }, + "options": { + "draftAndPublish": true + }, + "attributes": { + "parkOperatorName": { + "type": "string", + "required": true + }, + "defaultTitle": { + "type": "string", + "required": true + }, + "defaultDescription": { + "type": "customField", + "options": { + "preset": "toolbar" + }, + "customField": "plugin::ckeditor5.CKEditor" + }, + "defaultContactInformation": { + "type": "component", + "repeatable": true, + "component": "contact.link" + } + } +} diff --git a/src/cms/src/api/park-operator-contact/controllers/park-operator-contact.js b/src/cms/src/api/park-operator-contact/controllers/park-operator-contact.js new file mode 100644 index 000000000..408ea0057 --- /dev/null +++ b/src/cms/src/api/park-operator-contact/controllers/park-operator-contact.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * park-operator-contact controller + */ + +const { createCoreController } = require('@strapi/strapi').factories; + +module.exports = createCoreController('api::park-operator-contact.park-operator-contact'); diff --git a/src/cms/src/api/park-operator-contact/routes/park-operator-contact.js b/src/cms/src/api/park-operator-contact/routes/park-operator-contact.js new file mode 100644 index 000000000..f551e262e --- /dev/null +++ b/src/cms/src/api/park-operator-contact/routes/park-operator-contact.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * park-operator-contact router + */ + +const { createCoreRouter } = require('@strapi/strapi').factories; + +module.exports = createCoreRouter('api::park-operator-contact.park-operator-contact'); diff --git a/src/cms/src/api/park-operator-contact/services/park-operator-contact.js b/src/cms/src/api/park-operator-contact/services/park-operator-contact.js new file mode 100644 index 000000000..8cbc3da90 --- /dev/null +++ b/src/cms/src/api/park-operator-contact/services/park-operator-contact.js @@ -0,0 +1,9 @@ +'use strict'; + +/** + * park-operator-contact service + */ + +const { createCoreService } = require('@strapi/strapi').factories; + +module.exports = createCoreService('api::park-operator-contact.park-operator-contact'); diff --git a/src/cms/src/api/protected-area/content-types/protected-area/schema.json b/src/cms/src/api/protected-area/content-types/protected-area/schema.json index 54f913723..28a177420 100644 --- a/src/cms/src/api/protected-area/content-types/protected-area/schema.json +++ b/src/cms/src/api/protected-area/content-types/protected-area/schema.json @@ -334,6 +334,12 @@ "relation": "oneToMany", "target": "api::trail-report.trail-report", "mappedBy": "protectedArea" + }, + "parkContacts": { + "type": "relation", + "relation": "oneToMany", + "target": "api::park-contact.park-contact", + "mappedBy": "protectedArea" } } } diff --git a/src/cms/src/components/contact/link.json b/src/cms/src/components/contact/link.json new file mode 100644 index 000000000..db0bfd0a9 --- /dev/null +++ b/src/cms/src/components/contact/link.json @@ -0,0 +1,32 @@ +{ + "collectionName": "components_contact_links", + "info": { + "displayName": "Link", + "icon": "link", + "description": "" + }, + "options": {}, + "attributes": { + "contactType": { + "type": "enumeration", + "enum": [ + "Email", + "Phone", + "Website", + "Chat", + "Facebook", + "Instagram", + "X-twitter" + ], + "required": true + }, + "contactText": { + "type": "string", + "required": true + }, + "contactUrl": { + "type": "string", + "required": true + } + } +} diff --git a/src/gatsby/gatsby-config.js b/src/gatsby/gatsby-config.js index 47a61f13e..357731e40 100644 --- a/src/gatsby/gatsby-config.js +++ b/src/gatsby/gatsby-config.js @@ -48,6 +48,8 @@ module.exports = { "park-operation-sub-area", "park-operation-sub-area-date", "park-operation-sub-area-type", + "park-contact", + "park-operator-contact", "park-photo", "park-sub-page", "public-advisory", @@ -150,8 +152,11 @@ module.exports = { populate: ["isActive", { campingType: ["campingTypeNumber", "campingTypeCode", "isActive"] }] - }, + }, }, + parkContacts: { + populate: ["parkOperatorContact"] + } } }, queryLimit: 100 diff --git a/src/gatsby/gatsby-node.js b/src/gatsby/gatsby-node.js index 603518e6f..84112abd2 100644 --- a/src/gatsby/gatsby-node.js +++ b/src/gatsby/gatsby-node.js @@ -23,6 +23,43 @@ exports.createSchemaCustomization = ({ actions }) => { const { createTypes } = actions const typeDefs = ` + type STRAPI_PARK_OPERATOR_CONTACT_DEFAULTDESCRIPTION_TEXTNODE implements Node @dontInfer { + defaultDescription: String + } + + type STRAPI_PARK_OPERATOR_CONTACTDefaultDescription { + data: STRAPI_PARK_OPERATOR_CONTACT_DEFAULTDESCRIPTION_TEXTNODE @link(by: "id", from: "data___NODE") + } + + type STRAPI_PARK_OPERATOR_CONTACT implements Node @derivedTypes @dontInfer { + facilityOperatorName: String + defaultTitle: String + defaultDescription: STRAPI_PARK_OPERATOR_CONTACTDefaultDescription + defaultContactInformation: [STRAPI__COMPONENT_CONTACT_LINK] @link(by: "id", from: "defaultContactInformation___NODE") + } + type STRAPI_PARK_CONTACT_DESCRIPTION_TEXTNODE implements Node @dontInfer { + description: String + } + + type STRAPI_PARK_CONTACTDescription { + data: STRAPI_PARK_CONTACT_DESCRIPTION_TEXTNODE @link(by: "id", from: "data___NODE") + } + + type STRAPI__COMPONENT_CONTACT_LINK implements Node @dontInfer { + contactType: String + contactText: String + contactUrl: String + } + + type STRAPI_PARK_CONTACT implements Node @dontInfer { + title: String + description: STRAPI_PARK_CONTACTDescription + rank: Int + isActive: Boolean + contactInformation: [STRAPI__COMPONENT_CONTACT_LINK] @link(by: "id", from: "contactInformation___NODE") + parkOperatorContact: STRAPI_PARK_OPERATOR_CONTACT @link(by: "id", from: "parkOperatorContact___NODE") + } + type STRAPI_ACCESS_STATUS implements Node { groupLabel: String } @@ -132,6 +169,7 @@ exports.createSchemaCustomization = ({ actions }) => { hasDiscoverParksLink: Boolean nearbyParks: [STRAPI_PROTECTED_AREA] @link(by: "id", from: "nearbyParks___NODE") trailReports: [STRAPI_TRAIL_REPORT] @link(by: "id", from: "trailReports___NODE") + parkContacts: [STRAPI_PARK_CONTACT] @link(by: "id", from: "parkContacts___NODE") } type STRAPI__COMPONENT_PARKS_RTE_LIST_CONTENT_TEXTNODE implements Node @dontInfer { diff --git a/src/gatsby/src/components/park/contact.js b/src/gatsby/src/components/park/contact.js index 340992fbc..89ac4aa2a 100644 --- a/src/gatsby/src/components/park/contact.js +++ b/src/gatsby/src/components/park/contact.js @@ -1,11 +1,151 @@ import React from "react" +import FontAwesome from "../fontAwesome" import HtmlContent from "./htmlContent" -export default function Contact({ contact }) { +// Helper function to get href prefix based on the contactType +const getPrefix = (contactType) => { + switch (contactType) { + case "Email": + return "mailto:" + case "Phone": + return "tel:" + default: + return "" + } +} +// Helper function to get the icon based on the contactType +const getIcon = (contactType) => { + switch (contactType) { + case "Email": + return "envelope" + case "Phone": + return "phone" + case "Website": + return "laptop" + case "Chat": + return "messages" + case "Facebook": + return "facebook" + case "Instagram": + return "instagram" + case "X-twitter": + return "x-twitter" + default: + return "" + } +} + +export const ParkContact = ({ contact }) => { + const parkOperatorContact = contact.parkOperatorContact + const links = contact.contactInformation.length > 0 ? contact.contactInformation : + parkOperatorContact.defaultContactInformation.length > 0 ? parkOperatorContact.defaultContactInformation : [] + + return ( + + + {contact.title ? contact.title : parkOperatorContact.defaultTitle} + + + {(contact.description.data.description || + parkOperatorContact?.defaultDescription.data.defaultDescription) && + + {contact.description.data.description || + parkOperatorContact.defaultDescription.data.defaultDescription} + + } + {links.length > 0 && links.map((link, index) => ( +

+ + {link.contactUrl !== null ? + + {link.contactText} + + : + link.contactText + } +

+ ))} + + + ) +} + +export default function Contact({ contact, parkContacts, operations }) { + // Filter contacts by isActive and sort by rank + const sortedContacts = + parkContacts.filter(contact => contact.isActive).sort((a, b) => { + return a.rank - b.rank + }) + const hasAnyReservations = + operations.hasCanoeCircuitReservations || + operations.hasPicnicShelterReservations || + operations.hasFrontcountryReservations || + operations.hasFrontcountryCabinReservations || + operations.hasBackcountryPermits || + operations.hasBackcountryReservations || + operations.hasBackcountryGroupReservations || + operations.hasBackcountryShelterReservations || + operations.hasBackcountryWildernessReservations + return (

Contact

- {contact} + {sortedContacts.length > 0 ? ( +
+ + + {/* display it if hasAnyReservations is true */} + {hasAnyReservations && ( + + + + + )} + {sortedContacts.map((contact, index) => ( + + ))} + {/* display it always */} + + + + + +
Reservations, changes, and cancellations +

+ Our call centre is open from 7 am to 7 pm Pacific Time. + There is a $5 fee for reservations, changes, or cancellations made by phone. +

+

+ + 1-800-689-9025 (toll-free from Canada or the US) +

+

+ + 1-519-858-6161 (international) +

+

+ + Contact form +

+

+ + + Live chat + +

+
General questions and feedback for BC Parks +

+ We answer emails weekdays from 9 am to 5 pm Pacific Time. +

+

+ + parkinfo@gov.bc.ca +

+
+
+ ) : ( + {contact} + )}
) } diff --git a/src/gatsby/src/styles/global.scss b/src/gatsby/src/styles/global.scss index e50ebbb0b..b0667606c 100644 --- a/src/gatsby/src/styles/global.scss +++ b/src/gatsby/src/styles/global.scss @@ -120,17 +120,30 @@ figure.media div[data-oembed-url] { img { max-width: 100%; } // table -// This is temp as we should be rendering through CKEditor -// so that tables pick up standard boostrap styles figure.table { + color: $colorGrey; table { width: 100%; + tr:first-of-type { + th, td { + border-top: 1px solid $colorGreyLight; + } + } + th, td { + border-bottom: 1px solid $colorGreyLight; + } th { - background: $colorBackgroundGrey; - border-top: 1px solid $colorGreyLight; + background: $colorBrownLight; } td { - border-top: 1px solid $colorGreyLight; + & > p:last-of-type { + margin-bottom: 0; + } + svg { + width: 16px; + height: 16px; + margin-right: 8px; + } } } diff --git a/src/gatsby/src/styles/parks.scss b/src/gatsby/src/styles/parks.scss index d30312854..20cf41195 100644 --- a/src/gatsby/src/styles/parks.scss +++ b/src/gatsby/src/styles/parks.scss @@ -538,4 +538,13 @@ h2.section-heading { margin-bottom: 4px; } } +} + +// contact +#contact { + img { + width: 16px; + height: 16px; + margin-right: 8px; + } } \ No newline at end of file diff --git a/src/gatsby/src/templates/park.js b/src/gatsby/src/templates/park.js index 1762e523e..42e03ed0c 100644 --- a/src/gatsby/src/templates/park.js +++ b/src/gatsby/src/templates/park.js @@ -213,7 +213,7 @@ export default function ParkTemplate({ data }) { sectionIndex: 8, display: `Contact`, link: "#contact", - visible: !isNullOrWhiteSpace(contact) + visible: park.parkContacts.length > 0 || !isNullOrWhiteSpace(contact) } ] @@ -436,7 +436,11 @@ export default function ParkTemplate({ data }) { )} {menuItems[8].visible && (
- +
)} @@ -765,6 +769,35 @@ export const query = graphql` reportUrl reportDate } + parkContacts { + rank + isActive + title + description { + data { + description + } + } + contactInformation { + contactType + contactText + contactText + } + parkOperatorContact { + facilityOperatorName + defaultTitle + defaultDescription { + data { + defaultDescription + } + } + defaultContactInformation { + contactType + contactText + contactUrl + } + } + } } featuredPhotos: allStrapiParkPhoto( filter: { diff --git a/src/gatsby/static/fontAwesomeIcons/envelope.svg b/src/gatsby/static/fontAwesomeIcons/envelope.svg index 0ae62357c..61a51103c 100644 --- a/src/gatsby/static/fontAwesomeIcons/envelope.svg +++ b/src/gatsby/static/fontAwesomeIcons/envelope.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/gatsby/static/fontAwesomeIcons/facebook.svg b/src/gatsby/static/fontAwesomeIcons/facebook.svg index 1c7d882b3..41acbef6d 100644 --- a/src/gatsby/static/fontAwesomeIcons/facebook.svg +++ b/src/gatsby/static/fontAwesomeIcons/facebook.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/gatsby/static/fontAwesomeIcons/instagram.svg b/src/gatsby/static/fontAwesomeIcons/instagram.svg index 6f1859b1c..86081c05a 100644 --- a/src/gatsby/static/fontAwesomeIcons/instagram.svg +++ b/src/gatsby/static/fontAwesomeIcons/instagram.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/gatsby/static/fontAwesomeIcons/laptop.svg b/src/gatsby/static/fontAwesomeIcons/laptop.svg index e728af050..14b429714 100644 --- a/src/gatsby/static/fontAwesomeIcons/laptop.svg +++ b/src/gatsby/static/fontAwesomeIcons/laptop.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/gatsby/static/fontAwesomeIcons/messages.svg b/src/gatsby/static/fontAwesomeIcons/messages.svg index 42e4d67ca..a0580312b 100644 --- a/src/gatsby/static/fontAwesomeIcons/messages.svg +++ b/src/gatsby/static/fontAwesomeIcons/messages.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/gatsby/static/fontAwesomeIcons/phone.svg b/src/gatsby/static/fontAwesomeIcons/phone.svg index 07c58b5ea..72af9ecbc 100644 --- a/src/gatsby/static/fontAwesomeIcons/phone.svg +++ b/src/gatsby/static/fontAwesomeIcons/phone.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/gatsby/static/fontAwesomeIcons/x-twitter.svg b/src/gatsby/static/fontAwesomeIcons/x-twitter.svg index 15b8fb38e..d385684bb 100644 --- a/src/gatsby/static/fontAwesomeIcons/x-twitter.svg +++ b/src/gatsby/static/fontAwesomeIcons/x-twitter.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From daa33eeea93b8ab5af32c05777a9756f77842508 Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Fri, 9 Aug 2024 09:26:12 -0700 Subject: [PATCH 03/35] CMS-37: Fix gatsby build error (#1412) --- src/gatsby/src/templates/park.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gatsby/src/templates/park.js b/src/gatsby/src/templates/park.js index 42e03ed0c..a60799fda 100644 --- a/src/gatsby/src/templates/park.js +++ b/src/gatsby/src/templates/park.js @@ -213,7 +213,7 @@ export default function ParkTemplate({ data }) { sectionIndex: 8, display: `Contact`, link: "#contact", - visible: park.parkContacts.length > 0 || !isNullOrWhiteSpace(contact) + visible: park.parkContacts?.length > 0 || !isNullOrWhiteSpace(contact) } ] From 552523c08013846d6bcb5f8f66028fb7f8ce8250 Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Fri, 9 Aug 2024 11:23:58 -0700 Subject: [PATCH 04/35] NOBUG: Update campground icon (#1413) --- src/gatsby/src/components/park/subArea.js | 3 ++- src/gatsby/src/styles/parks.scss | 2 +- src/gatsby/static/fontAwesomeIcons/campground.svg | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/gatsby/src/components/park/subArea.js b/src/gatsby/src/components/park/subArea.js index 070d0b607..5480990fa 100644 --- a/src/gatsby/src/components/park/subArea.js +++ b/src/gatsby/src/components/park/subArea.js @@ -5,6 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" import { faCalendar } from "@fortawesome/free-regular-svg-icons" import HTMLArea from "../HTMLArea" +import FontAwesome from "../FontAwesome" import { countsList } from "../../utils/constants" export default function SubArea({ data, showHeading }) { @@ -76,7 +77,7 @@ export default function SubArea({ data, showHeading }) { .filter(count => isShown(count, data)).length > 0 && (
- +

Number of campsites

diff --git a/src/gatsby/src/styles/parks.scss b/src/gatsby/src/styles/parks.scss index 20cf41195..f758e9a34 100644 --- a/src/gatsby/src/styles/parks.scss +++ b/src/gatsby/src/styles/parks.scss @@ -508,7 +508,7 @@ h2.section-heading { display: flex; .subarea-icon { padding-right: 16px; - svg { + svg, img { width: 16px; height: 16px; } diff --git a/src/gatsby/static/fontAwesomeIcons/campground.svg b/src/gatsby/static/fontAwesomeIcons/campground.svg index 36ac506a1..bbbe5c294 100644 --- a/src/gatsby/static/fontAwesomeIcons/campground.svg +++ b/src/gatsby/static/fontAwesomeIcons/campground.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From f46b216db8a6054240d5bc54d091d3bf73fc5980 Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Fri, 9 Aug 2024 14:10:57 -0700 Subject: [PATCH 05/35] NOBUG: Fix typo to fix gatsby build error (#1416) --- src/gatsby/src/components/park/subArea.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gatsby/src/components/park/subArea.js b/src/gatsby/src/components/park/subArea.js index 5480990fa..178f48701 100644 --- a/src/gatsby/src/components/park/subArea.js +++ b/src/gatsby/src/components/park/subArea.js @@ -5,7 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" import { faCalendar } from "@fortawesome/free-regular-svg-icons" import HTMLArea from "../HTMLArea" -import FontAwesome from "../FontAwesome" +import FontAwesome from "../fontAwesome" import { countsList } from "../../utils/constants" export default function SubArea({ data, showHeading }) { From dfb394faaf85566d29bc2977b8e2168a1419f81c Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Mon, 12 Aug 2024 12:21:56 -0700 Subject: [PATCH 06/35] CMS-99: Update error messages for advisory form (#1418) --- .../src/components/composite/advisoryForm/AdvisoryForm.js | 8 ++++---- src/admin/src/validators/AdvisoryValidator.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/admin/src/components/composite/advisoryForm/AdvisoryForm.js b/src/admin/src/components/composite/advisoryForm/AdvisoryForm.js index cab23944e..db3765310 100644 --- a/src/admin/src/components/composite/advisoryForm/AdvisoryForm.js +++ b/src/admin/src/components/composite/advisoryForm/AdvisoryForm.js @@ -593,7 +593,7 @@ export default function AdvisoryForm({ onBlur={() => validateLink(l, idx, "type", setLinkTypeErrors)} /> - {linkTypeErrors[idx] && "Please enter link type too"} + {linkTypeErrors[idx] && "Please provide a link type"}
validateLink(l, idx, "title", setLinkTitleErrors)} />
@@ -646,7 +646,7 @@ export default function AdvisoryForm({ className="bcgov-input" variant="outlined" error={linkUrlErrors[idx]} - helperText={linkUrlErrors[idx] && "Please enter URL too"} + helperText={linkUrlErrors[idx] && "Please provide a URL"} onBlur={() => validateLink(l, idx, "url", setLinkUrlErrors)} inputProps={{ maxLength: 255 }} InputProps={{ @@ -1110,7 +1110,7 @@ export default function AdvisoryForm({ inputProps={{ maxLength: 255 }} InputProps={{ ...submitterInput }} error={submittedByError !== ""} - helperText={submittedByError} + helperText={submittedByError && "Please enter a name"} onBlur={() => { validateRequiredText(advisoryData.submittedBy); }} diff --git a/src/admin/src/validators/AdvisoryValidator.js b/src/admin/src/validators/AdvisoryValidator.js index c31fde914..c9159ab0b 100644 --- a/src/admin/src/validators/AdvisoryValidator.js +++ b/src/admin/src/validators/AdvisoryValidator.js @@ -107,7 +107,7 @@ export function validateLink(link, index, field, setErrors) { export function validateDisplayedDate(field) { const obj = field.value; if ((obj.displayedDateOption === "" || obj.displayedDateOption === "posting") && !obj.advisoryDate) { - field.setError("Please enter a date for 'Advisory date'"); + field.setError("Please choose a date to display"); return false; } else if (obj.displayedDateOption === "start" && !obj.startDate) { field.setError("Please enter a date for 'Start date'"); @@ -167,7 +167,7 @@ export function validAdvisoryData(advisoryData, linksRef, validateStatus, mode) } if (!validData) { advisoryData.formError( - "Please enter valid information in all required fields." + "Please complete required fields" ); } return validData; From ae123ef3907812da9c910a2c409073058d888e7b Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Mon, 12 Aug 2024 18:03:21 -0700 Subject: [PATCH 07/35] CMS-61: Add an icon to mega menu link if it has an external url (#1419) * CMS-61: Add an icon to mega menu link if it has an external url * CMS-61: Add arrow up right from square icon * CMS-61: Adjust spacing * CMS-61: Update regex --- src/gatsby/src/components/megaMenu.js | 15 ++++++++++++--- .../arrow-up-right-from-square.svg | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 src/gatsby/static/fontAwesomeIcons/arrow-up-right-from-square.svg diff --git a/src/gatsby/src/components/megaMenu.js b/src/gatsby/src/components/megaMenu.js index 8ee15eb1e..cbf2320cf 100644 --- a/src/gatsby/src/components/megaMenu.js +++ b/src/gatsby/src/components/megaMenu.js @@ -3,6 +3,7 @@ import PropTypes from "prop-types" import { Link, navigate } from "gatsby" import Logo from "../images/logo/BCParks_Primary_Reversed-cropped.svg" import LogoVertical from "../images/logo/BCParks_Primary_Reversed_Vertical.svg" +import FontAwesome from "../components/fontAwesome" import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" import { faBars, faXmark, faChevronLeft, faChevronRight, faCircleChevronRight } from "@fortawesome/free-solid-svg-icons" @@ -178,6 +179,11 @@ const MegaMenu = ({ content, menuMode }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [menuElements]) + const isExternalUrl = (url) => { + // a URL is considered external if it begins with "http://" or "https://" + return /^https?:\/\//.test(url) + } + useEffect(() => { // create sorted + structured menuTree from menuContent @@ -253,9 +259,10 @@ const MegaMenu = ({ content, menuMode }) => {
  • - {item.url.toLowerCase().startsWith('http') ? + {isExternalUrl(item.url) ? {item.title} + : { : "unselected") } > - {page.url.toLowerCase().startsWith('http') ? + {isExternalUrl(page.url) ? {page.title} + : {
  • )} @@ -133,7 +133,7 @@ const NearbyPark = ({ park }) => { alt="park carousel" className={`${errorStates[index] ? "search-result-logo-image" : "search-result-image"}`} src={errorStates[index] ? parksLogo : addSmallImagePrefix(item.imageUrl)} - onError={() => handleImgError(setErrorStates, index)} + onError={() => handleImgError(setErrorStates, index, addSmallImagePrefix(item.imageUrl))} /> ) diff --git a/src/gatsby/src/components/search/parkCard.js b/src/gatsby/src/components/search/parkCard.js index 73f1fc630..4b0fd7ba4 100644 --- a/src/gatsby/src/components/search/parkCard.js +++ b/src/gatsby/src/components/search/parkCard.js @@ -59,7 +59,7 @@ const ParkCard = ({ r }) => { alt="park" className={`${errorStates[0] ? "search-result-logo-image" : "search-result-image"}`} src={errorStates[0] ? parksLogo : addSmallImagePrefix(r.parkPhotos[0])} - onError={() => handleImgError(setErrorStates, 0)} + onError={() => handleImgError(setErrorStates, 0, addSmallImagePrefix(r.parkPhotos[0]))} /> )} @@ -90,7 +90,7 @@ const ParkCard = ({ r }) => { key={index} className={`${errorStates[index] ? "search-result-logo-image" : "search-result-image"}`} src={errorStates[index] ? parksLogo : addSmallImagePrefix(item)} - onError={() => handleImgError(setErrorStates, index)} + onError={() => handleImgError(setErrorStates, index, addSmallImagePrefix(item))} /> ) @@ -162,7 +162,7 @@ const ParkCard = ({ r }) => { alt="park" className={`${errorStates[0] ? "search-result-logo-image" : "search-result-image"}`} src={errorStates[0] ? parksLogo : addSmallImagePrefix(r.parkPhotos[0])} - onError={() => handleImgError(setErrorStates, 0)} + onError={() => handleImgError(setErrorStates, 0, addSmallImagePrefix(r.parkPhotos[0]))} /> )} @@ -185,7 +185,7 @@ const ParkCard = ({ r }) => { key={index} className={`${errorStates[index] ? "search-result-logo-image" : "search-result-image"}`} src={errorStates[index] ? parksLogo : addSmallImagePrefix(item)} - onError={() => handleImgError(setErrorStates, index)} + onError={() => handleImgError(setErrorStates, index, addSmallImagePrefix(item))} /> ) diff --git a/src/gatsby/src/utils/helpers.js b/src/gatsby/src/utils/helpers.js index 277c70719..c6e2c3c27 100644 --- a/src/gatsby/src/utils/helpers.js +++ b/src/gatsby/src/utils/helpers.js @@ -33,6 +33,7 @@ export const renderBreadcrumbs = (menuContent, pageContext) => { } export const addSmallImagePrefix = (str) => { if (!str) { + console.log("> addSmallImagePrefix: str is null") return parksLogo } let url = str @@ -45,15 +46,19 @@ export const addSmallImagePrefix = (str) => { } } const i = url.lastIndexOf("/") - return url.substring(0, i + 1) + "small_" + url.substring(i + 1, url.length) + const thumbnailUrl = + url.substring(0, i + 1) + "small_" + url.substring(i + 1, url.length) + console.log(`> addSmallImagePrefix: ${str} -> ${thumbnailUrl}`) + return thumbnailUrl } -export const handleImgError = (setErrorStates, index) => { +export const handleImgError = (setErrorStates, index, url) => { setErrorStates(prevErrorStates => { const newErrorStates = [...prevErrorStates] newErrorStates[index] = true + console.log("image error:", url) + return newErrorStates }) - console.clear() } export const useScreenSize = () => { const [screenSize, setScreenSize] = useState({ From a79463de9f3d315a7a45b85a71f4c5b94d033fde Mon Sep 17 00:00:00 2001 From: Duncan MacKenzie Date: Thu, 15 Aug 2024 10:10:35 -0700 Subject: [PATCH 09/35] Revert "CMS-267: Add console debugging for thumbnail images (#1428)" (#1429) This reverts commit 6b39d9e81b37a24e5fa913dcccdf0f5a89c20852. --- src/gatsby/src/components/park/nearbyPark.js | 4 ++-- src/gatsby/src/components/search/parkCard.js | 8 ++++---- src/gatsby/src/utils/helpers.js | 11 +++-------- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/gatsby/src/components/park/nearbyPark.js b/src/gatsby/src/components/park/nearbyPark.js index b8356f52f..968eb4ca3 100644 --- a/src/gatsby/src/components/park/nearbyPark.js +++ b/src/gatsby/src/components/park/nearbyPark.js @@ -103,7 +103,7 @@ const NearbyPark = ({ park }) => { alt="park" className={`${errorStates[0] ? "search-result-logo-image" : "search-result-image"}`} src={errorStates[0] ? parksLogo : addSmallImagePrefix(photos[0].imageUrl)} - onError={() => handleImgError(setErrorStates, 0, addSmallImagePrefix(photos[0].imageUrl))} + onError={() => handleImgError(setErrorStates, 0)} /> )} @@ -133,7 +133,7 @@ const NearbyPark = ({ park }) => { alt="park carousel" className={`${errorStates[index] ? "search-result-logo-image" : "search-result-image"}`} src={errorStates[index] ? parksLogo : addSmallImagePrefix(item.imageUrl)} - onError={() => handleImgError(setErrorStates, index, addSmallImagePrefix(item.imageUrl))} + onError={() => handleImgError(setErrorStates, index)} /> ) diff --git a/src/gatsby/src/components/search/parkCard.js b/src/gatsby/src/components/search/parkCard.js index 4b0fd7ba4..73f1fc630 100644 --- a/src/gatsby/src/components/search/parkCard.js +++ b/src/gatsby/src/components/search/parkCard.js @@ -59,7 +59,7 @@ const ParkCard = ({ r }) => { alt="park" className={`${errorStates[0] ? "search-result-logo-image" : "search-result-image"}`} src={errorStates[0] ? parksLogo : addSmallImagePrefix(r.parkPhotos[0])} - onError={() => handleImgError(setErrorStates, 0, addSmallImagePrefix(r.parkPhotos[0]))} + onError={() => handleImgError(setErrorStates, 0)} /> )} @@ -90,7 +90,7 @@ const ParkCard = ({ r }) => { key={index} className={`${errorStates[index] ? "search-result-logo-image" : "search-result-image"}`} src={errorStates[index] ? parksLogo : addSmallImagePrefix(item)} - onError={() => handleImgError(setErrorStates, index, addSmallImagePrefix(item))} + onError={() => handleImgError(setErrorStates, index)} /> ) @@ -162,7 +162,7 @@ const ParkCard = ({ r }) => { alt="park" className={`${errorStates[0] ? "search-result-logo-image" : "search-result-image"}`} src={errorStates[0] ? parksLogo : addSmallImagePrefix(r.parkPhotos[0])} - onError={() => handleImgError(setErrorStates, 0, addSmallImagePrefix(r.parkPhotos[0]))} + onError={() => handleImgError(setErrorStates, 0)} /> )} @@ -185,7 +185,7 @@ const ParkCard = ({ r }) => { key={index} className={`${errorStates[index] ? "search-result-logo-image" : "search-result-image"}`} src={errorStates[index] ? parksLogo : addSmallImagePrefix(item)} - onError={() => handleImgError(setErrorStates, index, addSmallImagePrefix(item))} + onError={() => handleImgError(setErrorStates, index)} /> ) diff --git a/src/gatsby/src/utils/helpers.js b/src/gatsby/src/utils/helpers.js index c6e2c3c27..277c70719 100644 --- a/src/gatsby/src/utils/helpers.js +++ b/src/gatsby/src/utils/helpers.js @@ -33,7 +33,6 @@ export const renderBreadcrumbs = (menuContent, pageContext) => { } export const addSmallImagePrefix = (str) => { if (!str) { - console.log("> addSmallImagePrefix: str is null") return parksLogo } let url = str @@ -46,19 +45,15 @@ export const addSmallImagePrefix = (str) => { } } const i = url.lastIndexOf("/") - const thumbnailUrl = - url.substring(0, i + 1) + "small_" + url.substring(i + 1, url.length) - console.log(`> addSmallImagePrefix: ${str} -> ${thumbnailUrl}`) - return thumbnailUrl + return url.substring(0, i + 1) + "small_" + url.substring(i + 1, url.length) } -export const handleImgError = (setErrorStates, index, url) => { +export const handleImgError = (setErrorStates, index) => { setErrorStates(prevErrorStates => { const newErrorStates = [...prevErrorStates] newErrorStates[index] = true - console.log("image error:", url) - return newErrorStates }) + console.clear() } export const useScreenSize = () => { const [screenSize, setScreenSize] = useState({ From 8d7819dd60741076c368e0e791033b999d7356ab Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:24:01 -0700 Subject: [PATCH 10/35] CMS-271: Add expand/collapse feature using hr for park camping type description (#1430) --- .../src/components/park/campingDetails.js | 90 ++++++++++++++++--- .../src/styles/cmsSnippets/parkInfoPage.scss | 15 +++- src/gatsby/src/styles/parks.scss | 28 +++++- 3 files changed, 112 insertions(+), 21 deletions(-) diff --git a/src/gatsby/src/components/park/campingDetails.js b/src/gatsby/src/components/park/campingDetails.js index 68160af8a..ccbe21757 100644 --- a/src/gatsby/src/components/park/campingDetails.js +++ b/src/gatsby/src/components/park/campingDetails.js @@ -1,7 +1,10 @@ -import React from "react" +import React, { useState, useEffect, useRef } from "react" import { navigate } from "gatsby" import Row from "react-bootstrap/Row" import Col from "react-bootstrap/Col" +import * as cheerio from "cheerio" +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" +import { faChevronUp, faChevronDown } from "@fortawesome/free-solid-svg-icons" import ParkDates from "./parkDates" import HtmlContent from "./htmlContent" @@ -10,21 +13,80 @@ import { isNullOrWhiteSpace } from "../../utils/helpers" import "../../styles/cmsSnippets/parkInfoPage.scss" export const CampingType = ({ camping }) => { + const [expanded, setExpanded] = useState(false) + const [height, setHeight] = useState(0) + const [sectionHeight, setSectionHeight] = useState(0) + const ref = useRef(null) + const isLong = height > 259 + const campingDescription = !isNullOrWhiteSpace(camping.description?.data) ? + camping.description.data : camping?.campingType?.defaultDescription.data + + const $ = cheerio.load(campingDescription) + $('a').attr('tabindex', '-1') + const collapsedDescription = $.html() + const hasHr = $('hr').length > 0 + + useEffect(() => { + setHeight(ref.current.clientHeight) + }, [expanded]) + + useEffect(() => { + if (ref.current) { + const h3 = ref.current.querySelector('h3.park-camping-title'); + const hr = ref.current.querySelector('hr'); + if (h3 && hr) { + // height from the

    to the
    + const height = hr?.getBoundingClientRect().top - h3.getBoundingClientRect().top + setSectionHeight(height) + } + } + }, []) + return ( - <> -
    - -

    - {camping?.campingType?.campingTypeName} -

    +
    +
    +
    + +

    + {camping?.campingType?.campingTypeName} +

    +
    + + {expanded ? campingDescription : collapsedDescription} + +
    - - {!isNullOrWhiteSpace(camping.description?.data) ? - camping.description.data : (camping?.campingType?.defaultDescription.data) - } - - - + {(hasHr || isLong) && + + } +
    ) } diff --git a/src/gatsby/src/styles/cmsSnippets/parkInfoPage.scss b/src/gatsby/src/styles/cmsSnippets/parkInfoPage.scss index 255ed7fe4..0a85692bc 100644 --- a/src/gatsby/src/styles/cmsSnippets/parkInfoPage.scss +++ b/src/gatsby/src/styles/cmsSnippets/parkInfoPage.scss @@ -25,15 +25,14 @@ } .expand-link.expand-icon, -.park-overview-link.expand-icon { +.park-overview-link.expand-icon, +.park-camping-link.expand-icon { display: inline-block; color: $colorBlueMed !important; - text-decoration: none !important; cursor: pointer; margin-bottom: 16px; &:hover, &:focus-visible { color: $colorBlue !important; - text-decoration: underline !important; } &:focus-visible { border-radius: 4px; @@ -47,7 +46,15 @@ } } -.park-overview-link.expand-icon { +.expand-link.expand-icon { + text-decoration: none !important; + &:hover, &:focus-visible { + text-decoration: underline !important; + } +} + +.park-overview-link.expand-icon, +.park-camping-link.expand-icon{ margin-top: 16px; } diff --git a/src/gatsby/src/styles/parks.scss b/src/gatsby/src/styles/parks.scss index 6f0ac4139..9a9653fd7 100644 --- a/src/gatsby/src/styles/parks.scss +++ b/src/gatsby/src/styles/parks.scss @@ -503,9 +503,31 @@ h2.section-heading { } } -.park-camping-type-description { - h3 { - font-size: 1rem; +// camping +#camping { + .park-camping { + & + .park-camping { + margin-top: 32px; + } + &--collapsed { + max-height: 260px; + overflow: hidden; + display: block; + text-overflow: ellipsis; + } + &--expanded { + max-height: none; + } + &-description { + h3 { + font-size: 1rem; + } + } + hr { + color: transparent; + border: none; + margin: 0; + } } } From d6848a1a02bcb0c4e85044498190702d1f5cd0be Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Fri, 16 Aug 2024 12:14:47 -0700 Subject: [PATCH 11/35] NOBUG: Fix gatsby build error (#1435) --- src/gatsby/src/components/park/campingDetails.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/gatsby/src/components/park/campingDetails.js b/src/gatsby/src/components/park/campingDetails.js index ccbe21757..c6c7fdfa0 100644 --- a/src/gatsby/src/components/park/campingDetails.js +++ b/src/gatsby/src/components/park/campingDetails.js @@ -19,7 +19,9 @@ export const CampingType = ({ camping }) => { const ref = useRef(null) const isLong = height > 259 const campingDescription = !isNullOrWhiteSpace(camping.description?.data) ? - camping.description.data : camping?.campingType?.defaultDescription.data + camping.description.data : + !isNullOrWhiteSpace(camping?.campingType?.defaultDescription?.data) ? + camping.campingType.defaultDescription.data : "" const $ = cheerio.load(campingDescription) $('a').attr('tabindex', '-1') @@ -28,7 +30,7 @@ export const CampingType = ({ camping }) => { useEffect(() => { setHeight(ref.current.clientHeight) - }, [expanded]) + }, [expanded, camping]) useEffect(() => { if (ref.current) { From 19d578fe723a3c5a99cc02a9d9c1c73c79bfdb6d Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Wed, 21 Aug 2024 09:33:13 -0700 Subject: [PATCH 12/35] CMS-360: Fix callout alignment issue (#1440) --- src/gatsby/src/styles/global.scss | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/gatsby/src/styles/global.scss b/src/gatsby/src/styles/global.scss index cd1bca79b..69f5c1fdf 100644 --- a/src/gatsby/src/styles/global.scss +++ b/src/gatsby/src/styles/global.scss @@ -103,8 +103,6 @@ blockquote { } .callout-box { - display: flex; - justify-content: center; color: $colorBlue; background-color: $colorBlueLight; font-size: 1rem; @@ -113,7 +111,7 @@ blockquote { border-radius: 4px; margin: 16px 0; padding: 20px; - p { + & > * { max-width: 600px; } } From a92351f0b8318930cbf719754778e01909e7818f Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Wed, 21 Aug 2024 09:38:29 -0700 Subject: [PATCH 13/35] CMS-350: Update table style (#1439) --- src/gatsby/src/styles/global.scss | 19 ++++++++++++++----- src/gatsby/src/styles/parks.scss | 2 -- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/gatsby/src/styles/global.scss b/src/gatsby/src/styles/global.scss index 69f5c1fdf..b9dcdfd84 100644 --- a/src/gatsby/src/styles/global.scss +++ b/src/gatsby/src/styles/global.scss @@ -127,9 +127,14 @@ figure.table { color: $colorGrey; table { width: 100%; - tr:first-of-type { - th, td { - border-top: 1px solid $colorGreyLight; + tr { + &:first-of-type { + th, td { + border-top: 1px solid $colorGreyLight; + } + } + & > :first-child { + background: $colorBrownLight !important; } } th, td { @@ -662,8 +667,12 @@ nav.breadcrumbs { } figure.table { table { - th { - background: $colorBrownMed; + tbody { + tr { + & > :first-child { + background: $colorBrownMed !important; + } + } } } } diff --git a/src/gatsby/src/styles/parks.scss b/src/gatsby/src/styles/parks.scss index a55a664a5..4335d28d3 100644 --- a/src/gatsby/src/styles/parks.scss +++ b/src/gatsby/src/styles/parks.scss @@ -1,6 +1,4 @@ @import "variables.scss"; -@import url("https://js.arcgis.com/4.10/esri/css/main.css"); -// @import url("https://governmentofbc.maps.arcgis.com/apps/webappviewer/jimu.js/css/jimu-theme.css"); .parks-container { max-width: none; From 9697b2cc62c5abda08610ac96091111e7ef11848 Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Thu, 22 Aug 2024 14:54:58 -0700 Subject: [PATCH 14/35] CMS-310: Add gradient to expandable description (#1442) * CMS-310: Add gradient to expandable description * CMS-310: Update gradient styling * CMS-310: Update gradient styling and components --- .../src/components/park/campingDetails.js | 2 +- .../src/components/park/parkOverview.js | 9 +----- src/gatsby/src/styles/parks.scss | 30 +++++++++---------- 3 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/gatsby/src/components/park/campingDetails.js b/src/gatsby/src/components/park/campingDetails.js index 9ec2a597d..015d9adfb 100644 --- a/src/gatsby/src/components/park/campingDetails.js +++ b/src/gatsby/src/components/park/campingDetails.js @@ -48,7 +48,7 @@ export const CampingType = ({ camping }) => {
    diff --git a/src/gatsby/src/components/park/parkOverview.js b/src/gatsby/src/components/park/parkOverview.js index 94bd01e29..8a7bce524 100644 --- a/src/gatsby/src/components/park/parkOverview.js +++ b/src/gatsby/src/components/park/parkOverview.js @@ -4,13 +4,6 @@ import { faChevronUp, faChevronDown } from "@fortawesome/free-solid-svg-icons" import HtmlContent from "./htmlContent" import * as cheerio from 'cheerio'; -const PREFIX = 'park-overview'; - -const classes = { - collapsed: `${PREFIX} collapsed`, - expanded: `${PREFIX} expanded` -}; - export default function ParkOverview({ data: parkOverview, type }) { const [expanded, setExpanded] = useState(false) const [height, setHeight] = useState(0) @@ -39,7 +32,7 @@ export default function ParkOverview({ data: parkOverview, type }) {
    {/* id="park-overview-container" should be removed once it's removed from the contents */} diff --git a/src/gatsby/src/styles/parks.scss b/src/gatsby/src/styles/parks.scss index 4335d28d3..a2f2ee989 100644 --- a/src/gatsby/src/styles/parks.scss +++ b/src/gatsby/src/styles/parks.scss @@ -398,15 +398,29 @@ h2.section-heading { } } -.park-overview { +// expandable description with gradient +.expandable-description { + position: relative; &.collapsed { max-height: 260px; overflow: hidden; display: block; text-overflow: ellipsis; + &.gradient::after { + content: ""; + width: 100%; + height: 100px; + position: absolute; + bottom: 0; + left: 0; + background: linear-gradient(180deg, rgba($colorWhite, 0) 0%, $colorWhite 100%); + } } &.expanded { max-height: none; + &.gradient::after { + display: none; + } } hr { color: transparent; @@ -511,25 +525,11 @@ h2.section-heading { & + .park-camping { margin-top: 32px; } - &--collapsed { - max-height: 260px; - overflow: hidden; - display: block; - text-overflow: ellipsis; - } - &--expanded { - max-height: none; - } &-description { h3 { font-size: 1rem; } } - hr { - color: transparent; - border: none; - margin: 0; - } } } From f037669db7a0060f38b0f66b29c8d796d2c593e4 Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Fri, 23 Aug 2024 10:53:30 -0700 Subject: [PATCH 15/35] CMS-361: Fix table of contents bug on park page (#1441) * CMS-361: Fix table of contents bug on park page * CMS-361: Update function to highlight side menu item * Revert "CMS-361: Update function to highlight side menu item" This reverts commit a1a57b0d850024a3171a1abaa967330c5207bf07. --- .../src/components/pageContent/pageMenu.js | 8 ++-- src/gatsby/src/templates/park.js | 2 - src/gatsby/src/templates/parkSubPage.js | 39 +++++++++---------- src/gatsby/src/templates/staticContent1.js | 9 ++--- 4 files changed, 25 insertions(+), 33 deletions(-) diff --git a/src/gatsby/src/components/pageContent/pageMenu.js b/src/gatsby/src/components/pageContent/pageMenu.js index 51a124e92..970c8c061 100644 --- a/src/gatsby/src/components/pageContent/pageMenu.js +++ b/src/gatsby/src/components/pageContent/pageMenu.js @@ -44,11 +44,9 @@ export default function PageMenu({ pageSections, activeSection, menuStyle }) { {pageSections.filter(s => s.visible).map( section => - section.sectionIndex > 0 && ( - - ) + )}
    diff --git a/src/gatsby/src/templates/park.js b/src/gatsby/src/templates/park.js index cb98f182f..57426f023 100644 --- a/src/gatsby/src/templates/park.js +++ b/src/gatsby/src/templates/park.js @@ -133,7 +133,6 @@ export default function ParkTemplate({ data }) { const parkOverviewRef = useRef("") const knowBeforeRef = useRef("") const mapLocationRef = useRef("") - const parkDatesRef = useRef("") const campingRef = useRef("") const activityRef = useRef("") const facilityRef = useRef("") @@ -145,7 +144,6 @@ export default function ParkTemplate({ data }) { parkOverviewRef, knowBeforeRef, mapLocationRef, - parkDatesRef, campingRef, activityRef, facilityRef, diff --git a/src/gatsby/src/templates/parkSubPage.js b/src/gatsby/src/templates/parkSubPage.js index ed533f6e0..e9122cd44 100644 --- a/src/gatsby/src/templates/parkSubPage.js +++ b/src/gatsby/src/templates/parkSubPage.js @@ -28,27 +28,6 @@ export default function ParkSubPage({ data }) { Boolean(c.strapi_component !== "parks.seo") ) || [] - let pageSections = [] - if (hasSections) { - let sectionIndex = 0 - for (const section of sections) { - sectionIndex += 1 - section.sectionIndex = sectionIndex - // if pageSection doesn't have a sectionTitle, display page title - if (!section.sectionTitle) { - section.sectionTitle = page.title - } - const titleId = slugify(section.sectionTitle).toLowerCase() - pageSections.push({ - display: section.sectionTitle, - sectionIndex: sectionIndex, - id: section.id, - link: "#" + titleId, - visible: true - }) - } - } - let sectionRefs = [ useRef(null), useRef(null), @@ -65,6 +44,24 @@ export default function ParkSubPage({ data }) { useRef(null), ] + let pageSections = [] + if (hasSections) { + let sectionIndex = 0 + for (const s of sections) { + sectionIndex += 1 + s.sectionIndex = sectionIndex + // each section needs an index to be used for in-page navigation + // and scrollspy highlighting + const titleId = slugify(s.sectionTitle).toLowerCase() + pageSections.push({ + sectionIndex: sectionIndex, + display: s.sectionTitle, + link: "#" + titleId, + visible: true + }) + } + } + const activeSection = useScrollSpy({ sectionElementRefs: sectionRefs, defaultValue: 0, diff --git a/src/gatsby/src/templates/staticContent1.js b/src/gatsby/src/templates/staticContent1.js index ea98da5f4..5fc4c6954 100644 --- a/src/gatsby/src/templates/staticContent1.js +++ b/src/gatsby/src/templates/staticContent1.js @@ -93,22 +93,21 @@ export default function StaticContent1({ pageContext }) { firstSectionTitle = current.title } pageSections = [ - { display: firstSectionTitle, sectionIndex: 0, id: 0, link: "#" }, + { sectionIndex: 0, display: firstSectionTitle, link: "#", visible: false }, ] let sectionIndex = 0 for (const s of sectionContents) { sectionIndex += 1 + s.sectionIndex = sectionIndex // each section needs an index to be used for in-page navigation // and scrollspy highlighting const titleId = slugify(s.sectionTitle).toLowerCase() - s.sectionIndex = sectionIndex pageSections.push({ - display: s.sectionTitle, sectionIndex: sectionIndex, - id: s.id, + display: s.sectionTitle, link: "#" + titleId, - visible: true // Default + visible: true }) } } From 439d6edf42f27afe62643b068e90f4e4e8866126 Mon Sep 17 00:00:00 2001 From: Michael Olund Date: Fri, 23 Aug 2024 13:08:17 -0700 Subject: [PATCH 16/35] CMS-353: Data migration for nature and culture (#1443) --- .../2024.08.22T00.00.20.nature-and-culture.js | 108 ++++++++++++++++++ .../content-types/protected-area/schema.json | 28 +++++ 2 files changed, 136 insertions(+) create mode 100644 src/cms/database/migrations/2024.08.22T00.00.20.nature-and-culture.js diff --git a/src/cms/database/migrations/2024.08.22T00.00.20.nature-and-culture.js b/src/cms/database/migrations/2024.08.22T00.00.20.nature-and-culture.js new file mode 100644 index 000000000..afc92a1cd --- /dev/null +++ b/src/cms/database/migrations/2024.08.22T00.00.20.nature-and-culture.js @@ -0,0 +1,108 @@ +'use strict' + +const cheerio = require('cheerio'); + +async function up(knex) { + if (await knex.schema.hasColumn('protected_areas', 'history')) { + + const protectedAreas = await strapi.db.query("api::protected-area.protected-area").findMany(); + + for (const pa of protectedAreas) { + let text = (pa.natureAndCulture || "").replace('

     

    ', ''); + text = text.replace(" 

    ", "

    "); + text = text.replace(":", "

    "); + text = text.replace(": ", "

    "); + text = text.replace(/<\/strong>
    /g, "

    "); + + let matchH3 = false; + let matchH4 = false; + let matchP = false; + + const $ = cheerio.load(`

    ${text}
    `, null, false); + let contentBlocks = [] + $("div > p > strong").each(function (i, elm) { + contentBlocks.push($(this).html().replace(": ", "").replace(":", "").replace(" ", "").toLowerCase()) + matchP = true; + }); + $("div > h3").each(function (i, elm) { + contentBlocks.push($(this).html().replace("", "").replace("", "").replace(": ", "").replace(":", "").replace(" ", "").toLowerCase()) + matchH3 = true; + }); + $("div > h4").each(function (i, elm) { + contentBlocks.push($(this).html().replace(": ", "").replace(":", "").replace(" ", "").toLowerCase()) + matchH4 = true; + }); + + let onlyHasWhitelistedCategories = true; + + for (let category of contentBlocks) { + if (!["conservation", "cultural heritage", "history", "wildlife"].includes(category)) { + onlyHasWhitelistedCategories = false; + } + } + + let hasInvalidStructure = false; + if (contentBlocks.length > 0 && !text.startsWith("

    ") && !text.startsWith("

    ") && !text.startsWith("

    ") && onlyHasWhitelistedCategories) { + hasInvalidStructure = true; + } + + let parsedSections = []; + if (contentBlocks.length > 0 && onlyHasWhitelistedCategories && !hasInvalidStructure) { + if (matchH3) { + let parts = text.split("

    "); + for (const p1 of parts) { + let parsedPart = { + title: p1.split("

    ")[0], + text: p1.split("

    ")[1] + } + parsedSections.push(parsedPart) + } + } + if (matchH4 && !matchH3) { + let parts = text.split("

    "); + for (const p2 of parts) { + let parsedPart = { + title: p2.split("

    ")[0], + text: p2.split("")[1] + } + parsedSections.push(parsedPart) + } + } + if (matchP && !matchH3 && !matchH4) { + let parts = text.split("

    "); + for (const p3 of parts) { + try { + let parsedPart = { + title: p3.split("

    ")[0], + text: p3.split("

    ")[1] + } + parsedSections.push(parsedPart) + } catch (ex) { + console.log(JSON.stringify(ex)) + } + } + } + } + + if (parsedSections.length) { + parsedSections.shift(); + const obj = {}; + if (parsedSections.length) { + for (const section of parsedSections) { + const fieldName = section.title.toLowerCase().replace(" ", "").replace("culturalheritage", "cultural_heritage").replace("", "").replace("", "").replace(" ", ""); + obj[fieldName] = section.text; + } + await knex('protected_areas').where({ id: pa.id }).update(obj); + } + } + } + + await knex.raw("update protected_areas set conservation = nature_and_culture where type = 'Ecological Reserve';"); + await knex.raw("update protected_areas set nature_and_culture = '' where type = 'Ecological Reserve';"); + + // todo: this line is disabled to allow QA to compare the fields + // await knex.raw("update protected_areas set nature_and_culture = '' where conservation <> '' or history <> '' or cultural_heritage <> '' or wildlife <> '';"); + } +} + +module.exports = { up }; diff --git a/src/cms/src/api/protected-area/content-types/protected-area/schema.json b/src/cms/src/api/protected-area/content-types/protected-area/schema.json index 28a177420..0f4332e02 100644 --- a/src/cms/src/api/protected-area/content-types/protected-area/schema.json +++ b/src/cms/src/api/protected-area/content-types/protected-area/schema.json @@ -340,6 +340,34 @@ "relation": "oneToMany", "target": "api::park-contact.park-contact", "mappedBy": "protectedArea" + }, + "conservation": { + "type": "customField", + "options": { + "preset": "toolbar" + }, + "customField": "plugin::ckeditor5.CKEditor" + }, + "culturalHeritage": { + "type": "customField", + "options": { + "preset": "toolbar" + }, + "customField": "plugin::ckeditor5.CKEditor" + }, + "history": { + "type": "customField", + "options": { + "preset": "toolbar" + }, + "customField": "plugin::ckeditor5.CKEditor" + }, + "wildlife": { + "type": "customField", + "options": { + "preset": "toolbar" + }, + "customField": "plugin::ckeditor5.CKEditor" } } } From c4334f92f23b980f6ddecb242b2bbb0271d3279c Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:48:24 -0700 Subject: [PATCH 17/35] CMS-362: Update reservations required text (#1444) --- src/gatsby/src/components/park/reservationsRequired.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gatsby/src/components/park/reservationsRequired.js b/src/gatsby/src/components/park/reservationsRequired.js index c20fa7988..95a5f1242 100644 --- a/src/gatsby/src/components/park/reservationsRequired.js +++ b/src/gatsby/src/components/park/reservationsRequired.js @@ -28,9 +28,9 @@ export default function ReservationsRequired({ operations }) { return hasAnyReservations && ( <> -

    Reservations required

    +

    Reservation policies

    - Review general guidelines for + Get information on:

      {(hasFrontcountryReservations || hasFirstComeFirstServed || hasFrontcountryCabinReservations) && From ea350fc3bed767b6b25b6719ef211cbd39658821 Mon Sep 17 00:00:00 2001 From: Michael Olund Date: Mon, 26 Aug 2024 11:43:09 -0700 Subject: [PATCH 18/35] CMS-371: Remove h3 tags from safety_info and special_notes (#1447) * CMS-371: Remove h3 tags from safety_info and special_notes * CMS-371: Fix tests --- .../2024.08.26T00.00.21.safety-h3.js | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/cms/database/migrations/2024.08.26T00.00.21.safety-h3.js diff --git a/src/cms/database/migrations/2024.08.26T00.00.21.safety-h3.js b/src/cms/database/migrations/2024.08.26T00.00.21.safety-h3.js new file mode 100644 index 000000000..3997b35a9 --- /dev/null +++ b/src/cms/database/migrations/2024.08.26T00.00.21.safety-h3.js @@ -0,0 +1,22 @@ +'use strict' + +async function up(knex) { + // turn h3 into h4 and turn h4 into h5 + if (await knex.schema.hasTable('protected_areas')) { + await knex.raw(`update protected_areas set safety_info = replace(safety_info, ' Date: Mon, 26 Aug 2024 14:48:47 -0700 Subject: [PATCH 19/35] CMS-61: Change mega menu item with window icon alignment (#1445) --- src/gatsby/src/components/megaMenu.js | 6 +++--- src/gatsby/src/styles/megaMenu/megaMenu.scss | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/gatsby/src/components/megaMenu.js b/src/gatsby/src/components/megaMenu.js index cbf2320cf..c18e8ee31 100644 --- a/src/gatsby/src/components/megaMenu.js +++ b/src/gatsby/src/components/megaMenu.js @@ -260,7 +260,7 @@ const MegaMenu = ({ content, menuMode }) => {
    • {isExternalUrl(item.url) ? - + {item.title} @@ -287,7 +287,7 @@ const MegaMenu = ({ content, menuMode }) => { > {isExternalUrl(page.url) ? @@ -334,7 +334,7 @@ const MegaMenu = ({ content, menuMode }) => {
      • {isExternalUrl(item.url) ? - + {item.title} : diff --git a/src/gatsby/src/styles/megaMenu/megaMenu.scss b/src/gatsby/src/styles/megaMenu/megaMenu.scss index 3f95f2e0c..bd1f6609e 100644 --- a/src/gatsby/src/styles/megaMenu/megaMenu.scss +++ b/src/gatsby/src/styles/megaMenu/megaMenu.scss @@ -130,6 +130,10 @@ nav { font-size: 1rem; margin: 0 0 2px 8px; } + &.external-link { + display: flex; + align-items: baseline; + } } .menu-button__arr { float: right; @@ -431,6 +435,10 @@ nav { outline-offset: 0px; border-radius: 4px; } + &.external-link { + display: flex; + align-items: baseline; + } .menu-button__arr { float: right; From cd648811fa21728121baa27d0018701102438713 Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Tue, 27 Aug 2024 11:55:14 -0700 Subject: [PATCH 20/35] CMS-279: Add reservation buttons in camping section (#1437) * CMS-279: Add reservation buttons in camping section * CMS-279: Update reservation buttons label * CMS-370: Move dates of operation from camping accordion --- src/gatsby/gatsby-node.js | 10 ++ .../src/components/park/campingDetails.js | 27 +---- src/gatsby/src/components/park/parkDates.js | 99 ++++++++++++++++++- .../src/components/park/parkFacility.js | 14 ++- src/gatsby/src/styles/parks.scss | 13 ++- src/gatsby/src/templates/park.js | 16 ++- 6 files changed, 144 insertions(+), 35 deletions(-) diff --git a/src/gatsby/gatsby-node.js b/src/gatsby/gatsby-node.js index 709f932e5..1603b4c5c 100644 --- a/src/gatsby/gatsby-node.js +++ b/src/gatsby/gatsby-node.js @@ -198,6 +198,16 @@ exports.createSchemaCustomization = ({ actions }) => { hasBackcountryShelterReservations: Boolean hasBackcountryWildernessReservations: Boolean hasGroupPicnicReservations: Boolean + frontcountryReservationUrl: String + frontcountryGroupReservationUrl: String + frontcountryCabinReservationUrl: String + backcountryReservationUrl: String + backcountryPermitUrl: String + backcountryGroupReservationUrl: String + backcountryWildernessReservationUrl: String + backcountryShelterReservationUrl: String + canoeCircuitReservationUrl: String + groupPicnicReservationUrl: String customReservationLinks: [STRAPI__COMPONENT_PARKS_RTE_LIST] @link(by: "id", from: "customReservationLinks___NODE") } diff --git a/src/gatsby/src/components/park/campingDetails.js b/src/gatsby/src/components/park/campingDetails.js index 015d9adfb..ad9a5023f 100644 --- a/src/gatsby/src/components/park/campingDetails.js +++ b/src/gatsby/src/components/park/campingDetails.js @@ -1,5 +1,4 @@ import React, { useState, useEffect, useRef } from "react" -import { navigate } from "gatsby" import Row from "react-bootstrap/Row" import Col from "react-bootstrap/Col" import * as cheerio from "cheerio" @@ -12,7 +11,7 @@ import StaticIcon from "./staticIcon" import { isNullOrWhiteSpace } from "../../utils/helpers" import "../../styles/cmsSnippets/parkInfoPage.scss" -export const CampingType = ({ camping }) => { +export const CampingType = ({ camping, parkOperation }) => { const [expanded, setExpanded] = useState(false) const [height, setHeight] = useState(0) const [sectionHeight, setSectionHeight] = useState(0) @@ -60,7 +59,6 @@ export const CampingType = ({ camping }) => { {expanded ? campingDescription : collapsedDescription} - {(hasHr || isLong) && } + ) } @@ -98,16 +97,6 @@ export default function CampingDetails({ data }) { const subAreas = data.subAreas || [] subAreas.sort((a, b) => (a.parkSubArea >= b.parkSubArea ? 1 : -1)) - if (activeCampings.length === 0) return null - - const toFrontCountryReservations = () => { - const reservationsURL = "https://camping.bcparks.ca" - const parkReservationsURL = parkOperation?.reservationUrl || reservationsURL - navigate(parkReservationsURL) - } - - if (activeCampings.length === 0) return null - return (
        @@ -117,17 +106,6 @@ export default function CampingDetails({ data }) { Camping - {data.hasReservations && ( -
    - - - )} @@ -136,6 +114,7 @@ export default function CampingDetails({ data }) { key={index} eventKey={index.toString()} camping={camping} + parkOperation={parkOperation} /> ))} diff --git a/src/gatsby/src/components/park/parkDates.js b/src/gatsby/src/components/park/parkDates.js index 68c917fa4..c36fd2c84 100644 --- a/src/gatsby/src/components/park/parkDates.js +++ b/src/gatsby/src/components/park/parkDates.js @@ -8,6 +8,88 @@ import { faChevronUp, faChevronDown } from "@fortawesome/free-solid-svg-icons" import HtmlContent from "./htmlContent" import SubArea from "./subArea" +export const ReservationButtons = ({ campingTypeCode, parkOperation }) => { + const reservationUrlRules = { + "backcountry-camping": { + buttons: [ + { + label: "Get permit", + fieldName: "backcountryPermitUrl" + }, + { + label: "Make a reservation", + fieldName: "backcountryReservationUrl" + }, + { + label: "Reserve canoe circuit", + fieldName: "canoeCircuitReservationUrl" + }, + { + label: "Book groupsite", + fieldName: "backcountryGroupReservationUrl" + }, + { + label: "Book shelter", + fieldName: "backcountryShelterReservationUrl" + } + ] + }, + "wilderness-camping": { + buttons: [ + { + label: "Book wilderness area", + fieldName: "backcountryWildernessReservationUrl" + } + ] + }, + "frontcountry-camping": { + buttons: [ + { + label: "Book camping", + fieldName: "frontcountryReservationUrl" + } + ] + }, + "group-camping": { + buttons: [ + { + label: "Book groupsite", + fieldName: "frontcountryGroupReservationUrl" + } + ] + }, + "cabins-huts": { + buttons: [ + { + label: "Book cabin", + fieldName: "frontcountryCabinReservationUrl" + } + ] + } + } + + const getReservationButtons = (code) => { + if (reservationUrlRules[code]) { + return reservationUrlRules[code].buttons + } + } + + return ( + getReservationButtons(campingTypeCode)?.length > 0 && + getReservationButtons(campingTypeCode).map((button, index) => ( + parkOperation && parkOperation[button.fieldName] && ( + + {button.label} + + ) + )) + ) +} + export const AccordionList = ({ eventKey, subArea, openAccordions, toggleAccordion, itemCount }) => { return ( (a.parkSubArea >= b.parkSubArea ? 1 : -1)) const [openAccordions, setOpenAccordions] = useState({}) @@ -80,13 +162,22 @@ export default function ParkDates({ data }) { } }, [subAreas.length]) + if (subAreas.length === 0) return null + return ( <> {subAreas.length > 0 && ( <> -

    - {data.campingType.pluralName} -

    + +
    +

    + {data.campingType.pluralName} +

    + + + + + <> diff --git a/src/gatsby/src/components/park/parkFacility.js b/src/gatsby/src/components/park/parkFacility.js index 896040777..90cb4a07c 100644 --- a/src/gatsby/src/components/park/parkFacility.js +++ b/src/gatsby/src/components/park/parkFacility.js @@ -11,7 +11,10 @@ import { isNullOrWhiteSpace } from "../../utils/helpers" import "../../styles/cmsSnippets/parkInfoPage.scss" import SubArea from "./subArea" -export const AccordionList = ({ eventKey, facility, openAccordions, toggleAccordion }) => { +export const AccordionList = ({ eventKey, facility, openAccordions, toggleAccordion, groupPicnicReservationUrl }) => { + const isPicnicFacility = + ["picnic-shelters", "picnic-areas"].includes(facility.facilityType.facilityCode) + return ( )} + {/* picnic shelter reservation button */} + {isPicnicFacility && groupPicnicReservationUrl && ( + + Book picnic shelter + + )} @@ -65,7 +74,7 @@ export const AccordionList = ({ eventKey, facility, openAccordions, toggleAccord ) } -export default function ParkFacility({ data }) { +export default function ParkFacility({ data, groupPicnicReservationUrl }) { const [facilityData] = useState( JSON.parse(JSON.stringify(data)) // deep copy ) @@ -166,6 +175,7 @@ export default function ParkFacility({ data }) { facility={facility} openAccordions={openAccordions} toggleAccordion={toggleAccordion} + groupPicnicReservationUrl={groupPicnicReservationUrl} /> ))} diff --git a/src/gatsby/src/styles/parks.scss b/src/gatsby/src/styles/parks.scss index a2f2ee989..a1fb29c3f 100644 --- a/src/gatsby/src/styles/parks.scss +++ b/src/gatsby/src/styles/parks.scss @@ -525,9 +525,16 @@ h2.section-heading { & + .park-camping { margin-top: 32px; } - &-description { - h3 { - font-size: 1rem; + .reservation-button-container { + padding-top: 16px; + @media (min-width: $smBreakpoint) { + padding-top: 0; + } + a.btn { + margin-top: 4px; + & + a.btn { + margin-left: 8px; + } } } } diff --git a/src/gatsby/src/templates/park.js b/src/gatsby/src/templates/park.js index 57426f023..50dbde742 100644 --- a/src/gatsby/src/templates/park.js +++ b/src/gatsby/src/templates/park.js @@ -413,7 +413,10 @@ export default function ParkTemplate({ data }) { )} {menuItems[5].visible && (
    - +
    )} {menuItems[6].visible && ( @@ -615,8 +618,17 @@ export const query = graphql` hasDayUsePass hasFirstComeFirstServed reservationUrl - backcountryPermitUrl dayUsePassUrl + frontcountryReservationUrl + frontcountryGroupReservationUrl + frontcountryCabinReservationUrl + backcountryReservationUrl + backcountryPermitUrl + backcountryGroupReservationUrl + backcountryWildernessReservationUrl + backcountryShelterReservationUrl + canoeCircuitReservationUrl + groupPicnicReservationUrl hasParkGate offSeasonUse openNote From 91b10a118b79c8d8e104913951fc8cf6fea0b7a1 Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Tue, 27 Aug 2024 12:51:17 -0700 Subject: [PATCH 21/35] CMS-310: Hide show more button if hr is at the bottom (#1446) * CMS-310: Hide show more button if hr is at the bottom * CMS-310: Update expandable text logic --- .../src/components/park/campingDetails.js | 17 +++++++++++------ src/gatsby/src/components/park/parkOverview.js | 15 ++++++++++----- src/gatsby/src/styles/parks.scss | 1 - 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/gatsby/src/components/park/campingDetails.js b/src/gatsby/src/components/park/campingDetails.js index ad9a5023f..58a67fbd4 100644 --- a/src/gatsby/src/components/park/campingDetails.js +++ b/src/gatsby/src/components/park/campingDetails.js @@ -16,7 +16,8 @@ export const CampingType = ({ camping, parkOperation }) => { const [height, setHeight] = useState(0) const [sectionHeight, setSectionHeight] = useState(0) const ref = useRef(null) - const isLong = height > 259 + const isLong = height >= 300 + const isMedium = height > 260 && height < 300 const campingDescription = !isNullOrWhiteSpace(camping.description?.data) ? camping.description.data : !isNullOrWhiteSpace(camping?.campingType?.defaultDescription?.data) ? @@ -26,10 +27,14 @@ export const CampingType = ({ camping, parkOperation }) => { $('a').attr('tabindex', '-1') const collapsedDescription = $.html() const hasHr = $('hr').length > 0 + const hrAtEnd = campingDescription.trim().endsWith('
    ') + const hasExpandCondition = (hasHr || isLong) && !isMedium && !hrAtEnd useEffect(() => { - setHeight(ref.current.clientHeight) - }, [expanded, camping]) + if (ref.current.clientHeight > 260) { + setHeight(ref.current.clientHeight) + } + }, [expanded]) useEffect(() => { if (ref.current) { @@ -47,8 +52,8 @@ export const CampingType = ({ camping, parkOperation }) => {
    @@ -60,7 +65,7 @@ export const CampingType = ({ camping, parkOperation }) => { {expanded ? campingDescription : collapsedDescription}
    - {(hasHr || isLong) && + {hasExpandCondition &&
    + {dataList.length > 1 && ( + + )} + {dataList.map((data, index) => ( + + ))} + + + ) : ( + {natureAndCulture} + )} ) } \ No newline at end of file diff --git a/src/gatsby/src/styles/global.scss b/src/gatsby/src/styles/global.scss index b9dcdfd84..1e7efeff5 100644 --- a/src/gatsby/src/styles/global.scss +++ b/src/gatsby/src/styles/global.scss @@ -649,6 +649,7 @@ nav.breadcrumbs { height: 24px; } } + &.about-accordion, &.dates-accordion { padding: 14px 16px; &.is-open { diff --git a/src/gatsby/src/styles/parks.scss b/src/gatsby/src/styles/parks.scss index 1e58d05dc..003c171b4 100644 --- a/src/gatsby/src/styles/parks.scss +++ b/src/gatsby/src/styles/parks.scss @@ -137,20 +137,13 @@ color: $colorGrey !important; } -// Unsure if this is ever used +// About this park #about-this-park { .ecological-list { - ul { - display: inline-block; - padding-left: 0; - } - li { - display: inline-block; - list-style: none; - margin-left: 8px; - } a { + font-weight: bold; text-decoration: none; + margin-right: 4px; &:hover { color: $colorBlue; text-decoration: underline; diff --git a/src/gatsby/src/templates/park.js b/src/gatsby/src/templates/park.js index 50dbde742..8991339fd 100644 --- a/src/gatsby/src/templates/park.js +++ b/src/gatsby/src/templates/park.js @@ -50,6 +50,10 @@ export default function ParkTemplate({ data }) { const specialNotes = park.specialNotes.data.specialNotes const locationNotes = park.locationNotes.data.locationNotes const natureAndCulture = park.natureAndCulture.data.natureAndCulture + const conservation = park.conservation.data.conservation + const culturalHeritage = park.culturalHeritage.data.culturalHeritage + const history = park.history.data.history + const wildlife = park.wildlife.data.wildlife const reconciliationNotes = park.reconciliationNotes.data.reconciliationNotes const maps = park.maps.data.maps const contact = park.parkContact.data.parkContact @@ -199,7 +203,9 @@ export default function ParkTemplate({ data }) { sectionIndex: 6, display: `About this ${parkType}`, link: "#about-this-park", - visible: !isNullOrWhiteSpace(natureAndCulture), + visible: !isNullOrWhiteSpace(natureAndCulture) || + !isNullOrWhiteSpace(conservation) || !isNullOrWhiteSpace(culturalHeritage) || + !isNullOrWhiteSpace(history) || !isNullOrWhiteSpace(wildlife) }, { sectionIndex: 7, @@ -424,6 +430,10 @@ export default function ParkTemplate({ data }) { Date: Thu, 29 Aug 2024 12:22:31 -0700 Subject: [PATCH 23/35] CMS-252: Support scheduling of firebans (#1451) --- .../fire-ban-prohibition/schema.json | 3 +++ .../services/fire-ban-prohibition.js | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/cms/src/api/fire-ban-prohibition/content-types/fire-ban-prohibition/schema.json b/src/cms/src/api/fire-ban-prohibition/content-types/fire-ban-prohibition/schema.json index 666ef070f..ff0f3a963 100644 --- a/src/cms/src/api/fire-ban-prohibition/content-types/fire-ban-prohibition/schema.json +++ b/src/cms/src/api/fire-ban-prohibition/content-types/fire-ban-prohibition/schema.json @@ -22,6 +22,9 @@ "effectiveDate": { "type": "datetime" }, + "rescindedDate": { + "type": "datetime" + }, "bulletinURL": { "type": "string" }, diff --git a/src/cms/src/api/fire-ban-prohibition/services/fire-ban-prohibition.js b/src/cms/src/api/fire-ban-prohibition/services/fire-ban-prohibition.js index 006a66306..12703a489 100644 --- a/src/cms/src/api/fire-ban-prohibition/services/fire-ban-prohibition.js +++ b/src/cms/src/api/fire-ban-prohibition/services/fire-ban-prohibition.js @@ -56,9 +56,20 @@ module.exports = createCoreService( }); let rowsUpdated = 0; + let banCount = 0; for (const ban of campfireBans) { + // skip bans where the effectiveDate is in the future + if (ban.effectiveDate > new Date().toISOString()) { + continue; + } + + // skip bans where the rescindedDate is in the past + if (ban.rescindedDate < new Date().toISOString()) { + continue; + } + if (ban.naturalResourceDistrict) { // get a list of protectedAreaIds to have firebans added. const protectedAreaIds = await getProtectedAreasByNaturalResourceDistrictToAddBan( @@ -93,9 +104,11 @@ module.exports = createCoreService( const count = await addProtectedAreaFireBans(protectedAreaIds, ban.effectiveDate); rowsUpdated += count; } + + banCount++; } return { - campfireBanCount: campfireBans.length, + campfireBanCount: banCount, parkCount: rowsUpdated }; }, From 281f7b3b499d67a4f380c5e00baccace8e26e1eb Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Thu, 29 Aug 2024 16:39:17 -0700 Subject: [PATCH 24/35] CMS-354: Not display conservation in accordion if the park is ecological reserve (#1450) --- src/gatsby/src/components/park/about.js | 65 +++++++++++++------------ src/gatsby/src/styles/parks.scss | 5 +- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/gatsby/src/components/park/about.js b/src/gatsby/src/components/park/about.js index 1c2107d6d..29f39eb46 100644 --- a/src/gatsby/src/components/park/about.js +++ b/src/gatsby/src/components/park/about.js @@ -170,37 +170,42 @@ export default function About({ {/* display conservation/culturalHeritage/history/wildlife accordion, otherwise display natureAndCulture */} {dataList.length > 0 ? ( - - - {dataList.length > 1 && ( - + {dataList.length > 1 && ( + - )} - {dataList.map((data, index) => ( - - ))} - - + + )} + {dataList.map((data, index) => ( + + ))} + + + ) ) : ( {natureAndCulture} )} diff --git a/src/gatsby/src/styles/parks.scss b/src/gatsby/src/styles/parks.scss index 003c171b4..9250a48b8 100644 --- a/src/gatsby/src/styles/parks.scss +++ b/src/gatsby/src/styles/parks.scss @@ -140,9 +140,12 @@ // About this park #about-this-park { .ecological-list { + margin-bottom: 0; + &:last-of-type { + margin-bottom: 16px; + } a { font-weight: bold; - text-decoration: none; margin-right: 4px; &:hover { color: $colorBlue; From d0fcadb71cbb90abe456bd70e2b81babdad074f3 Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Thu, 29 Aug 2024 16:57:45 -0700 Subject: [PATCH 25/35] CMS-279: Display book shelter button in cabins and huts accordion (#1453) --- .../src/components/park/campingDetails.js | 2 +- src/gatsby/src/components/park/parkDates.js | 29 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/gatsby/src/components/park/campingDetails.js b/src/gatsby/src/components/park/campingDetails.js index 58a67fbd4..a2f56f0d1 100644 --- a/src/gatsby/src/components/park/campingDetails.js +++ b/src/gatsby/src/components/park/campingDetails.js @@ -49,7 +49,7 @@ export const CampingType = ({ camping, parkOperation }) => { }, []) return ( -
    +
    { { label: "Book groupsite", fieldName: "backcountryGroupReservationUrl" - }, - { - label: "Book shelter", - fieldName: "backcountryShelterReservationUrl" } ] }, @@ -63,6 +59,10 @@ export const ReservationButtons = ({ campingTypeCode, parkOperation }) => { { label: "Book cabin", fieldName: "frontcountryCabinReservationUrl" + }, + { + label: "Book shelter", + fieldName: "backcountryShelterReservationUrl" } ] } @@ -95,8 +95,8 @@ export const AccordionList = ({ eventKey, subArea, openAccordions, toggleAccordi - {itemCount > 1 ? - ( 1 ? ( +
    - ) : - ( -
    - - {subArea.parkSubArea} - -
    - ) - } + + ) : ( +
    + + {subArea.parkSubArea} + +
    + )} From c343cb1ac2bf405ad46eaf9f7a10e98e88e678bc Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Fri, 30 Aug 2024 16:06:09 -0700 Subject: [PATCH 26/35] CMS-293: Add visitor guidelines, visit responsibly, and reservations required to site page (#1449) * CMS-293: Add visitor guidelines, visit responsibly, and reservations required to site page * CMS-293: Update park guideline lifecycle --- .../park-guideline/lifecycles.js | 4 + .../content-types/park-guideline/schema.json | 6 + .../api/site/content-types/site/schema.json | 12 ++ .../content-types/trail-report/schema.json | 6 + src/gatsby/gatsby-config.js | 6 + src/gatsby/gatsby-node.js | 2 + src/gatsby/src/templates/park.js | 28 ++-- src/gatsby/src/templates/site.js | 125 ++++++++++++++++-- 8 files changed, 165 insertions(+), 24 deletions(-) diff --git a/src/cms/src/api/park-guideline/content-types/park-guideline/lifecycles.js b/src/cms/src/api/park-guideline/content-types/park-guideline/lifecycles.js index e0f2809ae..0c84b2a54 100644 --- a/src/cms/src/api/park-guideline/content-types/park-guideline/lifecycles.js +++ b/src/cms/src/api/park-guideline/content-types/park-guideline/lifecycles.js @@ -7,12 +7,16 @@ const updateName = async (data, where) => { "api::park-guideline.park-guideline", id, { populate: '*' } ) const protectedArea = parkGuideline.protectedArea + const site = parkGuideline.site const guidelineType = parkGuideline.guidelineType data.name = "" if (protectedArea) { data.name = protectedArea.orcs } + if (site) { + data.name = site.orcsSiteNumber + } if (guidelineType) { data.name += ":" data.name += guidelineType.guidelineName; diff --git a/src/cms/src/api/park-guideline/content-types/park-guideline/schema.json b/src/cms/src/api/park-guideline/content-types/park-guideline/schema.json index 67d1f0a6c..30d40dcb3 100644 --- a/src/cms/src/api/park-guideline/content-types/park-guideline/schema.json +++ b/src/cms/src/api/park-guideline/content-types/park-guideline/schema.json @@ -44,6 +44,12 @@ "relation": "manyToOne", "target": "api::protected-area.protected-area", "inversedBy": "parkGuidelines" + }, + "site": { + "type": "relation", + "relation": "manyToOne", + "target": "api::site.site", + "inversedBy": "parkGuidelines" } } } diff --git a/src/cms/src/api/site/content-types/site/schema.json b/src/cms/src/api/site/content-types/site/schema.json index 8b7231203..c90532cd7 100644 --- a/src/cms/src/api/site/content-types/site/schema.json +++ b/src/cms/src/api/site/content-types/site/schema.json @@ -130,6 +130,18 @@ "relation": "oneToMany", "target": "api::park-photo.park-photo", "mappedBy": "site" + }, + "parkGuidelines": { + "type": "relation", + "relation": "oneToMany", + "target": "api::park-guideline.park-guideline", + "mappedBy": "site" + }, + "trailReports": { + "type": "relation", + "relation": "oneToMany", + "target": "api::trail-report.trail-report", + "mappedBy": "site" } } } diff --git a/src/cms/src/api/trail-report/content-types/trail-report/schema.json b/src/cms/src/api/trail-report/content-types/trail-report/schema.json index 4e199dacd..001f7b1b5 100644 --- a/src/cms/src/api/trail-report/content-types/trail-report/schema.json +++ b/src/cms/src/api/trail-report/content-types/trail-report/schema.json @@ -28,6 +28,12 @@ "relation": "manyToOne", "target": "api::protected-area.protected-area", "inversedBy": "trailReports" + }, + "site": { + "type": "relation", + "relation": "manyToOne", + "target": "api::site.site", + "inversedBy": "trailReports" } } } diff --git a/src/gatsby/gatsby-config.js b/src/gatsby/gatsby-config.js index 357731e40..d8d5c8fd3 100644 --- a/src/gatsby/gatsby-config.js +++ b/src/gatsby/gatsby-config.js @@ -177,8 +177,14 @@ module.exports = { parkCampingTypes: { populate: ["campingType"] }, + parkGuidelines: { + populate: ["guidelineType"] + }, parkOperation: { fields: "*" + }, + trailReports: { + fields: "*" } } } diff --git a/src/gatsby/gatsby-node.js b/src/gatsby/gatsby-node.js index 1603b4c5c..79ae0cf5e 100644 --- a/src/gatsby/gatsby-node.js +++ b/src/gatsby/gatsby-node.js @@ -316,6 +316,8 @@ exports.createSchemaCustomization = ({ actions }) => { parkActivities: [STRAPI_PARK_ACTIVITY] @link(by: "id", from: "parkActivities___NODE") parkFacilities: [STRAPI_PARK_FACILITY] @link(by: "id", from: "parkFacilities___NODE") parkCampingTypes: [STRAPI_PARK_CAMPING_TYPE] @link(by: "id", from: "parkCampingTypes___NODE") + parkGuidelines: [STRAPI_PARK_GUIDELINE] @link(by: "id", from: "parkGuidelines___NODE") + trailReports: [STRAPI_TRAIL_REPORT] @link(by: "id", from: "trailReports___NODE") } type STRAPI_MANAGEMENT_DOCUMENT_TYPE implements Node { diff --git a/src/gatsby/src/templates/park.js b/src/gatsby/src/templates/park.js index 8991339fd..f9a781cb0 100644 --- a/src/gatsby/src/templates/park.js +++ b/src/gatsby/src/templates/park.js @@ -2,41 +2,41 @@ import React, { useEffect, useState, useRef } from "react" import axios from "axios" import { sortBy, truncate } from "lodash" import { graphql, Link as GatsbyLink, navigate } from "gatsby" - +import Row from "react-bootstrap/Row" +import Col from "react-bootstrap/Col" import useScrollSpy from "react-use-scrollspy" import { isNullOrWhiteSpace } from "../utils/helpers"; import { loadAdvisories, WINTER_FULL_PARK_ADVISORY, WINTER_SUB_AREA_ADVISORY } from '../utils/advisoryHelper'; import { preProcessSubAreas, combineCampingTypes, combineFacilities } from '../utils/subAreaHelper'; +import About from "../components/park/about" +import AdvisoryDetails from "../components/park/advisoryDetails" import Breadcrumbs from "../components/breadcrumbs" +import CampingDetails from "../components/park/campingDetails" +import Contact from "../components/park/contact" import Footer from "../components/footer" import Header from "../components/header" +import MapLocation from "../components/park/mapLocation" +import NearbyParks from "../components/park/nearbyParks" import PageMenu from "../components/pageContent/pageMenu" -import Contact from "../components/park/contact" -import AdvisoryDetails from "../components/park/advisoryDetails" -import CampingDetails from "../components/park/campingDetails" -import About from "../components/park/about" -import Reconciliation from "../components/park/reconciliation" import ParkActivity from "../components/park/parkActivity" import ParkFacility from "../components/park/parkFacility" import ParkHeader from "../components/park/parkHeader" import ParkOverview from "../components/park/parkOverview" import ParkPhotoGallery from "../components/park/parkPhotoGallery" -import MapLocation from "../components/park/mapLocation" +import Reconciliation from "../components/park/reconciliation" +import ReservationsRequired from "../components/park/reservationsRequired" import SafetyInfo from "../components/park/safetyInfo" -import SpecialNote from "../components/park/specialNote" -import NearbyParks from "../components/park/nearbyParks" import ScrollToTop from "../components/scrollToTop" import Seo from "../components/seo" -import parksLogo from "../images/park-card.png" -import "../styles/parks.scss" -import Row from "react-bootstrap/Row" -import Col from "react-bootstrap/Col" +import SpecialNote from "../components/park/specialNote" import VisitResponsibly from "../components/park/visitResponsibly" -import ReservationsRequired from "../components/park/reservationsRequired" import VisitorGuidelines from "../components/park/visitorGuidelines" +import parksLogo from "../images/park-card.png" +import "../styles/parks.scss" + export default function ParkTemplate({ data }) { const apiBaseUrl = `${data.site.siteMetadata.apiURL}/api` diff --git a/src/gatsby/src/templates/site.js b/src/gatsby/src/templates/site.js index e5de5af20..857f05e77 100644 --- a/src/gatsby/src/templates/site.js +++ b/src/gatsby/src/templates/site.js @@ -2,29 +2,32 @@ import React, { useEffect, useState, useRef } from "react" import axios from "axios" import { sortBy, truncate } from "lodash" import { graphql, Link as GatsbyLink, navigate } from "gatsby" - +import Row from "react-bootstrap/Row" +import Col from "react-bootstrap/Col" import useScrollSpy from "react-use-scrollspy" import { isNullOrWhiteSpace } from "../utils/helpers"; import { loadAdvisories } from '../utils/advisoryHelper'; import { preProcessSubAreas, combineCampingTypes, combineFacilities } from '../utils/subAreaHelper'; +import AdvisoryDetails from "../components/park/advisoryDetails" import Breadcrumbs from "../components/breadcrumbs" +import CampingDetails from "../components/park/campingDetails" import Footer from "../components/footer" import Header from "../components/header" +import MapLocation from "../components/park/mapLocation" import PageMenu from "../components/pageContent/pageMenu" - -import AdvisoryDetails from "../components/park/advisoryDetails" -import CampingDetails from "../components/park/campingDetails" import ParkActivity from "../components/park/parkActivity" import ParkFacility from "../components/park/parkFacility" import ParkHeader from "../components/park/parkHeader" import ParkOverview from "../components/park/parkOverview" import ParkPhotoGallery from "../components/park/parkPhotoGallery" -import MapLocation from "../components/park/mapLocation" +import ReservationsRequired from "../components/park/reservationsRequired" import SafetyInfo from "../components/park/safetyInfo" import ScrollToTop from "../components/scrollToTop" import Seo from "../components/seo" +import VisitResponsibly from "../components/park/visitResponsibly" +import VisitorGuidelines from "../components/park/visitorGuidelines" import "../styles/parks.scss" @@ -39,6 +42,7 @@ export default function SiteTemplate({ data }) { const description = site.description.data.description const safetyInfo = site.safetyInfo?.data?.safetyInfo const locationNotes = site.locationNotes.data.locationNotes + const hasSiteGuidelines = site.parkGuidelines?.length > 0 const managementAreas = park.managementAreas || [] const searchArea = managementAreas[0]?.searchArea || {} @@ -144,7 +148,7 @@ export default function SiteTemplate({ data }) { }, { sectionIndex: 2, - display: "Maps and Location", + display: "Maps and location", link: "#maps-and-location", visible: !isNullOrWhiteSpace(locationNotes), }, @@ -221,7 +225,7 @@ export default function SiteTemplate({ data }) { advisoryLoadError={advisoryLoadError} isLoadingAdvisories={isLoadingAdvisories} searchArea={searchArea} - parkOperation={site.parkOperation} + parkOperation={operations} operationDates={park.parkOperationDates} subAreas={park.parkOperationSubAreas.filter(sa => sa.orcsSiteNumber === site.orcsSiteNumber)} /> @@ -281,9 +285,36 @@ export default function SiteTemplate({ data }) { {!isLoadingAdvisories && !advisoryLoadError && ( )} - {!isNullOrWhiteSpace(safetyInfo) && + {hasSiteGuidelines && + +
    + + + + } + {(!isNullOrWhiteSpace(safetyInfo) && !hasSiteGuidelines) && } +
    +

    + Review the detailed guides under visit responsibly for more information + on staying safe and preserving our natural spaces. +

    +
    + +
    + + + + + + )} @@ -300,18 +331,25 @@ export default function SiteTemplate({ data }) { reservations: site.reservations, hasDayUsePass: hasDayUsePass, hasReservations: hasReservations, + parkOperation: operations, + subAreas: park.parkOperationSubAreas.filter(sa => sa.orcsSiteNumber === site.orcsSiteNumber) }} /> )} {menuItems[4].visible && (
    - +
    )} {menuItems[5].visible && (
    - +
    )} @@ -500,9 +538,76 @@ export const query = graphql` campingTypeCode } } + parkGuidelines { + isActive + rank + title + description { + data { + description + } + } + guidelineType { + icon + hasTrailReport + defaultRank + defaultTitle + defaultDescription { + data { + defaultDescription + } + } + } + } parkOperation { + isActive hasReservations + hasBackcountryReservations + hasBackcountryPermits hasDayUsePass + hasFirstComeFirstServed + reservationUrl + dayUsePassUrl + frontcountryReservationUrl + frontcountryGroupReservationUrl + frontcountryCabinReservationUrl + backcountryReservationUrl + backcountryPermitUrl + backcountryGroupReservationUrl + backcountryWildernessReservationUrl + backcountryShelterReservationUrl + canoeCircuitReservationUrl + groupPicnicReservationUrl + hasParkGate + offSeasonUse + openNote + serviceNote + reservationsNote + offSeasonNote + generalNote + adminNote + gateOpenTime + gateCloseTime + hasGroupPicnicReservations + hasCanoeCircuitReservations + hasFrontcountryReservations + hasFrontcountryGroupReservations + hasFrontcountryCabinReservations + hasBackcountryGroupReservations + hasBackcountryShelterReservations + hasBackcountryWildernessReservations + customReservationLinks { + content { + data { + content + } + } + } + } + trailReports { + title + reportUrl + reportDate } } featuredPhotos: allStrapiParkPhoto( From 73c9c7731592432890e7480ffd2b1dc5f1f0958a Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Fri, 30 Aug 2024 16:34:22 -0700 Subject: [PATCH 27/35] CMS-312: Add lazy load image component (#1452) * CMS-312: Add lazy load image component * CMS-312: Update condition to display tldr section * CMS-312: Add space to advisory status line while loading advisory --- src/gatsby/gatsby-node.js | 9 +- src/gatsby/package-lock.json | 442 +------------------ src/gatsby/package.json | 2 +- src/gatsby/src/components/park/parkHeader.js | 119 ++--- src/gatsby/src/components/park/parkPhoto.js | 6 +- src/gatsby/src/styles/parks.scss | 4 + src/gatsby/src/templates/park.js | 72 +-- src/gatsby/src/templates/site.js | 46 +- 8 files changed, 150 insertions(+), 550 deletions(-) diff --git a/src/gatsby/gatsby-node.js b/src/gatsby/gatsby-node.js index 79ae0cf5e..0c16ac002 100644 --- a/src/gatsby/gatsby-node.js +++ b/src/gatsby/gatsby-node.js @@ -626,16 +626,11 @@ async function createRedirects(parkQuery, redirectQuery, { graphql, actions, rep }) } -exports.onCreateWebpackConfig = ({ stage, loaders, actions, getConfig }) => { +exports.onCreateWebpackConfig = ({ stage, actions, getConfig }) => { if (stage === "build-html" || stage === "develop-html") { actions.setWebpackConfig({ module: { - rules: [ - { - test: /@arcgis/, - use: loaders.null(), - }, - ], + rules: [], }, plugins: [new NodePolyfillPlugin()], }) diff --git a/src/gatsby/package-lock.json b/src/gatsby/package-lock.json index 739a0ec39..d7cd9b69b 100644 --- a/src/gatsby/package-lock.json +++ b/src/gatsby/package-lock.json @@ -8,7 +8,6 @@ "name": "bc-parks-gatsby", "version": "0.1.0", "dependencies": { - "@arcgis/core": "^4.30.3", "@bcgov/bc-sans": "^2.1.0", "@bcgov/bootstrap-theme": "https://github.com/bcgov/bootstrap-theme/releases/download/v1.1.1/bcgov-bootstrap-theme-1.1.1.tgz", "@emotion/react": "^11.11.4", @@ -50,6 +49,7 @@ "react-bootstrap-typeahead": "^6.3.2", "react-device-detect": "^2.0.1", "react-dom": "^18.3.1", + "react-lazy-load-image-component": "^1.6.2", "react-select": "^5.8.0", "react-typography": "^0.16.19", "react-use-query-param-string": "^2.1.5", @@ -84,21 +84,6 @@ "node": ">=6.0.0" } }, - "node_modules/@arcgis/core": { - "version": "4.30.3", - "resolved": "https://registry.npmjs.org/@arcgis/core/-/core-4.30.3.tgz", - "integrity": "sha512-TKaCBcPPxYY3Q0S2QsVRnBEAY0VFjWjbFh5d3nMuvbre/dWBjOftpuhE+sIAk6tN8cbcxWFp/cYdl+CeREmmaw==", - "dependencies": { - "@esri/arcgis-html-sanitizer": "~4.0.1", - "@esri/calcite-colors": "~6.1.0", - "@esri/calcite-components": "^2.8.5", - "@vaadin/grid": "~24.3.13", - "@zip.js/zip.js": "~2.7.44", - "luxon": "~3.4.4", - "marked": "~12.0.2", - "sortablejs": "~1.15.2" - } - }, "node_modules/@ardatan/relay-compiler": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/@ardatan/relay-compiler/-/relay-compiler-12.0.0.tgz", @@ -2299,56 +2284,6 @@ "node": ">= 4" } }, - "node_modules/@esri/arcgis-html-sanitizer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@esri/arcgis-html-sanitizer/-/arcgis-html-sanitizer-4.0.1.tgz", - "integrity": "sha512-6m/qIGmmbWWB2RXtyGcCBMIE/FkfM+dF9VzvW+kVH8ddvSjTtWiKcDrQXfDI73OyYPe2fDtANNPP9amu0L4sMQ==", - "dependencies": { - "xss": "1.0.13" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@esri/calcite-colors": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@esri/calcite-colors/-/calcite-colors-6.1.0.tgz", - "integrity": "sha512-wHQYWFtDa6c328EraXEVZvgOiaQyYr0yuaaZ0G3cB4C3lSkWefW34L/e5TLAhtuG3zJ/wR6pl5X1YYNfBc0/4Q==" - }, - "node_modules/@esri/calcite-components": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/@esri/calcite-components/-/calcite-components-2.10.1.tgz", - "integrity": "sha512-8M2ZBYcEk20x34TDxZ6H5rLCWtGrOJLtHA/k6yWL/GJTC85/gvY153XxbGN8F1ywJb/3imvhy/SlfNRPnVPQNQ==", - "dependencies": { - "@floating-ui/dom": "1.6.5", - "@stencil/core": "4.18.3", - "@types/color": "3.0.6", - "color": "4.2.3", - "composed-offset-position": "0.0.4", - "dayjs": "1.11.11", - "focus-trap": "7.5.4", - "lodash-es": "4.17.21", - "sortablejs": "1.15.1", - "timezone-groups": "0.8.0", - "type-fest": "4.18.2" - } - }, - "node_modules/@esri/calcite-components/node_modules/sortablejs": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.1.tgz", - "integrity": "sha512-P5Cjvb0UG1ZVNiDPj/n4V+DinttXG6K8n7vM/HQf0C25K3YKQTQY6fsr/sEGsJGpQ9exmPxluHxKBc0mLKU1lQ==" - }, - "node_modules/@esri/calcite-components/node_modules/type-fest": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.18.2.tgz", - "integrity": "sha512-+suCYpfJLAe4OXS6+PPXjW3urOS4IoP9waSiLuXfLgqZODKw/aWwASvzqE886wA0kQgGy0mIWyhd87VpqIy6Xg==", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@floating-ui/core": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", @@ -3744,19 +3679,6 @@ "@lezer/common": "^1.0.0" } }, - "node_modules/@lit-labs/ssr-dom-shim": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz", - "integrity": "sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g==" - }, - "node_modules/@lit/reactive-element": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz", - "integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==", - "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.2.0" - } - }, "node_modules/@lmdb/lmdb-darwin-arm64": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-2.5.3.tgz", @@ -4010,11 +3932,6 @@ "node": ">= 8" } }, - "node_modules/@open-wc/dedupe-mixin": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@open-wc/dedupe-mixin/-/dedupe-mixin-1.4.0.tgz", - "integrity": "sha512-Sj7gKl1TLcDbF7B6KUhtvr+1UCxdhMbNY5KxdU5IfMFWqL8oy1ZeAcCANjoB1TL0AJTcPmcCFsCbHf8X2jGDUA==" - }, "node_modules/@parcel/bundler-default": { "version": "2.8.3", "resolved": "https://registry.npmjs.org/@parcel/bundler-default/-/bundler-default-2.8.3.tgz", @@ -5301,14 +5218,6 @@ "node": ">=12" } }, - "node_modules/@polymer/polymer": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@polymer/polymer/-/polymer-3.5.1.tgz", - "integrity": "sha512-JlAHuy+1qIC6hL1ojEUfIVD58fzTpJAoCxFwV5yr0mYTXV1H8bz5zy0+rC963Cgr9iNXQ4T9ncSjC2fkF9BQfw==", - "dependencies": { - "@webcomponents/shadycss": "^1.9.1" - } - }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -5439,18 +5348,6 @@ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" }, - "node_modules/@stencil/core": { - "version": "4.18.3", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.18.3.tgz", - "integrity": "sha512-8yoG5AFQYEPocVtuoc5kvRS0Hku0MoDWDUpADRaXPVHsOFLmxR16LJENj25ucCz5GEfeTGQ/tCE8JAypPmr/fQ==", - "bin": { - "stencil": "bin/stencil" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.10.0" - } - }, "node_modules/@swc/helpers": { "version": "0.4.36", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.36.tgz", @@ -5561,27 +5458,6 @@ "@types/responselike": "^1.0.0" } }, - "node_modules/@types/color": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.6.tgz", - "integrity": "sha512-NMiNcZFRUAiUUCCf7zkAelY8eV3aKqfbzyFQlXpPIEeoNDbsEHGpb854V3gzTsGKYj830I5zPuOwU/TP5/cW6A==", - "dependencies": { - "@types/color-convert": "*" - } - }, - "node_modules/@types/color-convert": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-2.0.3.tgz", - "integrity": "sha512-2Q6wzrNiuEvYxVQqhh7sXM2mhIhvZR/Paq4FdsQkOMgWsCIkKvSGj8Le1/XalulrmgOzPMqNa0ix+ePY4hTrfg==", - "dependencies": { - "@types/color-name": "*" - } - }, - "node_modules/@types/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-hulKeREDdLFesGQjl96+4aoJSHY5b2GRjagzzcqCfIrWhe5vkCqIvrLbqzBaI1q94Vg8DNJZZqTR5ocdWmWclg==" - }, "node_modules/@types/common-tags": { "version": "1.8.4", "resolved": "https://registry.npmjs.org/@types/common-tags/-/common-tags-1.8.4.tgz", @@ -5880,11 +5756,6 @@ "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-gVC1InwyVrO326wbBZw+AO3u2vRXz/iRWq9jYhpG4W8LXyIgDv3ZmcLQ5Q4Gs+gFMyqx+viFoFT+l3p61QFCmQ==" }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" - }, "node_modules/@types/unist": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", @@ -6156,172 +6027,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@vaadin/a11y-base": { - "version": "24.3.14", - "resolved": "https://registry.npmjs.org/@vaadin/a11y-base/-/a11y-base-24.3.14.tgz", - "integrity": "sha512-IbF/T4m46vxLi2jZRexm/we8Q5hS7/dzFt2bnS0HKcdBENQ3KACqoIAczsOQKBoGTJqm+gD3DURGGNED7p8bRg==", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/component-base": "~24.3.14", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/checkbox": { - "version": "24.3.14", - "resolved": "https://registry.npmjs.org/@vaadin/checkbox/-/checkbox-24.3.14.tgz", - "integrity": "sha512-M84XdcYVwA9rZy4QyBOu3dqvm9+xxzJUrmAGRwkZ8woPR/AJ0bVZj4vvzX5y/9kGkXwXHMOjBq5TyjcOpvY0ZQ==", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.3.14", - "@vaadin/component-base": "~24.3.14", - "@vaadin/field-base": "~24.3.14", - "@vaadin/vaadin-lumo-styles": "~24.3.14", - "@vaadin/vaadin-material-styles": "~24.3.14", - "@vaadin/vaadin-themable-mixin": "~24.3.14", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/component-base": { - "version": "24.3.14", - "resolved": "https://registry.npmjs.org/@vaadin/component-base/-/component-base-24.3.14.tgz", - "integrity": "sha512-sQ3fBo+dWM7WhVDJhG19BM3419lp2YDkm1MM7AmLfN2Ll6HaFQdxHt4F4tifuu6ww1VkAhELxW0OkHQMOuvvnw==", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/vaadin-development-mode-detector": "^2.0.0", - "@vaadin/vaadin-usage-statistics": "^2.1.0", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/field-base": { - "version": "24.3.14", - "resolved": "https://registry.npmjs.org/@vaadin/field-base/-/field-base-24.3.14.tgz", - "integrity": "sha512-sRFvqx8THQ6GuWhCEUcG+D36cw4xDsqkXixd7X1JYfdWkVM/JiGSctJUnhqZYxLJ7gtZeDm9AIQ2Cc5F33jLUg==", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.3.14", - "@vaadin/component-base": "~24.3.14", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/grid": { - "version": "24.3.14", - "resolved": "https://registry.npmjs.org/@vaadin/grid/-/grid-24.3.14.tgz", - "integrity": "sha512-6noqdVlLDb3525aAO7KWTAY85gPWrX+g0bRvVAqClN5IgAMyBR0XS9nrzGCnX2q0watvg60hvkJnWuvetoPF7Q==", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.3.14", - "@vaadin/checkbox": "~24.3.14", - "@vaadin/component-base": "~24.3.14", - "@vaadin/lit-renderer": "~24.3.14", - "@vaadin/text-field": "~24.3.14", - "@vaadin/vaadin-lumo-styles": "~24.3.14", - "@vaadin/vaadin-material-styles": "~24.3.14", - "@vaadin/vaadin-themable-mixin": "~24.3.14" - } - }, - "node_modules/@vaadin/icon": { - "version": "24.3.14", - "resolved": "https://registry.npmjs.org/@vaadin/icon/-/icon-24.3.14.tgz", - "integrity": "sha512-iiHsFH3R6pv9ZPnxrv6Gwx3JdqWPTE4Ty0CZkQnndUqlHTzD9q6YVTXfL6VxH22qu1E4LbVTkEtDTeJtn4+jmw==", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/component-base": "~24.3.14", - "@vaadin/vaadin-lumo-styles": "~24.3.14", - "@vaadin/vaadin-themable-mixin": "~24.3.14", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/input-container": { - "version": "24.3.14", - "resolved": "https://registry.npmjs.org/@vaadin/input-container/-/input-container-24.3.14.tgz", - "integrity": "sha512-QbMebBSMQe5JkFlTEA66rXcZUKPjJSNq0IoEwjfpVUAjfA+bSVPbn/UCGyvggNOX3PxXCgHDJrBbbRirQhR/Aw==", - "dependencies": { - "@polymer/polymer": "^3.0.0", - "@vaadin/component-base": "~24.3.14", - "@vaadin/vaadin-lumo-styles": "~24.3.14", - "@vaadin/vaadin-material-styles": "~24.3.14", - "@vaadin/vaadin-themable-mixin": "~24.3.14", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/lit-renderer": { - "version": "24.3.14", - "resolved": "https://registry.npmjs.org/@vaadin/lit-renderer/-/lit-renderer-24.3.14.tgz", - "integrity": "sha512-Md+YDrHkjoOUEi+1IeK4usMle8AfRFJJUFg+RqEnZl6pKHJMW2NpqhIwHw2DRG+S5dZlhe+ga8xwel9IIuP71Q==", - "dependencies": { - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/text-field": { - "version": "24.3.14", - "resolved": "https://registry.npmjs.org/@vaadin/text-field/-/text-field-24.3.14.tgz", - "integrity": "sha512-6gqUUHxim6dI46k4wEyYLCF6kT0wxn8jDPaE3DPFf3LPiNuWCkIaTN889i6FhyWK6Syikc+97kPLYluHdEWp+Q==", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "@polymer/polymer": "^3.0.0", - "@vaadin/a11y-base": "~24.3.14", - "@vaadin/component-base": "~24.3.14", - "@vaadin/field-base": "~24.3.14", - "@vaadin/input-container": "~24.3.14", - "@vaadin/vaadin-lumo-styles": "~24.3.14", - "@vaadin/vaadin-material-styles": "~24.3.14", - "@vaadin/vaadin-themable-mixin": "~24.3.14", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/vaadin-development-mode-detector": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@vaadin/vaadin-development-mode-detector/-/vaadin-development-mode-detector-2.0.7.tgz", - "integrity": "sha512-9FhVhr0ynSR3X2ao+vaIEttcNU5XfzCbxtmYOV8uIRnUCtNgbvMOIcyGBvntsX9I5kvIP2dV3cFAOG9SILJzEA==" - }, - "node_modules/@vaadin/vaadin-lumo-styles": { - "version": "24.3.14", - "resolved": "https://registry.npmjs.org/@vaadin/vaadin-lumo-styles/-/vaadin-lumo-styles-24.3.14.tgz", - "integrity": "sha512-ma3CQ0jp6ywt/I3GI9iMFUySv062wbTkDSoj1kOd1lprabmxqVsf1s2BH4dBfNlSCIXtoDnSxVRlMHi9ikywuw==", - "dependencies": { - "@polymer/polymer": "^3.0.0", - "@vaadin/component-base": "~24.3.14", - "@vaadin/icon": "~24.3.14", - "@vaadin/vaadin-themable-mixin": "~24.3.14" - } - }, - "node_modules/@vaadin/vaadin-material-styles": { - "version": "24.3.14", - "resolved": "https://registry.npmjs.org/@vaadin/vaadin-material-styles/-/vaadin-material-styles-24.3.14.tgz", - "integrity": "sha512-FKJ6X7HjFZCBv2LTqWz4JfyTF12PsFYr+Ol4CKyQYqNhkT9aln5/37BJXgHreKtO2HHl1gCoEmlZUaqoaCOJcg==", - "dependencies": { - "@polymer/polymer": "^3.0.0", - "@vaadin/component-base": "~24.3.14", - "@vaadin/vaadin-themable-mixin": "~24.3.14" - } - }, - "node_modules/@vaadin/vaadin-themable-mixin": { - "version": "24.3.14", - "resolved": "https://registry.npmjs.org/@vaadin/vaadin-themable-mixin/-/vaadin-themable-mixin-24.3.14.tgz", - "integrity": "sha512-e8P2+EK5k7R5dWeZo8ohZP5IuU8EWHxNRC+kGEHHSLqFfEwdUzuTZ/cnwFwdIyQRwsJKGUGe05DZE2zeupxaIQ==", - "dependencies": { - "@open-wc/dedupe-mixin": "^1.3.0", - "lit": "^3.0.0" - } - }, - "node_modules/@vaadin/vaadin-usage-statistics": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@vaadin/vaadin-usage-statistics/-/vaadin-usage-statistics-2.1.2.tgz", - "integrity": "sha512-xKs1PvRfTXsG0eWWcImLXWjv7D+f1vfoIvovppv6pZ5QX8xgcxWUdNgERlOOdGt3CTuxQXukTBW3+Qfva+OXSg==", - "hasInstallScript": true, - "dependencies": { - "@vaadin/vaadin-development-mode-detector": "^2.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, "node_modules/@vercel/webpack-asset-relocator-loader": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/@vercel/webpack-asset-relocator-loader/-/webpack-asset-relocator-loader-1.7.3.tgz", @@ -6461,11 +6166,6 @@ "@xtuc/long": "4.2.2" } }, - "node_modules/@webcomponents/shadycss": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/@webcomponents/shadycss/-/shadycss-1.11.2.tgz", - "integrity": "sha512-vRq+GniJAYSBmTRnhCYPAPq6THYqovJ/gzGThWbgEZUQaBccndGTi1hdiUP15HzEco0I6t4RCtXyX0rsSmwgPw==" - }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -6476,16 +6176,6 @@ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, - "node_modules/@zip.js/zip.js": { - "version": "2.7.45", - "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.45.tgz", - "integrity": "sha512-Mm2EXF33DJQ/3GWWEWeP1UCqzpQ5+fiMvT3QWspsXY05DyqqxWu7a9awSzU4/spHMHVFrTjani1PR0vprgZpow==", - "engines": { - "bun": ">=0.7.0", - "deno": ">=1.0.0", - "node": ">=16.5.0" - } - }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -8773,11 +8463,6 @@ "arity-n": "^1.0.4" } }, - "node_modules/composed-offset-position": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/composed-offset-position/-/composed-offset-position-0.0.4.tgz", - "integrity": "sha512-vMlvu1RuNegVE0YsCDSV/X4X10j56mq7PCIyOKK74FxkXzGLwhOUmdkJLSdOBOMwWycobGUMgft2lp+YgTe8hw==" - }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -9324,11 +9009,6 @@ "node": ">=4" } }, - "node_modules/cssfilter": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", - "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==" - }, "node_modules/cssnano": { "version": "5.1.15", "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.15.tgz", @@ -9485,11 +9165,6 @@ "url": "https://opencollective.com/date-fns" } }, - "node_modules/dayjs": { - "version": "1.11.11", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", - "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==" - }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -11707,14 +11382,6 @@ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" }, - "node_modules/focus-trap": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz", - "integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==", - "dependencies": { - "tabbable": "^6.2.0" - } - }, "node_modules/follow-redirects": { "version": "1.15.6", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", @@ -17383,34 +17050,6 @@ "resolved": "https://registry.npmjs.org/linkfs/-/linkfs-2.1.0.tgz", "integrity": "sha512-kmsGcmpvjStZ0ATjuHycBujtNnXiZR28BTivEu0gAMDTT7GEyodcK6zSRtu6xsrdorrPZEIN380x7BD7xEYkew==" }, - "node_modules/lit": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/lit/-/lit-3.1.4.tgz", - "integrity": "sha512-q6qKnKXHy2g1kjBaNfcoLlgbI3+aSOZ9Q4tiGa9bGYXq5RBXxkVTqTIVmP2VWMp29L4GyvCFm8ZQ2o56eUAMyA==", - "dependencies": { - "@lit/reactive-element": "^2.0.4", - "lit-element": "^4.0.4", - "lit-html": "^3.1.2" - } - }, - "node_modules/lit-element": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.0.6.tgz", - "integrity": "sha512-U4sdJ3CSQip7sLGZ/uJskO5hGiqtlpxndsLr6mt3IQIjheg93UKYeGQjWMRql1s/cXNOaRrCzC2FQwjIwSUqkg==", - "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.2.0", - "@lit/reactive-element": "^2.0.4", - "lit-html": "^3.1.2" - } - }, - "node_modules/lit-html": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.1.4.tgz", - "integrity": "sha512-yKKO2uVv7zYFHlWMfZmqc+4hkmSbFp8jgjdZY9vvR9jr4J8fH6FUMXhr+ljfELgmjpvlF7Z1SJ5n5/Jeqtc9YA==", - "dependencies": { - "@types/trusted-types": "^2.0.2" - } - }, "node_modules/lmdb": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-2.5.3.tgz", @@ -17479,11 +17118,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" - }, "node_modules/lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", @@ -17539,6 +17173,11 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" + }, "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -17609,14 +17248,6 @@ "es5-ext": "~0.10.2" } }, - "node_modules/luxon": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", - "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", - "engines": { - "node": ">=12" - } - }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -17690,17 +17321,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/marked": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", - "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/mathml-tag-names": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", @@ -21165,6 +20785,18 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-lazy-load-image-component": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/react-lazy-load-image-component/-/react-lazy-load-image-component-1.6.2.tgz", + "integrity": "sha512-dAdH5PsRgvDMlHC7QpZRA9oRzEZl1kPFwowmR9Mt0IUUhxk2wwq43PB6Ffwv84HFYuPmsxDUCka0E9KVXi8roQ==", + "dependencies": { + "lodash.debounce": "^4.0.8", + "lodash.throttle": "^4.1.1" + }, + "peerDependencies": { + "react": "^15.x.x || ^16.x.x || ^17.x.x || ^18.x.x" + } + }, "node_modules/react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", @@ -22879,11 +22511,6 @@ "node": ">=10.0.0" } }, - "node_modules/sortablejs": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.2.tgz", - "integrity": "sha512-FJF5jgdfvoKn1MAKSdGs33bIqLi3LmsgVTliuX6iITj834F+JRQZN90Z93yql8h0K2t0RwDPBmxwlbZfDcxNZA==" - }, "node_modules/source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -23841,11 +23468,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/tabbable": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" - }, "node_modules/table": { "version": "6.8.1", "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", @@ -24073,14 +23695,6 @@ "next-tick": "1" } }, - "node_modules/timezone-groups": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/timezone-groups/-/timezone-groups-0.8.0.tgz", - "integrity": "sha512-t7E/9sPfCU0m0ZbS7Cqw52D6CB/UyeaiIBmyJCokI1SyOyOgA/ESiQ/fbreeFaUG9QSenGlZSSk/7rEbkipbOA==", - "bin": { - "timezone-groups": "dist/cli.cjs" - } - }, "node_modules/title-case": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/title-case/-/title-case-3.0.3.tgz", @@ -25428,26 +25042,6 @@ "node": ">=0.4.0" } }, - "node_modules/xss": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.13.tgz", - "integrity": "sha512-clu7dxTm1e8Mo5fz3n/oW3UCXBfV89xZ72jM8yzo1vR/pIS0w3sgB3XV2H8Vm6zfGnHL0FzvLJPJEBhd86/z4Q==", - "dependencies": { - "commander": "^2.20.3", - "cssfilter": "0.0.10" - }, - "bin": { - "xss": "bin/xss" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/xss/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, "node_modules/xstate": { "version": "4.38.3", "resolved": "https://registry.npmjs.org/xstate/-/xstate-4.38.3.tgz", diff --git a/src/gatsby/package.json b/src/gatsby/package.json index fc07036eb..b2af920df 100644 --- a/src/gatsby/package.json +++ b/src/gatsby/package.json @@ -4,7 +4,6 @@ "description": "BC Parks - public site", "version": "0.1.0", "dependencies": { - "@arcgis/core": "^4.30.3", "@bcgov/bc-sans": "^2.1.0", "@bcgov/bootstrap-theme": "https://github.com/bcgov/bootstrap-theme/releases/download/v1.1.1/bcgov-bootstrap-theme-1.1.1.tgz", "@emotion/react": "^11.11.4", @@ -46,6 +45,7 @@ "react-bootstrap-typeahead": "^6.3.2", "react-device-detect": "^2.0.1", "react-dom": "^18.3.1", + "react-lazy-load-image-component": "^1.6.2", "react-select": "^5.8.0", "react-typography": "^0.16.19", "react-use-query-param-string": "^2.1.5", diff --git a/src/gatsby/src/components/park/parkHeader.js b/src/gatsby/src/components/park/parkHeader.js index a286a6a71..b11605a90 100644 --- a/src/gatsby/src/components/park/parkHeader.js +++ b/src/gatsby/src/components/park/parkHeader.js @@ -74,6 +74,8 @@ export default function ParkHeader({ advisories, advisoryLoadError, isLoadingAdvisories, + protectedAreaLoadError, + isLoadingProtectedArea, searchArea, parkOperation, operationDates, @@ -93,65 +95,68 @@ export default function ParkHeader({

    {parkName}

    - {!isLoadingAdvisories && !advisoryLoadError && ( - <> - {searchArea?.searchAreaName && ( -
    - - {searchArea.searchAreaName}.  - {latitude && longitude && ( - <>View detailed map. - )} -
    - )} -
    - -
    - {parkDates && ( -
    - -
    -

    - The {parkType} {parkOperation?.hasParkGate !== false && "gate"} is open {parkDates} - {(parkOperation?.gateOpenTime && parkOperation?.gateCloseTime) ? ( - <> - , from {formattedTime(parkOperation.gateOpenTime)}{" "} - to {formattedTime(parkOperation.gateCloseTime)}, daily. - - ) : "."} -

    - {(campings.length > 0 || facilities.length > 0) && ( -

    - {campings.length > 0 && "Check campgrounds"} - {facilities.length > 0 && (campings.length > 0 ? " and facilities " : "Check facilities ")} - for additional dates. -

    - )} -
    -
    + {searchArea?.searchAreaName && ( +
    + + {searchArea.searchAreaName}.  + {latitude && longitude && ( + <>View detailed map. )} - {hasCampfireBan && -
    - -
    - } - +
    )} -
    - {hasReservations && ( - Book camping - )} - {hasDayUsePass && ( - Get a day-use pass - )} +
    + {(!isLoadingAdvisories && !advisoryLoadError) ? + + : + // display a space if it's loading advisories + <>  + }
    + {parkDates && ( +
    + +
    +

    + The {parkType} {parkOperation?.hasParkGate !== false && "gate"} is open {parkDates} + {(parkOperation?.gateOpenTime && parkOperation?.gateCloseTime) ? ( + <> + , from {formattedTime(parkOperation.gateOpenTime)}{" "} + to {formattedTime(parkOperation.gateCloseTime)}, daily. + + ) : "."} +

    + {(campings.length > 0 || facilities.length > 0) && ( +

    + {campings.length > 0 && "Check campgrounds"} + {facilities.length > 0 && (campings.length > 0 ? " and facilities " : "Check facilities ")} + for additional dates. +

    + )} +
    +
    + )} + {(!isLoadingProtectedArea && !protectedAreaLoadError && hasCampfireBan) && +
    + +
    + } + {(hasReservations || hasDayUsePass) && +
    + {hasReservations && ( + Book camping + )} + {hasDayUsePass && ( + Get a day-use pass + )} +
    + }
    {searchArea?.searchAreaName && ( @@ -184,6 +189,8 @@ ParkHeader.propTypes = { advisories: PropTypes.array, advisoryLoadError: PropTypes.any, isLoadingAdvisories: PropTypes.bool.isRequired, + protectedAreaLoadError: PropTypes.any, + isLoadingProtectedArea: PropTypes.bool.isRequired, searchArea: PropTypes.object.isRequired, parkOperation: PropTypes.object, operationDates: PropTypes.array.isRequired, diff --git a/src/gatsby/src/components/park/parkPhoto.js b/src/gatsby/src/components/park/parkPhoto.js index 2518cf2bd..e5a37ab98 100644 --- a/src/gatsby/src/components/park/parkPhoto.js +++ b/src/gatsby/src/components/park/parkPhoto.js @@ -1,12 +1,12 @@ import React from "react" import PropTypes from "prop-types" +import { LazyLoadImage } from "react-lazy-load-image-component" +import "react-lazy-load-image-component/src/effects/blur.css" -/* Simple image wrapper - essentially GatsbyImage for remote images */ export default function ParkPhoto({ type, src, alt }) { - return (
    - {alt +
    ) } diff --git a/src/gatsby/src/styles/parks.scss b/src/gatsby/src/styles/parks.scss index 9250a48b8..3d9fba35c 100644 --- a/src/gatsby/src/styles/parks.scss +++ b/src/gatsby/src/styles/parks.scss @@ -183,6 +183,10 @@ height: 196px; } } + .lazy-load-image-loaded { + width: 100%; + height: 100%; + } img { width: 100%; height: 100%; diff --git a/src/gatsby/src/templates/park.js b/src/gatsby/src/templates/park.js index f9a781cb0..47103eb03 100644 --- a/src/gatsby/src/templates/park.js +++ b/src/gatsby/src/templates/park.js @@ -268,45 +268,45 @@ export default function ParkTemplate({ data }) { return (
    - {!isLoadingProtectedArea && !protectedAreaLoadError && ( -
    -
    -
    - -
    - +
    +
    +
    +
    -
    0}`}> - {photos.length > 0 && ( -
    - )} -
    - -
    + +
    +
    0}`}> + {photos.length > 0 && ( +
    + )} +
    +
    - )} +
    - {!isLoadingProtectedArea && !protectedAreaLoadError && ( - sa.orcsSiteNumber === site.orcsSiteNumber)} - /> - )} + sa.orcsSiteNumber === site.orcsSiteNumber)} + />
    0}`}> {photos.length > 0 && ( From 840fb9355dc4c11d0406d70b7ca5d42e79204929 Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Fri, 6 Sep 2024 14:21:23 -0700 Subject: [PATCH 28/35] CMS-260: Display default contact even if there is no data in protectedArea.parkContact (#1460) --- src/gatsby/src/components/park/contact.js | 4 ++-- src/gatsby/src/templates/park.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gatsby/src/components/park/contact.js b/src/gatsby/src/components/park/contact.js index 89ac4aa2a..e4912e0e9 100644 --- a/src/gatsby/src/components/park/contact.js +++ b/src/gatsby/src/components/park/contact.js @@ -90,7 +90,7 @@ export default function Contact({ contact, parkContacts, operations }) { return (

    Contact

    - {sortedContacts.length > 0 ? ( + {contact === "" ? (
    @@ -124,7 +124,7 @@ export default function Contact({ contact, parkContacts, operations }) { )} - {sortedContacts.map((contact, index) => ( + {sortedContacts.length > 0 && sortedContacts.map((contact, index) => ( ))} {/* display it always */} diff --git a/src/gatsby/src/templates/park.js b/src/gatsby/src/templates/park.js index 47103eb03..9f59064d2 100644 --- a/src/gatsby/src/templates/park.js +++ b/src/gatsby/src/templates/park.js @@ -217,7 +217,7 @@ export default function ParkTemplate({ data }) { sectionIndex: 8, display: `Contact`, link: "#contact", - visible: park.parkContacts?.length > 0 || !isNullOrWhiteSpace(contact) + visible: true } ] From b8234556d2450bc83349e206e41986e9ccd2dbbf Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Fri, 6 Sep 2024 14:32:58 -0700 Subject: [PATCH 29/35] CMS-392: Adjust links in tldr (#1459) --- src/gatsby/src/components/park/parkHeader.js | 10 +++++++--- src/gatsby/src/components/park/subArea.js | 4 ++-- src/gatsby/src/styles/global.scss | 2 +- src/gatsby/src/styles/parks.scss | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/gatsby/src/components/park/parkHeader.js b/src/gatsby/src/components/park/parkHeader.js index b11605a90..c430ab8cb 100644 --- a/src/gatsby/src/components/park/parkHeader.js +++ b/src/gatsby/src/components/park/parkHeader.js @@ -134,9 +134,13 @@ export default function ParkHeader({

    {(campings.length > 0 || facilities.length > 0) && (

    - {campings.length > 0 && "Check campgrounds"} - {facilities.length > 0 && (campings.length > 0 ? " and facilities " : "Check facilities ")} - for additional dates. + {campings.length > 0 && <>Check campgrounds} + {facilities.length > 0 && + <> + {campings.length > 0 ? " and " : "Check "} + facilities + + } for additional dates.

    )} diff --git a/src/gatsby/src/components/park/subArea.js b/src/gatsby/src/components/park/subArea.js index 178f48701..f22815603 100644 --- a/src/gatsby/src/components/park/subArea.js +++ b/src/gatsby/src/components/park/subArea.js @@ -28,7 +28,7 @@ export default function SubArea({ data, showHeading }) {
    {showHeading && (

    {data.parkSubArea}

    )} -
    +
    @@ -75,7 +75,7 @@ export default function SubArea({ data, showHeading }) { {countsList .filter(count => isShown(count, data)).length > 0 && ( - +
    diff --git a/src/gatsby/src/styles/global.scss b/src/gatsby/src/styles/global.scss index 1e7efeff5..fad0281c1 100644 --- a/src/gatsby/src/styles/global.scss +++ b/src/gatsby/src/styles/global.scss @@ -609,7 +609,7 @@ nav.breadcrumbs { } &-content { padding-top: 32px; - & > * { + & > :not(.subarea-container) { max-width: 600px !important; } } diff --git a/src/gatsby/src/styles/parks.scss b/src/gatsby/src/styles/parks.scss index 3d9fba35c..155d4740d 100644 --- a/src/gatsby/src/styles/parks.scss +++ b/src/gatsby/src/styles/parks.scss @@ -561,7 +561,7 @@ h2.section-heading { } &--right { margin-top: 32px; - @media (min-width: $smBreakpoint) { + @media (min-width: $lgBreakpoint) { margin-top: 0; } .subarea-list { From 61111ce60a58444788afcb36da3e8b005b774bda Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Fri, 6 Sep 2024 15:23:16 -0700 Subject: [PATCH 30/35] CMS-293: Fix an issue that adding parkOperation to site did not work (#1462) --- src/gatsby/gatsby-node.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gatsby/gatsby-node.js b/src/gatsby/gatsby-node.js index 0c16ac002..9eceb5af1 100644 --- a/src/gatsby/gatsby-node.js +++ b/src/gatsby/gatsby-node.js @@ -312,7 +312,7 @@ exports.createSchemaCustomization = ({ actions }) => { type STRAPI_SITE implements Node { safetyInfo: STRAPI_SITE_SAFETYINFO - parkOperation: STRAPI_PARK_OPERATION + parkOperation: STRAPI_PARK_OPERATION @link(by: "id", from: "parkOperation___NODE") parkActivities: [STRAPI_PARK_ACTIVITY] @link(by: "id", from: "parkActivities___NODE") parkFacilities: [STRAPI_PARK_FACILITY] @link(by: "id", from: "parkFacilities___NODE") parkCampingTypes: [STRAPI_PARK_CAMPING_TYPE] @link(by: "id", from: "parkCampingTypes___NODE") From 0a92cf5e0c4b545c61ddfae01250edab2b3c600e Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Mon, 9 Sep 2024 16:59:06 -0700 Subject: [PATCH 31/35] CMS-279: Update condition for reservation deep link buttons (#1463) --- src/gatsby/src/components/park/parkDates.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gatsby/src/components/park/parkDates.js b/src/gatsby/src/components/park/parkDates.js index 67fef4948..8a2eb171c 100644 --- a/src/gatsby/src/components/park/parkDates.js +++ b/src/gatsby/src/components/park/parkDates.js @@ -23,10 +23,6 @@ export const ReservationButtons = ({ campingTypeCode, parkOperation }) => { { label: "Reserve canoe circuit", fieldName: "canoeCircuitReservationUrl" - }, - { - label: "Book groupsite", - fieldName: "backcountryGroupReservationUrl" } ] }, @@ -51,6 +47,10 @@ export const ReservationButtons = ({ campingTypeCode, parkOperation }) => { { label: "Book groupsite", fieldName: "frontcountryGroupReservationUrl" + }, + { + label: "Book groupsite", + fieldName: "backcountryGroupReservationUrl" } ] }, From 9f8c64d5ac27f33c6a79e5a2820e0a3d82915b28 Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Mon, 9 Sep 2024 17:09:56 -0700 Subject: [PATCH 32/35] CMS-399: Change the order of about section contents (#1464) --- src/gatsby/src/components/park/about.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gatsby/src/components/park/about.js b/src/gatsby/src/components/park/about.js index 29f39eb46..ec107da80 100644 --- a/src/gatsby/src/components/park/about.js +++ b/src/gatsby/src/components/park/about.js @@ -51,9 +51,9 @@ export default function About({ parkType, natureAndCulture, conservation, culturalHeritage, history, wildlife, biogeoclimaticZones, terrestrialEcosections, marineEcosections }) { const dataList = [ - { "title": "Conservation", "code": "conservation", "description": conservation }, { "title": "Cultural Heritage", "code": "cultural-heritage", "description": culturalHeritage }, { "title": "History", "code": "history", "description": history }, + { "title": "Conservation", "code": "conservation", "description": conservation }, { "title": "Wildlife", "code": "wildlife", "description": wildlife } ].filter(data => data.description) From 275f4928d54000b4536eb9b3e12a6bcc46972149 Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Mon, 9 Sep 2024 17:29:20 -0700 Subject: [PATCH 33/35] CMS-361: Update page menu on mobile (#1465) * CMS-361: Update page menu on mobile * CMS-361: Small fix --- src/gatsby/src/components/pageContent/pageMenu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gatsby/src/components/pageContent/pageMenu.js b/src/gatsby/src/components/pageContent/pageMenu.js index 970c8c061..92c0373b7 100644 --- a/src/gatsby/src/components/pageContent/pageMenu.js +++ b/src/gatsby/src/components/pageContent/pageMenu.js @@ -41,7 +41,7 @@ export default function PageMenu({ pageSections, activeSection, menuStyle }) { onChange={handleSectionChange} title="mobile-navigation" > - + {pageSections.filter(s => s.visible).map( section =>
    {/* display it if hasAnyReservations is true */} diff --git a/src/gatsby/src/components/park/parkFacility.js b/src/gatsby/src/components/park/parkFacility.js index 90cb4a07c..72dd9204a 100644 --- a/src/gatsby/src/components/park/parkFacility.js +++ b/src/gatsby/src/components/park/parkFacility.js @@ -44,8 +44,8 @@ export const AccordionList = ({ eventKey, facility, openAccordions, toggleAccord <> - {facility.subAreas.map((subArea) => ( - + {facility.subAreas.map((subArea, index) => ( + ))}
    diff --git a/src/gatsby/src/components/park/subArea.js b/src/gatsby/src/components/park/subArea.js index f22815603..76417e0b4 100644 --- a/src/gatsby/src/components/park/subArea.js +++ b/src/gatsby/src/components/park/subArea.js @@ -98,8 +98,8 @@ export default function SubArea({ data, showHeading }) { {subAreasNotesList .filter(note => data[note.noteVar]) .map((note, index) => ( - -
    + +
    {note.display && (

    From e521bb3b24c2ab706ddc40d241bd93229698251e Mon Sep 17 00:00:00 2001 From: Ayumi Tanaka <90415568+ayumi-oxd@users.noreply.github.com> Date: Tue, 10 Sep 2024 12:51:18 -0700 Subject: [PATCH 35/35] CMS-367: Change the wording in tldr (#1467) --- src/gatsby/src/components/park/parkHeader.js | 2 +- src/gatsby/src/components/park/subArea.js | 3 ++- src/gatsby/src/pages/plan-your-trip/park-operating-dates.js | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/gatsby/src/components/park/parkHeader.js b/src/gatsby/src/components/park/parkHeader.js index c430ab8cb..a16086772 100644 --- a/src/gatsby/src/components/park/parkHeader.js +++ b/src/gatsby/src/components/park/parkHeader.js @@ -134,7 +134,7 @@ export default function ParkHeader({

    {(campings.length > 0 || facilities.length > 0) && (

    - {campings.length > 0 && <>Check campgrounds} + {campings.length > 0 && <>Check camping} {facilities.length > 0 && <> {campings.length > 0 ? " and " : "Check "} diff --git a/src/gatsby/src/components/park/subArea.js b/src/gatsby/src/components/park/subArea.js index 76417e0b4..f4ba89441 100644 --- a/src/gatsby/src/components/park/subArea.js +++ b/src/gatsby/src/components/park/subArea.js @@ -64,7 +64,8 @@ export default function SubArea({ data, showHeading }) { ) : ( data.operationDates.length > 0 ? ( <> - {data.operationDates[0].includes("Year-round") ? "Limited services" : "No services"} + {data.operationDates[0].toLowerCase().includes("year-round") ? + "Limited services" : "No services"} ) : ( <>Not known diff --git a/src/gatsby/src/pages/plan-your-trip/park-operating-dates.js b/src/gatsby/src/pages/plan-your-trip/park-operating-dates.js index 254d93bd9..d728b3a4a 100644 --- a/src/gatsby/src/pages/plan-your-trip/park-operating-dates.js +++ b/src/gatsby/src/pages/plan-your-trip/park-operating-dates.js @@ -122,7 +122,8 @@ const ParkLink = ({ park, advisories }) => { ) : ( subArea.operationDates.length > 0 ? ( <> - {subArea.operationDates[0].includes("Year-round") ? "Limited services" : "No services"} + {subArea.operationDates[0].toLowerCase().includes("year-round") ? + "Limited services" : "No services"} ) : ( <>Not known