From b78e1794cab81f922cafa99ccbcd16afaa2c4ecd Mon Sep 17 00:00:00 2001 From: bcho892 Date: Tue, 12 Nov 2024 14:03:08 +1300 Subject: [PATCH 1/7] fetch prices dynamically --- client/src/app/Home.story.tsx | 2 +- client/src/app/HomeComponent.tsx | 34 ++++++++++-- client/src/app/bookings/page.tsx | 3 + client/src/app/page.tsx | 15 +++-- .../src/app/sections/PricingSection.story.tsx | 4 +- client/src/app/sections/PricingSection.tsx | 6 +- client/src/app/sections/utils/Pricing.ts | 14 +++-- .../BookingCreation/BookingCreation.story.tsx | 7 ++- .../BookingCreation/BookingCreation.tsx | 17 ++++-- .../ProtectedCreateBookingSection.tsx | 7 ++- .../BookingInformationAndCreation.tsx | 15 ++++- client/src/services/AppData/AppDataService.ts | 55 +++++++++++++++++-- client/src/utils/Constants.ts | 3 + 13 files changed, 145 insertions(+), 37 deletions(-) diff --git a/client/src/app/Home.story.tsx b/client/src/app/Home.story.tsx index df259fd08..9a74836d8 100644 --- a/client/src/app/Home.story.tsx +++ b/client/src/app/Home.story.tsx @@ -9,5 +9,5 @@ const meta: Meta = { export default meta export const DefaultHomePage = () => { - return + return } diff --git a/client/src/app/HomeComponent.tsx b/client/src/app/HomeComponent.tsx index 23d1c31ee..498cef2da 100644 --- a/client/src/app/HomeComponent.tsx +++ b/client/src/app/HomeComponent.tsx @@ -1,22 +1,36 @@ import { Footer } from "@/components/generic/Footer/Footer" import { HomePage } from "@/models/sanity/HomePage/Utils" -import { Prices } from "@/services/AppData/AppDataService" +import { + LodgePricingProps, + MembershipPrices +} from "@/services/AppData/AppDataService" import AboutSection from "./sections/AboutSection" import BenefitSection from "./sections/BenefitSection" import LandingSection from "./sections/LandingSection" import PricingSection from "./sections/PricingSection" import { benefits } from "./sections/utils/Benefits" -import { pricingBannerContent } from "./sections/utils/Pricing" +import { pricingBannerMessages } from "./sections/utils/Pricing" export type HomeProps = { - pricingData: Prices[] + /** + * Fetched prices for how much each type of membership costs + */ + membershipPricingData: MembershipPrices[] + /** + * Fetched prices showing how much it costs to book the lodge + */ + lodgePricing: LodgePricingProps content?: HomePage } /** * @deprecated do not use, use `WrappedHomeComponent` instead */ -const HomeComponent = ({ pricingData, content }: HomeProps) => { +const HomeComponent = ({ + membershipPricingData, + lodgePricing, + content +}: HomeProps) => { return ( <>
@@ -35,8 +49,16 @@ const HomeComponent = ({ pricingData, content }: HomeProps) => { />
diff --git a/client/src/app/bookings/page.tsx b/client/src/app/bookings/page.tsx index 3dfe9dd99..76319f521 100644 --- a/client/src/app/bookings/page.tsx +++ b/client/src/app/bookings/page.tsx @@ -8,12 +8,14 @@ import { PortableText } from "@portabletext/react" import { Policies, POLICIES_GROQ_QUERY } from "@/models/sanity/Policies/Utils" import BookingPolicyStorage from "./BookingPolicyStorage" import { PolicyWithTextBlocks } from "@/components/composite/Booking/BookingContext" +import AppDataService from "@/services/AppData/AppDataService" const BookingPage = async () => { const lodgeInfo = await sanityQuery( LODGE_INFORMATION_GROQ_QUERY ) + const lodgePrices = await AppDataService.getLodgePrices() /** * We assume there will be only one based on the way {@link LodgeInformation} * is set up in sanity @@ -55,6 +57,7 @@ const BookingPage = async () => { <> , imageSrcs: processedImages diff --git a/client/src/app/page.tsx b/client/src/app/page.tsx index 633a45892..d9530d896 100644 --- a/client/src/app/page.tsx +++ b/client/src/app/page.tsx @@ -1,22 +1,29 @@ -import AppDataService, { Prices } from "@/services/AppData/AppDataService" +import AppDataService, { + MembershipPrices +} from "@/services/AppData/AppDataService" import HomeComponent from "./HomeComponent" import { sanityQuery } from "../../sanity/lib/utils" import { HOME_PAGE_GROQ_QUERY, HomePage } from "@/models/sanity/HomePage/Utils" const Home = async () => { - let pricingData: Prices[] + let pricingData: MembershipPrices[] try { pricingData = await AppDataService.getMembershipPricingDetails() } catch (e) { pricingData = [] } + const lodgePricing = await AppDataService.getLodgePrices() + const [content] = await sanityQuery(HOME_PAGE_GROQ_QUERY) - console.log(content) return ( <> - + ) } diff --git a/client/src/app/sections/PricingSection.story.tsx b/client/src/app/sections/PricingSection.story.tsx index 9b7922085..9c0b74a45 100644 --- a/client/src/app/sections/PricingSection.story.tsx +++ b/client/src/app/sections/PricingSection.story.tsx @@ -10,7 +10,9 @@ const meta: Meta = { export default meta -export const DefaultPricingSection = ({ pricingData: data }: HomeProps) => { +export const DefaultPricingSection = ({ + membershipPricingData: data +}: HomeProps) => { return ( ( diff --git a/client/src/app/sections/utils/Pricing.ts b/client/src/app/sections/utils/Pricing.ts index 8c83fd7c5..3ff5d3904 100644 --- a/client/src/app/sections/utils/Pricing.ts +++ b/client/src/app/sections/utils/Pricing.ts @@ -1,13 +1,15 @@ -import { Pricing, PricingBannerContent } from "@/components/utils/types" +import { Pricing } from "@/components/utils/types" export const pricingNote = "*We have a discounted membership price on offer until Sunday 17th March so lock in now for a year of awesome memories!" -export const pricingBannerContent: PricingBannerContent = { - headline: "Great nightly rates", - priceInformation: "$40 per night*", - disclaimer: "*$60 when booking a single Friday or Saturday" -} +export const pricingBannerMessages = { + headline: "Great nightly rates" as const, + priceInformation: (normalPrice: number) => + `$${normalPrice} per night*` as const, + disclaimer: (moreExpensivePrice: number) => + `*$${moreExpensivePrice} when booking a single Friday or Saturday` as const +} as const export const Pricings: Pricing[] = [ { diff --git a/client/src/components/composite/Booking/BookingCreation/BookingCreation.story.tsx b/client/src/components/composite/Booking/BookingCreation/BookingCreation.story.tsx index 92c0bb589..1890255a1 100644 --- a/client/src/components/composite/Booking/BookingCreation/BookingCreation.story.tsx +++ b/client/src/components/composite/Booking/BookingCreation/BookingCreation.story.tsx @@ -10,5 +10,10 @@ type Story = StoryObj export const DefaultCreateBookingPage: Story = { decorators: [(Story) => ], - args: {} + args: { + lodgePrices: { + normal: 69, + moreExpensive: 420 + } + } } diff --git a/client/src/components/composite/Booking/BookingCreation/BookingCreation.tsx b/client/src/components/composite/Booking/BookingCreation/BookingCreation.tsx index 019872d77..6a75d6f56 100644 --- a/client/src/components/composite/Booking/BookingCreation/BookingCreation.tsx +++ b/client/src/components/composite/Booking/BookingCreation/BookingCreation.tsx @@ -16,6 +16,7 @@ import { import { Timestamp } from "firebase/firestore" import Checkbox from "@/components/generic/Checkbox/Checkbox" import { DateRange, DateUtils } from "@/components/utils/DateUtils" +import { LodgePricingProps } from "@/services/AppData/AppDataService" /* * Swaps around dates if invalid @@ -67,10 +68,9 @@ export interface ICreateBookingSection { * If a request related to creating/fetching a booking is in progress */ isPending?: boolean -} -const NORMAL_PRICE = 40 as const -const SPECIAL_PRICE = 60 as const + lodgePrices: LodgePricingProps +} /** * A notification to the user informing the actual @@ -110,13 +110,20 @@ export const CreateBookingSection = ({ handleBookingCreation, handleAllergyChange, hasExistingSession, - isPending + isPending, + lodgePrices }: ICreateBookingSection) => { const [selectedDateRange, setSelectedDateRange] = useState({ startDate: new Date(), endDate: new Date() }) + /** + * Derive prices from the props + */ + const NORMAL_PRICE = lodgePrices.normal + const SPECIAL_PRICE = lodgePrices.moreExpensive + const [isValidForCreation, setIsValidForCreation] = useState(false) const { startDate: currentStartDate, endDate: currentEndDate } = @@ -220,7 +227,7 @@ export const CreateBookingSection = ({ : NORMAL_PRICE return `$${requiredPrice} * ${nights} night${nights > 1 ? "s" : ""} = $${requiredPrice * nights}` as const - }, [currentStartDate, currentEndDate]) + }, [currentStartDate, currentEndDate, SPECIAL_PRICE, NORMAL_PRICE]) return ( <> diff --git a/client/src/components/composite/Booking/BookingCreation/ProtectedCreateBookingSection.tsx b/client/src/components/composite/Booking/BookingCreation/ProtectedCreateBookingSection.tsx index 3c7bd9c11..4eb84901a 100644 --- a/client/src/components/composite/Booking/BookingCreation/ProtectedCreateBookingSection.tsx +++ b/client/src/components/composite/Booking/BookingCreation/ProtectedCreateBookingSection.tsx @@ -3,14 +3,16 @@ import { useAppData } from "@/store/Store" import { SignUpNotif } from "@/components/generic/SignUpNotif/SignUpNotif" import { useAvailableBookingsQuery } from "@/services/Booking/BookingQueries" -import { CreateBookingSection } from "./BookingCreation" +import { CreateBookingSection, ICreateBookingSection } from "./BookingCreation" import { useContext, useEffect } from "react" import { BookingContext } from "../BookingContext" /** * @deprecated not for direct consumption on pages, use `BookingInformationAndCreation` instead */ -export const ProtectedCreateBookingSection = () => { +export const ProtectedCreateBookingSection = ({ + lodgePrices +}: Pick) => { const [{ currentUser, currentUserClaims }] = useAppData() const { data } = useAvailableBookingsQuery() @@ -38,6 +40,7 @@ export const ProtectedCreateBookingSection = () => { handleAllergyChange={setAllergies} hasExistingSession={!!clientSecret} isPending={isPending} + lodgePrices={lodgePrices} /> ) } diff --git a/client/src/components/composite/Booking/BookingInformationAndCreation/BookingInformationAndCreation.tsx b/client/src/components/composite/Booking/BookingInformationAndCreation/BookingInformationAndCreation.tsx index 5d89156a4..9ab48d6c1 100644 --- a/client/src/components/composite/Booking/BookingInformationAndCreation/BookingInformationAndCreation.tsx +++ b/client/src/components/composite/Booking/BookingInformationAndCreation/BookingInformationAndCreation.tsx @@ -8,6 +8,7 @@ import { } from "../BookingCreation/BookingCreation" import { ProtectedCreateBookingSection } from "../BookingCreation/ProtectedCreateBookingSection" import { useSearchParams } from "next/navigation" +import { LodgePricingProps } from "@/services/AppData/AppDataService" /** * Utility type determining what should be displayed to the user in {@link BookingInformationAndCreation} @@ -34,6 +35,8 @@ interface IBookingInformationAndCreation { * uses the implementation of{@link CreateBookingSection} */ enableNetworkRequests?: boolean + + lodgePrices: LodgePricingProps } /** @@ -43,7 +46,8 @@ interface IBookingInformationAndCreation { const BookingInformationAndCreation = ({ bookingCreationProps, lodgeInfoProps, - enableNetworkRequests + enableNetworkRequests, + lodgePrices }: IBookingInformationAndCreation) => { const params = useSearchParams() @@ -66,9 +70,14 @@ const BookingInformationAndCreation = ({ ) case "booking-creation": if (enableNetworkRequests) { - return + return } else { - return + return ( + + ) } } } diff --git a/client/src/services/AppData/AppDataService.ts b/client/src/services/AppData/AppDataService.ts index b69e77530..d9a721d6f 100644 --- a/client/src/services/AppData/AppDataService.ts +++ b/client/src/services/AppData/AppDataService.ts @@ -1,7 +1,8 @@ import { MembershipTypes } from "@/models/Payment" import fetchClient from "@/services/OpenApiFetchClient" +import { DEFAULT_NORMAL_PRICE, DEFAULT_SPECIAL_PRICE } from "@/utils/Constants" -export type Prices = { +export type MembershipPrices = { title: string name: MembershipTypes priceString: string @@ -9,6 +10,20 @@ export type Prices = { extraInfo?: string } +/** + * Helper type to be used when components consume information about the lodge prices + */ +export interface LodgePricingProps { + /** + * Price (per night) for when a user books the lodge + */ + normal: number + /** + * Price (per night) for when a user books a single Friday or Saturday + */ + moreExpensive: number +} + const MembershipLongNames = { ALL_UOA_STUDENTS: "All UoA Students", NON_STUDENT_RETURNING: "Non-Student: Returning", @@ -16,7 +31,7 @@ const MembershipLongNames = { ALL_OTHER_STUDENTS: "All Other Students" } as const -const fallbackData: Prices[] = [ +const fallbackData: MembershipPrices[] = [ { title: MembershipLongNames.ALL_UOA_STUDENTS, name: "uoa_student", @@ -46,7 +61,9 @@ const membershipOrder = { new_non_student: 4 } -const sortMembershipPrices = (prices: Prices[]): Prices[] => { +const sortMembershipPrices = ( + prices: MembershipPrices[] +): MembershipPrices[] => { return prices.sort( (a, b) => membershipOrder[a.name] - membershipOrder[b.name] ) @@ -61,12 +78,40 @@ const AppDataService = { } return data }, - getMembershipPricingDetails: async function (): Promise { + getLodgePrices: async function (): Promise { + try { + const { data } = await fetchClient.GET("/payment/lodge_prices") + const priceList = data?.data + + const normalPrice = priceList?.find( + (price) => price.name === "normal" + )?.displayPrice + const moreExpensivePrice = priceList?.find( + (price) => price.name === "single_friday_or_saturday" + )?.displayPrice + + return { + normal: normalPrice + ? Number.parseInt(normalPrice) + : DEFAULT_NORMAL_PRICE, + moreExpensive: moreExpensivePrice + ? Number.parseInt(moreExpensivePrice) + : DEFAULT_SPECIAL_PRICE + } + } catch (e) { + console.error("Failed to fetch lodge prices", e) + return { + normal: DEFAULT_NORMAL_PRICE, + moreExpensive: DEFAULT_SPECIAL_PRICE + } + } + }, + getMembershipPricingDetails: async function (): Promise { try { const { data } = await fetchClient.GET("/payment/membership_prices") if (data && data.data) { - const transformedData: Prices[] = + const transformedData: MembershipPrices[] = data.data && data.data.map((data) => { let displayName diff --git a/client/src/utils/Constants.ts b/client/src/utils/Constants.ts index 955a24eca..d48e1a148 100644 --- a/client/src/utils/Constants.ts +++ b/client/src/utils/Constants.ts @@ -2,6 +2,9 @@ export const MS_IN_SECOND = 1000 as const export const DEFAULT_BOOKING_AVAILABILITY = 32 as const export const MEMBER_TABLE_MAX_DATA = 100 +export const DEFAULT_NORMAL_PRICE = 40 as const +export const DEFAULT_SPECIAL_PRICE = 60 as const + /** * Need to remove time data from this */ From f6409ddc1910c6bb2c7501c10103734069bcb3e6 Mon Sep 17 00:00:00 2001 From: bcho892 Date: Tue, 12 Nov 2024 14:05:48 +1300 Subject: [PATCH 2/7] add docs --- client/src/app/sections/utils/Pricing.ts | 20 +++++++++++++++++++ client/src/services/AppData/AppDataService.ts | 3 +++ 2 files changed, 23 insertions(+) diff --git a/client/src/app/sections/utils/Pricing.ts b/client/src/app/sections/utils/Pricing.ts index 3ff5d3904..982f69014 100644 --- a/client/src/app/sections/utils/Pricing.ts +++ b/client/src/app/sections/utils/Pricing.ts @@ -3,10 +3,30 @@ import { Pricing } from "@/components/utils/types" export const pricingNote = "*We have a discounted membership price on offer until Sunday 17th March so lock in now for a year of awesome memories!" +/** + * An object containing messages for the pricing banner. + */ export const pricingBannerMessages = { + /** + * The headline message for the pricing banner. + */ headline: "Great nightly rates" as const, + + /** + * A function that returns a formatted price information string. + * + * @param {number} normalPrice - The normal price per night. + * @returns {string} The formatted price information string. + */ priceInformation: (normalPrice: number) => `$${normalPrice} per night*` as const, + + /** + * A function that returns a formatted disclaimer message. + * + * @param {number} moreExpensivePrice - The price when booking a single Friday or Saturday. + * @returns {string} The formatted disclaimer message. + */ disclaimer: (moreExpensivePrice: number) => `*$${moreExpensivePrice} when booking a single Friday or Saturday` as const } as const diff --git a/client/src/services/AppData/AppDataService.ts b/client/src/services/AppData/AppDataService.ts index d9a721d6f..6bbcd2df1 100644 --- a/client/src/services/AppData/AppDataService.ts +++ b/client/src/services/AppData/AppDataService.ts @@ -78,6 +78,9 @@ const AppDataService = { } return data }, + /** + * Gives the prices of the two different types of bookings (uses a fallback otherwise) + */ getLodgePrices: async function (): Promise { try { const { data } = await fetchClient.GET("/payment/lodge_prices") From dba4976a2af44d24fe0432b3a6ad58eb091b3ab8 Mon Sep 17 00:00:00 2001 From: bcho892 Date: Tue, 12 Nov 2024 14:10:24 +1300 Subject: [PATCH 3/7] refactor and add docs --- client/src/app/Home.story.tsx | 10 +++++++++- client/src/app/HomeComponent.tsx | 13 +++++++------ client/src/app/bookings/page.tsx | 2 +- client/src/app/sections/utils/Pricing.ts | 9 +++------ .../BookingInformationAndCreation.tsx | 11 +++++++---- client/src/components/utils/types.tsx | 2 +- 6 files changed, 28 insertions(+), 19 deletions(-) diff --git a/client/src/app/Home.story.tsx b/client/src/app/Home.story.tsx index 9a74836d8..fe40ddcaf 100644 --- a/client/src/app/Home.story.tsx +++ b/client/src/app/Home.story.tsx @@ -9,5 +9,13 @@ const meta: Meta = { export default meta export const DefaultHomePage = () => { - return + return ( + + ) } diff --git a/client/src/app/HomeComponent.tsx b/client/src/app/HomeComponent.tsx index 498cef2da..1e7ef640b 100644 --- a/client/src/app/HomeComponent.tsx +++ b/client/src/app/HomeComponent.tsx @@ -9,7 +9,7 @@ import BenefitSection from "./sections/BenefitSection" import LandingSection from "./sections/LandingSection" import PricingSection from "./sections/PricingSection" import { benefits } from "./sections/utils/Benefits" -import { pricingBannerMessages } from "./sections/utils/Pricing" +import { lodgeBookingPricingBannerMessages } from "./sections/utils/Pricing" export type HomeProps = { /** @@ -51,11 +51,12 @@ const HomeComponent = ({ note={content?.pricing?.discount} pricings={membershipPricingData} bannerContent={{ - headline: pricingBannerMessages.headline, - priceInformation: pricingBannerMessages.priceInformation( - lodgePricing.normal - ), - disclaimer: pricingBannerMessages.priceInformation( + headline: lodgeBookingPricingBannerMessages.headline, + priceInformation: + lodgeBookingPricingBannerMessages.priceInformation( + lodgePricing.normal + ), + disclaimer: lodgeBookingPricingBannerMessages.priceInformation( lodgePricing.moreExpensive ) }} diff --git a/client/src/app/bookings/page.tsx b/client/src/app/bookings/page.tsx index 76319f521..b584be5bc 100644 --- a/client/src/app/bookings/page.tsx +++ b/client/src/app/bookings/page.tsx @@ -57,7 +57,7 @@ const BookingPage = async () => { <> , imageSrcs: processedImages diff --git a/client/src/app/sections/utils/Pricing.ts b/client/src/app/sections/utils/Pricing.ts index 982f69014..bea5c0bfa 100644 --- a/client/src/app/sections/utils/Pricing.ts +++ b/client/src/app/sections/utils/Pricing.ts @@ -1,12 +1,9 @@ -import { Pricing } from "@/components/utils/types" - -export const pricingNote = - "*We have a discounted membership price on offer until Sunday 17th March so lock in now for a year of awesome memories!" +import { MembershipPricing } from "@/components/utils/types" /** * An object containing messages for the pricing banner. */ -export const pricingBannerMessages = { +export const lodgeBookingPricingBannerMessages = { /** * The headline message for the pricing banner. */ @@ -31,7 +28,7 @@ export const pricingBannerMessages = { `*$${moreExpensivePrice} when booking a single Friday or Saturday` as const } as const -export const Pricings: Pricing[] = [ +export const MembershipPricings: MembershipPricing[] = [ { title: "UoA Student", discountedPrice: "$45", diff --git a/client/src/components/composite/Booking/BookingInformationAndCreation/BookingInformationAndCreation.tsx b/client/src/components/composite/Booking/BookingInformationAndCreation/BookingInformationAndCreation.tsx index 9ab48d6c1..8932c1704 100644 --- a/client/src/components/composite/Booking/BookingInformationAndCreation/BookingInformationAndCreation.tsx +++ b/client/src/components/composite/Booking/BookingInformationAndCreation/BookingInformationAndCreation.tsx @@ -36,7 +36,10 @@ interface IBookingInformationAndCreation { */ enableNetworkRequests?: boolean - lodgePrices: LodgePricingProps + /** + * How much each the different types of bookings cost, based on {@link LodgePricingProps} + */ + lodgePricing: LodgePricingProps } /** @@ -47,7 +50,7 @@ const BookingInformationAndCreation = ({ bookingCreationProps, lodgeInfoProps, enableNetworkRequests, - lodgePrices + lodgePricing }: IBookingInformationAndCreation) => { const params = useSearchParams() @@ -70,12 +73,12 @@ const BookingInformationAndCreation = ({ ) case "booking-creation": if (enableNetworkRequests) { - return + return } else { return ( ) } diff --git a/client/src/components/utils/types.tsx b/client/src/components/utils/types.tsx index c882503e8..2221fe212 100644 --- a/client/src/components/utils/types.tsx +++ b/client/src/components/utils/types.tsx @@ -17,7 +17,7 @@ export type Benefit = { /** * For use with the pricing cards that have the `home` variant */ -export type Pricing = { +export type MembershipPricing = { title: string originalPrice?: string discountedPrice: string From 6537eb966bd25d5abc12e374a1a97069133ac96e Mon Sep 17 00:00:00 2001 From: bcho892 Date: Tue, 12 Nov 2024 14:03:08 +1300 Subject: [PATCH 4/7] fetch prices dynamically --- client/src/app/Home.story.tsx | 2 +- client/src/app/HomeComponent.tsx | 34 ++++++++++-- client/src/app/bookings/page.tsx | 3 + client/src/app/page.tsx | 15 +++-- .../src/app/sections/PricingSection.story.tsx | 4 +- client/src/app/sections/PricingSection.tsx | 6 +- client/src/app/sections/utils/Pricing.ts | 14 +++-- .../BookingCreation/BookingCreation.story.tsx | 7 ++- .../BookingCreation/BookingCreation.tsx | 17 ++++-- .../ProtectedCreateBookingSection.tsx | 7 ++- .../BookingInformationAndCreation.tsx | 15 ++++- client/src/services/AppData/AppDataService.ts | 55 +++++++++++++++++-- client/src/utils/Constants.ts | 3 + 13 files changed, 145 insertions(+), 37 deletions(-) diff --git a/client/src/app/Home.story.tsx b/client/src/app/Home.story.tsx index df259fd08..9a74836d8 100644 --- a/client/src/app/Home.story.tsx +++ b/client/src/app/Home.story.tsx @@ -9,5 +9,5 @@ const meta: Meta = { export default meta export const DefaultHomePage = () => { - return + return } diff --git a/client/src/app/HomeComponent.tsx b/client/src/app/HomeComponent.tsx index 23d1c31ee..498cef2da 100644 --- a/client/src/app/HomeComponent.tsx +++ b/client/src/app/HomeComponent.tsx @@ -1,22 +1,36 @@ import { Footer } from "@/components/generic/Footer/Footer" import { HomePage } from "@/models/sanity/HomePage/Utils" -import { Prices } from "@/services/AppData/AppDataService" +import { + LodgePricingProps, + MembershipPrices +} from "@/services/AppData/AppDataService" import AboutSection from "./sections/AboutSection" import BenefitSection from "./sections/BenefitSection" import LandingSection from "./sections/LandingSection" import PricingSection from "./sections/PricingSection" import { benefits } from "./sections/utils/Benefits" -import { pricingBannerContent } from "./sections/utils/Pricing" +import { pricingBannerMessages } from "./sections/utils/Pricing" export type HomeProps = { - pricingData: Prices[] + /** + * Fetched prices for how much each type of membership costs + */ + membershipPricingData: MembershipPrices[] + /** + * Fetched prices showing how much it costs to book the lodge + */ + lodgePricing: LodgePricingProps content?: HomePage } /** * @deprecated do not use, use `WrappedHomeComponent` instead */ -const HomeComponent = ({ pricingData, content }: HomeProps) => { +const HomeComponent = ({ + membershipPricingData, + lodgePricing, + content +}: HomeProps) => { return ( <>
@@ -35,8 +49,16 @@ const HomeComponent = ({ pricingData, content }: HomeProps) => { />
diff --git a/client/src/app/bookings/page.tsx b/client/src/app/bookings/page.tsx index 3dfe9dd99..76319f521 100644 --- a/client/src/app/bookings/page.tsx +++ b/client/src/app/bookings/page.tsx @@ -8,12 +8,14 @@ import { PortableText } from "@portabletext/react" import { Policies, POLICIES_GROQ_QUERY } from "@/models/sanity/Policies/Utils" import BookingPolicyStorage from "./BookingPolicyStorage" import { PolicyWithTextBlocks } from "@/components/composite/Booking/BookingContext" +import AppDataService from "@/services/AppData/AppDataService" const BookingPage = async () => { const lodgeInfo = await sanityQuery( LODGE_INFORMATION_GROQ_QUERY ) + const lodgePrices = await AppDataService.getLodgePrices() /** * We assume there will be only one based on the way {@link LodgeInformation} * is set up in sanity @@ -55,6 +57,7 @@ const BookingPage = async () => { <> , imageSrcs: processedImages diff --git a/client/src/app/page.tsx b/client/src/app/page.tsx index 633a45892..d9530d896 100644 --- a/client/src/app/page.tsx +++ b/client/src/app/page.tsx @@ -1,22 +1,29 @@ -import AppDataService, { Prices } from "@/services/AppData/AppDataService" +import AppDataService, { + MembershipPrices +} from "@/services/AppData/AppDataService" import HomeComponent from "./HomeComponent" import { sanityQuery } from "../../sanity/lib/utils" import { HOME_PAGE_GROQ_QUERY, HomePage } from "@/models/sanity/HomePage/Utils" const Home = async () => { - let pricingData: Prices[] + let pricingData: MembershipPrices[] try { pricingData = await AppDataService.getMembershipPricingDetails() } catch (e) { pricingData = [] } + const lodgePricing = await AppDataService.getLodgePrices() + const [content] = await sanityQuery(HOME_PAGE_GROQ_QUERY) - console.log(content) return ( <> - + ) } diff --git a/client/src/app/sections/PricingSection.story.tsx b/client/src/app/sections/PricingSection.story.tsx index 9b7922085..9c0b74a45 100644 --- a/client/src/app/sections/PricingSection.story.tsx +++ b/client/src/app/sections/PricingSection.story.tsx @@ -10,7 +10,9 @@ const meta: Meta = { export default meta -export const DefaultPricingSection = ({ pricingData: data }: HomeProps) => { +export const DefaultPricingSection = ({ + membershipPricingData: data +}: HomeProps) => { return ( ( diff --git a/client/src/app/sections/utils/Pricing.ts b/client/src/app/sections/utils/Pricing.ts index 8c83fd7c5..3ff5d3904 100644 --- a/client/src/app/sections/utils/Pricing.ts +++ b/client/src/app/sections/utils/Pricing.ts @@ -1,13 +1,15 @@ -import { Pricing, PricingBannerContent } from "@/components/utils/types" +import { Pricing } from "@/components/utils/types" export const pricingNote = "*We have a discounted membership price on offer until Sunday 17th March so lock in now for a year of awesome memories!" -export const pricingBannerContent: PricingBannerContent = { - headline: "Great nightly rates", - priceInformation: "$40 per night*", - disclaimer: "*$60 when booking a single Friday or Saturday" -} +export const pricingBannerMessages = { + headline: "Great nightly rates" as const, + priceInformation: (normalPrice: number) => + `$${normalPrice} per night*` as const, + disclaimer: (moreExpensivePrice: number) => + `*$${moreExpensivePrice} when booking a single Friday or Saturday` as const +} as const export const Pricings: Pricing[] = [ { diff --git a/client/src/components/composite/Booking/BookingCreation/BookingCreation.story.tsx b/client/src/components/composite/Booking/BookingCreation/BookingCreation.story.tsx index 92c0bb589..1890255a1 100644 --- a/client/src/components/composite/Booking/BookingCreation/BookingCreation.story.tsx +++ b/client/src/components/composite/Booking/BookingCreation/BookingCreation.story.tsx @@ -10,5 +10,10 @@ type Story = StoryObj export const DefaultCreateBookingPage: Story = { decorators: [(Story) => ], - args: {} + args: { + lodgePrices: { + normal: 69, + moreExpensive: 420 + } + } } diff --git a/client/src/components/composite/Booking/BookingCreation/BookingCreation.tsx b/client/src/components/composite/Booking/BookingCreation/BookingCreation.tsx index 019872d77..6a75d6f56 100644 --- a/client/src/components/composite/Booking/BookingCreation/BookingCreation.tsx +++ b/client/src/components/composite/Booking/BookingCreation/BookingCreation.tsx @@ -16,6 +16,7 @@ import { import { Timestamp } from "firebase/firestore" import Checkbox from "@/components/generic/Checkbox/Checkbox" import { DateRange, DateUtils } from "@/components/utils/DateUtils" +import { LodgePricingProps } from "@/services/AppData/AppDataService" /* * Swaps around dates if invalid @@ -67,10 +68,9 @@ export interface ICreateBookingSection { * If a request related to creating/fetching a booking is in progress */ isPending?: boolean -} -const NORMAL_PRICE = 40 as const -const SPECIAL_PRICE = 60 as const + lodgePrices: LodgePricingProps +} /** * A notification to the user informing the actual @@ -110,13 +110,20 @@ export const CreateBookingSection = ({ handleBookingCreation, handleAllergyChange, hasExistingSession, - isPending + isPending, + lodgePrices }: ICreateBookingSection) => { const [selectedDateRange, setSelectedDateRange] = useState({ startDate: new Date(), endDate: new Date() }) + /** + * Derive prices from the props + */ + const NORMAL_PRICE = lodgePrices.normal + const SPECIAL_PRICE = lodgePrices.moreExpensive + const [isValidForCreation, setIsValidForCreation] = useState(false) const { startDate: currentStartDate, endDate: currentEndDate } = @@ -220,7 +227,7 @@ export const CreateBookingSection = ({ : NORMAL_PRICE return `$${requiredPrice} * ${nights} night${nights > 1 ? "s" : ""} = $${requiredPrice * nights}` as const - }, [currentStartDate, currentEndDate]) + }, [currentStartDate, currentEndDate, SPECIAL_PRICE, NORMAL_PRICE]) return ( <> diff --git a/client/src/components/composite/Booking/BookingCreation/ProtectedCreateBookingSection.tsx b/client/src/components/composite/Booking/BookingCreation/ProtectedCreateBookingSection.tsx index 3c7bd9c11..4eb84901a 100644 --- a/client/src/components/composite/Booking/BookingCreation/ProtectedCreateBookingSection.tsx +++ b/client/src/components/composite/Booking/BookingCreation/ProtectedCreateBookingSection.tsx @@ -3,14 +3,16 @@ import { useAppData } from "@/store/Store" import { SignUpNotif } from "@/components/generic/SignUpNotif/SignUpNotif" import { useAvailableBookingsQuery } from "@/services/Booking/BookingQueries" -import { CreateBookingSection } from "./BookingCreation" +import { CreateBookingSection, ICreateBookingSection } from "./BookingCreation" import { useContext, useEffect } from "react" import { BookingContext } from "../BookingContext" /** * @deprecated not for direct consumption on pages, use `BookingInformationAndCreation` instead */ -export const ProtectedCreateBookingSection = () => { +export const ProtectedCreateBookingSection = ({ + lodgePrices +}: Pick) => { const [{ currentUser, currentUserClaims }] = useAppData() const { data } = useAvailableBookingsQuery() @@ -38,6 +40,7 @@ export const ProtectedCreateBookingSection = () => { handleAllergyChange={setAllergies} hasExistingSession={!!clientSecret} isPending={isPending} + lodgePrices={lodgePrices} /> ) } diff --git a/client/src/components/composite/Booking/BookingInformationAndCreation/BookingInformationAndCreation.tsx b/client/src/components/composite/Booking/BookingInformationAndCreation/BookingInformationAndCreation.tsx index 5d89156a4..9ab48d6c1 100644 --- a/client/src/components/composite/Booking/BookingInformationAndCreation/BookingInformationAndCreation.tsx +++ b/client/src/components/composite/Booking/BookingInformationAndCreation/BookingInformationAndCreation.tsx @@ -8,6 +8,7 @@ import { } from "../BookingCreation/BookingCreation" import { ProtectedCreateBookingSection } from "../BookingCreation/ProtectedCreateBookingSection" import { useSearchParams } from "next/navigation" +import { LodgePricingProps } from "@/services/AppData/AppDataService" /** * Utility type determining what should be displayed to the user in {@link BookingInformationAndCreation} @@ -34,6 +35,8 @@ interface IBookingInformationAndCreation { * uses the implementation of{@link CreateBookingSection} */ enableNetworkRequests?: boolean + + lodgePrices: LodgePricingProps } /** @@ -43,7 +46,8 @@ interface IBookingInformationAndCreation { const BookingInformationAndCreation = ({ bookingCreationProps, lodgeInfoProps, - enableNetworkRequests + enableNetworkRequests, + lodgePrices }: IBookingInformationAndCreation) => { const params = useSearchParams() @@ -66,9 +70,14 @@ const BookingInformationAndCreation = ({ ) case "booking-creation": if (enableNetworkRequests) { - return + return } else { - return + return ( + + ) } } } diff --git a/client/src/services/AppData/AppDataService.ts b/client/src/services/AppData/AppDataService.ts index b69e77530..d9a721d6f 100644 --- a/client/src/services/AppData/AppDataService.ts +++ b/client/src/services/AppData/AppDataService.ts @@ -1,7 +1,8 @@ import { MembershipTypes } from "@/models/Payment" import fetchClient from "@/services/OpenApiFetchClient" +import { DEFAULT_NORMAL_PRICE, DEFAULT_SPECIAL_PRICE } from "@/utils/Constants" -export type Prices = { +export type MembershipPrices = { title: string name: MembershipTypes priceString: string @@ -9,6 +10,20 @@ export type Prices = { extraInfo?: string } +/** + * Helper type to be used when components consume information about the lodge prices + */ +export interface LodgePricingProps { + /** + * Price (per night) for when a user books the lodge + */ + normal: number + /** + * Price (per night) for when a user books a single Friday or Saturday + */ + moreExpensive: number +} + const MembershipLongNames = { ALL_UOA_STUDENTS: "All UoA Students", NON_STUDENT_RETURNING: "Non-Student: Returning", @@ -16,7 +31,7 @@ const MembershipLongNames = { ALL_OTHER_STUDENTS: "All Other Students" } as const -const fallbackData: Prices[] = [ +const fallbackData: MembershipPrices[] = [ { title: MembershipLongNames.ALL_UOA_STUDENTS, name: "uoa_student", @@ -46,7 +61,9 @@ const membershipOrder = { new_non_student: 4 } -const sortMembershipPrices = (prices: Prices[]): Prices[] => { +const sortMembershipPrices = ( + prices: MembershipPrices[] +): MembershipPrices[] => { return prices.sort( (a, b) => membershipOrder[a.name] - membershipOrder[b.name] ) @@ -61,12 +78,40 @@ const AppDataService = { } return data }, - getMembershipPricingDetails: async function (): Promise { + getLodgePrices: async function (): Promise { + try { + const { data } = await fetchClient.GET("/payment/lodge_prices") + const priceList = data?.data + + const normalPrice = priceList?.find( + (price) => price.name === "normal" + )?.displayPrice + const moreExpensivePrice = priceList?.find( + (price) => price.name === "single_friday_or_saturday" + )?.displayPrice + + return { + normal: normalPrice + ? Number.parseInt(normalPrice) + : DEFAULT_NORMAL_PRICE, + moreExpensive: moreExpensivePrice + ? Number.parseInt(moreExpensivePrice) + : DEFAULT_SPECIAL_PRICE + } + } catch (e) { + console.error("Failed to fetch lodge prices", e) + return { + normal: DEFAULT_NORMAL_PRICE, + moreExpensive: DEFAULT_SPECIAL_PRICE + } + } + }, + getMembershipPricingDetails: async function (): Promise { try { const { data } = await fetchClient.GET("/payment/membership_prices") if (data && data.data) { - const transformedData: Prices[] = + const transformedData: MembershipPrices[] = data.data && data.data.map((data) => { let displayName diff --git a/client/src/utils/Constants.ts b/client/src/utils/Constants.ts index 955a24eca..d48e1a148 100644 --- a/client/src/utils/Constants.ts +++ b/client/src/utils/Constants.ts @@ -2,6 +2,9 @@ export const MS_IN_SECOND = 1000 as const export const DEFAULT_BOOKING_AVAILABILITY = 32 as const export const MEMBER_TABLE_MAX_DATA = 100 +export const DEFAULT_NORMAL_PRICE = 40 as const +export const DEFAULT_SPECIAL_PRICE = 60 as const + /** * Need to remove time data from this */ From 0112c84d66abb91163b2050f4f4dc8d06615b0ef Mon Sep 17 00:00:00 2001 From: bcho892 Date: Tue, 12 Nov 2024 14:05:48 +1300 Subject: [PATCH 5/7] add docs --- client/src/app/sections/utils/Pricing.ts | 20 +++++++++++++++++++ client/src/services/AppData/AppDataService.ts | 3 +++ 2 files changed, 23 insertions(+) diff --git a/client/src/app/sections/utils/Pricing.ts b/client/src/app/sections/utils/Pricing.ts index 3ff5d3904..982f69014 100644 --- a/client/src/app/sections/utils/Pricing.ts +++ b/client/src/app/sections/utils/Pricing.ts @@ -3,10 +3,30 @@ import { Pricing } from "@/components/utils/types" export const pricingNote = "*We have a discounted membership price on offer until Sunday 17th March so lock in now for a year of awesome memories!" +/** + * An object containing messages for the pricing banner. + */ export const pricingBannerMessages = { + /** + * The headline message for the pricing banner. + */ headline: "Great nightly rates" as const, + + /** + * A function that returns a formatted price information string. + * + * @param {number} normalPrice - The normal price per night. + * @returns {string} The formatted price information string. + */ priceInformation: (normalPrice: number) => `$${normalPrice} per night*` as const, + + /** + * A function that returns a formatted disclaimer message. + * + * @param {number} moreExpensivePrice - The price when booking a single Friday or Saturday. + * @returns {string} The formatted disclaimer message. + */ disclaimer: (moreExpensivePrice: number) => `*$${moreExpensivePrice} when booking a single Friday or Saturday` as const } as const diff --git a/client/src/services/AppData/AppDataService.ts b/client/src/services/AppData/AppDataService.ts index d9a721d6f..6bbcd2df1 100644 --- a/client/src/services/AppData/AppDataService.ts +++ b/client/src/services/AppData/AppDataService.ts @@ -78,6 +78,9 @@ const AppDataService = { } return data }, + /** + * Gives the prices of the two different types of bookings (uses a fallback otherwise) + */ getLodgePrices: async function (): Promise { try { const { data } = await fetchClient.GET("/payment/lodge_prices") From a355b384dc2929c875b014f8a0cc028b71603252 Mon Sep 17 00:00:00 2001 From: bcho892 Date: Tue, 12 Nov 2024 14:10:24 +1300 Subject: [PATCH 6/7] refactor and add docs --- client/src/app/Home.story.tsx | 10 +++++++++- client/src/app/HomeComponent.tsx | 13 +++++++------ client/src/app/bookings/page.tsx | 2 +- client/src/app/sections/utils/Pricing.ts | 9 +++------ .../BookingInformationAndCreation.tsx | 11 +++++++---- client/src/components/utils/types.tsx | 2 +- 6 files changed, 28 insertions(+), 19 deletions(-) diff --git a/client/src/app/Home.story.tsx b/client/src/app/Home.story.tsx index 9a74836d8..fe40ddcaf 100644 --- a/client/src/app/Home.story.tsx +++ b/client/src/app/Home.story.tsx @@ -9,5 +9,13 @@ const meta: Meta = { export default meta export const DefaultHomePage = () => { - return + return ( + + ) } diff --git a/client/src/app/HomeComponent.tsx b/client/src/app/HomeComponent.tsx index 498cef2da..1e7ef640b 100644 --- a/client/src/app/HomeComponent.tsx +++ b/client/src/app/HomeComponent.tsx @@ -9,7 +9,7 @@ import BenefitSection from "./sections/BenefitSection" import LandingSection from "./sections/LandingSection" import PricingSection from "./sections/PricingSection" import { benefits } from "./sections/utils/Benefits" -import { pricingBannerMessages } from "./sections/utils/Pricing" +import { lodgeBookingPricingBannerMessages } from "./sections/utils/Pricing" export type HomeProps = { /** @@ -51,11 +51,12 @@ const HomeComponent = ({ note={content?.pricing?.discount} pricings={membershipPricingData} bannerContent={{ - headline: pricingBannerMessages.headline, - priceInformation: pricingBannerMessages.priceInformation( - lodgePricing.normal - ), - disclaimer: pricingBannerMessages.priceInformation( + headline: lodgeBookingPricingBannerMessages.headline, + priceInformation: + lodgeBookingPricingBannerMessages.priceInformation( + lodgePricing.normal + ), + disclaimer: lodgeBookingPricingBannerMessages.priceInformation( lodgePricing.moreExpensive ) }} diff --git a/client/src/app/bookings/page.tsx b/client/src/app/bookings/page.tsx index 76319f521..b584be5bc 100644 --- a/client/src/app/bookings/page.tsx +++ b/client/src/app/bookings/page.tsx @@ -57,7 +57,7 @@ const BookingPage = async () => { <> , imageSrcs: processedImages diff --git a/client/src/app/sections/utils/Pricing.ts b/client/src/app/sections/utils/Pricing.ts index 982f69014..bea5c0bfa 100644 --- a/client/src/app/sections/utils/Pricing.ts +++ b/client/src/app/sections/utils/Pricing.ts @@ -1,12 +1,9 @@ -import { Pricing } from "@/components/utils/types" - -export const pricingNote = - "*We have a discounted membership price on offer until Sunday 17th March so lock in now for a year of awesome memories!" +import { MembershipPricing } from "@/components/utils/types" /** * An object containing messages for the pricing banner. */ -export const pricingBannerMessages = { +export const lodgeBookingPricingBannerMessages = { /** * The headline message for the pricing banner. */ @@ -31,7 +28,7 @@ export const pricingBannerMessages = { `*$${moreExpensivePrice} when booking a single Friday or Saturday` as const } as const -export const Pricings: Pricing[] = [ +export const MembershipPricings: MembershipPricing[] = [ { title: "UoA Student", discountedPrice: "$45", diff --git a/client/src/components/composite/Booking/BookingInformationAndCreation/BookingInformationAndCreation.tsx b/client/src/components/composite/Booking/BookingInformationAndCreation/BookingInformationAndCreation.tsx index 9ab48d6c1..8932c1704 100644 --- a/client/src/components/composite/Booking/BookingInformationAndCreation/BookingInformationAndCreation.tsx +++ b/client/src/components/composite/Booking/BookingInformationAndCreation/BookingInformationAndCreation.tsx @@ -36,7 +36,10 @@ interface IBookingInformationAndCreation { */ enableNetworkRequests?: boolean - lodgePrices: LodgePricingProps + /** + * How much each the different types of bookings cost, based on {@link LodgePricingProps} + */ + lodgePricing: LodgePricingProps } /** @@ -47,7 +50,7 @@ const BookingInformationAndCreation = ({ bookingCreationProps, lodgeInfoProps, enableNetworkRequests, - lodgePrices + lodgePricing }: IBookingInformationAndCreation) => { const params = useSearchParams() @@ -70,12 +73,12 @@ const BookingInformationAndCreation = ({ ) case "booking-creation": if (enableNetworkRequests) { - return + return } else { return ( ) } diff --git a/client/src/components/utils/types.tsx b/client/src/components/utils/types.tsx index c882503e8..2221fe212 100644 --- a/client/src/components/utils/types.tsx +++ b/client/src/components/utils/types.tsx @@ -17,7 +17,7 @@ export type Benefit = { /** * For use with the pricing cards that have the `home` variant */ -export type Pricing = { +export type MembershipPricing = { title: string originalPrice?: string discountedPrice: string From f66e29c7a038b0ea1dd7bbf8c1db027f2c34a06f Mon Sep 17 00:00:00 2001 From: bcho892 Date: Tue, 12 Nov 2024 14:15:50 +1300 Subject: [PATCH 7/7] update stories --- client/src/app/sections/PricingSection.story.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/src/app/sections/PricingSection.story.tsx b/client/src/app/sections/PricingSection.story.tsx index 9c0b74a45..98dbad530 100644 --- a/client/src/app/sections/PricingSection.story.tsx +++ b/client/src/app/sections/PricingSection.story.tsx @@ -1,7 +1,6 @@ import type { Meta } from "@storybook/react" import PricingSection from "./PricingSection" -import { pricingBannerContent, pricingNote } from "./utils/Pricing" import { HomeProps } from "../HomeComponent" const meta: Meta = { @@ -16,8 +15,11 @@ export const DefaultPricingSection = ({ return ( ) }