Skip to content

Commit

Permalink
Add model for reservation search page
Browse files Browse the repository at this point in the history
  • Loading branch information
villasv committed Aug 18, 2024
1 parent 1ad0683 commit 7a8faaf
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 106 deletions.
25 changes: 13 additions & 12 deletions app/(aspects)/cycle/packing/page.mdx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ParksOrg, ParkReserveLink } from "@/components/park-reserve-link";
import { Park, ParkReserveLink } from "@/components/park-reserve-link";

# 🏕️ Bike Packing

Expand All @@ -15,21 +15,18 @@ Resources: [BC Parks Camping][bc-parks] // [Parks Canada Camping][pc-parks] //
### [Parks Canada Sites][parks-canada]
[parks-canada]: https://web.archive.org/web/20240526195011/https://www.canadream.com/Website/media/Files/Parks-Canada-Discovery-Pass-Brochure-Map.pdf

#### [K6: Fort Langley][langley-nhs] <ParkReserveLink org={ParksOrg.ParksCanada} />
#### [K6: Fort Langley][langley-nhs] <ParkReserveLink park={Park.FortLangleyNHS} />
[langley-nhs]: https://parks.canada.ca/lhn-nhs/bc/langley

- ~30 km cycling from King George
- ~50 km cycling from Waterfront via Central Valley Greenway
- ⛺ oTENTik available (May 15 to Sept 15)

Still waiting for an opportunity to try this one. Reservation spots for the
historic site campgrounds are hard to come by.

#### [K7: Gulf Islands National Park Reserve][gulf-np]
[gulf-np]:https://parks.canada.ca/pn-np/bc/gulf

- ⛺ frontcountry campgrounds available (May 15 to Sept 30)
- 🪵 backcountry campgrounds available all year round
- ⛺ frontcountry campsites available (May 15 to Sept 30)
- 🪵 backcountry campsites available all year round

Routes to Swartz Bay:

Expand All @@ -47,22 +44,26 @@ Three frontcountry campgrounds: SMONEĆTEN on Vancouver Island, Prior Centennial
on Pender Island, and Sidney Spit on Sidney Island. Backcountry campgrounds are
accessible year round but are not regularly maintained from Oct 1 to May 14.

##### [SMONEĆTEN][smonecten]
##### [SMONEĆTEN][smonecten] <ParkReserveLink park={Park.SMONEĆTEN} />
[smonecten]: https://parks.canada.ca/pn-np/bc/gulf/activ/camping/campinglavantpays-frontcountrycamping#McDonald

The most bike-friendly campground is SMONEĆTEN, with access to the Lochside
Cycling Trail (from Swartz Bay to Downtown Victoria) and walk-in sites close to
entrance in pleasant wooded area, with a communal campfire ring. From Swartz Bay
to SMONEĆTEN via Swartz Bay Road it's about three more kms. The downside of this
campsite is being next to a highway; depending on the campsite position it's
possible to hear traffic noise.
entrance in pleasant wooded area, and a communal campfire ring. From Swartz Bay
to SMONEĆTEN via Swartz Bay Road it's about 3 km. The downside of this campsite
is being next to a highway; depending on the campsite position it's possible to
hear traffic noise.

##### Prior Centennial

##### Pender Island

Ferry from Tsawwassen to Mayne Island then another to Pender Island.

##### Shingle Bay

##### Narvaez Bay

#### K8: Fisgard Lighthouse

#### K9: Fort Rodd Hill
Expand Down
73 changes: 7 additions & 66 deletions components/park-reserve-link/index.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import {
getReservationUrl,
Park,
ParkReserveParams,
} from "@/projects/outdoors/reservations";
import styles from "./styles.module.css";

export enum ParksOrg {
ParksCanada,
}

export interface ParkReserveLinkProps {
org: ParksOrg;
checkInDate?: Date;
checkOutDate?: Date;
searchTime?: Date;
}

