stashing code
This commit is contained in:
5
apps/frontend/src/routes/(main)/+layout.server.ts
Normal file
5
apps/frontend/src/routes/(main)/+layout.server.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import type { LayoutServerLoad } from "./$types";
|
||||
|
||||
export const load: LayoutServerLoad = async ({ locals }) => {
|
||||
return { session: locals.session, user: locals.user };
|
||||
};
|
||||
73
apps/frontend/src/routes/(main)/+layout.svelte
Normal file
73
apps/frontend/src/routes/(main)/+layout.svelte
Normal 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}
|
||||
438
apps/frontend/src/routes/(main)/+page.svelte
Normal file
438
apps/frontend/src/routes/(main)/+page.svelte
Normal 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>
|
||||
@@ -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"));
|
||||
};
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
35
apps/frontend/src/routes/(main)/legal/(main)/+page.svelte
Normal file
35
apps/frontend/src/routes/(main)/legal/(main)/+page.svelte
Normal 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>
|
||||
36
apps/frontend/src/routes/(main)/legal/+layout.svelte
Normal file
36
apps/frontend/src/routes/(main)/legal/+layout.svelte
Normal 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>
|
||||
@@ -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>
|
||||
27
apps/frontend/src/routes/(main)/legal/legal.articles.ts
Normal file
27
apps/frontend/src/routes/(main)/legal/legal.articles.ts
Normal 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",
|
||||
},
|
||||
];
|
||||
@@ -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>
|
||||
177
apps/frontend/src/routes/(main)/legal/refund-policy/+page.svelte
Normal file
177
apps/frontend/src/routes/(main)/legal/refund-policy/+page.svelte
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
56
apps/frontend/src/routes/(main)/search/+page.svelte
Normal file
56
apps/frontend/src/routes/(main)/search/+page.svelte
Normal 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>
|
||||
59
apps/frontend/src/routes/(main)/track/+page.svelte
Normal file
59
apps/frontend/src/routes/(main)/track/+page.svelte
Normal 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>
|
||||
40
apps/frontend/src/routes/+layout.svelte
Normal file
40
apps/frontend/src/routes/+layout.svelte
Normal file
@@ -0,0 +1,40 @@
|
||||
<script lang="ts">
|
||||
import "../app.css";
|
||||
import { Toaster } from "$lib/components/ui/sonner";
|
||||
import { browser } from "$app/environment";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/svelte-query";
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { enabled: browser } },
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>FlyTicketTravel</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Book a flight to your dream destination at an unbelieavably low price"
|
||||
/>
|
||||
|
||||
<script
|
||||
async
|
||||
src="https://www.googletagmanager.com/gtag/js?id=AW-17085207253"
|
||||
></script>
|
||||
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
window.gtag = function () {
|
||||
dataLayer.push(arguments);
|
||||
};
|
||||
window.gtag("js", new Date());
|
||||
window.gtag("config", "AW-17085207253");
|
||||
</script>
|
||||
</svelte:head>
|
||||
|
||||
<Toaster />
|
||||
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{@render children?.()}
|
||||
</QueryClientProvider>
|
||||
10
apps/frontend/src/routes/api/logs/+server.ts
Normal file
10
apps/frontend/src/routes/api/logs/+server.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Logger } from "@pkg/logger";
|
||||
import type { LogEntry } from "@pkg/logger/client";
|
||||
import type { RequestHandler } from "@sveltejs/kit";
|
||||
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
const payload = (await request.json()) as LogEntry;
|
||||
Logger.info("From the frontend");
|
||||
Logger.info(payload);
|
||||
return new Response(null, { status: 200 });
|
||||
};
|
||||
27
apps/frontend/src/routes/auth/login/+page.svelte
Normal file
27
apps/frontend/src/routes/auth/login/+page.svelte
Normal file
@@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
import Title from "$lib/components/atoms/title.svelte";
|
||||
import LoginForm from "$lib/domains/auth/view/login-form.svelte";
|
||||
import { trpcApiStore, svTrpcApiStore } from "$lib/stores/api";
|
||||
import { trpc, trpcRaw } from "$lib/trpc/trpc";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
svTrpcApiStore.set(trpc());
|
||||
|
||||
onMount(() => {
|
||||
trpcApiStore.set(trpcRaw());
|
||||
});
|
||||
</script>
|
||||
|
||||
<main class="grid h-screen w-screen place-items-center">
|
||||
<section
|
||||
class="flex w-full max-w-md flex-col items-center justify-center gap-8"
|
||||
>
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<Title weight="medium" size="h2" color="gradientPrimary"
|
||||
>Sign in</Title
|
||||
>
|
||||
<p class="max-w-96 font-light">Enter your credentials to continue</p>
|
||||
</div>
|
||||
<LoginForm />
|
||||
</section>
|
||||
</main>
|
||||
1
apps/frontend/src/routes/u/profile/+page.svelte
Normal file
1
apps/frontend/src/routes/u/profile/+page.svelte
Normal file
@@ -0,0 +1 @@
|
||||
<span>profile page here</span>
|
||||
Reference in New Issue
Block a user