forked from Somnia/SomniaRoomsApp
Added config var usage: Use SomniaRooms config vars.
Improvements to app footer and header.
This commit is contained in:
@@ -2,11 +2,13 @@ import { Separator } from "../ui/separator";
|
||||
import { Card, CardContent } from "../ui/card";
|
||||
import { Typography } from "../ui/typography";
|
||||
import { UserIcon } from "lucide-react";
|
||||
import { RoomCategory, RoomCategoryPhoto } from "@/model/RoomCategory";
|
||||
import { RoomCategory } from "@/model/RoomCategory";
|
||||
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 { Icon } from "@iconify-icon/react";
|
||||
import { useConfigVariable } from "@/utils/hooks";
|
||||
import { eConfigVarKeys } from "@/model/ConfigVar";
|
||||
|
||||
interface RoomItemCardProps {
|
||||
room: RoomCategory;
|
||||
@@ -27,11 +29,18 @@ export function RoomItemCard({
|
||||
room = defaultRoom,
|
||||
className,
|
||||
}: RoomItemCardProps) {
|
||||
const { value: varPhotoAspect } = useConfigVariable(
|
||||
eConfigVarKeys.varPhotoAspectRatio,
|
||||
"4/3",
|
||||
);
|
||||
|
||||
//FIXME: Use PhotoOrder.
|
||||
const mainPhoto: RoomCategoryPhoto | undefined =
|
||||
room.roomCategoryPhotos.length > 0
|
||||
? room.roomCategoryPhotos.at(0)
|
||||
: undefined;
|
||||
const getRoomMainPhoto = () => {
|
||||
if (room.roomCategoryPhotos.length < 0) {
|
||||
return defaultRoomImg;
|
||||
}
|
||||
return room.roomCategoryPhotos.at(0)?.path ?? defaultRoomImg;
|
||||
};
|
||||
|
||||
const bedTypeIconList = room.roomCategoryBedTypes.map((e) => {
|
||||
return (
|
||||
@@ -45,6 +54,7 @@ export function RoomItemCard({
|
||||
);
|
||||
});
|
||||
|
||||
const photoAspectRationClassName = "aspect-" + varPhotoAspect;
|
||||
return (
|
||||
<Card key={room.id} className={cn(className, "relative", "py-0")}>
|
||||
{/* <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">
|
||||
<div className="">
|
||||
<img
|
||||
src={mainPhoto?.path ?? roomImg}
|
||||
className="aspect-4/3 w-full rounded-t-md object-cover"
|
||||
src={getRoomMainPhoto()}
|
||||
className={
|
||||
photoAspectRationClassName + " w-full rounded-t-md object-cover"
|
||||
}
|
||||
></img>
|
||||
<Separator className="my-0 border-2" />
|
||||
</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() {
|
||||
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 (
|
||||
<>
|
||||
<img
|
||||
src={varCompanyLogo}
|
||||
alt="company-logo"
|
||||
className={logoSizeClass}
|
||||
/>
|
||||
<div>
|
||||
<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 (
|
||||
<div className="hidden flex-col bg-secondary">
|
||||
<div className="">Created with Love by me :)</div>
|
||||
<div className="">
|
||||
<p>Más información</p>
|
||||
<ul className="list-outside">
|
||||
<li>Info</li>
|
||||
<li>Info2</li>
|
||||
<li>Info3</li>
|
||||
<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>
|
||||
</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 { Typography } from "./ui/typography";
|
||||
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() {
|
||||
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 (
|
||||
<div className="relative flex flex-row items-center gap-2">
|
||||
<img src={varCompanyLogo} className={logoSizeClass}></img>
|
||||
<Typography variant={"largeText"} className="text-nowrap">
|
||||
{varCompanyName}
|
||||
</Typography>
|
||||
<a href={varCompanyWeb} className="absolute h-full w-full"></a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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">
|
||||
<img src="public/SR2.svg" className="w-[64px]"></img>
|
||||
<Typography variant={"largeText"}>Somnia Rooms</Typography>
|
||||
<a
|
||||
href="https://duck.com"
|
||||
className="absolute h-full w-full"
|
||||
></a>
|
||||
<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`}>
|
||||
<Button variant="ghost" className="h-15 hover:cursor-pointer">
|
||||
<HomeIcon className="mx-auto" />
|
||||
Home
|
||||
</Button>
|
||||
</Link>
|
||||
<Link to={`search`}>
|
||||
<Button variant="ghost" className="h-15 hover:cursor-pointer">
|
||||
<BookAIcon className="mx-auto" />
|
||||
Reservar
|
||||
</Button>
|
||||
</Link>
|
||||
<Link to={`home`}>
|
||||
<Button variant="ghost" className="h-15 hover:cursor-pointer">
|
||||
<Bed className="mx-auto" />
|
||||
Mi reserva
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
<ModeToggle />
|
||||
</div>
|
||||
<Button variant="ghost" className="h-15">
|
||||
<Link to={`home`}>
|
||||
<HomeIcon className="mx-auto" />
|
||||
Home
|
||||
</Link>
|
||||
</Button>
|
||||
<Button variant="ghost" className="h-15">
|
||||
<Link to={`search`}>
|
||||
<BookAIcon className="mx-auto" />
|
||||
Reservar
|
||||
</Link>
|
||||
</Button>
|
||||
<ModeToggle />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,22 +2,36 @@ import * as React from "react";
|
||||
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
||||
|
||||
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({
|
||||
className,
|
||||
variant,
|
||||
orientation = "horizontal",
|
||||
decorative = true,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
|
||||
}: React.ComponentProps<typeof SeparatorPrimitive.Root> &
|
||||
VariantProps<typeof separatorVariants>) {
|
||||
return (
|
||||
<SeparatorPrimitive.Root
|
||||
data-slot="separator"
|
||||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"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,
|
||||
)}
|
||||
className={cn(separatorVariants({ variant, className }))}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ function Root() {
|
||||
<>
|
||||
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
|
||||
<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 />
|
||||
</div>
|
||||
<Toaster />
|
||||
|
||||
@@ -13,14 +13,18 @@ import { LoaderIcon } from "lucide-react";
|
||||
import "./home-page.css";
|
||||
import { useAppStore } from "@/store";
|
||||
import { isValidSearchFilter, makeDayUTCDate } from "@/utils/utils";
|
||||
import { useConfigVariable } from "@/utils/hooks";
|
||||
import { eConfigVarKeys } from "@/model/ConfigVar";
|
||||
|
||||
function Homepage() {
|
||||
|
||||
const appSearchFilter = useAppStore((state) => state.searchFilter);
|
||||
const setAppSearchFilter = useAppStore((state) => state.setSearchFilter);
|
||||
const setAppBookingState = useAppStore((state) => state.setBookingState);
|
||||
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 onBtSearchClick = (filter: SearchFilter) => {
|
||||
@@ -44,15 +48,15 @@ function Homepage() {
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<Typography variant={"h1"}>Somnia Rooms</Typography>
|
||||
<Separator className="my-10" />
|
||||
<Typography variant={"h1"}>{varCompanyName}</Typography>
|
||||
<Separator />
|
||||
<SearchFilterCard
|
||||
onSearchClick={onBtSearchClick}
|
||||
initialsearchFilter={appSearchFilter}
|
||||
/>
|
||||
<Separator className="my-10" />
|
||||
<Separator />
|
||||
<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">
|
||||
<LoaderIcon />
|
||||
@@ -62,8 +66,8 @@ function Homepage() {
|
||||
</Skeleton>
|
||||
</>
|
||||
)}
|
||||
<Separator className="my-10" />
|
||||
<BackendConnectionStatus isLoading={isLoading} isError={isError} />
|
||||
<Separator />
|
||||
<BackendConnectionStatus isLoading={isLoadingCategories} isError={isError} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ function SearchPage() {
|
||||
const pageContentLoaded = (
|
||||
<div className="2xl:grid 2xl:grid-cols-60/40 2xl:gap-6">
|
||||
<div className="flex flex-col gap-5">{roomList}</div>
|
||||
<Separator className="my-10 2xl:hidden" />
|
||||
<Separator className="2xl:hidden" />
|
||||
<BookingDetails
|
||||
onBookingConfirm={onBookingContinue}
|
||||
mode={"Book"}
|
||||
@@ -124,9 +124,9 @@ function SearchPage() {
|
||||
onSearchClick={onBtSearchClick}
|
||||
initialsearchFilter={appSearchFilter}
|
||||
/>
|
||||
<Separator className="my-10" />
|
||||
<Separator />
|
||||
{getPageContent()}
|
||||
<Separator className="my-10" />
|
||||
<Separator />
|
||||
<BackendConnectionStatus isLoading={isLoading} isError={isError} />
|
||||
</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";
|
||||
Reference in New Issue
Block a user