Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

660 frontend put lodge schemas on bookings page #763

Merged
merged 3 commits into from
Aug 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"generate-types": "openapi-typescript ../server/src/middleware/__generated__/swagger.json -o ./src/models/__generated__/schema.d.ts "
},
"dependencies": {
"@portabletext/react": "^3.1.0",
"@sanity/image-url": "1",
"@sanity/vision": "3",
"@stripe/react-stripe-js": "^2.7.3",
Expand Down
4 changes: 2 additions & 2 deletions client/sanity/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ export class SanityImageUrl {
}

/**
* Appends the height query parameter to the URL.
* Appends the width query parameter to the URL.
*
* @param h - The height in pixels to set the image to.
* @param w - width height in pixels to set the image to.
*/
public width(w: number) {
this.url.searchParams.append("w", w.toString())
Expand Down
43 changes: 39 additions & 4 deletions client/src/app/bookings/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,46 @@
"use client"
import BookingInformationAndCreation from "@/components/composite/Booking/BookingInformationAndCreation/BookingInformationAndCreation"
import { SanityImageUrl, sanityQuery } from "../../../sanity/lib/utils"
import {
LODGE_INFORMATION_GROQ_QUERY,
LodgeInformation
} from "@/models/sanity/LodgeInfo/Utils"
import { PortableText } from "@portabletext/react"

import { ProtectedCreateBookingSection } from "@/components/composite/Booking/BookingCreation/ProtectedCreateBookingSection"
const BookingPage = async () => {
const lodgeInfo = await sanityQuery<LodgeInformation[]>(
LODGE_INFORMATION_GROQ_QUERY
)

/**
* We assume there will be only one based on the way {@link LodgeInformation}
* is set up in sanity
*/
const lodgeInfoSingleton = lodgeInfo[0]

const RenderedContent = () => {
return (
lodgeInfoSingleton.information && (
<PortableText value={lodgeInfoSingleton.information} />
)
)
}

const processedImages =
lodgeInfoSingleton.imageUrls?.map((item) =>
item.url
? new SanityImageUrl(item.url).autoFormat().width(700).toString()
: ""
) || []

const BookingPage = () => {
return (
<>
<ProtectedCreateBookingSection />
<BookingInformationAndCreation
enableNetworkRequests
lodgeInfoProps={{
children: <RenderedContent />,
images: processedImages
}}
/>
</>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const handleDateRangeInputChange = (
}
}

interface ICreateBookingSection {
export interface ICreateBookingSection {
/**
* The "unfiltered" booking slots for processing
*/
Expand Down Expand Up @@ -102,6 +102,9 @@ const ActualBookingStayRange = ({
)
}

/**
* @deprecated not for direct consumption on pages
*/
export const CreateBookingSection = ({
bookingSlots = [],
handleBookingCreation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { CreateBookingSection } from "./BookingCreation"
import { useContext, useEffect } from "react"
import { BookingContext } from "../BookingContext"

/**
* @deprecated not for direct consumption on pages, use `BookingInformationAndCreation` instead
*/
export const ProtectedCreateBookingSection = () => {
const [{ currentUser, currentUserClaims }] = useAppData()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ type props = IBookingInfoProps

const Divider = () => <span className="bg-dark-blue-100 my-3 h-[1px] w-full" />

/**
* Do **not** confuse with `LodgfeInfoComponent` which is used for display on the `/bookings` route
*/
const BookingInfoComponent = ({
pricePerNight,
priceSingleFridayOrSaturday
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { Meta, StoryObj } from "@storybook/react"
import BookingInformationAndCreation from "./BookingInformationAndCreation"

const meta: Meta<typeof BookingInformationAndCreation> = {
component: BookingInformationAndCreation
}

export default meta

type Story = StoryObj<typeof meta>

export const DefaultBookingInformationAndCreation: Story = {
args: {
lodgeInfoProps: {
children: (
<>
<h2 className="italic">About our lodge</h2>
<p>
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Sit
molestiae repellendus nulla voluptatibus iure! Quasi earum quis
velit facilis mollitia minus a consequuntur blanditiis, excepturi
omnis harum laudantium ad dolores.
</p>
</>
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"use client"

import { useState } from "react"
import LodgeInfo, { ILodgeInfo } from "../../LodgeInfo/LodgeInfo"
import {
CreateBookingSection,
ICreateBookingSection
} from "../BookingCreation/BookingCreation"
import { ProtectedCreateBookingSection } from "../BookingCreation/ProtectedCreateBookingSection"
import { useSearchParams } from "next/navigation"

/**
* Utility type determining what should be displayed to the user in {@link BookingInformationAndCreation}
*/
type BookingStages = "booking-information" | "booking-creation"

interface IBookingInformationAndCreation {
/**
* The required props for {@link CreateBookingSection}
*
* Optional if {@link enableNetworkRequests} is set to `false`
*/
bookingCreationProps?: ICreateBookingSection

/**
* The required props for {@link LodgeInfo}
*/
lodgeInfoProps?: Omit<ILodgeInfo, "handleBookLodgeClick">

/**
* Only set to `true` if embedding on page, for storybook purposes keep as false.
*
* Uses the {@link ProtectedCreateBookingSection} component if set to `true` otherwise
* uses the implementation of{@link CreateBookingSection}
*/
enableNetworkRequests?: boolean
}

/**
* Wrapper component that handles presentation for the booking creation page,
* with the information screen to allow users to know more about the lodge
*/
const BookingInformationAndCreation = ({
bookingCreationProps,
lodgeInfoProps,
enableNetworkRequests
}: IBookingInformationAndCreation) => {
const params = useSearchParams()

const defaultStage: BookingStages =
params.get("skip-info") === "true"
? "booking-creation"
: "booking-information"

const [currentStage, setCurrentStage] = useState<BookingStages>(defaultStage)

switch (currentStage) {
case "booking-information":
return (
<LodgeInfo
{...lodgeInfoProps}
handleBookLodgeClick={() => {
setCurrentStage("booking-creation")
}}
/>
)
case "booking-creation":
if (enableNetworkRequests) {
return <ProtectedCreateBookingSection />
} else {
return <CreateBookingSection {...bookingCreationProps} />
}
}
}

export default BookingInformationAndCreation
18 changes: 13 additions & 5 deletions client/src/components/composite/LodgeInfo/LodgeInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,31 @@ import LodgeInfoComponent from "./LodgeInfoComponent/LodgeInfoComponent"
import LodgeInfoGallery from "./LodgeInfoGallery/LodgeInfoGallery"
import { ReactNode } from "react"

interface ILodgeInfo {
export interface ILodgeInfo {
/**
* **Pre-formatted** content that should be displayed to the user
*/
children?: ReactNode
/**
* List of image srcs
*/
images: string[]
handleBookLodgeClick: () => void
images?: string[]
/**
* Handler to be called once the user clicks the call to action
*/
handleBookLodgeClick?: () => void
}

const LodgeInfo = ({ children, images, handleBookLodgeClick }: ILodgeInfo) => {
return (
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<LodgeInfoGallery images={images}></LodgeInfoGallery>
<LodgeInfoGallery images={images || []}></LodgeInfoGallery>
</div>
<div>
<LodgeInfoComponent handleBookLodgeClick={handleBookLodgeClick}>
<LodgeInfoComponent
handleBookLodgeClick={() => handleBookLodgeClick?.()}
>
{children}
</LodgeInfoComponent>
</div>
Expand Down
13 changes: 12 additions & 1 deletion client/src/models/sanity/LodgeInfo/Utils.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,13 @@
import { PortableTextBlock } from "@portabletext/types"

export const LODGE_INFORMATION_GROQ_QUERY =
`[_type == "lodge-information"]` as const
`*[_type == "lodge-information"]{"imageUrls": images[]{"url": asset->url}, ...}` as const

type LodgeInformationImage = {
url?: string
}

export type LodgeInformation = {
imageUrls?: LodgeInformationImage[]
information?: PortableTextBlock[]
}
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10909,6 +10909,7 @@ __metadata:
resolution: "client@workspace:client"
dependencies:
"@chromatic-com/storybook": "npm:1.6.1"
"@portabletext/react": "npm:^3.1.0"
"@sanity/image-url": "npm:1"
"@sanity/vision": "npm:3"
"@storybook/addon-essentials": "npm:^8.1.11"
Expand Down
Loading