stashing code

This commit is contained in:
user
2025-10-20 17:07:41 +03:00
commit f5b99afc8f
890 changed files with 54823 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
import type { LayoutServerLoad } from "./$types";
export const load: LayoutServerLoad = async ({ locals }) => {
return { session: locals.session, user: locals.user };
};

View File

@@ -0,0 +1,73 @@
<script lang="ts">
import { onMount } from "svelte";
import { trpcApiStore, svTrpcApiStore } from "$lib/stores/api";
import { trpc, trpcRaw } from "$lib/trpc/trpc";
import { sessionInfo, sessionUserInfo } from "$lib/stores/session.info";
import type { LayoutData } from "./$types";
import Navbar from "$lib/components/molecules/navbar/navbar.svelte";
import Footer from "$lib/components/molecules/footer/footer.svelte";
import { currencyVM } from "$lib/domains/currency/view/currency.vm.svelte";
import Icon from "$lib/components/atoms/icon.svelte";
import UpIcon from "~icons/material-symbols/keyboard-double-arrow-up-rounded";
let {
children,
data,
}: {
children: any;
data: LayoutData;
} = $props();
sessionUserInfo.set(data.user);
sessionInfo.set(data.session);
svTrpcApiStore.set(trpc());
// let invert = $derived(page.url.pathname === "/");
let showScrollTop = $state(false);
function scrollToTop() {
window.scrollTo({ top: 0, behavior: "smooth" });
}
function handleScroll() {
showScrollTop = window.scrollY > 200; // Show button when scrolled down 200px
}
onMount(() => {
trpcApiStore.set(trpcRaw());
currencyVM.loadCachedSelection();
setTimeout(() => {
currencyVM.getCurrencies();
}, 500);
// Add scroll event listener
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
});
</script>
<Navbar invert={false} />
<div class="grid h-full w-full place-items-center overflow-x-hidden">
<main class="flex w-full flex-col gap-8 overflow-x-hidden">
{@render children?.()}
</main>
</div>
<Footer />
<!-- Scroll to top FAB -->
{#if showScrollTop}
<button
onclick={scrollToTop}
class="fixed bottom-4 left-4 z-50 flex h-12 w-12 items-center justify-center rounded-full border-2 border-white/60 bg-primary text-white shadow-lg transition-opacity hover:bg-primary/90 lg:bottom-8 lg:right-8"
aria-label="Scroll to top"
>
<Icon icon={UpIcon} cls="w-auto h-6 text-white" />
</button>
{/if}

View File

@@ -0,0 +1,438 @@
<script lang="ts">
import Title from "$lib/components/atoms/title.svelte";
import TicketSearchInput from "$lib/domains/ticket/view/ticket-search-input.svelte";
import Button from "$lib/components/ui/button/button.svelte";
import CheckIcon from "~icons/material-symbols/check";
import Icon from "$lib/components/atoms/icon.svelte";
import { flightTicketVM } from "$lib/domains/ticket/view/ticket.vm.svelte";
import MaxWidthWrapper from "$lib/components/molecules/max-width-wrapper.svelte";
import {
ArrowRight,
Calendar,
CreditCard,
Headphones,
Plane,
Search,
Shield,
} from "@lucide/svelte";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "$lib/components/ui/accordion";
import Badge from "$lib/components/ui/badge/badge.svelte";
import Card from "$lib/components/ui/card/card.svelte";
import CardContent from "$lib/components/ui/card/card-content.svelte";
import Testimonials from "$lib/components/organisms/testimonials.svelte";
import { airportVM } from "$lib/domains/airport/view/airport.vm.svelte";
type Destination = {
id: number;
name: string;
country: string;
image: string;
code: string;
discount?: number;
popular?: boolean;
};
const destinations: Destination[] = [
{
id: 1,
name: "Bali",
country: "Indonesia",
code: "DPS",
image: "https://images.unsplash.com/photo-1537996194471-e657df975ab4?auto=format&fit=crop&q=80&w=2338&ixlib=rb-4.0.3",
discount: 25,
popular: true,
},
{
id: 2,
name: "Tokyo",
country: "Japan",
code: "HND",
image: "https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&q=80&w=1788&ixlib=rb-4.0.3",
discount: 25,
},
{
id: 3,
name: "Dubai",
country: "United Arab Emirates",
code: "DXB",
image: "/assets/images/destinations/dubai.jpg",
discount: 25,
popular: true,
},
{
id: 4,
name: "Santorini",
country: "Greece",
code: "JTR",
image: "https://images.unsplash.com/photo-1469796466635-455ede028aca?auto=format&fit=crop&q=80&w=2338&ixlib=rb-4.0.3",
discount: 25,
},
];
const features = [
{
icon: Search,
title: "Smart Flight Search",
description:
"Our advanced algorithm finds the best connections and prices across 500+ airlines, often saving you hundreds on each booking.",
},
{
icon: Shield,
title: "Secure Booking Protection",
description:
"Bank-level security for your payment details, with fraud monitoring and instant booking confirmations for peace of mind.",
},
{
icon: Calendar,
title: "Flexible Date Options",
description:
"Compare prices across multiple dates to find the best fares, with calendar view showing price variations throughout the month.",
},
{
icon: Headphones,
title: "24/7 Flight Assistance",
description:
"Our flight specialists are available around the clock for rebooking, cancellations, or help with unexpected travel changes.",
},
{
icon: CreditCard,
title: "Transparent Pricing",
description:
"See all fees and charges upfront with no hidden costs. Choose from multiple payment options including installment plans.",
},
{
icon: Plane,
title: "Flight Status Updates",
description:
"Receive real-time notifications about gate changes, delays, or cancellations directly to your email or mobile device.",
},
];
const faqItems = [
{
question: "How does the 25% discount work on FlyTicketTravel?",
answer: "Our 25% discount is automatically applied to all flight bookings made through FlyTicketTravel. There's no need for discount codes or special memberships - simply search for your flight, and you'll see the discounted price reflected in your total. This exclusive limited time discount is available on all routes and airlines in our system, making every destination more affordable for you.",
},
{
question: "How do I find the best flight deals on FlyTicketTravel?",
answer: "FlyTicketTravel searches across 300+ airlines to find the best prices. For the best deals, we recommend booking 6-8 weeks in advance, using our flexible date search option, and setting up price alerts for your desired route. Our smart search algorithm compares prices to ensure you always get competitive rates.",
},
{
question: "Can I cancel or change my flight after booking?",
answer: "Yes, most flights can be changed or canceled via contacting us. The specific policies and fees depend on the airline and fare type you've selected. You can view the change/cancellation policies before completing your booking. For assistance with changes, our customer support team is available 24/7.",
},
{
question: "How early should I arrive at the airport for my flight?",
answer: "We recommend arriving 2 hours before departure for domestic flights and 3 hours for international flights. During peak travel seasons or at busy airports, consider adding an extra 30 minutes. This allows sufficient time for check-in, security screening, and boarding procedures.",
},
{
question: "Does FlyTicketTravel charge booking fees?",
answer: "FlyTicketTravel maintains complete transparency in pricing. We don't cost you anything to use our platform. All mandatory fees and taxes are included in the prices you see during the search process. We do offer premium booking options with additional services for a small fee, but these are clearly marked and always optional.",
},
{
question: "How do I check in for my flight?",
answer: "Most airlines offer online check-in 24-48 hours before departure. You'll receive an email reminder from FlyTicketTravel when check-in opens for your flight with a direct link to the airline's check-in page. Alternatively, you can check in at the airport using the airline's kiosks or check-in counters.",
},
{
question: "What if my flight is delayed or canceled?",
answer: "FlyTicketTravel will notify you immediately via email and SMS if your flight is delayed or canceled. Our system automatically searches for alternative options when disruptions occur. You can also contact our 24/7 support team who will assist in rebooking or securing refunds according to the airline's policies.",
},
{
question: "How do baggage allowances work?",
answer: "Baggage allowances vary by airline, route, and fare class. The specific baggage details for your flight are displayed during the booking process and in your confirmation email. Most airlines include a personal item and cabin baggage in standard fares, while checked baggage may incur additional fees depending on the ticket type.",
},
{
question: "Is my payment information secure?",
answer: "Absolutely. FlyTicketTravel employs bank-level encryption and security protocols to protect your payment information. We are PCI-DSS compliant and never store your complete credit card details on our servers. We also offer secure payment options including credit/debit cards, PayPal, and digital wallets.",
},
];
async function handleDestinationSelect(destination: Destination) {
await airportVM.searchForAirportByCode(destination.code);
const searchElement = document.querySelector("#flight-search-form");
if (searchElement) {
searchElement.scrollIntoView({ behavior: "smooth" });
}
}
</script>
<MaxWidthWrapper cls="px-4">
<section
class="grid min-h-[70vh] w-full place-items-center rounded-xl bg-cover bg-right md:bg-center"
style="background-image: url('/assets/images/hero-bg.jpg');"
>
<div
class="grid h-full w-full place-items-center rounded-xl bg-gradient-to-br from-black/50 via-brand-950/50 to-black/50 p-5 lg:p-12 xl:px-20"
>
<div class="w-full pt-28">
<div
class="flex h-full w-full flex-col items-center justify-center gap-4 md:gap-8"
>
<div class="flex w-full flex-col items-center gap-4 md:gap-8">
<Title color="white" size="h3" weight="semibold" center>
Get 25% Off Your Next Adventure
</Title>
<p
class="max-w-prose text-center text-white/80 lg:text-lg"
>
Enjoy exclusive savings on flights worldwide. Search,
compare, and book with confidence on FlyTicketTravel.
</p>
</div>
<div
id="flight-search-form"
class="w-full rounded-lg bg-white/90 p-4 drop-shadow-xl md:p-8"
>
<TicketSearchInput
rowify
onSubmit={() => {
flightTicketVM.beginSearch();
}}
/>
</div>
</div>
</div>
</div>
</section>
</MaxWidthWrapper>
<section
id="destinations"
class="bg-gradient-to-b from-white via-brand-100 to-white py-24"
>
<div class="container mx-auto px-4">
<div class="mx-auto mb-16 flex max-w-3xl flex-col gap-2 text-center">
<Title color="black" weight="semibold" size="h3">
Top Flight Destinations
</Title>
<p class="text-lg text-muted-foreground">
Explore our most popular routes with competitive fares and
frequent departures. Click on a destination to start saving today.
</p>
</div>
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4">
{#each destinations as destination}
<Card
class="card-hover cursor-pointer overflow-hidden transition-all hover:scale-[1.02] hover:shadow-lg"
onclick={() => handleDestinationSelect(destination)}
>
<div class="relative h-48 overflow-hidden">
<img
src={destination.image}
alt={destination.name}
class="h-full w-full object-cover transition-transform duration-700 hover:scale-110"
/>
{#if destination.discount}
<div class="absolute left-3 top-3">
<Badge class="bg-brand-500">
{destination.discount}% OFF
</Badge>
</div>
{/if}
{#if destination.popular}
<div class="absolute right-3 top-3">
<Badge variant="secondary">Trending</Badge>
</div>
{/if}
</div>
<CardContent class="p-5">
<div class="flex items-start justify-between">
<div>
<h3 class="text-lg font-bold">
{destination.name}
</h3>
<p class="text-muted-foreground">
{destination.country}
</p>
<p class="mt-1 text-xs text-brand-600">
{destination.code} · Click to book
</p>
</div>
</div>
</CardContent>
</Card>
{/each}
</div>
</div>
</section>
<section class="py-24">
<div class="container mx-auto px-4">
<div class="mx-auto mb-16 flex max-w-3xl flex-col gap-2 text-center">
<Title color="black" weight="semibold" size="h3">
Flight Booking Features
</Title>
<p class="text-lg text-muted-foreground">
Enjoy premium booking tools with our guaranteed 25% discount on
every flight you book through our platform.
</p>
</div>
<div class="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-3">
{#each features as feature}
<div
class="rounded-xl border border-border bg-card p-6 transition-colors hover:border-brand-300"
>
<div class="mb-4 inline-block rounded-full bg-brand-100 p-3">
<Icon icon={feature.icon} cls="h-8 w-8 text-brand-600" />
</div>
<h3 class="mb-2 text-xl font-semibold">{feature.title}</h3>
<p class="text-muted-foreground">{feature.description}</p>
</div>
{/each}
</div>
</div>
</section>
<MaxWidthWrapper
cls="grid grid-cols-1 md:place-items-center gap-8 p-4 md:p-8 md:grid-cols-2"
>
<div class="grid w-full place-items-center">
<img
src="/assets/images/about.jpg"
alt="Flight booking experience"
class="aspect-square h-full w-full max-w-xl rounded-xl object-cover"
/>
</div>
<div class="flex w-full flex-col gap-1 p-4 text-start">
<Title color="gradientPrimary" size="p" capitalize weight="medium">
Flight Booking Made Easy
</Title>
<Title color="black" size="h4">
Save a quarter of your travel budget with our exclusive discount
</Title>
<p>
Our advanced flight search technology compares thousands of routes and
fares in seconds, finding you the best value tickets with transparent
pricing and instant confirmation. Enjoy a seamless booking experience
from search to takeoff.
</p>
<div class="mt-4 flex flex-col gap-4">
<div class="flex items-start gap-3">
<Icon
icon={CheckIcon}
cls="h-6 w-auto text-brand-600 flex-shrink-0 mt-1"
/>
<div>
<h4 class="text-lg font-semibold">
Guaranteed 25% Discount Technology
</h4>
<p class="text-xs text-gray-600 lg:text-base">
Our engine analyzes millions of flight combinations across
hundreds of airlines to find you the the most competitive
price with convenience.
</p>
</div>
</div>
<div class="flex items-start gap-3">
<Icon
icon={CheckIcon}
cls="h-6 w-auto text-brand-600 flex-shrink-0 mt-1"
/>
<div>
<h4 class="text-lg font-semibold">
Real-Time Flight Information
</h4>
<p class="text-xs text-gray-600 lg:text-base">
From booking confirmation to landing, receive timely
updates about your flight status, gate changes, and
check-in reminders to ensure a smooth travel experience.
</p>
</div>
</div>
</div>
</div>
</MaxWidthWrapper>
<Testimonials />
<section id="faq" class="bg-gradient-to-b from-white via-brand-50 to-white py-24">
<div class="container mx-auto px-4">
<div class="mx-auto mb-16 max-w-3xl text-center">
<h2 class="mb-4 text-3xl font-bold md:text-4xl">
Frequently Asked Questions
</h2>
<p class="text-lg text-muted-foreground">
Get answers to common questions about booking flights with
FlyTicketTravel.
</p>
</div>
<div class="mx-auto max-w-3xl">
<div class="rounded-xl border border-border bg-card p-6">
<Accordion type="single" class="w-full">
{#each faqItems as item, i}
<AccordionItem value="item-{i}">
<AccordionTrigger>{item.question}</AccordionTrigger>
<AccordionContent>
<p class="text-muted-foreground">{item.answer}</p>
</AccordionContent>
</AccordionItem>
{/each}
</Accordion>
</div>
</div>
</div>
</section>
<section class="relative overflow-hidden bg-brand-600 py-24">
<div
class="absolute left-0 top-0 h-64 w-64 rounded-full bg-brand-500 opacity-20 blur-3xl"
></div>
<div
class="absolute bottom-0 right-0 h-96 w-96 rounded-full bg-brand-700 opacity-20 blur-3xl"
></div>
<div class="container relative z-10 mx-auto px-4">
<div class="mx-auto max-w-4xl text-center text-white">
<h2 class="mb-6 text-3xl font-bold md:text-5xl">
Ready to Book Your Flight?
</h2>
<p class="mb-10 text-xl text-white/90 md:text-2xl">
Find and book your perfect flight in just a few clicks. No hidden
fees, instant confirmation, and the best prices guaranteed.
</p>
<div class="flex flex-col justify-center gap-4 sm:flex-row">
<a href="/search">
<Button size="lg" variant="white">
Search Flights <ArrowRight class="ml-2 h-5 w-5" />
</Button>
</a>
<a href="/search">
<Button size="lg" variant="glassWhite">
View Flight Deals <ArrowRight class="ml-2 h-5 w-5" />
</Button>
</a>
</div>
<div class="mt-12 grid grid-cols-2 gap-6 text-center md:grid-cols-4">
<div>
<p class="text-3xl font-bold md:text-4xl">100+</p>
<p class="text-white/80">Airlines</p>
</div>
<div>
<p class="text-3xl font-bold md:text-4xl">2.4k+</p>
<p class="text-white/80">Flights Booked</p>
</div>
<div>
<p class="text-3xl font-bold md:text-4xl">24/7</p>
<p class="text-white/80">Flight Support</p>
</div>
<div>
<p class="text-3xl font-bold md:text-4xl">95%</p>
<p class="text-white/80">Customer Satisfaction</p>
</div>
</div>
</div>
</div>
</section>

View File

@@ -0,0 +1,6 @@
import { getTC } from "$lib/domains/ticket/domain/controller";
import type { PageServerLoad } from "./$types";
export const load: PageServerLoad = async ({ params }) => {
return await getTC().getTicketById(Number(params.tid ?? "-1"));
};

View File

@@ -0,0 +1,110 @@
<script lang="ts">
import { onDestroy, onMount } from "svelte";
import type { PageData } from "./$types";
import Icon from "$lib/components/atoms/icon.svelte";
import Title from "$lib/components/atoms/title.svelte";
import Button from "$lib/components/ui/button/button.svelte";
import SearchIcon from "~icons/solar/magnifer-linear";
import { toast } from "svelte-sonner";
import { ticketCheckoutVM } from "$lib/domains/ticket/view/checkout/flight-checkout.vm.svelte";
import { CheckoutStep } from "$lib/domains/ticket/data/entities/index";
import InitialInfoSection from "$lib/domains/ticket/view/checkout/initial-info-section.svelte";
import { flightTicketStore } from "$lib/domains/ticket/data/store";
import CheckoutLoadingSection from "$lib/domains/ticket/view/checkout/checkout-loading-section.svelte";
import CheckoutConfirmationSection from "$lib/domains/ticket/view/checkout/checkout-confirmation-section.svelte";
import CheckoutStepsIndicator from "$lib/domains/ticket/view/checkout/checkout-steps-indicator.svelte";
import PaymentInfoSection from "$lib/domains/ticket/view/checkout/payment-info-section/index.svelte";
import PaymentVerificationSection from "$lib/domains/ticket/view/checkout/payment-verification-section.svelte";
import PaymentSummary from "$lib/domains/ticket/view/checkout/payment-summary.svelte";
import MaxWidthWrapper from "$lib/components/molecules/max-width-wrapper.svelte";
import { ckFlowVM } from "$lib/domains/ckflow/view/ckflow.vm.svelte";
import UpdatePriceDialog from "$lib/domains/ticket/view/checkout/update-price-dialog.svelte";
let { data: pageData }: { data: PageData } = $props();
function trackConversion() {
console.log("ct-ing");
let callback = function () {
console.log("ct-ed");
};
window.gtag("event", "conversion", {
send_to: "AW-17085207253/ZFAVCLD6tMkaENWl7tI_",
value: 1.0,
currency: "USD",
event_callback: callback,
});
return false;
}
onMount(() => {
if (pageData.error) {
toast.error(pageData.error.message, {
description: pageData.error.userHint,
});
return;
}
if (!pageData.data) {
return;
}
flightTicketStore.set(pageData.data);
ticketCheckoutVM.loading = false;
ticketCheckoutVM.setupPinger();
setTimeout(async () => {
await ckFlowVM.initFlow();
trackConversion();
}, 2000);
});
onDestroy(() => {
ticketCheckoutVM.reset();
});
</script>
<UpdatePriceDialog />
<div class="grid h-full w-full place-items-center">
<MaxWidthWrapper cls="p-4 md:p-8 lg:p-10 3xl:p-0">
{#if !pageData.data || !!pageData.error}
<div class="grid h-full min-h-screen w-full place-items-center">
<div class="flex flex-col items-center justify-center gap-4">
<Icon icon={SearchIcon} cls="w-12 h-12" />
<Title size="h4" color="black">No ticket found</Title>
<Button href="/search" variant="default">
Search for another ticket
</Button>
</div>
</div>
{:else if ticketCheckoutVM.checkoutStep === CheckoutStep.Confirmation}
<div class="grid w-full place-items-center p-4 py-32">
<CheckoutConfirmationSection />
</div>
{:else}
<CheckoutStepsIndicator />
<div class="flex w-full flex-col gap-8 lg:flex-row">
<div class="flex w-full flex-col">
<div class="flex w-full flex-col gap-12">
{#if ticketCheckoutVM.loading}
<CheckoutLoadingSection />
{:else if ticketCheckoutVM.checkoutStep === CheckoutStep.Initial}
<InitialInfoSection />
{:else if ticketCheckoutVM.checkoutStep === CheckoutStep.Payment}
<PaymentInfoSection />
{:else if ticketCheckoutVM.checkoutStep === CheckoutStep.Verification}
<PaymentVerificationSection />
{/if}
</div>
</div>
<div
class="grid w-full place-items-center lg:max-w-lg lg:place-items-start"
>
<div
class="flex w-full flex-col gap-8 pt-8 md:max-w-md lg:max-w-full lg:pt-0"
>
<PaymentSummary />
</div>
</div>
</div>
{/if}
</MaxWidthWrapper>
</div>

View File

@@ -0,0 +1,40 @@
<script lang="ts">
import Icon from "$lib/components/atoms/icon.svelte";
import Title from "$lib/components/atoms/title.svelte";
import MaxWidthWrapper from "$lib/components/molecules/max-width-wrapper.svelte";
import CheckIcon from "~icons/ic/round-check";
</script>
<div class="grid min-h-[80vh] w-full place-items-center px-4 sm:px-6">
<MaxWidthWrapper
cls="flex flex-col gap-6 sm:gap-8 items-center justify-center"
>
<div
class="flex w-full max-w-md flex-col items-center justify-center gap-6 rounded-xl bg-white p-4 drop-shadow-lg sm:gap-8 sm:p-6 md:p-8 md:py-12 lg:max-w-xl"
>
<div
class="rounded-full bg-emerald-100 p-1.5 text-emerald-600 drop-shadow-lg sm:p-2"
>
<Icon
icon={CheckIcon}
cls="w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12"
/>
</div>
<Title size="h3" center weight="medium">Booking confirmed</Title>
<p
class="w-full max-w-prose text-center text-sm text-gray-600 sm:text-base"
>
Thank you for booking your flight! Your order has been placed
successfully. You will receive a confirmation email shortly.
</p>
<p
class="w-full max-w-prose text-center text-sm text-gray-600 sm:text-base"
>
In it you will not only find the booking details, but also a
tracking number for your flight.
</p>
</div>
</MaxWidthWrapper>
</div>

View File

@@ -0,0 +1,61 @@
<script lang="ts">
import { page } from "$app/state";
import Icon from "$lib/components/atoms/icon.svelte";
import Title from "$lib/components/atoms/title.svelte";
import MaxWidthWrapper from "$lib/components/molecules/max-width-wrapper.svelte";
import Button from "$lib/components/ui/button/button.svelte";
import CloseIcon from "~icons/mdi/window-close";
import ArrowRightIcon from "~icons/solar/multiple-forward-right-line-duotone";
import RestartIcon from "~icons/material-symbols/restart-alt-rounded";
import { onMount } from "svelte";
let canRedirect = $state(false);
let sid = $derived(page.url.searchParams.get("sid") as any as string);
let tid = $derived(page.url.searchParams.get("tid") as any as string);
onMount(() => {
if (sid && tid && sid.length > 0 && tid.length > 0) {
canRedirect = true;
}
});
</script>
<div class="grid min-h-[80vh] w-full place-items-center">
<MaxWidthWrapper
cls="flex flex-col gap-8 items-center justify-center p-4 md:p-8"
>
<div
class="flex w-full flex-col items-center justify-center gap-8 rounded-xl bg-white p-4 drop-shadow-lg md:p-8 md:py-12 lg:w-max"
>
<div
class="rounded-full bg-rose-100 p-2 text-rose-600 drop-shadow-lg"
>
<Icon icon={CloseIcon} cls="w-12 h-12" />
</div>
<Title size="h3" center weight="medium">Session Terminated</Title>
<p class="w-full max-w-prose text-center text-gray-600">
Unfortunately, your session has been terminated due to inactivity
or expiration of the booking.
</p>
<div class="flex w-full flex-col justify-center gap-4 md:flex-row">
{#if canRedirect}
<Button
variant="default"
size="lg"
href={`/checkout/${sid}/${tid}`}
>
<Icon icon={RestartIcon} cls="w-auto h-6" />
<span>Try Again</span>
</Button>
{/if}
<Button variant="defaultInverted" size="lg" href="/search">
<Icon icon={ArrowRightIcon} cls="w-auto h-6" />
<span>Back To Search</span>
</Button>
</div>
</div>
</MaxWidthWrapper>
</div>

View File

@@ -0,0 +1,35 @@
<script lang="ts">
import Title from "$lib/components/atoms/title.svelte";
import TableOfContents from "../table-of-contents.svelte";
const toc = [{ title: "Introduction", link: "#introduction" }];
</script>
<section class="flex w-full flex-col-reverse gap-4 md:gap-8 lg:flex-row">
<div
class="col-span-3 flex w-full flex-col gap-4 rounded-lg border border-brand-200 bg-gradient-to-b from-white/50 to-brand-50 p-4 md:p-8"
>
<Title size="h2" weight="medium">Introduction</Title>
<p>
Welcome to FlyTicketTravel's legal documentation. As a flight booking
platform dedicated to providing seamless travel experiences, we take
our legal obligations seriously, particularly regarding payment
processing, data protection, and user privacy. This section outlines
our Privacy Policy, Terms and Conditions, Cookie Policy, and Refund
Policy, ensuring transparency in how we handle your personal data and
transactions.
</p>
<p>
Our policies are designed to comply with international data protection
laws, including GDPR. They cover everything from how we process your
flight bookings to how we secure your payment information. We
encourage you to read each section carefully to understand your rights
and our commitments to protecting your privacy while using
FlyTicketTravel's services.
</p>
</div>
<TableOfContents {toc} />
</section>

View File

@@ -0,0 +1,36 @@
<script lang="ts">
import { cn } from "$lib/utils";
import { TRANSITION_COLORS } from "$lib/core/constants";
import { legalArticles } from "./legal.articles";
import MaxWidthWrapper from "$lib/components/molecules/max-width-wrapper.svelte";
interface Props {
children?: import("svelte").Snippet;
}
let { children }: Props = $props();
</script>
<MaxWidthWrapper
cls="my-32 grid grid-cols-1 gap-4 px-4 md:px-8 lg:grid-cols-5 lg:gap-8"
>
<div
class="flex h-max flex-col gap-4 rounded-lg border border-brand-200 bg-brand-50/50 p-4 lg:col-span-1"
>
{#each legalArticles as article}
<a
href={article.link}
class={cn(
"cursor-pointer text-brand-950 hover:text-brand-600",
TRANSITION_COLORS,
)}
>
{article.title}
</a>
{/each}
</div>
<div class="lg:col-span-4">
{@render children?.()}
</div>
</MaxWidthWrapper>

View File

@@ -0,0 +1,93 @@
<script lang="ts">
import Title from "$lib/components/atoms/title.svelte";
import { CONTACT_INFO } from "$lib/core/constants";
import TableOfContents from "../table-of-contents.svelte";
const toc = [
{ title: "What Are Cookies?", link: "#what-are-cookies" },
{ title: "How We Use Cookies", link: "#how-we-use-cookies" },
{ title: "Types of Cookies We Use", link: "#types-of-cookies" },
{ title: "Managing Cookies", link: "#managing-cookies" },
{
title: "Changes to This Cookie Policy",
link: "#changes-to-this-cookie-policy",
},
{ title: "Contact Us", link: "#contact-us" },
];
const lastUpdated = "December 3, 2024";
</script>
<section class="flex w-full flex-col-reverse gap-6 md:gap-8 lg:flex-row">
<div
class="col-span-3 flex w-full flex-col gap-4 rounded-lg border border-brand-200 bg-gradient-to-b from-white/50 to-brand-50 p-4 md:p-8"
>
<Title size="h2" weight="medium">Cookie Policy</Title>
<p>
FlyTicketTravel ("we", "our", "us") uses cookies and similar
technologies to provide you with a better experience while using our
flight booking platform. This policy explains how we use cookies and
your choices regarding them.
</p>
<Title size="h3" weight="medium" id="what-are-cookies"
>1. What Are Cookies?</Title
>
<p>
Cookies are small text files stored on your device when you visit our
platform. They help us provide essential features and improve your
experience with FlyTicketTravel.
</p>
<Title size="h3" weight="medium" id="how-we-use-cookies"
>2. How We Use Cookies</Title
>
<p>We use cookies to:</p>
<ul class="list-disc pl-4">
<li>Keep you signed in to your FlyTicketTravel account securely</li>
<li>Remember your flight search preferences and settings</li>
<li>Ensure the security of your payment information</li>
<li>Improve our platform's performance</li>
<li>Analyze how our services are used</li>
</ul>
<Title size="h3" weight="medium" id="types-of-cookies"
>3. Types of Cookies We Use</Title
>
<p>Our platform uses:</p>
<ul class="list-disc pl-4">
<li>Essential cookies: Required for basic platform functionality</li>
<li>Authentication cookies: To keep you securely logged in</li>
<li>Preference cookies: To remember your settings</li>
<li>Analytics cookies: To improve our services</li>
</ul>
<Title size="h3" weight="medium" id="managing-cookies"
>4. Managing Cookies</Title
>
<p>
You can control cookies through your browser settings. Note that
disabling certain cookies may limit your access to some features of
FlyTicketTravel, particularly those related to secure booking and
payment processing.
</p>
<Title size="h3" weight="medium" id="changes-to-this-cookie-policy"
>5. Changes to This Cookie Policy</Title
>
<p>
We may update this policy as we enhance our platform. Any changes will
be posted here with an updated revision date.
</p>
<Title size="h3" weight="medium" id="contact-us">6. Contact Us</Title>
<p>
For questions about our cookie practices, contact us at {CONTACT_INFO.email}.
</p>
<small class="text-gray-500">Last Updated: {lastUpdated}</small>
</div>
<TableOfContents {toc} />
</section>

View File

@@ -0,0 +1,27 @@
export const legalArticles = [
{
id: "introduction",
title: "Introduction",
link: "/legal",
},
{
id: "privacy-policy",
title: "Privacy Policy",
link: "/legal/privacy-policy",
},
{
id: "terms-and-conditions",
title: "Terms & Conditions",
link: "/legal/terms-and-conditions",
},
{
id: "refund-policy",
title: "Refund Policy",
link: "/legal/refund-policy",
},
{
id: "cookie-policy",
title: "Cookie Policy",
link: "/legal/cookie-policy",
},
];

View File

@@ -0,0 +1,133 @@
<script lang="ts">
import Title from "$lib/components/atoms/title.svelte";
import { CONTACT_INFO } from "$lib/core/constants";
import TableOfContents from "../table-of-contents.svelte";
const toc = [
{ title: "Information We Collect", link: "#information-we-collect" },
{ title: "Booking Data", link: "#booking-data" },
{
title: "How We Use Your Information",
link: "#how-we-use-your-information",
},
{ title: "Data Protection", link: "#data-protection" },
{ title: "International Transfers", link: "#international-transfers" },
{ title: "Your Rights", link: "#your-rights" },
{ title: "Changes to Policy", link: "#changes-to-policy" },
{ title: "Contact Us", link: "#contact-us" },
];
const lastUpdated = "December 3, 2024";
</script>
<section class="flex w-full flex-col-reverse gap-6 md:gap-8 lg:flex-row">
<div
class="col-span-3 flex w-full flex-col gap-4 rounded-lg border border-brand-200 bg-gradient-to-b from-white/50 to-brand-50 p-4 md:p-8"
>
<Title size="h2" weight="medium">Privacy Policy</Title>
<p>
FlyTicketTravel is committed to protecting your privacy and ensuring
the security of your personal information. This Privacy Policy
explains how we collect, use, and protect your data when you use our
flight booking platform.
</p>
<Title size="h3" weight="medium" id="information-we-collect"
>1. Information We Collect</Title
>
<p>We collect the following types of information:</p>
<ul class="list-disc pl-4">
<li>
Personal Information:
<ul class="list-disc pl-4">
<li>Full name and contact details</li>
<li>Travel document information (passport/ID)</li>
<li>Payment details</li>
<li>Date of birth and nationality</li>
</ul>
</li>
<li>
Usage Information:
<ul class="list-disc pl-4">
<li>How you use our platform</li>
<li>Search history and preferences</li>
<li>Device and browser information</li>
</ul>
</li>
</ul>
<Title size="h3" weight="medium" id="booking-data">2. Booking Data</Title>
<p>We handle travel booking information including:</p>
<ul class="list-disc pl-4">
<li>Flight reservation details</li>
<li>Itinerary information</li>
<li>Special requests (meals, seating, etc.)</li>
</ul>
<p>
This data is processed in accordance with GDPR and relevant travel
industry regulations. We implement additional security measures for
payment data protection.
</p>
<Title size="h3" weight="medium" id="how-we-use-your-information"
>3. How We Use Your Information</Title
>
<p>We use your information to:</p>
<ul class="list-disc pl-4">
<li>Process flight bookings</li>
<li>Provide customer support</li>
<li>Send booking confirmations and updates</li>
<li>Improve our platform</li>
<li>Comply with legal obligations</li>
</ul>
<Title size="h3" weight="medium" id="data-protection"
>4. Data Protection</Title
>
<p>We implement robust security measures including:</p>
<ul class="list-disc pl-4">
<li>Encryption for payment data</li>
<li>Secure access controls</li>
<li>Regular security audits</li>
<li>Staff training on data protection</li>
</ul>
<Title size="h3" weight="medium" id="international-transfers"
>5. International Transfers</Title
>
<p>
As a flight booking service, we may transfer data across borders. All
transfers comply with GDPR and relevant data protection laws, using
appropriate safeguards and security measures.
</p>
<Title size="h3" weight="medium" id="your-rights">6. Your Rights</Title>
<p>Under GDPR, you have the right to:</p>
<ul class="list-disc pl-4">
<li>Access your personal data</li>
<li>Correct inaccurate data</li>
<li>Request data deletion</li>
<li>Restrict processing</li>
<li>Data portability</li>
<li>Object to processing</li>
</ul>
<Title size="h3" weight="medium" id="changes-to-policy"
>7. Changes to Policy</Title
>
<p>
We may update this policy as our services evolve. Significant changes
will be notified to users directly through the platform.
</p>
<Title size="h3" weight="medium" id="contact-us">8. Contact Us</Title>
<p>
For privacy-related queries or to exercise your rights, contact our
Data Protection Officer at {CONTACT_INFO.email}.
</p>
<small class="text-gray-500">Last Updated: {lastUpdated}</small>
</div>
<TableOfContents {toc} />
</section>

View File

@@ -0,0 +1,177 @@
<script lang="ts">
import Title from "$lib/components/atoms/title.svelte";
import { CONTACT_INFO } from "$lib/core/constants";
import TableOfContents from "../table-of-contents.svelte";
const toc = [
{ title: "Booking Cancellations", link: "#booking-cancellations" },
{
title: "Airline-Controlled Refunds",
link: "#airline-controlled-refunds",
},
{
title: "FlyTicketTravel Service Fees",
link: "#FlyTicketTravel-service-fees",
},
{ title: "Refund Processing", link: "#refund-processing" },
{ title: "Flight Changes", link: "#flight-changes" },
{ title: "Special Circumstances", link: "#special-circumstances" },
{ title: "Contact Us", link: "#contact-us" },
];
const lastUpdated = "December 3, 2024";
</script>
<section class="flex w-full flex-col-reverse gap-6 md:gap-8 lg:flex-row">
<div
class="col-span-3 flex w-full flex-col gap-4 rounded-lg border border-brand-200 bg-gradient-to-b from-white/50 to-brand-50 p-4 md:p-8"
>
<Title size="h2" weight="medium">Refund Policy</Title>
<p>
At FlyTicketTravel, we understand that travel plans can change. This
policy outlines our approach to refunds and cancellations for flight
bookings made through our platform.
</p>
<Title size="h3" weight="medium" id="booking-cancellations"
>1. Booking Cancellations</Title
>
<p>Our cancellation policy varies based on ticket type:</p>
<ul class="list-disc pl-4">
<li>
<strong>Refundable Tickets:</strong> Eligible for full or partial refunds
as per airline terms
</li>
<li>
<strong>Non-Refundable Tickets:</strong> Generally not eligible for
refunds, but may receive airline credits or partial refunds in special
circumstances
</li>
<li>
<strong>Flexible Tickets:</strong> Can be changed with minimal or no
fees, subject to fare difference
</li>
</ul>
<p>
The specific refund terms for your booking are displayed during the
booking process and in your confirmation email.
</p>
<Title size="h3" weight="medium" id="airline-controlled-refunds"
>2. Airline-Controlled Refunds</Title
>
<p>Important information about airline policies:</p>
<ul class="list-disc pl-4">
<li>
Most refunds are subject to the airline's fare rules and
conditions of carriage
</li>
<li>Each airline has its own cancellation fees and policies</li>
<li>
Some low-cost carriers offer no refunds under any circumstances
</li>
<li>Premium airlines typically offer more flexible refund options</li>
</ul>
<p>
FlyTicketTravel will advocate on your behalf with airlines, but we
cannot override their refund policies.
</p>
<Title size="h3" weight="medium" id="FlyTicketTravel-service-fees"
>3. FlyTicketTravel Service Fees</Title
>
<p>Our policy regarding service fees:</p>
<ul class="list-disc pl-4">
<li>
FlyTicketTravel booking service fees are non-refundable after 24
hours from booking
</li>
<li>
Within 24 hours of booking, service fees are fully refundable if
you cancel
</li>
<li>
Premium support fees are non-refundable once the service has been
provided
</li>
<li>
Seat selection and other ancillary fees follow the airline's
refund policy
</li>
</ul>
<Title size="h3" weight="medium" id="refund-processing"
>4. Refund Processing</Title
>
<p>When you're eligible for a refund:</p>
<ul class="list-disc pl-4">
<li>
Refunds are processed to the original payment method used for
booking
</li>
<li>
Processing time is typically 7-14 business days after airline
approval
</li>
<li>
Credit card refunds may take an additional 1-2 billing cycles to
appear
</li>
<li>
You'll receive email confirmation when your refund is processed
</li>
</ul>
<Title size="h3" weight="medium" id="flight-changes"
>5. Flight Changes</Title
>
<p>Our policy for changing flights:</p>
<ul class="list-disc pl-4">
<li>
Most tickets allow date/time changes for a fee plus any fare
difference
</li>
<li>
Changes must be made at least 24 hours before departure (varies by
airline)
</li>
<li>Name changes are generally not permitted by airlines</li>
<li>Route changes are treated as a cancellation and new booking</li>
</ul>
<Title size="h3" weight="medium" id="special-circumstances"
>6. Special Circumstances</Title
>
<p>We offer additional flexibility for:</p>
<ul class="list-disc pl-4">
<li>
<strong>Flight Cancellations by Airline:</strong> Full refund or rebooking
assistance
</li>
<li>
<strong>Significant Schedule Changes:</strong> Option to accept change
or request refund
</li>
<li>
<strong>Medical Emergencies:</strong> We'll help you request special
consideration from airlines (documentation required)
</li>
<li>
<strong>COVID-19 Related Changes:</strong> Subject to current airline
and regulatory policies
</li>
</ul>
<Title size="h3" weight="medium" id="contact-us">7. Contact Us</Title>
<p>
To request a refund or for questions about our refund policy, please
contact our customer support team at {CONTACT_INFO.email} or through the
support section in your FlyTicketTravel account.
</p>
<small class="text-gray-500">Last Updated: {lastUpdated}</small>
</div>
<TableOfContents {toc} />
</section>

View File

@@ -0,0 +1,31 @@
<script lang="ts">
import Title from "$lib/components/atoms/title.svelte";
import { cn } from "$lib/utils";
import { TRANSITION_COLORS } from "$lib/core/constants";
interface Props {
toc?: any;
}
let { toc = [] }: Props = $props();
</script>
<div class="flex w-full flex-col gap-4 lg:max-w-[15rem]">
<div
class="flex w-full flex-col gap-4 rounded-lg border border-brand-200 bg-brand-50/50 p-4"
>
<Title capitalize size="h5" weight="medium">Table of contents</Title>
{#each toc as each}
<a
class={cn(
"cursor-pointer text-brand-950 hover:text-brand-600",
TRANSITION_COLORS,
)}
href={each.link}
>
{each.title}
</a>
{/each}
</div>
</div>

View File

@@ -0,0 +1,119 @@
<script lang="ts">
import Title from "$lib/components/atoms/title.svelte";
import { CONTACT_INFO } from "$lib/core/constants";
import TableOfContents from "../table-of-contents.svelte";
const toc = [
{ title: "Platform Use", link: "#platform-use" },
{ title: "Booking Disclaimer", link: "#booking-disclaimer" },
{ title: "User Accounts", link: "#user-accounts" },
{ title: "Booking & Payment", link: "#booking-payment" },
{ title: "Flight Information", link: "#flight-information" },
{ title: "Liability", link: "#liability" },
{ title: "Changes to Terms", link: "#changes-to-terms" },
{ title: "Contact Us", link: "#contact-us" },
];
const lastUpdated = "December 3, 2024";
</script>
<section class="flex w-full flex-col-reverse gap-6 md:gap-8 lg:flex-row">
<div
class="col-span-3 flex w-full flex-col gap-4 rounded-lg border border-brand-200 bg-gradient-to-b from-white/50 to-brand-50 p-4 md:p-8"
>
<Title size="h2" weight="medium">Terms & Conditions</Title>
<p>
Welcome to FlyTicketTravel. By using our platform, you agree to these
terms. Please read them carefully as they govern your use of our
flight booking services.
</p>
<Title size="h3" weight="medium" id="platform-use">1. Platform Use</Title>
<p>
FlyTicketTravel provides tools for flight search, booking, and
management. You agree to:
</p>
<ul class="list-disc pl-4">
<li>Provide accurate information</li>
<li>Use the platform legally and appropriately</li>
<li>Not misuse or attempt to manipulate our services</li>
<li>Maintain the confidentiality of your account</li>
</ul>
<Title size="h3" weight="medium" id="booking-disclaimer"
>2. Booking Disclaimer</Title
>
<p>FlyTicketTravel is a flight booking platform:</p>
<ul class="list-disc pl-4">
<li>We facilitate bookings between you and airlines</li>
<li>The airline's conditions of carriage apply to your travel</li>
<li>
We are not responsible for airline schedule changes or
cancellations
</li>
</ul>
<Title size="h3" weight="medium" id="user-accounts"
>3. User Accounts</Title
>
<p>To use FlyTicketTravel, you must:</p>
<ul class="list-disc pl-4">
<li>Be at least 18 years old or have guardian consent</li>
<li>Provide valid identification when required</li>
<li>Maintain accurate account information</li>
<li>Protect your account credentials</li>
</ul>
<Title size="h3" weight="medium" id="booking-payment"
>4. Booking & Payment</Title
>
<p>When making bookings through our platform:</p>
<ul class="list-disc pl-4">
<li>All prices are displayed in the selected currency</li>
<li>Payment processing is secure and PCI-DSS compliant</li>
<li>Booking is confirmed only after payment is processed</li>
<li>Refunds are subject to our Refund Policy and airline terms</li>
</ul>
<Title size="h3" weight="medium" id="flight-information"
>5. Flight Information</Title
>
<p>Regarding flight details:</p>
<ul class="list-disc pl-4">
<li>We strive to provide accurate and up-to-date information</li>
<li>Flight schedules are subject to change by airlines</li>
<li>It is your responsibility to check final departure details</li>
<li>We will notify you of major changes when possible</li>
</ul>
<Title size="h3" weight="medium" id="liability"
>6. Limitation of Liability</Title
>
<p>FlyTicketTravel is not liable for:</p>
<ul class="list-disc pl-4">
<li>Airline service quality or performance</li>
<li>Flight delays, cancellations, or schedule changes</li>
<li>Lost or damaged baggage</li>
<li>Accuracy of third-party information</li>
<li>Service interruptions or technical issues</li>
</ul>
<Title size="h3" weight="medium" id="changes-to-terms"
>7. Changes to Terms</Title
>
<p>
We may update these terms as our services evolve. Continued use after
changes constitutes acceptance of new terms.
</p>
<Title size="h3" weight="medium" id="contact-us">8. Contact Us</Title>
<p>
For questions about these terms, contact us at {CONTACT_INFO.email}.
</p>
<small class="text-gray-500">Last Updated: {lastUpdated}</small>
</div>
<TableOfContents {toc} />
</section>

View File

@@ -0,0 +1,56 @@
<script lang="ts">
import { page } from "$app/stores";
import TicketFiltersSheet from "$lib/domains/ticket/view/search/ticket-filters-sheet.svelte";
import TicketSearchInputSheet from "$lib/domains/ticket/view/search/ticket-search-input-sheet.svelte";
import SearchedTicketsList from "$lib/domains/ticket/view/searched-tickets-list.svelte";
import TicketFiltersSelect from "$lib/domains/ticket/view/ticket-filters-select.svelte";
import TicketSearchInput from "$lib/domains/ticket/view/ticket-search-input.svelte";
import MaxWidthWrapper from "$lib/components/molecules/max-width-wrapper.svelte";
import { flightTicketVM } from "$lib/domains/ticket/view/ticket.vm.svelte";
import { onMount } from "svelte";
import { toast } from "svelte-sonner";
function onSubmit() {
const out = flightTicketVM.setURLParams();
if (out.data) {
flightTicketVM.searchForTickets();
}
if (out.error) {
toast.error(out.error.message, { description: out.error.userHint });
}
}
onMount(() => {
flightTicketVM.loadStore($page.url.searchParams);
flightTicketVM.searching = true;
setTimeout(() => flightTicketVM.searchForTickets());
});
</script>
<div class="grid h-full min-h-[70vh] w-full place-items-center">
<section class="max-w-8xl flex w-full flex-col items-center gap-8 p-4 md:p-8">
<MaxWidthWrapper cls="flex w-full flex-col items-center gap-2">
<div
class="hidden w-full rounded-lg bg-white p-4 drop-shadow-lg lg:block"
>
<TicketSearchInput rowify {onSubmit} />
</div>
</MaxWidthWrapper>
<MaxWidthWrapper cls="flex gap-8 w-full h-full">
{#if flightTicketVM.tickets.length > 0}
<div
class="hidden h-max w-full max-w-sm flex-col rounded-lg bg-white p-8 shadow-lg lg:flex"
>
<TicketFiltersSelect />
</div>
{/if}
<SearchedTicketsList />
</MaxWidthWrapper>
</section>
</div>
<div class="fixed bottom-4 right-4 z-50 flex flex-col gap-4 lg:hidden">
<TicketFiltersSheet />
<TicketSearchInputSheet {onSubmit} />
</div>

View File

@@ -0,0 +1,59 @@
<script lang="ts">
import MaxWidthWrapper from "$lib/components/molecules/max-width-wrapper.svelte";
import { Input } from "$lib/components/ui/input";
import { Button } from "$lib/components/ui/button";
import { trackVM } from "$lib/domains/order/view/track/track.vm.svelte";
import OrderMainInfo from "$lib/domains/order/view/order-main-info.svelte";
import ButtonLoadableText from "$lib/components/atoms/button-loadable-text.svelte";
import OrderMiscInfo from "$lib/domains/order/view/order-misc-info.svelte";
import { onMount } from "svelte";
import { page } from "$app/state";
let searchDisabled = $derived(trackVM.loading || !trackVM.pnr);
onMount(() => {
const pnr = page.url.searchParams.get("pnr");
if (pnr) {
trackVM.pnr = pnr;
trackVM.searchBooking();
}
});
</script>
<div
class="grid h-full w-full place-items-center gap-12 p-8 pt-32 lg:p-12 lg:pt-32"
>
<MaxWidthWrapper cls="flex max-w-7xl flex-col gap-8">
<div class="flex flex-col gap-4">
<h2 class="text-2xl font-bold">Track Your Booking</h2>
<div class="flex gap-2">
<Input
placeholder="Enter your PNR number"
bind:value={trackVM.pnr}
/>
<Button
disabled={searchDisabled}
onclick={() => trackVM.searchBooking()}
>
<ButtonLoadableText
loading={trackVM.loading}
loadingText="Searching..."
text="Search"
/>
</Button>
</div>
{#if trackVM.error}
<p class="text-red-500">{trackVM.error}</p>
{/if}
</div>
</MaxWidthWrapper>
<MaxWidthWrapper cls="max-w-7xl w-full">
<div class="grid w-full grid-cols-1 gap-8 lg:grid-cols-2">
{#if trackVM.bookingData}
<OrderMainInfo order={trackVM.bookingData} />
<OrderMiscInfo order={trackVM.bookingData} />
{/if}
</div>
</MaxWidthWrapper>
</div>