cleanup: ticket and legal stuff

This commit is contained in:
user
2025-10-21 16:07:17 +03:00
parent 8440c6a2dd
commit de2fbd41d6
18 changed files with 92 additions and 1575 deletions

View File

@@ -1,80 +1,110 @@
<script lang="ts">
import { passengerInfoVM } from "$lib/domains/passengerinfo/view/passenger.info.vm.svelte";
import Icon from "$lib/components/atoms/icon.svelte";
import { capitalize } from "$lib/core/string.utils";
import BackpackIcon from "~icons/solar/backpack-linear";
import BagIcon from "~icons/lucide/briefcase";
import SuitcaseIcon from "~icons/bi/suitcase2";
import SeatIcon from "~icons/solar/armchair-2-linear";
import Title from "$lib/components/atoms/title.svelte";
import { customerInfoVM } from "$lib/domains/customerinfo/view/customerinfo.vm.svelte";
import { productStore } from "$lib/domains/product/store";
import MailIcon from "~icons/lucide/mail";
import MapPinIcon from "~icons/lucide/map-pin";
import PackageIcon from "~icons/lucide/package";
import PhoneIcon from "~icons/lucide/phone";
import UserIcon from "~icons/lucide/user";
</script>
<div class="flex flex-col gap-6">
{#each passengerInfoVM.passengerInfos as passenger, index}
<div class="flex flex-col gap-4">
<div class="flex items-center justify-between">
<span class="font-semibold">
Passenger {index + 1} ({capitalize(passenger.passengerType)})
</span>
<!-- Product Summary -->
{#if $productStore}
<div class="flex flex-col gap-3">
<div class="flex items-center gap-2">
<Icon icon={PackageIcon} cls="h-5 w-5 text-gray-600" />
<Title size="p" weight="semibold">Product</Title>
</div>
<div class="rounded-lg border bg-gray-50 p-4">
<p class="font-medium">{$productStore.title}</p>
<p class="mt-1 text-sm text-gray-600">
{$productStore.description}
</p>
</div>
</div>
{/if}
<!-- Customer Information Summary -->
{#if customerInfoVM.customerInfo}
<div class="flex flex-col gap-3">
<div class="flex items-center gap-2">
<Icon icon={UserIcon} cls="h-5 w-5 text-gray-600" />
<Title size="p" weight="semibold">Customer Information</Title>
</div>
<!-- Personal Info -->
<div class="rounded-lg border bg-gray-50 p-4">
<div class="grid grid-cols-2 gap-3 text-sm md:grid-cols-3">
<div class="flex flex-col gap-3">
<!-- Name -->
<div>
<span class="text-gray-500">Name</span>
<span class="text-xs text-gray-500">Full Name</span>
<p class="font-medium">
{passenger.passengerPii.firstName}
{passenger.passengerPii.lastName}
{customerInfoVM.customerInfo.firstName}
{#if customerInfoVM.customerInfo.middleName}
{customerInfoVM.customerInfo.middleName}
{/if}
{customerInfoVM.customerInfo.lastName}
</p>
</div>
<div>
<span class="text-gray-500">Nationality</span>
<p class="font-medium">
{passenger.passengerPii.nationality}
</p>
<!-- Email -->
<div class="flex items-center gap-2">
<Icon icon={MailIcon} cls="h-4 w-4 text-gray-500" />
<div class="flex-1">
<span class="text-xs text-gray-500">Email</span>
<p class="text-sm">
{customerInfoVM.customerInfo.email}
</p>
</div>
</div>
<div>
<span class="text-gray-500">Date of Birth</span>
<p class="font-medium">{passenger.passengerPii.dob}</p>
<!-- Phone -->
<div class="flex items-center gap-2">
<Icon icon={PhoneIcon} cls="h-4 w-4 text-gray-500" />
<div class="flex-1">
<span class="text-xs text-gray-500">Phone</span>
<p class="text-sm">
{customerInfoVM.customerInfo.phoneCountryCode}
{customerInfoVM.customerInfo.phoneNumber}
</p>
</div>
</div>
<!-- Address -->
<div class="flex items-start gap-2">
<Icon
icon={MapPinIcon}
cls="h-4 w-4 text-gray-500 mt-1"
/>
<div class="flex-1">
<span class="text-xs text-gray-500">Address</span>
<p class="text-sm">
{customerInfoVM.customerInfo.address}
{#if customerInfoVM.customerInfo.address2}
, {customerInfoVM.customerInfo.address2}
{/if}
</p>
<p class="text-sm text-gray-600">
{customerInfoVM.customerInfo.city},
{customerInfoVM.customerInfo.state}
{customerInfoVM.customerInfo.zipCode}
</p>
<p class="text-sm text-gray-600">
{customerInfoVM.customerInfo.country}
</p>
</div>
</div>
</div>
</div>
<!-- Baggage Selection -->
<div class="flex flex-wrap gap-4 text-sm">
{#if passenger.bagSelection.personalBags > 0}
<div class="flex items-center gap-2">
<Icon icon={BackpackIcon} cls="h-5 w-5 text-gray-600" />
<span>Personal Item</span>
</div>
{/if}
{#if passenger.bagSelection.handBags > 0}
<div class="flex items-center gap-2">
<Icon icon={SuitcaseIcon} cls="h-5 w-5 text-gray-600" />
<span>{passenger.bagSelection.handBags} x Cabin Bag</span>
</div>
{/if}
{#if passenger.bagSelection.checkedBags > 0}
<div class="flex items-center gap-2">
<Icon icon={BagIcon} cls="h-5 w-5 text-gray-600" />
<span>
{passenger.bagSelection.checkedBags} x Checked Bag
</span>
</div>
{/if}
</div>
<!-- Seat Selection -->
{#if passenger.seatSelection.number}
<div class="flex items-center gap-2 text-sm">
<Icon icon={SeatIcon} cls="h-5 w-5 text-gray-600" />
<span>Seat {passenger.seatSelection.number}</span>
</div>
{/if}
</div>
{#if index < passengerInfoVM.passengerInfos.length - 1}
<div class="border-b border-dashed"></div>
{/if}
{/each}
{:else}
<div class="rounded-lg border border-dashed p-6 text-center">
<p class="text-sm text-gray-500">
Customer information not yet provided
</p>
</div>
{/if}
</div>

View File

@@ -1,8 +1,7 @@
<script lang="ts">
import Input from "$lib/components/ui/input/input.svelte";
import LabelWrapper from "$lib/components/atoms/label-wrapper.svelte";
import Input from "$lib/components/ui/input/input.svelte";
import { paymentInfoVM } from "./payment.info.vm.svelte";
import { chunk } from "$lib/core/array.utils";
function formatCardNumberForDisplay(value: string) {
// return in format "XXXX XXXX XXXX XXXX" from "XXXXXXXXXXXXXXXX"

View File

@@ -1,33 +0,0 @@
<script lang="ts">
import * as Select from "$lib/components/ui/select";
import type { SelectOption } from "$lib/core/data.types";
import { capitalize } from "$lib/core/string.utils";
let {
opts,
onselect,
}: { opts: SelectOption[]; onselect: (e: string) => void } = $props();
let chosenOpt = $state<SelectOption | undefined>(undefined);
function setOpt(val: string) {
chosenOpt = opts.find((e) => e.value === val);
onselect(val);
}
</script>
<Select.Root type="single" required onValueChange={(e) => setOpt(e)} name="role">
<Select.Trigger class="w-full border-border/20">
{capitalize(
opts?.find((e) => e.value === chosenOpt?.value)?.label ??
"Cabin Class",
)}
</Select.Trigger>
<Select.Content>
{#each opts as each}
<Select.Item value={each.value}>
{each.label}
</Select.Item>
{/each}
</Select.Content>
</Select.Root>

View File

@@ -1,104 +0,0 @@
<script lang="ts">
import CalendarIcon from "@lucide/svelte/icons/calendar";
import {
DateFormatter,
getLocalTimeZone,
today,
toCalendarDate,
type DateValue,
} from "@internationalized/date";
import { cn } from "$lib/utils.js";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import * as Popover from "$lib/components/ui/popover/index.js";
import Icon from "$lib/components/atoms/icon.svelte";
import Calendar from "$lib/components/ui/calendar/calendar.svelte";
import {
parseCalDateToDateString,
makeDateStringISO,
} from "$lib/core/date.utils";
import { ticketSearchStore } from "../data/store";
import { onMount } from "svelte";
const df = new DateFormatter("en-US", {
month: "short",
day: "numeric",
year: "numeric",
timeZone: getLocalTimeZone(),
});
let contentRef = $state<HTMLElement | null>(null);
let open = $state(false);
const todayDate = today(getLocalTimeZone());
let value = $state(today(getLocalTimeZone()));
let startFmt = $derived(value.toDate(getLocalTimeZone()));
let defaultPlaceholder = "Pick a date";
let placeholder = $derived(
value ? `${df.format(startFmt)}` : defaultPlaceholder,
);
function updateValInStore() {
const val = parseCalDateToDateString(value);
ticketSearchStore.update((prev) => {
return {
...prev,
departureDate: makeDateStringISO(val),
returnDate: makeDateStringISO(val),
};
});
}
function isDateDisabled(date: DateValue) {
return date.compare(todayDate) < 0;
}
function handleDateSelection(v: DateValue | undefined) {
if (!v) {
value = today(getLocalTimeZone());
} else {
value = toCalendarDate(v);
}
setTimeout(() => {
open = false;
}, 0);
}
$effect(() => {
updateValInStore();
});
onMount(() => {
updateValInStore();
});
</script>
<Popover.Root bind:open>
<Popover.Trigger
class={cn(
buttonVariants({
variant: "white",
class: "w-full justify-start text-left font-normal",
}),
!value && "text-muted-foreground",
)}
>
<Icon icon={CalendarIcon} cls="w-auto h-4" />
<span class="text-sm md:text-base">{placeholder}</span>
</Popover.Trigger>
<Popover.Content bind:ref={contentRef} class="w-auto p-0">
<Calendar
type="single"
{value}
minValue={todayDate}
{isDateDisabled}
onValueChange={(v) => handleDateSelection(v)}
class="rounded-md border border-white"
/>
</Popover.Content>
</Popover.Root>

View File

@@ -1,133 +0,0 @@
<script lang="ts">
import CalendarIcon from "@lucide/svelte/icons/calendar";
import {
DateFormatter,
getLocalTimeZone,
toCalendarDate,
today,
type DateValue,
} from "@internationalized/date";
import { cn } from "$lib/utils.js";
import { buttonVariants } from "$lib/components/ui/button/index.js";
import * as Popover from "$lib/components/ui/popover/index.js";
import { RangeCalendar } from "$lib/components/ui/range-calendar/index.js";
import Icon from "$lib/components/atoms/icon.svelte";
import { ticketSearchStore } from "../data/store";
import { onMount } from "svelte";
import {
makeDateStringISO,
parseCalDateToDateString,
} from "$lib/core/date.utils";
import Button from "$lib/components/ui/button/button.svelte";
const df = new DateFormatter("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
let contentRef = $state<HTMLElement | null>(null);
let open = $state(false);
const todayDate = today(getLocalTimeZone());
const start = today(getLocalTimeZone());
const end = start.add({ days: 7 });
let value = $state({ start, end });
let startFmt = $derived(value?.start?.toDate(getLocalTimeZone()));
let endFmt = $derived(value?.end?.toDate(getLocalTimeZone()));
let defaultPlaceholder = "Pick a date";
let placeholder = $derived(
value?.start && value?.end && startFmt && endFmt
? `${df.formatRange(startFmt, endFmt)}`
: value?.start && startFmt
? `${df.format(startFmt)} - Select end date`
: defaultPlaceholder,
);
function isDateDisabled(date: DateValue) {
return date.compare(todayDate) < 0;
}
function handleStartChange(v: DateValue | undefined) {
if (!v) {
value.start = today(getLocalTimeZone());
return;
}
value.start = toCalendarDate(v);
}
function handleEndChange(v: DateValue | undefined) {
if (!v) {
value.end = today(getLocalTimeZone());
return;
}
value.end = toCalendarDate(v);
}
function updateValsInStore() {
const sVal = parseCalDateToDateString(value.start);
const eVal = parseCalDateToDateString(value.end);
ticketSearchStore.update((prev) => {
return {
...prev,
departureDate: makeDateStringISO(sVal),
returnDate: makeDateStringISO(eVal),
};
});
}
$effect(() => {
updateValsInStore();
});
onMount(() => {
updateValsInStore();
});
</script>
<Popover.Root
{open}
onOpenChange={(o) => {
open = o;
}}
>
<Popover.Trigger
class={cn(
buttonVariants({
variant: "white",
class: "w-full justify-start text-left font-normal",
}),
!value && "text-muted-foreground",
)}
>
<Icon icon={CalendarIcon} cls="w-auto h-4" />
<span class="text-sm md:text-base">{placeholder}</span>
</Popover.Trigger>
<Popover.Content bind:ref={contentRef} class="w-auto p-0">
<RangeCalendar
{value}
{isDateDisabled}
onStartValueChange={handleStartChange}
onEndValueChange={handleEndChange}
class="rounded-md border border-white"
/>
<div class="w-full p-2">
<Button
class="w-full"
onclick={() => {
open = false;
}}
>
Confirm
</Button>
</div>
</Popover.Content>
</Popover.Root>

View File

@@ -1,116 +0,0 @@
<script lang="ts">
import Counter from "$lib/components/atoms/counter.svelte";
import Title from "$lib/components/atoms/title.svelte";
import { buttonVariants } from "$lib/components/ui/button";
import * as Popover from "$lib/components/ui/popover/index.js";
import { cn } from "$lib/utils";
import { CabinClass } from "../data/entities";
import { ticketSearchStore } from "../data/store";
import { snakeToSpacedPascal } from "$lib/core/string.utils";
import Icon from "$lib/components/atoms/icon.svelte";
import ChevronDownIcon from "~icons/lucide/chevron-down";
import CheckIcon from "~icons/lucide/check";
let cabinClass = $state($ticketSearchStore.cabinClass);
$effect(() => {
ticketSearchStore.update((prev) => {
return { ...prev, cabinClass: cabinClass };
});
});
let adultCount = $state(1);
let childCount = $state(0);
$effect(() => {
ticketSearchStore.update((prev) => {
return {
...prev,
passengerCounts: { adults: adultCount, children: childCount },
};
});
});
let passengerCounts = $derived(adultCount + childCount);
let cabinClassOpen = $state(false);
</script>
<div class="flex w-full flex-col items-center justify-end gap-2 sm:flex-row">
<Popover.Root bind:open={cabinClassOpen}>
<Popover.Trigger
class={cn(
buttonVariants({ variant: "white" }),
"w-full justify-between",
)}
>
{snakeToSpacedPascal(cabinClass.toLowerCase() ?? "Select")}
<Icon icon={ChevronDownIcon} cls="w-auto h-4" />
</Popover.Trigger>
<Popover.Content>
<div class="flex flex-col gap-2">
{#each Object.values(CabinClass) as each}
<button
onclick={() => {
cabinClass = each;
cabinClassOpen = false;
}}
class={cn(
"flex items-center gap-2 rounded-md p-2 px-4 text-start hover:bg-gray-200",
)}
>
{#if cabinClass === each}
<Icon
icon={CheckIcon}
cls="w-auto h-4 text-brand-600"
/>
{:else}
<div
class="h-4 w-4 rounded-full bg-transparent"
></div>
{/if}
{snakeToSpacedPascal(each.toLowerCase())}
</button>
{/each}
</div>
</Popover.Content>
</Popover.Root>
<Popover.Root>
<Popover.Trigger
class={cn(
buttonVariants({ variant: "white" }),
"w-full justify-between",
)}
>
{passengerCounts} Passenger(s)
<Icon icon={ChevronDownIcon} cls="w-auto h-4" />
</Popover.Trigger>
<Popover.Content>
<div class="flex flex-col gap-8">
<Title size="h5" weight="normal" color="black"
>Passenger Selection</Title
>
<div class="flex flex-col gap-4">
<div class="flex items-center justify-between gap-4">
<div class="flex flex-col gap-1">
<p>Adults</p>
<p class="text-gray-500">Aged 16+</p>
</div>
<Counter bind:value={adultCount} />
</div>
<div class="flex items-center justify-between gap-4">
<div class="flex flex-col gap-1">
<p>Children</p>
<p class="text-gray-500">Aged 0-16</p>
</div>
<Counter bind:value={childCount} />
</div>
</div>
</div>
</Popover.Content>
</Popover.Root>
</div>

View File

@@ -1,41 +0,0 @@
<script lang="ts">
import Badge from "$lib/components/ui/badge/badge.svelte";
import Button from "$lib/components/ui/button/button.svelte";
import { flightTicketVM } from "./ticket.vm.svelte";
import TicketCard from "./ticket/ticket-card.svelte";
</script>
<div class="flex w-full flex-col gap-4">
{#if flightTicketVM.searching}
{#each Array(5) as _}
<div
class="h-64 w-full animate-pulse rounded-lg bg-gray-300 shadow-lg"
></div>
{/each}
{:else if flightTicketVM.renderedTickets.length > 0}
<Badge variant="outline" class="w-max">
Showing {flightTicketVM.renderedTickets.length} tickets
</Badge>
{#each flightTicketVM.renderedTickets as each}
<TicketCard data={each} />
{/each}
<Button
class="text-center"
variant="white"
onclick={() => {
flightTicketVM.searchForTickets(true);
}}
>
Load More
</Button>
{:else}
<div
class="flex flex-col items-center justify-center gap-4 p-8 py-32 text-center"
>
<div class="text-2xl font-bold">No tickets found</div>
<div class="text-sm text-gray-500">
Try searching for a different flight
</div>
</div>
{/if}
</div>

View File

@@ -1,260 +0,0 @@
<script lang="ts">
import Title from "$lib/components/atoms/title.svelte";
import { Slider } from "$lib/components/ui/slider/index.js";
import {
ticketFiltersStore,
MaxStops,
SortOption,
} from "$lib/domains/ticket/view/ticket-filters.vm.svelte";
import { flightTicketVM } from "./ticket.vm.svelte";
import { RadioGroup, RadioGroupItem } from "$lib/components/ui/radio-group";
import { Checkbox } from "$lib/components/ui/checkbox";
import { Label } from "$lib/components/ui/label";
import Button from "$lib/components/ui/button/button.svelte";
import {
convertAndFormatCurrency,
currencyStore,
currencyVM,
} from "$lib/domains/currency/view/currency.vm.svelte";
let { onApplyClick }: { onApplyClick?: () => void } = $props();
function onApply() {
flightTicketVM.applyFilters();
if (onApplyClick) {
onApplyClick();
}
}
let maxPrice = $state(
flightTicketVM.tickets.length > 0
? flightTicketVM.tickets.sort(
(a, b) =>
b.priceDetails.displayPrice - a.priceDetails.displayPrice,
)[0]?.priceDetails.displayPrice
: 100,
);
let priceRange = $state([
0,
currencyVM.convertFromUsd(
$ticketFiltersStore.priceRange.max,
$currencyStore.code,
),
]);
// Time ranges
let departureTimeRange = $state([0, $ticketFiltersStore.time.departure.max]);
let arrivalTimeRange = $state([0, $ticketFiltersStore.time.arrival.max]);
let durationRange = $state([0, $ticketFiltersStore.duration.max]);
let maxStops = $state($ticketFiltersStore.maxStops);
let allowOvernight = $state($ticketFiltersStore.allowOvernight);
$effect(() => {
maxPrice =
flightTicketVM.tickets.length > 0
? flightTicketVM.tickets.sort(
(a, b) =>
b.priceDetails.displayPrice -
a.priceDetails.displayPrice,
)[0]?.priceDetails.displayPrice
: 100;
if (priceRange[0] > maxPrice || priceRange[1] > maxPrice) {
priceRange = [
0,
currencyVM.convertFromUsd(maxPrice, $currencyStore.code),
];
}
});
$effect(() => {
ticketFiltersStore.update((prev) => ({
...prev,
priceRange: { min: priceRange[0], max: priceRange[1] },
maxStops,
allowOvernight,
time: {
departure: {
min: departureTimeRange[0],
max: departureTimeRange[1],
},
arrival: { min: arrivalTimeRange[0], max: arrivalTimeRange[1] },
},
duration: { min: durationRange[0], max: durationRange[1] },
}));
});
</script>
<div class="flex w-full max-w-sm flex-col gap-6">
<Title size="h5" color="black">Sort By</Title>
{#if flightTicketVM.searching}
<div class="h-16 w-full animate-pulse rounded-lg bg-gray-300"></div>
{:else}
<RadioGroup
value={$ticketFiltersStore.sortBy}
onValueChange={(value) => {
ticketFiltersStore.update((prev) => ({
...prev,
sortBy: value as SortOption,
}));
}}
>
<div class="flex items-center space-x-2">
<RadioGroupItem
value={SortOption.Default}
id="price_low_to_high"
/>
<Label for="default">Price: Default</Label>
</div>
<div class="flex items-center space-x-2">
<RadioGroupItem
value={SortOption.PriceLowToHigh}
id="price_low_to_high"
/>
<Label for="price_low_to_high">Price: Low to High</Label>
</div>
<div class="flex items-center space-x-2">
<RadioGroupItem
value={SortOption.PriceHighToLow}
id="price_high_to_low"
/>
<Label for="price_high_to_low">Price: High to Low</Label>
</div>
</RadioGroup>
{/if}
<Title size="h5" color="black">Price</Title>
{#if flightTicketVM.searching}
<div class="h-6 w-full animate-pulse rounded-full bg-gray-300"></div>
{:else}
<div class="flex w-full flex-col gap-2">
<Slider
type="multiple"
bind:value={priceRange}
min={0}
max={maxPrice}
step={1}
/>
<div class="flex items-center justify-between gap-2">
<div class="flex flex-col gap-1">
<p class="text-sm font-medium">Min</p>
<p class="text-sm text-gray-500">{priceRange[0]}</p>
</div>
<div class="flex flex-col gap-1">
<p class="text-sm font-medium">Max</p>
<p class="text-sm text-gray-500">{priceRange[1]}</p>
</div>
</div>
</div>
{/if}
<Title size="h5" color="black">Max Stops</Title>
{#if flightTicketVM.searching}
<div class="h-24 w-full animate-pulse rounded-lg bg-gray-300"></div>
{:else}
<RadioGroup
value={$ticketFiltersStore.maxStops}
onValueChange={(value) => {
ticketFiltersStore.update((prev) => ({
...prev,
maxStops: value as MaxStops,
}));
}}
>
{#each Object.entries(MaxStops) as [label, value]}
<div class="flex items-center space-x-2">
<RadioGroupItem {value} id={value} />
<Label for={value}>{label}</Label>
</div>
{/each}
</RadioGroup>
{/if}
{#if flightTicketVM.searching}
<div class="h-8 w-full animate-pulse rounded-lg bg-gray-300"></div>
{:else}
<div class="flex items-center space-x-2">
<Checkbox
checked={$ticketFiltersStore.allowOvernight}
onCheckedChange={(checked) => {
ticketFiltersStore.update((prev) => ({
...prev,
allowOvernight: checked,
}));
}}
id="overnight"
/>
<Label for="overnight">Allow Overnight Flights</Label>
</div>
{/if}
<Title size="h5" color="black">Time</Title>
{#if flightTicketVM.searching}
<div class="space-y-4">
<div class="h-16 w-full animate-pulse rounded-lg bg-gray-300"></div>
<div class="h-16 w-full animate-pulse rounded-lg bg-gray-300"></div>
</div>
{:else}
<div class="space-y-4">
<div class="flex flex-col gap-2">
<p class="mb-2 text-sm font-medium">Departure Time</p>
<Slider
type="multiple"
bind:value={departureTimeRange}
min={0}
max={24}
step={1}
/>
<div class="flex items-center justify-between gap-2">
<small>{departureTimeRange[0]}:00</small>
<small>{departureTimeRange[1]}:00</small>
</div>
</div>
<div class="flex flex-col gap-2">
<p class="mb-2 text-sm font-medium">Arrival Time</p>
<Slider
type="multiple"
bind:value={arrivalTimeRange}
min={0}
max={24}
step={1}
/>
<div class="flex items-center justify-between gap-2">
<small>{arrivalTimeRange[0]}:00</small>
<small>{arrivalTimeRange[1]}:00</small>
</div>
</div>
</div>
{/if}
<Title size="h5" color="black">Duration</Title>
{#if flightTicketVM.searching}
<div class="h-16 w-full animate-pulse rounded-lg bg-gray-300"></div>
{:else}
<div class="flex flex-col gap-2">
<p class="mb-2 text-sm font-medium">Max Duration</p>
<Slider
type="multiple"
bind:value={durationRange}
min={0}
max={100}
step={1}
/>
<div class="flex items-center justify-between gap-2">
<small>{durationRange[0]} hours</small>
<small>{durationRange[1]} hours</small>
</div>
</div>
{/if}
<Button onclick={() => onApply()} class="w-full">Apply Changes</Button>
</div>

View File

@@ -1,24 +0,0 @@
import { writable } from "svelte/store";
export enum SortOption {
Default = "default",
PriceLowToHigh = "price_low_to_high",
PriceHighToLow = "price_high_to_low",
}
export enum MaxStops {
Any = "any",
Direct = "direct",
One = "one",
Two = "two",
}
export const ticketFiltersStore = writable({
priceRange: { min: 0, max: 0 },
excludeCountries: [] as string[],
maxStops: MaxStops.Any,
allowOvernight: true,
time: { departure: { min: 0, max: 0 }, arrival: { min: 0, max: 0 } },
duration: { min: 0, max: 0 },
sortBy: SortOption.Default,
});

View File

@@ -1,150 +0,0 @@
<script lang="ts">
import { TicketType } from "../data/entities/index";
import AirportSearchInput from "$lib/domains/airport/view/airport-search-input.svelte";
import { ticketSearchStore } from "../data/store";
import FlightDateInput from "./flight-date-input.svelte";
import FlightDateRangeInput from "./flight-date-range-input.svelte";
import Icon from "$lib/components/atoms/icon.svelte";
import SearchIcon from "~icons/solar/magnifer-linear";
import SwapIcon from "~icons/ant-design/swap-outlined";
import Button from "$lib/components/ui/button/button.svelte";
import PassengerAndCabinClassSelect from "./passenger-and-cabin-class-select.svelte";
import { Label } from "$lib/components/ui/label/index.js";
import * as RadioGroup from "$lib/components/ui/radio-group/index.js";
import { airportVM } from "$lib/domains/airport/view/airport.vm.svelte";
import { browser } from "$app/environment";
import { onDestroy, onMount } from "svelte";
let { onSubmit, rowify = false }: { onSubmit: () => void; rowify?: boolean } =
$props();
let currTicketType = $state($ticketSearchStore.ticketType);
let isMobileView = $state(false);
function checkIfMobile() {
if (browser) {
isMobileView = window.innerWidth < 768;
}
}
function setupResizeListener() {
if (browser) {
window.addEventListener("resize", checkIfMobile);
checkIfMobile(); // Initial check
return () => {
window.removeEventListener("resize", checkIfMobile);
};
}
}
$effect(() => {
ticketSearchStore.update((prev) => {
return { ...prev, ticketType: currTicketType };
});
});
let cleanup: any | undefined = $state(undefined);
onMount(() => {
cleanup = setupResizeListener();
});
onDestroy(() => {
cleanup?.();
});
</script>
<div class="flex w-full flex-col gap-4">
<div
class="flex w-full flex-col items-center justify-between gap-4 lg:grid lg:grid-cols-2 lg:gap-4"
>
<RadioGroup.Root
bind:value={currTicketType}
class="flex w-full flex-row gap-6 p-2 lg:p-4"
>
<div class="flex items-center space-x-2">
<RadioGroup.Item
value={TicketType.Return}
id={TicketType.Return}
onselect={() => {
ticketSearchStore.update((prev) => {
return { ...prev, ticketType: TicketType.Return };
});
}}
/>
<Label for={TicketType.Return} class="md:text-lg">Return</Label>
</div>
<div class="flex items-center space-x-2">
<RadioGroup.Item
value={TicketType.OneWay}
id={TicketType.OneWay}
onselect={() => {
ticketSearchStore.update((prev) => {
return { ...prev, ticketType: TicketType.OneWay };
});
}}
/>
<Label for={TicketType.OneWay} class="md:text-lg">One Way</Label>
</div>
</RadioGroup.Root>
<PassengerAndCabinClassSelect />
</div>
<div
class="flex w-full flex-col items-center justify-between gap-2 lg:grid lg:grid-cols-2 lg:gap-4"
>
<div
class="flex w-full flex-col items-center justify-between gap-2 lg:flex-row"
>
<AirportSearchInput
currentValue={airportVM.departure}
onChange={(e) => {
airportVM.setDepartureAirport(e);
}}
placeholder="Depart from"
searchPlaceholder="Departure airport search"
isMobile={isMobileView}
fieldType="departure"
/>
<div class="hidden w-full max-w-fit md:block">
<Button
size="icon"
variant={rowify ? "outlineWhite" : "defaultInverted"}
onclick={() => {
airportVM.swapDepartureAndArrival();
}}
>
<Icon icon={SwapIcon} cls="w-auto h-6" />
</Button>
</div>
<AirportSearchInput
currentValue={airportVM.arrival}
onChange={(e) => {
airportVM.setArrivalAirport(e);
}}
placeholder="Arrive at"
searchPlaceholder="Arrival airport search"
isMobile={isMobileView}
fieldType="arrival"
/>
</div>
<div
class="flex w-full flex-col items-center justify-between gap-2 lg:flex-row lg:gap-2"
>
{#if $ticketSearchStore.ticketType === TicketType.Return}
<FlightDateRangeInput />
{:else}
<FlightDateInput />
{/if}
<Button onclick={() => onSubmit()} class={"w-full"} variant="default">
<Icon icon={SearchIcon} cls="w-auto h-4" />
Search flights
</Button>
</div>
</div>
</div>