export async function ParkReserveLink(props: ParkReserveLinkProps) {
export async function ParkReserveLink(props: ParkReserveParams) {
const url = getReservationUrl(props);
return (
<a href={url.toString()} className={styles.link}>
Expand All @@ -20,57 +14,4 @@ export async function ParkReserveLink(props: ParkReserveLinkProps) {
);
}

export function getReservationUrl({
org,
checkInDate,
checkOutDate,
searchTime,
}: ParkReserveLinkProps): string {
const url = new URL(baseUrl(org));
url.searchParams.set("resourceLocationId", "-2147483623");
url.searchParams.set("mapId", "-2147483535");
url.searchParams.set("searchTabGroupId", "2");
url.searchParams.set("bookingCategoryId", "1");

const [t1, t2] = todayAndTomorrow();
url.searchParams.set(
"startDate",
(checkInDate ?? t1).toLocaleDateString("sv")
);
url.searchParams.set(
"endDate",
(checkOutDate ?? t2).toLocaleDateString("sv")
);

url.searchParams.set("nights", "1"); // TODO: compute
url.searchParams.set("isReserving", "true");
url.searchParams.set("partySize", "2"); // TODO: prop param
url.searchParams.set(
"searchTime",
(searchTime ?? new Date()).toLocaleString("sv").replace(" ", "T")
);
url.searchParams.set("flexibleSearch", "[false,false,null,1]"); // TODO how to use this?
url.searchParams.set("filterData", '{"-32756":"[[1],0,0,0]"}'); // TODO how to use this?
return url.toString();
}

function baseUrl(org: ParksOrg): string {
switch (org) {
case ParksOrg.ParksCanada:
return "https://reservation.pc.gc.ca/create-booking/results";
default:
throw new Error(`${org} not mapped to a reservation website`);
}
}

/**
* Returns today and tomorrow to be used in search params.
* @returns a pair of strings in yyyy-mm-dd format
*/
function todayAndTomorrow(): [Date, Date] {
const offset = new Date().getTimezoneOffset();
const today = new Date(new Date().getTime() - offset * 60 * 1000);
const tomorrow = new Date(new Date().getTime() - offset * 60 * 1000);
tomorrow.setDate(today.getDate() + 1);
return [today, tomorrow];
}
export { Park };
28 changes: 0 additions & 28 deletions components/park-reserve-link/reserve-link.test.ts

This file was deleted.

140 changes: 140 additions & 0 deletions projects/outdoors/reservations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
export enum Park {
FortLangleyNHS,
SMONEĆTEN,
}

export enum Org {
ParksCanada,
BCParks,
}

export enum Group {
Frontcountry = 0,
Backcountry = 1,
Accommodations = 2,
DayUse = 3,
}

export enum Category {
Campsite = 0,
Accommodation = 1,
GroupCampsite = 2,
BackcountryCampsite = 5,
BackcountryZone = 7,
}

export enum EquipmentType {
TentOrVehicle = "-32768",
TentsOnly = "-32767",
}

export enum TentOrVehicleSubtype {
SmallTent = "-32768",
MediumTent = "-32767",
LargeTent = "-32766",
VanOrPickup = "-32765",
}

export interface TentOrVehicleSpec {
equipment: EquipmentType.TentOrVehicle;
subtype: TentOrVehicleSubtype[];
}

export type EquipmentSpec = TentOrVehicleSpec;

export interface ParkInfo {
org: Org;
group: Group;
category: Category;
mapId: string;
resourceLocationId: string;
equipment?: EquipmentSpec;
}

function getParkInfo(park: Park): ParkInfo {
return PARKS[park];
}

export interface ParkReserveParams {
park: Park;
checkInDate?: Date;
checkOutDate?: Date;
searchTime?: Date;
}

export function getReservationUrl({
park,
checkInDate,
checkOutDate,
searchTime,
}: ParkReserveParams): string {
const { org, group, category, mapId, resourceLocationId } = getParkInfo(park);
const url = new URL(baseUrl(org));
url.searchParams.set("searchTabGroupId", group.toString());
url.searchParams.set("bookingCategoryId", category.toString());
url.searchParams.set("mapId", mapId);
url.searchParams.set("resourceLocationId", resourceLocationId);

const [t1, t2] = todayAndTomorrow();
url.searchParams.set(
"startDate",
(checkInDate ?? t1).toLocaleDateString("sv")
);
url.searchParams.set(
"endDate",
(checkOutDate ?? t2).toLocaleDateString("sv")
);

url.searchParams.set("nights", "1"); // TODO: compute
url.searchParams.set("isReserving", "true");
url.searchParams.set("partySize", "2"); // TODO: prop param
url.searchParams.set(
"searchTime",
(searchTime ?? new Date()).toLocaleString("sv").replace(" ", "T")
);
url.searchParams.set("flexibleSearch", "[false,false,null,1]"); // TODO how to use this?
url.searchParams.set("filterData", '{"-32756":"[[1],0,0,0]"}'); // TODO how to use this?
return url.toString();
}

function baseUrl(org: Org): string {
switch (org) {
case Org.ParksCanada:
return "https://reservation.pc.gc.ca/create-booking/results";
default:
throw new Error(`${org} not mapped to a reservation website`);
}
}

const PARKS: Record<Park, ParkInfo> = {
[Park.FortLangleyNHS]: {
org: Org.ParksCanada,
group: Group.Accommodations,
category: Category.Accommodation,
mapId: "-2147483535",
resourceLocationId: "-2147483623",
},
[Park.SMONEĆTEN]: {
org: Org.ParksCanada,
group: Group.Frontcountry,
category: Category.Campsite,
mapId: "-2147483477",
resourceLocationId: "-2147483601",
equipment: {
equipment: EquipmentType.TentOrVehicle,
subtype: [TentOrVehicleSubtype.SmallTent],
},
},
};

/**
* Returns today and tomorrow to be used in search params.
* @returns a pair of strings in yyyy-mm-dd format
*/
function todayAndTomorrow(): [Date, Date] {
const offset = new Date().getTimezoneOffset();
const today = new Date(new Date().getTime() - offset * 60 * 1000);
const tomorrow = new Date(new Date().getTime() - offset * 60 * 1000);
tomorrow.setDate(today.getDate() + 1);
return [today, tomorrow];
}
50 changes: 50 additions & 0 deletions projects/outdoors/reserve-link.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { getReservationUrl, Park } from "./reservations";

describe(getReservationUrl.name, () => {
const defaultArgs = {
searchTime: new Date(1704236645000), // 2024-01-02 3:04:05 PM (GMT-08:00)
checkInDate: new Date(1704323045000), // 1 day after t0
checkOutDate: new Date(1704409445000), // 1 day after t1
};
const defaultParams = [
"&startDate=2024-01-03&endDate=2024-01-04&nights=1",
"&isReserving=true&partySize=2",
"&searchTime=2024-01-02T15%3A04%3A05",
"&flexibleSearch=%5Bfalse%2Cfalse%2Cnull%2C1%5D",
];

it("should work for Fort Langley National Historic Site", () => {
const url = getReservationUrl({
park: Park.FortLangleyNHS,
...defaultArgs,
});
const expectedUrl = [
"https://reservation.pc.gc.ca/create-booking/results",
"?searchTabGroupId=2", // accommodation
"&bookingCategoryId=1", // accommodation
"&mapId=-2147483535",
"&resourceLocationId=-2147483623",
...defaultParams,
"&filterData=%7B%22-32756%22%3A%22%5B%5B1%5D%2C0%2C0%2C0%5D%22%7D",
].join("");
expect(url).toBe(expectedUrl);
});

it("should work for SMONEĆTEN", () => {
const url = getReservationUrl({
park: Park.SMONEĆTEN,
...defaultArgs,
});
const expectedUrl = [
"https://reservation.pc.gc.ca/create-booking/results",
"?searchTabGroupId=0", // frontcountry
"&bookingCategoryId=0", // campsite
"&mapId=-2147483477",
"&resourceLocationId=-2147483601",
...defaultParams,
"&filterData=%7B%7D&equipmentId=-32768",
"&subEquipmentId=-32768",
].join("");
expect(url).toBe(expectedUrl);
});
});

0 comments on commit 7a8faaf

Please sign in to comment.