General improvements:

- Search control - Improved error messages.
- Fixed lint errors.
This commit is contained in:
2026-01-13 14:12:58 +01:00
parent 94312de6e2
commit b8ec6f7bdd
6 changed files with 42 additions and 24 deletions
+19 -10
View File
@@ -8,7 +8,6 @@ import { SearchIcon } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { SearchFilter } from "@/model/InternalTypes"; import { SearchFilter } from "@/model/InternalTypes";
import { DateRange } from "react-day-picker"; import { DateRange } from "react-day-picker";
import { useToast } from "../ui/use-toast";
import { isValidSearchFilter } from "@/utils/utils"; import { isValidSearchFilter } from "@/utils/utils";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { ConstsNav, ConstsTexts } from "@/utils/Consts"; import { ConstsNav, ConstsTexts } from "@/utils/Consts";
@@ -28,6 +27,7 @@ export function SearchFilterCard({
const [searchFilter, setSearchFilter] = useState<SearchFilter | null>( const [searchFilter, setSearchFilter] = useState<SearchFilter | null>(
initialsearchFilter ?? null, initialsearchFilter ?? null,
); );
const [errorMsg, setErrorMsg] = useState<string | null>(null);
const [isContactDialogOpen, setIsContactDialogOpen] = const [isContactDialogOpen, setIsContactDialogOpen] =
useState<boolean>(false); useState<boolean>(false);
@@ -37,18 +37,12 @@ export function SearchFilterCard({
shouldShowContactDialog: nightCountShowDialog, shouldShowContactDialog: nightCountShowDialog,
} = useCheckForNightCount(searchFilter); } = useCheckForNightCount(searchFilter);
const { toast } = useToast();
const navigate = useNavigate(); const navigate = useNavigate();
// Validate and call callback on btSearch click. // Validate and call callback on btSearch click.
//TODOME: Show validation messages as form instead of toasts.
const btSearchClick = () => { const btSearchClick = () => {
if (!isValidSearchFilter(searchFilter)) { if (!isValidSearchFilter(searchFilter)) {
toast({ setErrorMsg("Select a valid 'from' and 'to' dates!");
variant: "destructive",
title: "Uh oh! Invalid Search.",
description: "You should select a 'from' and 'to' dates",
});
return; return;
} }
@@ -76,10 +70,16 @@ export function SearchFilterCard({
// Handle internal Searchfilter updates. // Handle internal Searchfilter updates.
const updateDateRange = (newRange: DateRange) => { const updateDateRange = (newRange: DateRange) => {
setSearchFilter({ const newSearchFilter = {
...searchFilter, ...searchFilter,
dateRange: newRange, dateRange: newRange,
}); };
setSearchFilter(newSearchFilter);
if (isValidSearchFilter(newSearchFilter)) {
setErrorMsg(null);
}
}; };
const onContactDialogClose = () => { const onContactDialogClose = () => {
@@ -110,6 +110,13 @@ export function SearchFilterCard({
></DatePickerWithRange> ></DatePickerWithRange>
); );
const errorMsgLabel = (
<Typography variant={"errorText"} className="mt-2 text-lg">
{errorMsg}
</Typography>
);
const searchHasErrors = errorMsg != null;
return ( return (
<> <>
<ContactUsDialog <ContactUsDialog
@@ -125,11 +132,13 @@ export function SearchFilterCard({
</CardHeader> </CardHeader>
<CardContent> <CardContent>
{datePickerElement} {datePickerElement}
{searchHasErrors && errorMsgLabel}
<Separator className="my-4" /> <Separator className="my-4" />
<Button <Button
size={"lg"} size={"lg"}
onClick={btSearchClick} onClick={btSearchClick}
disabled={isSearchButtonDisabled} disabled={isSearchButtonDisabled}
variant={searchHasErrors ? "destructive" : "default"}
> >
<SearchIcon className="me-2 text-primary-foreground" /> <SearchIcon className="me-2 text-primary-foreground" />
<Typography variant={"h3"} className="text-primary-foreground"> <Typography variant={"h3"} className="text-primary-foreground">
+3 -3
View File
@@ -90,7 +90,7 @@ export function DatePickerWithRange({
id="dateFrom" id="dateFrom"
variant="outline" variant="outline"
data-empty={!dateRange} data-empty={!dateRange}
className="w-[100%] justify-start text-left font-normal data-[empty=true]:text-muted-foreground" className="w-full justify-start text-left font-normal data-[empty=true]:text-muted-foreground"
> >
<CalendarIcon /> <CalendarIcon />
{dateRange?.from ? ( {dateRange?.from ? (
@@ -117,11 +117,11 @@ export function DatePickerWithRange({
id="dateTo" id="dateTo"
variant={"outline"} variant={"outline"}
data-empty={!dateRange} data-empty={!dateRange}
className="w-[100%] justify-start text-left font-normal data-[empty=true]:text-muted-foreground" className="w-full justify-start text-left font-normal data-[empty=true]:text-muted-foreground"
> >
<CalendarIcon className="mr-2 h-4 w-4" /> <CalendarIcon className="mr-2 h-4 w-4" />
{dateRange?.to ? ( {dateRange?.to ? (
format(dateRange.to, "LLL dd, y") format(dateRange.to, "PPP")
) : ( ) : (
<span>Pick a date</span> <span>Pick a date</span>
)} )}
+4 -1
View File
@@ -24,6 +24,7 @@ const typographyVariants = cva("text-foreground", {
largeText: "text-lg font-semibold", largeText: "text-lg font-semibold",
smallText: "text-sm leading-none font-medium", smallText: "text-sm leading-none font-medium",
mutedText: "text-muted-foreground text-sm", mutedText: "text-muted-foreground text-sm",
errorText: "text-destructive text-sm font-normal",
}, },
}, },
defaultVariants: { defaultVariants: {
@@ -51,11 +52,13 @@ const variantElementMap: Record<
smallText: "small", smallText: "small",
lead: "p", lead: "p",
mutedText: "p", mutedText: "p",
errorText: "p",
ul: "ul", ul: "ul",
}; };
export interface TypographyProps export interface TypographyProps
extends React.HTMLAttributes<HTMLElement>, extends
React.HTMLAttributes<HTMLElement>,
VariantProps<typeof typographyVariants> { VariantProps<typeof typographyVariants> {
asChild?: boolean; asChild?: boolean;
as?: string; as?: string;
+11 -8
View File
@@ -22,6 +22,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { eBookingState } from "@/utils/AppStateManager"; import { eBookingState } from "@/utils/AppStateManager";
import { useBookingStateDispacher } from "@/utils/hooks"; import { useBookingStateDispacher } from "@/utils/hooks";
import { ConstsNav } from "@/utils/Consts"; import { ConstsNav } from "@/utils/Consts";
import { NewBookingCustomerDetails } from "@/model/Bookings";
enum PageState { enum PageState {
showForm, showForm,
@@ -53,12 +54,13 @@ function BookPage() {
if (bookingData == null) { if (bookingData == null) {
return; return;
} }
const newCustomerData = bookingData.customerDetails; const newCustomerData: NewBookingCustomerDetails = {
newCustomerData.name = values.name; ...bookingData.customerDetails,
newCustomerData.surname = values.surname; name: values.name,
newCustomerData.email = values.email; surname: values.surname,
newCustomerData.phoneNumber = values.phone; email: values.email,
phoneNumber: values.phone,
};
setCustomerData(newCustomerData); setCustomerData(newCustomerData);
setPageState(PageState.sending); setPageState(PageState.sending);
@@ -80,8 +82,9 @@ function BookPage() {
//Redirect to home if no booking is active. //Redirect to home if no booking is active.
if ( if (
!appBookingState || !appBookingState ||
appBookingState.state != eBookingState.Booking || !bookingData ||
!bookingData (appBookingState.state != eBookingState.Booking &&
appBookingState.state != eBookingState.Booked)
) { ) {
navigate(ConstsNav.page_Home); navigate(ConstsNav.page_Home);
} }
+4 -1
View File
@@ -14,11 +14,14 @@ function SuccessPage() {
const location = useLocation(); const location = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
if (pageState == null && location.state != null) {
setPageState(location.state);
}
useEffect(() => { useEffect(() => {
if (location.state == null) { if (location.state == null) {
navigate(ConstsNav.page_Home); navigate(ConstsNav.page_Home);
} }
setPageState(location.state);
}, [location, navigate]); }, [location, navigate]);
const handleBtBackClick = () => { const handleBtBackClick = () => {
+1 -1
View File
@@ -13,7 +13,7 @@ export const ConstsNav = {
//TMP - The following texts should be replaced for a proper intl system. //TMP - The following texts should be replaced for a proper intl system.
export const ConstsTexts = { export const ConstsTexts = {
navToContact_maxAllowedNightCount: navToContact_maxAllowedNightCount:
"We are sorry, but the selected date range is too large. Search on a smaller range or contact us for a personalized quote.", "We are sorry, but the selected date range is too large. Search for a smaller range or contact us for a personalized quote.",
navToContact_maxAllowedGuestNumber: navToContact_maxAllowedGuestNumber:
"We are sorry, group bookings are not allowed. Contact us for a personalized quote.", "We are sorry, group bookings are not allowed. Contact us for a personalized quote.",
contactUsAlertDialog_title: "Contact us!", contactUsAlertDialog_title: "Contact us!",