Added config var usage: Use SomniaRooms config vars.

Improvements to app footer and header.
This commit is contained in:
2025-10-13 13:51:27 +02:00
parent 3e77426d1d
commit 99653371a8
8 changed files with 247 additions and 60 deletions
+20 -8
View File
@@ -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>
+122 -9
View File
@@ -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 (
<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>
<>
<img
src={varCompanyLogo}
alt="company-logo"
className={logoSizeClass}
/>
<div>
<Typography variant={"p_inline"} className="text-nowrap">
{varCompanyName} - &copy; {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>
</div>
</div>
<Separator className="my-4" />
<Typography className="text-center" variant={"mutedText"}>
<a href={c_sominaRooms_webpage}>{c_somniaRooms_notice_text}</a>
</Typography>
</footer>
);
}
+55 -14
View File
@@ -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 (
<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>
<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>
<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`}>
<Button variant="ghost" className="h-15 hover:cursor-pointer">
<HomeIcon className="mx-auto" />
Home
</Link>
</Button>
<Button variant="ghost" className="h-15">
</Link>
<Link to={`search`}>
<Button variant="ghost" className="h-15 hover:cursor-pointer">
<BookAIcon className="mx-auto" />
Reservar
</Link>
</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>
</CardContent>
</Card>
</header>
);
}
+19 -5
View File
@@ -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
View File
@@ -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 />
+11 -7
View File
@@ -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} />
</>
);
}
+3 -3
View File
@@ -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>
);
+3
View File
@@ -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";