Compare commits
4
Commits
590d7f6fa1
...
70e483bc45
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70e483bc45 | ||
|
|
99653371a8 | ||
|
|
3e77426d1d | ||
|
|
550beffba8 |
@@ -9,6 +9,7 @@ export async function APISendCreateBooking(bookingData: NewBooking | null) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
const rs = await fetcherWithFilter<NewBookingResponse>(
|
const rs = await fetcherWithFilter<NewBookingResponse>(
|
||||||
"/api/booking/create",
|
"/api/booking/create",
|
||||||
bookingData,
|
bookingData,
|
||||||
@@ -18,4 +19,11 @@ export async function APISendCreateBooking(bookingData: NewBooking | null) {
|
|||||||
bookingResponse: rs,
|
bookingResponse: rs,
|
||||||
isError: false,
|
isError: false,
|
||||||
};
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
bookingResponse: null,
|
||||||
|
isError: true,
|
||||||
|
errorMessage: error,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { ConfigVar } from "@/model/ConfigVar";
|
||||||
|
import { fetcher, fetcherUnwraper } from "./backend";
|
||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
|
export function useAPIConfigVarList() {
|
||||||
|
const { data, error, isLoading } = useSWR<ConfigVar[]>(
|
||||||
|
"/api/config",
|
||||||
|
fetcherUnwraper,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
configVarList: data,
|
||||||
|
isLoading,
|
||||||
|
isError: error,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAPIConfigVar(key: string) {
|
||||||
|
const url = `/api/config/var/${key}`;
|
||||||
|
|
||||||
|
const { data, error, isLoading } = useSWR<ConfigVar>(url, fetcher);
|
||||||
|
|
||||||
|
return {
|
||||||
|
configVar: data,
|
||||||
|
isLoading,
|
||||||
|
isError: error,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -40,7 +40,7 @@ export function useAPIRoomPrices(filter: FareFilter | null) {
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
const { data, isLoading, error } = useSWR(
|
const { data, isLoading, error } = useSWR(
|
||||||
request ? ["/api/search/prices", request] : null,
|
request ? ["/api/search/pricesCategory", request] : null,
|
||||||
([url, request]) => fetcherUnwraperWithFilter<RoomPrice[]>(url, request),
|
([url, request]) => fetcherUnwraperWithFilter<RoomPrice[]>(url, request),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ import { Separator } from "../ui/separator";
|
|||||||
import { Card, CardContent } from "../ui/card";
|
import { Card, CardContent } from "../ui/card";
|
||||||
import { Typography } from "../ui/typography";
|
import { Typography } from "../ui/typography";
|
||||||
import { UserIcon } from "lucide-react";
|
import { UserIcon } from "lucide-react";
|
||||||
import { RoomCategory, RoomCategoryPhoto } from "@/model/RoomCategory";
|
import { RoomCategory } from "@/model/RoomCategory";
|
||||||
import { IconWithTooltip } from "../ui/icon-number-tooltip";
|
import { IconWithTooltip } from "../ui/icon-number-tooltip";
|
||||||
import roomImg from "@/res/SomniaRoomImage.svg";
|
import defaultRoomImg from "@/res/SomniaRoomImage.svg";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Icon } from "@iconify-icon/react";
|
import { Icon } from "@iconify-icon/react";
|
||||||
|
import { useConfigVariable } from "@/utils/hooks";
|
||||||
|
import { eConfigVarKeys } from "@/model/ConfigVar";
|
||||||
|
|
||||||
interface RoomItemCardProps {
|
interface RoomItemCardProps {
|
||||||
room: RoomCategory;
|
room: RoomCategory;
|
||||||
@@ -27,11 +29,18 @@ export function RoomItemCard({
|
|||||||
room = defaultRoom,
|
room = defaultRoom,
|
||||||
className,
|
className,
|
||||||
}: RoomItemCardProps) {
|
}: RoomItemCardProps) {
|
||||||
|
const { value: varPhotoAspect } = useConfigVariable(
|
||||||
|
eConfigVarKeys.varPhotoAspectRatio,
|
||||||
|
"4/3",
|
||||||
|
);
|
||||||
|
|
||||||
//FIXME: Use PhotoOrder.
|
//FIXME: Use PhotoOrder.
|
||||||
const mainPhoto: RoomCategoryPhoto | undefined =
|
const getRoomMainPhoto = () => {
|
||||||
room.roomCategoryPhotos.length > 0
|
if (room.roomCategoryPhotos.length < 0) {
|
||||||
? room.roomCategoryPhotos.at(0)
|
return defaultRoomImg;
|
||||||
: undefined;
|
}
|
||||||
|
return room.roomCategoryPhotos.at(0)?.path ?? defaultRoomImg;
|
||||||
|
};
|
||||||
|
|
||||||
const bedTypeIconList = room.roomCategoryBedTypes.map((e) => {
|
const bedTypeIconList = room.roomCategoryBedTypes.map((e) => {
|
||||||
return (
|
return (
|
||||||
@@ -45,6 +54,7 @@ export function RoomItemCard({
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const photoAspectRationClassName = "aspect-" + varPhotoAspect;
|
||||||
return (
|
return (
|
||||||
<Card key={room.id} className={cn(className, "relative", "py-0")}>
|
<Card key={room.id} className={cn(className, "relative", "py-0")}>
|
||||||
{/* <CardHeader className="p-0 m-0 mb-5">
|
{/* <CardHeader className="p-0 m-0 mb-5">
|
||||||
@@ -52,8 +62,10 @@ export function RoomItemCard({
|
|||||||
<CardContent className="flex h-full w-full flex-col p-0">
|
<CardContent className="flex h-full w-full flex-col p-0">
|
||||||
<div className="">
|
<div className="">
|
||||||
<img
|
<img
|
||||||
src={mainPhoto?.path ?? roomImg}
|
src={getRoomMainPhoto()}
|
||||||
className="aspect-4/3 w-full rounded-t-md object-cover"
|
className={
|
||||||
|
photoAspectRationClassName + " w-full rounded-t-md object-cover"
|
||||||
|
}
|
||||||
></img>
|
></img>
|
||||||
<Separator className="my-0 border-2" />
|
<Separator className="my-0 border-2" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,16 +1,129 @@
|
|||||||
|
import { useConfigVariable } from "@/utils/hooks";
|
||||||
|
import { Separator } from "./ui/separator";
|
||||||
|
import { Typography } from "./ui/typography";
|
||||||
|
import { eConfigVarKeys } from "@/model/ConfigVar";
|
||||||
|
import {
|
||||||
|
c_sominaRooms_webpage,
|
||||||
|
c_somniaRooms_notice_text,
|
||||||
|
} from "@/utils/Consts";
|
||||||
|
import { Skeleton } from "./ui/skeleton";
|
||||||
|
import { Mail, Phone } from "lucide-react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
function AppFooter() {
|
function AppFooter() {
|
||||||
|
const { value: varCompanyLogo, isLoading: isLogoLoading } = useConfigVariable(
|
||||||
|
eConfigVarKeys.varCompanyLogo,
|
||||||
|
"public/SR2.svg",
|
||||||
|
);
|
||||||
|
const { value: varCompanyLogoSize, isLoading: isLogoSizeLoading } =
|
||||||
|
useConfigVariable(eConfigVarKeys.varCompanyLogoSize, "64px");
|
||||||
|
const { value: varCompanyName, isLoading: isNameLoading } = useConfigVariable(
|
||||||
|
eConfigVarKeys.varCompanyName,
|
||||||
|
"Somnia Rooms",
|
||||||
|
);
|
||||||
|
const { value: varCompanyAddress, isLoading: isAddressLoading } =
|
||||||
|
useConfigVariable(eConfigVarKeys.varCompanyAddress, "");
|
||||||
|
const { value: varCompanyPhone, isLoading: isPhoneLoading } =
|
||||||
|
useConfigVariable(eConfigVarKeys.varCompanyPhone, "");
|
||||||
|
const { value: varCompanyMail, isLoading: isMailLoading } = useConfigVariable(
|
||||||
|
eConfigVarKeys.varCompanyEmail,
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
|
||||||
|
const logoSizeClass = "w-[" + varCompanyLogoSize + "]";
|
||||||
|
|
||||||
|
const isLoading =
|
||||||
|
isLogoLoading ||
|
||||||
|
isLogoSizeLoading ||
|
||||||
|
isNameLoading ||
|
||||||
|
isAddressLoading ||
|
||||||
|
isPhoneLoading ||
|
||||||
|
isMailLoading;
|
||||||
|
const getCompanyElement = () => {
|
||||||
|
if (isLoading) {
|
||||||
|
return <Skeleton className="h-[64px]" />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="hidden flex-col bg-secondary">
|
<>
|
||||||
<div className="">Created with Love by me :)</div>
|
<img
|
||||||
<div className="">
|
src={varCompanyLogo}
|
||||||
<p>Más información</p>
|
alt="company-logo"
|
||||||
<ul className="list-outside">
|
className={logoSizeClass}
|
||||||
<li>Info</li>
|
/>
|
||||||
<li>Info2</li>
|
<div>
|
||||||
<li>Info3</li>
|
<Typography variant={"p_inline"} className="text-nowrap">
|
||||||
|
{varCompanyName} - © {new Date().getUTCFullYear()}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant={"p_inline"}>{varCompanyAddress}</Typography>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getContactElement = () => {
|
||||||
|
if (isLoading) {
|
||||||
|
return <Skeleton className="w-[64px]" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<li className="flex items-center gap-2">
|
||||||
|
<Phone size={"1.2em"} />
|
||||||
|
<Typography variant={"p_inline"}>{varCompanyPhone}</Typography>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-center gap-2">
|
||||||
|
<Mail size={"1.2em"} />
|
||||||
|
<Typography variant={"p_inline"}>{varCompanyMail}</Typography>
|
||||||
|
</li>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<footer className="w-full p-8 pt-0">
|
||||||
|
<Separator className="my-4" />
|
||||||
|
<div className="flex flex-row flex-wrap items-center justify-center gap-x-12 gap-y-6 text-center lg:justify-between">
|
||||||
|
<div className="flex flex-row items-center gap-2">
|
||||||
|
{getCompanyElement()}
|
||||||
|
</div>
|
||||||
|
<ul className="flex flex-wrap items-start gap-x-10 gap-y-2 px-4 py-4">
|
||||||
|
<li>
|
||||||
|
<ul className="text-start">
|
||||||
|
<li>
|
||||||
|
<Typography variant={"largeText"} className="text-start">
|
||||||
|
Legal
|
||||||
|
</Typography>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Typography asChild className="hover:text-blue-500">
|
||||||
|
<Link to={"terms"}>Terms and conditions</Link>
|
||||||
|
</Typography>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Typography asChild className="hover:text-blue-500">
|
||||||
|
<Link to={"privacy"}>Privacy policy</Link>
|
||||||
|
</Typography>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<Typography variant={"largeText"} className="text-start">
|
||||||
|
Contact Us
|
||||||
|
</Typography>
|
||||||
|
</li>
|
||||||
|
{getContactElement()}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<Separator className="my-4" />
|
||||||
|
<Typography className="text-center" variant={"mutedText"}>
|
||||||
|
<a href={c_sominaRooms_webpage}>{c_somniaRooms_notice_text}</a>
|
||||||
|
</Typography>
|
||||||
|
</footer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,37 +3,78 @@ import { Button } from "./ui/button";
|
|||||||
import { Card, CardContent } from "./ui/card";
|
import { Card, CardContent } from "./ui/card";
|
||||||
import { Typography } from "./ui/typography";
|
import { Typography } from "./ui/typography";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { BookAIcon, HomeIcon } from "lucide-react";
|
import { Bed, BookAIcon, HomeIcon } from "lucide-react";
|
||||||
|
import { eConfigVarKeys } from "@/model/ConfigVar";
|
||||||
|
import { Skeleton } from "./ui/skeleton";
|
||||||
|
import { useConfigVariable } from "@/utils/hooks";
|
||||||
|
|
||||||
function AppHeader() {
|
function AppHeader() {
|
||||||
|
const { value: varCompanyLogo, isLoading: isLogoLoading } = useConfigVariable(
|
||||||
|
eConfigVarKeys.varCompanyLogo,
|
||||||
|
"public/SR2.svg",
|
||||||
|
);
|
||||||
|
const { value: varCompanyLogoSize, isLoading: isLogoSizeLoading } =
|
||||||
|
useConfigVariable(eConfigVarKeys.varCompanyLogoSize, "64px");
|
||||||
|
const { value: varCompanyName, isLoading: isNameLoading } = useConfigVariable(
|
||||||
|
eConfigVarKeys.varCompanyName,
|
||||||
|
"Somnia Rooms",
|
||||||
|
);
|
||||||
|
const { value: varCompanyWeb, isLoading: isWebLoading } = useConfigVariable(
|
||||||
|
eConfigVarKeys.varCompanyWebsite,
|
||||||
|
"#",
|
||||||
|
);
|
||||||
|
|
||||||
|
const isLoading =
|
||||||
|
isLogoLoading || isNameLoading || isWebLoading || isLogoSizeLoading;
|
||||||
|
|
||||||
|
const getElementLogo = () => {
|
||||||
|
if (isLoading) {
|
||||||
|
return <Skeleton className="w-[64px]" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const logoSizeClass = "w-[" + varCompanyLogoSize + "]";
|
||||||
return (
|
return (
|
||||||
<Card className="mx-10 my-4 flex items-center py-0">
|
|
||||||
<CardContent className="w-full py-2">
|
|
||||||
<div className="flex items-center justify-around">
|
|
||||||
<div className="relative flex flex-row items-center gap-2">
|
<div className="relative flex flex-row items-center gap-2">
|
||||||
<img src="public/SR2.svg" className="w-[64px]"></img>
|
<img src={varCompanyLogo} className={logoSizeClass}></img>
|
||||||
<Typography variant={"largeText"}>Somnia Rooms</Typography>
|
<Typography variant={"largeText"} className="text-nowrap">
|
||||||
<a
|
{varCompanyName}
|
||||||
href="https://duck.com"
|
</Typography>
|
||||||
className="absolute h-full w-full"
|
<a href={varCompanyWeb} className="absolute h-full w-full"></a>
|
||||||
></a>
|
|
||||||
</div>
|
</div>
|
||||||
<Button variant="ghost" className="h-15">
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header>
|
||||||
|
<Card className="mx-10 my-4 items-center py-0">
|
||||||
|
<CardContent className="w-full py-2">
|
||||||
|
<div className="flex flex-col items-center justify-around gap-y-3 md:flex-row">
|
||||||
|
{getElementLogo()}
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
<Link to={`home`}>
|
<Link to={`home`}>
|
||||||
|
<Button variant="ghost" className="h-15 hover:cursor-pointer">
|
||||||
<HomeIcon className="mx-auto" />
|
<HomeIcon className="mx-auto" />
|
||||||
Home
|
Home
|
||||||
</Link>
|
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" className="h-15">
|
</Link>
|
||||||
<Link to={`search`}>
|
<Link to={`search`}>
|
||||||
|
<Button variant="ghost" className="h-15 hover:cursor-pointer">
|
||||||
<BookAIcon className="mx-auto" />
|
<BookAIcon className="mx-auto" />
|
||||||
Reservar
|
Reservar
|
||||||
</Link>
|
|
||||||
</Button>
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<Link to={`home`}>
|
||||||
|
<Button variant="ghost" className="h-15 hover:cursor-pointer">
|
||||||
|
<Bed className="mx-auto" />
|
||||||
|
Mi reserva
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
<ModeToggle />
|
<ModeToggle />
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,22 +2,36 @@ import * as React from "react";
|
|||||||
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { cva, VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
|
const separatorVariants = cva(
|
||||||
|
"shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "my-10",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
function Separator({
|
function Separator({
|
||||||
className,
|
className,
|
||||||
|
variant,
|
||||||
orientation = "horizontal",
|
orientation = "horizontal",
|
||||||
decorative = true,
|
decorative = true,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
|
}: React.ComponentProps<typeof SeparatorPrimitive.Root> &
|
||||||
|
VariantProps<typeof separatorVariants>) {
|
||||||
return (
|
return (
|
||||||
<SeparatorPrimitive.Root
|
<SeparatorPrimitive.Root
|
||||||
data-slot="separator"
|
data-slot="separator"
|
||||||
decorative={decorative}
|
decorative={decorative}
|
||||||
orientation={orientation}
|
orientation={orientation}
|
||||||
className={cn(
|
className={cn(separatorVariants({ variant, className }))}
|
||||||
"shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
export type ConfigVar = {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum eConfigVarKeys {
|
||||||
|
varCompanyName = "CompanyName",
|
||||||
|
varCompanyWebsite = "CompanyWebsite",
|
||||||
|
varCompanyLogo = "CompanyLogo",
|
||||||
|
varCompanyLogoSize = "CompanyLogoSize",
|
||||||
|
varCompanyEmail = "CompanyEmail",
|
||||||
|
varCompanyPhone = "CompanyPhone",
|
||||||
|
varCompanyAddress = "CompanyAddress",
|
||||||
|
varNightAmountForContactDialog = "NightAmountForContactDialog",
|
||||||
|
varGuestNumberForContactDialog = "GuestNumberForContactDialog",
|
||||||
|
varContactDialogShowBehaviour = "ContactDialogShowBehaviour",
|
||||||
|
varShowNonAvailableRooms = "ShowNonAvailableRooms",
|
||||||
|
varSomniaAttributionNotice = "SomniaAttributionNotice",
|
||||||
|
varPhotoAspectRatio = "PhotoAspectRatio",
|
||||||
|
}
|
||||||
+1
-1
@@ -10,7 +10,7 @@ function Root() {
|
|||||||
<>
|
<>
|
||||||
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
|
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
|
||||||
<AppHeader />
|
<AppHeader />
|
||||||
<div className="mx-auto w-full bg-background p-8 text-center text-foreground sm:w-[90%] md:w-[80%]">
|
<div className="mx-auto w-full min-h-[100vh] bg-background p-8 text-center text-foreground sm:w-[90%] md:w-[80%]">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
|
|||||||
@@ -19,19 +19,22 @@ import { useForm } from "react-hook-form";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { eBookingState } from "@/utils/AppStateManager";
|
||||||
|
|
||||||
enum PageState {
|
enum PageState {
|
||||||
noData,
|
|
||||||
showForm,
|
showForm,
|
||||||
sending,
|
sending,
|
||||||
created,
|
|
||||||
error,
|
error,
|
||||||
}
|
}
|
||||||
|
|
||||||
function BookPage() {
|
function BookPage() {
|
||||||
const [pageState, setPageState] = useState<PageState>(PageState.showForm);
|
const [pageState, setPageState] = useState<PageState>(PageState.showForm);
|
||||||
const bookingData = useAppStore((state) => state.currentBooking);
|
const bookingData = useAppStore((state) => state.currentBooking);
|
||||||
const setCustomerData = useAppStore((state) => state.currentBookingSetCustomer);
|
const appBookingState = useAppStore((state) => state.bookingState);
|
||||||
|
const setAppBookingState = useAppStore((state) => state.setBookingState);
|
||||||
|
const setCustomerData = useAppStore(
|
||||||
|
(state) => state.currentBookingSetCustomer,
|
||||||
|
);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const bookingForm = useForm<z.infer<typeof BookingFormSchema>>({
|
const bookingForm = useForm<z.infer<typeof BookingFormSchema>>({
|
||||||
@@ -67,16 +70,28 @@ function BookPage() {
|
|||||||
if (rs.isError) {
|
if (rs.isError) {
|
||||||
setPageState(PageState.error);
|
setPageState(PageState.error);
|
||||||
} else {
|
} else {
|
||||||
|
setAppBookingState(eBookingState.Booked);
|
||||||
navigate("/success", { state: rs.bookingResponse });
|
navigate("/success", { state: rs.bookingResponse });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!bookingData || !bookingData.customerDetails) {
|
//Redirect to home if no booking is active.
|
||||||
return (
|
if (
|
||||||
<Typography variant={"h2"}>You must select some rooms...</Typography>
|
!appBookingState ||
|
||||||
);
|
appBookingState.state != eBookingState.Booking ||
|
||||||
|
!bookingData
|
||||||
|
) {
|
||||||
|
navigate("/home");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const errorMsg = (
|
||||||
|
<div>
|
||||||
|
<Typography variant={"inlineCode"}>
|
||||||
|
Server error. Try again later!
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography variant={"h1"}>Booking Details</Typography>
|
<Typography variant={"h1"}>Booking Details</Typography>
|
||||||
@@ -150,6 +165,7 @@ function BookPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
{pageState == PageState.error ? errorMsg : null}
|
||||||
</div>
|
</div>
|
||||||
<Separator className="my-10 lg:hidden" />
|
<Separator className="my-10 lg:hidden" />
|
||||||
<BookingDetails mode="View"></BookingDetails>
|
<BookingDetails mode="View"></BookingDetails>
|
||||||
|
|||||||
@@ -13,14 +13,18 @@ import { LoaderIcon } from "lucide-react";
|
|||||||
import "./home-page.css";
|
import "./home-page.css";
|
||||||
import { useAppStore } from "@/store";
|
import { useAppStore } from "@/store";
|
||||||
import { isValidSearchFilter, makeDayUTCDate } from "@/utils/utils";
|
import { isValidSearchFilter, makeDayUTCDate } from "@/utils/utils";
|
||||||
|
import { useConfigVariable } from "@/utils/hooks";
|
||||||
|
import { eConfigVarKeys } from "@/model/ConfigVar";
|
||||||
|
|
||||||
function Homepage() {
|
function Homepage() {
|
||||||
|
|
||||||
const appSearchFilter = useAppStore((state) => state.searchFilter);
|
const appSearchFilter = useAppStore((state) => state.searchFilter);
|
||||||
const setAppSearchFilter = useAppStore((state) => state.setSearchFilter);
|
const setAppSearchFilter = useAppStore((state) => state.setSearchFilter);
|
||||||
const setAppBookingState = useAppStore((state) => state.setBookingState);
|
const setAppBookingState = useAppStore((state) => state.setBookingState);
|
||||||
const initAppBooking = useAppStore((state) => state.initCurrentBooking);
|
const initAppBooking = useAppStore((state) => state.initCurrentBooking);
|
||||||
|
|
||||||
const { roomCategoryList, isLoading, isError } = useAPIRoomCategoryList();
|
const { value: varCompanyName } = useConfigVariable(eConfigVarKeys.varCompanyName, "");
|
||||||
|
const { roomCategoryList, isLoading: isLoadingCategories, isError } = useAPIRoomCategoryList();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const onBtSearchClick = (filter: SearchFilter) => {
|
const onBtSearchClick = (filter: SearchFilter) => {
|
||||||
@@ -44,15 +48,15 @@ function Homepage() {
|
|||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography variant={"h1"}>Somnia Rooms</Typography>
|
<Typography variant={"h1"}>{varCompanyName}</Typography>
|
||||||
<Separator className="my-10" />
|
<Separator />
|
||||||
<SearchFilterCard
|
<SearchFilterCard
|
||||||
onSearchClick={onBtSearchClick}
|
onSearchClick={onBtSearchClick}
|
||||||
initialsearchFilter={appSearchFilter}
|
initialsearchFilter={appSearchFilter}
|
||||||
/>
|
/>
|
||||||
<Separator className="my-10" />
|
<Separator />
|
||||||
<div className="homePageRoomGrid grid gap-4">{roomList}</div>
|
<div className="homePageRoomGrid grid gap-4">{roomList}</div>
|
||||||
{isLoading && (
|
{isLoadingCategories && (
|
||||||
<>
|
<>
|
||||||
<Skeleton className="mx-auto flex h-[200px] w-full flex-col items-center justify-center">
|
<Skeleton className="mx-auto flex h-[200px] w-full flex-col items-center justify-center">
|
||||||
<LoaderIcon />
|
<LoaderIcon />
|
||||||
@@ -62,8 +66,8 @@ function Homepage() {
|
|||||||
</Skeleton>
|
</Skeleton>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Separator className="my-10" />
|
<Separator />
|
||||||
<BackendConnectionStatus isLoading={isLoading} isError={isError} />
|
<BackendConnectionStatus isLoading={isLoadingCategories} isError={isError} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ function SearchPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onBookingContinue = () => {
|
const onBookingContinue = () => {
|
||||||
|
setAppBookingState(eBookingState.Booking);
|
||||||
navigate("/book");
|
navigate("/book");
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -93,7 +94,7 @@ function SearchPage() {
|
|||||||
const pageContentLoaded = (
|
const pageContentLoaded = (
|
||||||
<div className="2xl:grid 2xl:grid-cols-60/40 2xl:gap-6">
|
<div className="2xl:grid 2xl:grid-cols-60/40 2xl:gap-6">
|
||||||
<div className="flex flex-col gap-5">{roomList}</div>
|
<div className="flex flex-col gap-5">{roomList}</div>
|
||||||
<Separator className="my-10 2xl:hidden" />
|
<Separator className="2xl:hidden" />
|
||||||
<BookingDetails
|
<BookingDetails
|
||||||
onBookingConfirm={onBookingContinue}
|
onBookingConfirm={onBookingContinue}
|
||||||
mode={"Book"}
|
mode={"Book"}
|
||||||
@@ -102,7 +103,9 @@ function SearchPage() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const getPageContent = () => {
|
const getPageContent = () => {
|
||||||
const isSearched = appBookingState.state == eBookingState.Searched;
|
const isSearched =
|
||||||
|
appBookingState.state == eBookingState.Searched ||
|
||||||
|
appBookingState.state == eBookingState.Booking;
|
||||||
if (!isSearched) {
|
if (!isSearched) {
|
||||||
return pageContentSearch;
|
return pageContentSearch;
|
||||||
}
|
}
|
||||||
@@ -121,9 +124,9 @@ function SearchPage() {
|
|||||||
onSearchClick={onBtSearchClick}
|
onSearchClick={onBtSearchClick}
|
||||||
initialsearchFilter={appSearchFilter}
|
initialsearchFilter={appSearchFilter}
|
||||||
/>
|
/>
|
||||||
<Separator className="my-10" />
|
<Separator />
|
||||||
{getPageContent()}
|
{getPageContent()}
|
||||||
<Separator className="my-10" />
|
<Separator />
|
||||||
<BackendConnectionStatus isLoading={isLoading} isError={isError} />
|
<BackendConnectionStatus isLoading={isLoading} isError={isError} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
export const c_somniaRooms_notice_text = "Powered by SomniaRooms, an Open-Source hospitality booking platform!";
|
||||||
|
export const c_sominaRooms_webpage = "https://somnia.dev";
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { useAPIConfigVarList } from "@/api/api-configVars";
|
||||||
|
import { getConfigVarFromList } from "./utils";
|
||||||
|
import { eConfigVarKeys } from "@/model/ConfigVar";
|
||||||
|
|
||||||
|
export function useConfigVariable(key: eConfigVarKeys, defaultValue: string) {
|
||||||
|
const { configVarList, isLoading } = useAPIConfigVarList();
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return {
|
||||||
|
value: defaultValue,
|
||||||
|
isLoading: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const configVar = getConfigVarFromList(configVarList, key);
|
||||||
|
|
||||||
|
return { value: configVar?.value ?? defaultValue, isLoading: isLoading };
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { AvailabilityFilter, FareFilter } from "@/model/RoomSearch";
|
import { AvailabilityFilter, FareFilter } from "@/model/RoomSearch";
|
||||||
import { SearchFilter } from "@/model/InternalTypes";
|
import { SearchFilter } from "@/model/InternalTypes";
|
||||||
|
import { ConfigVar } from "@/model/ConfigVar";
|
||||||
|
|
||||||
export function makeDayUTCDate(date: Date | undefined): Date {
|
export function makeDayUTCDate(date: Date | undefined): Date {
|
||||||
if (!date) {
|
if (!date) {
|
||||||
@@ -50,3 +51,14 @@ export function makeFareFilterFromSearchFilter(
|
|||||||
roomCategory: roomCategoryId,
|
roomCategory: roomCategoryId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getConfigVarFromList(
|
||||||
|
varList: ConfigVar[] | undefined,
|
||||||
|
key: string,
|
||||||
|
): ConfigVar | undefined {
|
||||||
|
if (!varList) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return varList.find((v) => v.key === key);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user