diff --git a/apps/frontend/src/lib/domains/checkout/flight-checkout.vm.svelte.ts b/apps/frontend/src/lib/domains/checkout/checkout.vm.svelte.ts
similarity index 67%
rename from apps/frontend/src/lib/domains/checkout/flight-checkout.vm.svelte.ts
rename to apps/frontend/src/lib/domains/checkout/checkout.vm.svelte.ts
index 540acca..f997fe1 100644
--- a/apps/frontend/src/lib/domains/checkout/flight-checkout.vm.svelte.ts
+++ b/apps/frontend/src/lib/domains/checkout/checkout.vm.svelte.ts
@@ -1,6 +1,5 @@
import { ckFlowVM } from "$lib/domains/ckflow/view/ckflow.vm.svelte";
import { CheckoutStep, newOrderModel } from "$lib/domains/order/data/entities";
-import { passengerInfoVM } from "$lib/domains/passengerinfo/view/passenger.info.vm.svelte";
import {
paymentInfoPayloadModel,
PaymentMethod,
@@ -8,9 +7,10 @@ import {
import { trpcApiStore } from "$lib/stores/api";
import { toast } from "svelte-sonner";
import { get } from "svelte/store";
-import { flightTicketStore } from "../ticket/data/store";
+import { customerInfoVM } from "../customerinfo/view/customerinfo.vm.svelte";
+import { productStore } from "../product/store";
import { paymentInfoVM } from "./payment-info-section/payment.info.vm.svelte";
-import { calculateTicketPrices } from "./total.calculator";
+import { calculateFinalPrices } from "./utils";
class CheckoutViewModel {
checkoutStep = $state(CheckoutStep.Initial);
@@ -44,14 +44,8 @@ class CheckoutViewModel {
if (!api) {
return false;
}
- const ticket = get(flightTicketStore);
- if (!ticket || !ticket.refOIds) {
- return false;
- }
- const out = await api.ticket.ping.query({
- tid: ticket.id,
- refOIds: ticket.refOIds,
- });
+
+ // TODO: no need to ping now – REMOVE THIS PINGING LOGIC
}
async checkout() {
@@ -65,34 +59,21 @@ class CheckoutViewModel {
this.checkoutSubmitted = false;
return false;
}
+ const product = get(productStore);
- const ticket = get(flightTicketStore);
+ if (!product || !customerInfoVM.customerInfo) {
+ this.checkoutSubmitted = false;
+ return false;
+ }
- const prices = calculateTicketPrices(
- ticket,
- passengerInfoVM.passengerInfos,
+ const priceDetails = calculateFinalPrices(
+ product,
+ customerInfoVM.customerInfo,
);
- const validatedPrices = {
- subtotal: isNaN(prices.subtotal) ? 0 : prices.subtotal,
- discountAmount: isNaN(prices.discountAmount)
- ? 0
- : prices.discountAmount,
- finalTotal: isNaN(prices.finalTotal) ? 0 : prices.finalTotal,
- pricePerPassenger: isNaN(prices.pricePerPassenger)
- ? 0
- : prices.pricePerPassenger,
- };
-
const parsed = newOrderModel.safeParse({
- basePrice: validatedPrices.subtotal,
- discountAmount: validatedPrices.discountAmount,
- displayPrice: validatedPrices.finalTotal,
- orderPrice: validatedPrices.finalTotal, // Same as displayPrice
- fullfilledPrice: validatedPrices.finalTotal, // Same as displayPrice
- pricePerPassenger: validatedPrices.pricePerPassenger,
- flightTicketInfoId: -1,
- paymentInfoId: -1,
+ ...priceDetails,
+ productId: product.id,
});
if (parsed.error) {
@@ -107,7 +88,7 @@ class CheckoutViewModel {
const pInfoParsed = paymentInfoPayloadModel.safeParse({
method: PaymentMethod.Card,
cardDetails: paymentInfoVM.cardDetails,
- flightTicketInfoId: ticket.id,
+ productId: get(productStore)?.id,
});
if (pInfoParsed.error) {
console.log(parsed.error);
@@ -122,11 +103,10 @@ class CheckoutViewModel {
console.log("Creating order");
this.loading = true;
const out = await api.order.createOrder.mutate({
- flightTicketId: ticket.id,
+ productId: get(productStore)?.id,
orderModel: parsed.data,
- passengerInfos: passengerInfoVM.passengerInfos,
+ customerInfo: customerInfoVM.customerInfo!,
paymentInfo: pInfoParsed.data,
- refOIds: ticket.refOIds,
flowId: ckFlowVM.flowId,
});
diff --git a/apps/frontend/src/lib/domains/checkout/initial-info-section.svelte b/apps/frontend/src/lib/domains/checkout/initial-info-section.svelte
index 112661d..479cc1f 100644
--- a/apps/frontend/src/lib/domains/checkout/initial-info-section.svelte
+++ b/apps/frontend/src/lib/domains/checkout/initial-info-section.svelte
@@ -2,31 +2,24 @@
import ButtonLoadableText from "$lib/components/atoms/button-loadable-text.svelte";
import Icon from "$lib/components/atoms/icon.svelte";
import Title from "$lib/components/atoms/title.svelte";
- import Badge from "$lib/components/ui/badge/badge.svelte";
import Button from "$lib/components/ui/button/button.svelte";
- import { capitalize } from "$lib/core/string.utils";
+ import { checkoutVM } from "$lib/domains/checkout/checkout.vm.svelte";
import { ckFlowVM } from "$lib/domains/ckflow/view/ckflow.vm.svelte";
- import PassengerBagSelection from "$lib/domains/passengerinfo/view/passenger-bag-selection.svelte";
- import PassengerPiiForm from "$lib/domains/passengerinfo/view/passenger-pii-form.svelte";
- import { passengerInfoVM } from "$lib/domains/passengerinfo/view/passenger.info.vm.svelte";
+ import { CheckoutStep } from "$lib/domains/order/data/entities";
import { cn } from "$lib/utils";
import { onMount } from "svelte";
import { toast } from "svelte-sonner";
import RightArrowIcon from "~icons/solar/arrow-right-broken";
- import { CheckoutStep } from "../../data/entities";
- import { flightTicketStore } from "../../data/store";
- import TripDetails from "../ticket/trip-details.svelte";
- import { checkoutVM } from "./checkout.vm.svelte";
+ import CustomerPiiForm from "../customerinfo/view/customer-pii-form.svelte";
+ import { customerInfoVM } from "../customerinfo/view/customerinfo.vm.svelte";
const cardStyle =
"flex w-full flex-col gap-4 rounded-lg bg-white p-4 shadow-lg md:p-8";
$effect(() => {
- const primaryPassenger = passengerInfoVM.passengerInfos[0];
- if (!ckFlowVM.flowId || !ckFlowVM.setupDone || !primaryPassenger) return;
+ const personalInfo = customerInfoVM.customerInfo;
- const personalInfo = primaryPassenger.passengerPii;
- if (!personalInfo) return;
+ if (!ckFlowVM.flowId || !ckFlowVM.setupDone || !personalInfo) return;
// to trigger the effect
const {
@@ -40,11 +33,6 @@
city,
state,
country,
- nationality,
- gender,
- dob,
- passportNo,
- passportExpiry,
} = personalInfo;
if (
firstName ||
@@ -56,12 +44,7 @@
state ||
zipCode ||
address ||
- address2 ||
- nationality ||
- gender ||
- dob ||
- passportNo ||
- passportExpiry
+ address2
) {
console.log("pi ping");
ckFlowVM.debouncePersonalInfoSync(personalInfo);
@@ -69,9 +52,9 @@
});
async function proceedToNextStep() {
- passengerInfoVM.validateAllPII();
- console.log(passengerInfoVM.piiErrors);
- if (!passengerInfoVM.isPIIValid()) {
+ customerInfoVM.validateCustomerInfo();
+ console.log(customerInfoVM.errors);
+ if (!customerInfoVM.isValid()) {
return toast.error("Some or all info is invalid", {
description: "Please properly fill out all of the fields",
});
@@ -90,63 +73,32 @@
onMount(() => {
window.scrollTo(0, 0);
setTimeout(() => {
- passengerInfoVM.setupPassengerInfo(
- $flightTicketStore.passengerCounts,
- );
+ customerInfoVM.initializeCustomerInfo();
}, 200);
});
-{#if $flightTicketStore}
-
-
-
-
- {#if passengerInfoVM.passengerInfos.length > 0}
- {#each passengerInfoVM.passengerInfos as info, idx}
- {@const name =
- info.passengerPii.firstName.length > 0 ||
- info.passengerPii.lastName.length > 0
- ? `${info.passengerPii.firstName} ${info.passengerPii.lastName}`
- : `Passenger #${idx + 1}`}
-
-
-
- {name}
-
-
-
- {capitalize(info.passengerType)}
-
-
-
-
-
-
- {/each}
- {/if}
-
-
-
-
-
+{#if customerInfoVM.customerInfo}
+
+
Personal Info
+
{/if}
+
+
diff --git a/apps/frontend/src/lib/domains/checkout/payment-info-section/billing-details-form.svelte b/apps/frontend/src/lib/domains/checkout/payment-info-section/billing-details-form.svelte
index f4bd3d2..07343c8 100644
--- a/apps/frontend/src/lib/domains/checkout/payment-info-section/billing-details-form.svelte
+++ b/apps/frontend/src/lib/domains/checkout/payment-info-section/billing-details-form.svelte
@@ -4,10 +4,10 @@
import * as Select from "$lib/components/ui/select";
import { COUNTRIES_SELECT } from "$lib/core/countries";
import { capitalize } from "$lib/core/string.utils";
- import type { CustomerInfo } from "$lib/domains/ticket/data/entities/create.entities";
+ import type { CustomerInfoModel } from "$lib/domains/ticket/data/entities/create.entities";
import { billingDetailsVM } from "./billing.details.vm.svelte";
- let { info = $bindable() }: { info: CustomerInfo } = $props();
+ let { info = $bindable() }: { info: CustomerInfoModel } = $props();
function onSubmit(e: SubmitEvent) {
e.preventDefault();
diff --git a/apps/frontend/src/lib/domains/checkout/payment-info-section/billing.details.vm.svelte.ts b/apps/frontend/src/lib/domains/checkout/payment-info-section/billing.details.vm.svelte.ts
index 5d78eb1..4da2ac2 100644
--- a/apps/frontend/src/lib/domains/checkout/payment-info-section/billing.details.vm.svelte.ts
+++ b/apps/frontend/src/lib/domains/checkout/payment-info-section/billing.details.vm.svelte.ts
@@ -1,15 +1,12 @@
-import {
- customerInfoModel,
- type CustomerInfo,
-} from "$lib/domains/ticket/data/entities/create.entities";
-import { Gender } from "$lib/domains/ticket/data/entities/index";
+import { type CustomerInfoModel, Gender } from "$lib/domains/customerinfo/data";
+import { customerInfoModel } from "$lib/domains/passengerinfo/data/entities";
import { z } from "zod";
export class BillingDetailsViewModel {
// @ts-ignore
- billingDetails = $state
(undefined);
+ billingDetails = $state(undefined);
- piiErrors = $state>>({});
+ piiErrors = $state>>({});
constructor() {
this.reset();
@@ -34,15 +31,15 @@ export class BillingDetailsViewModel {
zipCode: "",
address: "",
address2: "",
- } as CustomerInfo;
+ } as CustomerInfoModel;
this.piiErrors = {};
}
- setPII(info: CustomerInfo) {
+ setPII(info: CustomerInfoModel) {
this.billingDetails = info;
}
- validatePII(info: CustomerInfo) {
+ validatePII(info: CustomerInfoModel) {
try {
const result = customerInfoModel.parse(info);
this.piiErrors = {};
@@ -51,11 +48,11 @@ export class BillingDetailsViewModel {
if (error instanceof z.ZodError) {
this.piiErrors = error.errors.reduce(
(acc, curr) => {
- const path = curr.path[0] as keyof CustomerInfo;
+ const path = curr.path[0] as keyof CustomerInfoModel;
acc[path] = curr.message;
return acc;
},
- {} as Record,
+ {} as Record,
);
}
return null;
diff --git a/apps/frontend/src/lib/domains/checkout/total.calculator.ts b/apps/frontend/src/lib/domains/checkout/total.calculator.ts
deleted file mode 100644
index d27a936..0000000
--- a/apps/frontend/src/lib/domains/checkout/total.calculator.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-import type { PassengerInfo } from "$lib/domains/passengerinfo/data/entities";
-import type { FlightTicket } from "../ticket/data/entities";
-
-export interface BaggageCost {
- passengerId: number;
- passengerName: string;
- personalBagCost: number;
- handBagCost: number;
- checkedBagCost: number;
- totalBaggageCost: number;
-}
-
-export interface PriceBreakdown {
- baseTicketPrice: number;
- pricePerPassenger: number;
- passengerBaggageCosts: BaggageCost[];
- totalBaggageCost: number;
- subtotal: number;
- discountAmount: number;
- finalTotal: number;
-}
-
-export function calculateTicketPrices(
- ticket: FlightTicket,
- passengerInfos: PassengerInfo[],
-): PriceBreakdown {
- if (!ticket || !passengerInfos || passengerInfos.length === 0) {
- return {
- baseTicketPrice: 0,
- pricePerPassenger: 0,
- passengerBaggageCosts: [],
- totalBaggageCost: 0,
- subtotal: 0,
- discountAmount: 0,
- finalTotal: 0,
- };
- }
-
- const displayPrice = ticket.priceDetails?.displayPrice ?? 0;
- const originalBasePrice = ticket.priceDetails?.basePrice ?? 0;
- const baseTicketPrice = Math.max(displayPrice, originalBasePrice);
- const pricePerPassenger =
- passengerInfos.length > 0
- ? baseTicketPrice / passengerInfos.length
- : baseTicketPrice;
-
- const passengerBaggageCosts: BaggageCost[] = passengerInfos.map(
- (passenger) => {
- // const personalBagCost =
- // (passenger.bagSelection.personalBags || 0) *
- // (ticket?.bagsInfo.details.personalBags.price ?? 0);
- // const handBagCost =
- // (passenger.bagSelection.handBags || 0) *
- // (ticket?.bagsInfo.details.handBags.price ?? 0);
- // const checkedBagCost =
- // (passenger.bagSelection.checkedBags || 0) *
- // (ticket?.bagsInfo.details.checkedBags.price ?? 0);
-
- return {
- passengerId: passenger.id,
- passengerName: `${passenger.passengerPii.firstName} ${passenger.passengerPii.lastName}`,
- personalBagCost: 0,
- handBagCost: 0,
- checkedBagCost: 0,
- totalBaggageCost: 0,
- // totalBaggageCost: personalBagCost + handBagCost + checkedBagCost,
- };
- },
- );
-
- // const totalBaggageCost = passengerBaggageCosts.reduce(
- // (acc, curr) => acc + curr.totalBaggageCost,
- // 0,
- // );
- const totalBaggageCost = 0;
-
- const subtotal = baseTicketPrice + totalBaggageCost;
-
- const discountAmount =
- originalBasePrice > displayPrice
- ? (ticket?.priceDetails.discountAmount ?? 0)
- : 0;
-
- const finalTotal = subtotal - discountAmount;
-
- return {
- baseTicketPrice,
- pricePerPassenger,
- passengerBaggageCosts,
- totalBaggageCost,
- subtotal,
- discountAmount,
- finalTotal,
- };
-}
diff --git a/apps/frontend/src/lib/domains/checkout/utils.ts b/apps/frontend/src/lib/domains/checkout/utils.ts
new file mode 100644
index 0000000..23f8da1
--- /dev/null
+++ b/apps/frontend/src/lib/domains/checkout/utils.ts
@@ -0,0 +1,50 @@
+import type { CustomerInfoModel } from "@pkg/logic/domains/customerinfo/data";
+import type { OrderPriceDetailsModel } from "@pkg/logic/domains/order/data/entities";
+import type { ProductModel } from "@pkg/logic/domains/product/data";
+
+/**
+ * Calculates final prices for a product checkout using OrderPriceDetailsModel
+ * @param product - The product being purchased
+ * @param customerInfo - Customer information (included for future extensibility)
+ * @returns OrderPriceDetailsModel with all price fields calculated
+ */
+export function calculateFinalPrices(
+ product: ProductModel | null,
+ customerInfo?: CustomerInfoModel | null,
+): OrderPriceDetailsModel {
+ if (!product) {
+ return {
+ currency: "USD",
+ basePrice: 0,
+ discountAmount: 0,
+ displayPrice: 0,
+ orderPrice: 0,
+ fullfilledPrice: 0,
+ };
+ }
+
+ const basePrice = product.price || 0;
+ const discountPrice = product.discountPrice || 0;
+
+ // Calculate discount amount: if discountPrice is set and less than base price
+ const hasDiscount = discountPrice > 0 && discountPrice < basePrice;
+ const discountAmount = hasDiscount ? basePrice - discountPrice : 0;
+
+ // Display price is either the discounted price or the base price
+ const displayPrice = hasDiscount ? discountPrice : basePrice;
+
+ // For single product checkout:
+ // - orderPrice = displayPrice (what customer pays)
+ // - fullfilledPrice = displayPrice (same as order price for immediate fulfillment)
+ const orderPrice = displayPrice;
+ const fullfilledPrice = displayPrice;
+
+ return {
+ currency: "USD",
+ basePrice,
+ discountAmount,
+ displayPrice,
+ orderPrice,
+ fullfilledPrice,
+ };
+}
diff --git a/apps/frontend/src/lib/domains/ckflow/data/repository.ts b/apps/frontend/src/lib/domains/ckflow/data/repository.ts
index 31f80ef..b38bb3d 100644
--- a/apps/frontend/src/lib/domains/ckflow/data/repository.ts
+++ b/apps/frontend/src/lib/domains/ckflow/data/repository.ts
@@ -1,5 +1,5 @@
+import type { CustomerInfoModel } from "$lib/domains/customerinfo/data";
import { CheckoutStep } from "$lib/domains/order/data/entities";
-import type { CustomerInfo } from "$lib/domains/passengerinfo/data/entities";
import type { PaymentInfoPayload } from "$lib/domains/paymentinfo/data/entities";
import type { Database } from "@pkg/db";
import { and, eq } from "@pkg/db";
@@ -202,7 +202,7 @@ export class CheckoutFlowRepository {
async syncPersonalInfo(
flowId: string,
- personalInfo: CustomerInfo,
+ personalInfo: CustomerInfoModel,
): Promise> {
try {
const existingSession = await this.db
diff --git a/apps/frontend/src/lib/domains/ckflow/domain/router.ts b/apps/frontend/src/lib/domains/ckflow/domain/router.ts
index 356c122..0a23a47 100644
--- a/apps/frontend/src/lib/domains/ckflow/domain/router.ts
+++ b/apps/frontend/src/lib/domains/ckflow/domain/router.ts
@@ -1,6 +1,6 @@
import {
customerInfoModel,
- type CustomerInfo,
+ type CustomerInfoModel,
} from "$lib/domains/passengerinfo/data/entities";
import {
paymentInfoPayloadModel,
@@ -41,7 +41,7 @@ export const ckflowRouter = createTRPCRouter({
return getCKUseCases().createFlow({
domain: input.domain,
refOIds: input.refOIds,
- ticketId: input.ticketId,
+ productId: input.productId,
ipAddress,
userAgent,
initialUrl: "",
@@ -72,7 +72,7 @@ export const ckflowRouter = createTRPCRouter({
.mutation(async ({ input }) => {
return getCKUseCases().syncPersonalInfo(
input.flowId,
- input.personalInfo as CustomerInfo,
+ input.personalInfo as CustomerInfoModel,
);
}),
diff --git a/apps/frontend/src/lib/domains/ckflow/domain/usecases.ts b/apps/frontend/src/lib/domains/ckflow/domain/usecases.ts
index c229f76..ff93f7c 100644
--- a/apps/frontend/src/lib/domains/ckflow/domain/usecases.ts
+++ b/apps/frontend/src/lib/domains/ckflow/domain/usecases.ts
@@ -1,4 +1,4 @@
-import type { CustomerInfo } from "$lib/domains/passengerinfo/data/entities";
+import type { CustomerInfoModel } from "$lib/domains/customerinfo/data";
import type { PaymentInfoPayload } from "$lib/domains/paymentinfo/data/entities";
import { db } from "@pkg/db";
import { isTimestampMoreThan1MinAgo } from "@pkg/logic/core/date.utils";
@@ -54,7 +54,7 @@ export class CheckoutFlowUseCases {
return this.repo.executePaymentStep(flowId, payload);
}
- async syncPersonalInfo(flowId: string, personalInfo: CustomerInfo) {
+ async syncPersonalInfo(flowId: string, personalInfo: CustomerInfoModel) {
return this.repo.syncPersonalInfo(flowId, personalInfo);
}
diff --git a/apps/frontend/src/lib/domains/ckflow/view/ckflow.vm.svelte.ts b/apps/frontend/src/lib/domains/ckflow/view/ckflow.vm.svelte.ts
index d063882..aa24c03 100644
--- a/apps/frontend/src/lib/domains/ckflow/view/ckflow.vm.svelte.ts
+++ b/apps/frontend/src/lib/domains/ckflow/view/ckflow.vm.svelte.ts
@@ -1,4 +1,7 @@
import { page } from "$app/state";
+import { checkoutVM } from "$lib/domains/checkout/checkout.vm.svelte";
+import { billingDetailsVM } from "$lib/domains/checkout/payment-info-section/billing.details.vm.svelte";
+import { paymentInfoVM } from "$lib/domains/checkout/payment-info-section/payment.info.vm.svelte";
import {
CKActionType,
SessionOutcome,
@@ -6,21 +9,16 @@ import {
type PendingAction,
type PendingActions,
} from "$lib/domains/ckflow/data/entities";
+import type { CustomerInfoModel } from "$lib/domains/customerinfo/data";
import {
- customerInfoModel,
- type CustomerInfo,
-} from "$lib/domains/passengerinfo/data/entities";
+ CheckoutStep,
+ type OrderPriceDetailsModel,
+} from "$lib/domains/order/data/entities";
+import { customerInfoModel } from "$lib/domains/passengerinfo/data/entities";
import { passengerInfoVM } from "$lib/domains/passengerinfo/view/passenger.info.vm.svelte";
import type { PaymentInfoPayload } from "$lib/domains/paymentinfo/data/entities";
import { PaymentMethod } from "$lib/domains/paymentinfo/data/entities";
-import {
- CheckoutStep,
- type FlightPriceDetails,
-} from "$lib/domains/ticket/data/entities";
-import { flightTicketStore } from "$lib/domains/ticket/data/store";
-import { checkoutVM } from "$lib/domains/ticket/view/checkout/checkout.vm.svelte";
-import { billingDetailsVM } from "$lib/domains/ticket/view/checkout/payment-info-section/billing.details.vm.svelte";
-import { paymentInfoVM } from "$lib/domains/ticket/view/checkout/payment-info-section/payment.info.vm.svelte";
+import { productStore } from "$lib/domains/product/store";
import { trpcApiStore } from "$lib/stores/api";
import { ClientLogger } from "@pkg/logger/client";
import { toast } from "svelte-sonner";
@@ -185,7 +183,7 @@ export class CKFlowViewModel {
private paymentInfoDebounceTimer: NodeJS.Timeout | null = null;
syncInterval = 300; // 300ms debounce for syncing
- updatedPrices = $state(undefined);
+ updatedPrices = $state(undefined);
constructor() {
this.actionRunner = new ActionRunner();
@@ -216,17 +214,10 @@ export class CKFlowViewModel {
return;
}
- const ticket = get(flightTicketStore);
- const refOIds = ticket.refOIds;
- if (!refOIds) {
- this.setupDone = true;
- return; // Since we don't have any attached order(s), we don't need to worry about this dude
- }
-
const info = await api.ckflow.initiateCheckout.mutate({
domain: window.location.hostname,
- refOIds,
- ticketId: ticket.id,
+ refOIds: [],
+ productId: get(productStore)?.id,
});
if (info.error) {
@@ -236,7 +227,7 @@ export class CKFlowViewModel {
if (!info.data) {
toast.error("Error while creating checkout flow", {
- description: "Try refreshing page or search for ticket again",
+ description: "Try refreshing page or contact us",
});
return;
}
@@ -248,7 +239,7 @@ export class CKFlowViewModel {
this.setupDone = true;
}
- debouncePersonalInfoSync(personalInfo: CustomerInfo) {
+ debouncePersonalInfoSync(personalInfo: CustomerInfoModel) {
this.clearPersonalInfoDebounce();
this.personalInfoDebounceTimer = setTimeout(() => {
this.syncPersonalInfo(personalInfo);
@@ -262,9 +253,10 @@ export class CKFlowViewModel {
this.paymentInfoDebounceTimer = setTimeout(() => {
const paymentInfo = {
cardDetails: paymentInfoVM.cardDetails,
- flightTicketInfoId: get(flightTicketStore).id,
method: PaymentMethod.Card,
- };
+ orderId: -1,
+ productId: get(productStore)?.id,
+ } as PaymentInfoPayload;
this.syncPaymentInfo(paymentInfo);
}, this.syncInterval);
}
@@ -276,12 +268,12 @@ export class CKFlowViewModel {
);
}
- isPersonalInfoValid(personalInfo: CustomerInfo): boolean {
+ isPersonalInfoValid(personalInfo: CustomerInfoModel): boolean {
const parsed = customerInfoModel.safeParse(personalInfo);
return !parsed.error && !!parsed.data;
}
- async syncPersonalInfo(personalInfo: CustomerInfo) {
+ async syncPersonalInfo(personalInfo: CustomerInfoModel) {
if (!this.flowId || !this.setupDone) {
return;
}
@@ -546,7 +538,7 @@ export class CKFlowViewModel {
const out = await api.ckflow.executePrePaymentStep.mutate({
flowId: this.flowId!,
payload: {
- initialUrl: get(flightTicketStore).checkoutUrl,
+ initialUrl: "",
personalInfo: primaryPassengerInfo,
},
});
@@ -570,9 +562,10 @@ export class CKFlowViewModel {
const paymentInfo = {
cardDetails: paymentInfoVM.cardDetails,
- flightTicketInfoId: get(flightTicketStore).id,
method: PaymentMethod.Card,
- };
+ orderId: -1,
+ productId: get(productStore)?.id,
+ } as PaymentInfoPayload;
const out = await api.ckflow.executePaymentStep.mutate({
flowId: this.flowId!,
diff --git a/apps/frontend/src/lib/domains/currency/utils.ts b/apps/frontend/src/lib/domains/currency/utils.ts
deleted file mode 100644
index e69de29..0000000
diff --git a/apps/frontend/src/lib/domains/customerinfo/controller.ts b/apps/frontend/src/lib/domains/customerinfo/controller.ts
new file mode 100644
index 0000000..dcd117d
--- /dev/null
+++ b/apps/frontend/src/lib/domains/customerinfo/controller.ts
@@ -0,0 +1,47 @@
+import { Logger } from "@pkg/logger";
+import type { Result } from "@pkg/result";
+import type { CreateCustomerInfoPayload, CustomerInfoModel } from "./data";
+import type { CustomerInfoRepository } from "./repository";
+
+/**
+ * CustomerInfoController handles business logic for customer information operations.
+ * Coordinates between the repository layer and the UI/API layer.
+ */
+export class CustomerInfoController {
+ repo: CustomerInfoRepository;
+
+ constructor(repo: CustomerInfoRepository) {
+ this.repo = repo;
+ }
+
+ /**
+ * Creates a new customer information record
+ * @param payload - Customer information data to create
+ * @returns Result containing the new customer info ID or error
+ */
+ async createCustomerInfo(
+ payload: CreateCustomerInfoPayload,
+ ): Promise> {
+ Logger.info("Creating customer info", { email: payload.email });
+ return this.repo.createCustomerInfo(payload);
+ }
+
+ /**
+ * Retrieves customer information by ID
+ * @param id - Customer info ID to retrieve
+ * @returns Result containing customer info model or error
+ */
+ async getCustomerInfo(id: number): Promise> {
+ Logger.info(`Retrieving customer info with ID: ${id}`);
+ return this.repo.getCustomerInfoById(id);
+ }
+
+ /**
+ * Retrieves all customer information records
+ * @returns Result containing array of customer info models or error
+ */
+ async getAllCustomerInfo(): Promise> {
+ Logger.info("Retrieving all customer info records");
+ return this.repo.getAllCustomerInfo();
+ }
+}
diff --git a/apps/frontend/src/lib/domains/customerinfo/router.ts b/apps/frontend/src/lib/domains/customerinfo/router.ts
deleted file mode 100644
index 2ad9489..0000000
--- a/apps/frontend/src/lib/domains/customerinfo/router.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { protectedProcedure } from "$lib/server/trpc/t";
-import { createTRPCRouter } from "$lib/trpc/t";
-import { z } from "zod";
-import { createCustomerInfoPayload, updateCustomerInfoPayload } from "./data";
-import { getCustomerInfoUseCases } from "./usecases";
-
-export const customerInfoRouter = createTRPCRouter({
- getAllCustomerInfo: protectedProcedure.query(async ({}) => {
- const controller = getCustomerInfoUseCases();
- return controller.getAllCustomerInfo();
- }),
-
- getCustomerInfoById: protectedProcedure
- .input(z.object({ id: z.number() }))
- .query(async ({ input }) => {
- const controller = getCustomerInfoUseCases();
- return controller.getCustomerInfoById(input.id);
- }),
-
- createCustomerInfo: protectedProcedure
- .input(createCustomerInfoPayload)
- .mutation(async ({ input }) => {
- const controller = getCustomerInfoUseCases();
- return controller.createCustomerInfo(input);
- }),
-
- updateCustomerInfo: protectedProcedure
- .input(updateCustomerInfoPayload)
- .mutation(async ({ input }) => {
- const controller = getCustomerInfoUseCases();
- return controller.updateCustomerInfo(input);
- }),
-
- deleteCustomerInfo: protectedProcedure
- .input(z.object({ id: z.number() }))
- .mutation(async ({ input }) => {
- const controller = getCustomerInfoUseCases();
- return controller.deleteCustomerInfo(input.id);
- }),
-});
diff --git a/apps/frontend/src/lib/domains/customerinfo/usecases.ts b/apps/frontend/src/lib/domains/customerinfo/usecases.ts
deleted file mode 100644
index 29e3df0..0000000
--- a/apps/frontend/src/lib/domains/customerinfo/usecases.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from "@pkg/logic/domains/customerinfo/usecases";
diff --git a/apps/frontend/src/lib/domains/customerinfo/view/customer-pii-form.svelte b/apps/frontend/src/lib/domains/customerinfo/view/customer-pii-form.svelte
index cd31173..0708418 100644
--- a/apps/frontend/src/lib/domains/customerinfo/view/customer-pii-form.svelte
+++ b/apps/frontend/src/lib/domains/customerinfo/view/customer-pii-form.svelte
@@ -4,238 +4,121 @@
import Input from "$lib/components/ui/input/input.svelte";
import * as Select from "$lib/components/ui/select";
import { COUNTRIES_SELECT } from "$lib/core/countries";
- import type { SelectOption } from "$lib/core/data.types";
import { capitalize } from "$lib/core/string.utils";
- import type { CustomerInfo } from "$lib/domains/ticket/data/entities/create.entities";
- import { Gender } from "$lib/domains/ticket/data/entities/index";
import { PHONE_COUNTRY_CODES } from "@pkg/logic/core/data/phonecc";
+ import type { CustomerInfoModel } from "../data";
+ import { customerInfoVM } from "./customerinfo.vm.svelte";
- let { info = $bindable(), idx }: { info: CustomerInfo; idx: number } =
- $props();
-
- const genderOpts = [
- { label: capitalize(Gender.Male), value: Gender.Male },
- { label: capitalize(Gender.Female), value: Gender.Female },
- { label: capitalize(Gender.Other), value: Gender.Other },
- ] as SelectOption[];
+ let { info = $bindable() }: { info: CustomerInfoModel } = $props();
function onSubmit(e: SubmitEvent) {
e.preventDefault();
- passengerInfoVM.validatePII(info, idx);
+ customerInfoVM.validateCustomerInfo(info);
}
- let validationTimeout = $state(undefined as undefined | NodeJS.Timer);
-
function debounceValidate() {
- if (validationTimeout) {
- clearTimeout(validationTimeout);
- }
- validationTimeout = setTimeout(() => {
- passengerInfoVM.validatePII(info, idx);
- }, 500);
+ customerInfoVM.debounceValidate(info);
}
diff --git a/apps/frontend/src/lib/domains/customerinfo/view/customerinfo.vm.svelte.ts b/apps/frontend/src/lib/domains/customerinfo/view/customerinfo.vm.svelte.ts
index b202bd4..2d550ff 100644
--- a/apps/frontend/src/lib/domains/customerinfo/view/customerinfo.vm.svelte.ts
+++ b/apps/frontend/src/lib/domains/customerinfo/view/customerinfo.vm.svelte.ts
@@ -1,10 +1,281 @@
-import type { CustomerInfoModel } from "../data";
+import { trpcApiStore } from "$lib/stores/api";
+import { toast } from "svelte-sonner";
+import { get } from "svelte/store";
+import { z } from "zod";
+import type {
+ CreateCustomerInfoPayload,
+ CustomerInfoModel,
+ UpdateCustomerInfoPayload,
+} from "../data";
+import { customerInfoModel } from "../data";
+/**
+ * CustomerInfoViewModel manages single customer information state for checkout.
+ * Handles validation, API interactions, and form state for one customer per checkout.
+ */
export class CustomerInfoViewModel {
- customerInfos = $state([] as CustomerInfoModel[]);
+ // State: current customer info being edited/created (single customer for checkout)
+ customerInfo = $state(null);
+ // State: validation errors for the current customer info
+ errors = $state>>({});
+
+ // Loading states
loading = $state(false);
- query = $state("");
+ formLoading = $state(false);
+
+ /**
+ * Initializes customer info with default empty values for checkout
+ */
+ initializeCustomerInfo() {
+ this.customerInfo = this.getDefaultCustomerInfo() as CustomerInfoModel;
+ this.errors = {};
+ }
+
+ /**
+ * Fetches a single customer info record by ID and sets it as current
+ * @param id - Customer info ID to fetch
+ * @returns true if successful, false otherwise
+ */
+ async fetchCustomerInfoById(id: number): Promise {
+ const api = get(trpcApiStore);
+ if (!api) {
+ toast.error("API client not initialized");
+ return false;
+ }
+
+ this.loading = true;
+ try {
+ const result = await api.customerInfo.getCustomerInfoById.query({
+ id,
+ });
+ if (result.error) {
+ toast.error(result.error.message, {
+ description: result.error.userHint,
+ });
+ return false;
+ }
+
+ this.customerInfo = result.data || null;
+ return true;
+ } catch (e) {
+ console.error(e);
+ toast.error("Failed to fetch customer information", {
+ description: "Please try again later",
+ });
+ return false;
+ } finally {
+ this.loading = false;
+ }
+ }
+
+ /**
+ * Creates a new customer information record
+ * @param payload - Customer info data to create
+ * @returns Customer info ID if successful, null otherwise
+ */
+ async createCustomerInfo(
+ payload: CreateCustomerInfoPayload,
+ ): Promise {
+ const api = get(trpcApiStore);
+ if (!api) {
+ toast.error("API client not initialized");
+ return null;
+ }
+
+ // Validate before submitting
+ if (!this.validateCustomerInfo(payload)) {
+ toast.error("Please fix validation errors before submitting");
+ return null;
+ }
+
+ this.formLoading = true;
+ try {
+ const result =
+ await api.customerInfo.createCustomerInfo.mutate(payload);
+ if (result.error) {
+ toast.error(result.error.message, {
+ description: result.error.userHint,
+ });
+ return null;
+ }
+
+ toast.success("Customer information saved successfully");
+ return result.data || null;
+ } catch (e) {
+ console.error(e);
+ toast.error("Failed to save customer information", {
+ description: "Please try again later",
+ });
+ return null;
+ } finally {
+ this.formLoading = false;
+ }
+ }
+
+ /**
+ * Updates the current customer information record
+ * @param payload - Customer info data to update (must include id)
+ * @returns true if successful, false otherwise
+ */
+ async updateCustomerInfo(
+ payload: UpdateCustomerInfoPayload,
+ ): Promise {
+ const api = get(trpcApiStore);
+ if (!api) {
+ toast.error("API client not initialized");
+ return false;
+ }
+
+ if (!payload.id) {
+ toast.error("Customer ID is required for update");
+ return false;
+ }
+
+ this.formLoading = true;
+ try {
+ const result =
+ await api.customerInfo.updateCustomerInfo.mutate(payload);
+ if (result.error) {
+ toast.error(result.error.message, {
+ description: result.error.userHint,
+ });
+ return false;
+ }
+
+ toast.success("Customer information updated successfully");
+
+ // Update local state with the updated data
+ if (this.customerInfo && this.customerInfo.id === payload.id) {
+ this.customerInfo = { ...this.customerInfo, ...payload };
+ }
+
+ return true;
+ } catch (e) {
+ console.error(e);
+ toast.error("Failed to update customer information", {
+ description: "Please try again later",
+ });
+ return false;
+ } finally {
+ this.formLoading = false;
+ }
+ }
+
+ /**
+ * Validates customer information data using Zod schema
+ * @param info - Customer info to validate
+ * @returns true if valid, false otherwise
+ */
+ validateCustomerInfo(info?: Partial): boolean {
+ try {
+ customerInfoModel.parse(info || this.customerInfo);
+ this.errors = {};
+ return true;
+ } catch (error) {
+ if (error instanceof z.ZodError) {
+ this.errors = error.errors.reduce(
+ (acc, curr) => {
+ const path = curr.path[0] as keyof CustomerInfoModel;
+ acc[path] = curr.message;
+ return acc;
+ },
+ {} as Record,
+ );
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Debounced validation for real-time form feedback
+ * @param info - Customer info to validate
+ * @param delay - Debounce delay in milliseconds (default: 500)
+ */
+ private validationTimeout: NodeJS.Timeout | undefined;
+
+ debounceValidate(info: Partial, delay = 500) {
+ if (this.validationTimeout) {
+ clearTimeout(this.validationTimeout);
+ }
+ this.validationTimeout = setTimeout(() => {
+ this.validateCustomerInfo(info);
+ }, delay);
+ }
+
+ /**
+ * Checks if the current customer info has validation errors
+ * @returns true if there are no errors, false otherwise
+ */
+ isValid(): boolean {
+ return Object.keys(this.errors).length === 0;
+ }
+
+ /**
+ * Checks if customer info is filled out (has required fields)
+ * @returns true if customer info exists and has data, false otherwise
+ */
+ isFilled(): boolean {
+ if (!this.customerInfo) return false;
+
+ return (
+ this.customerInfo.firstName.length > 0 &&
+ this.customerInfo.lastName.length > 0 &&
+ this.customerInfo.email.length > 0 &&
+ this.customerInfo.phoneNumber.length > 0
+ );
+ }
+
+ /**
+ * Sets the current customer info (for editing existing records)
+ * @param customerInfo - Customer info to edit
+ */
+ setCustomerInfo(customerInfo: CustomerInfoModel) {
+ this.customerInfo = { ...customerInfo };
+ this.errors = {};
+ }
+
+ /**
+ * Resets the customer info and errors
+ */
+ resetCustomerInfo() {
+ this.customerInfo = null;
+ this.errors = {};
+ }
+
+ /**
+ * Returns a default/empty customer info object for forms
+ * @returns Default customer info payload
+ */
+ getDefaultCustomerInfo(): CreateCustomerInfoPayload {
+ return {
+ firstName: "",
+ middleName: "",
+ lastName: "",
+ email: "",
+ phoneCountryCode: "",
+ phoneNumber: "",
+ country: "",
+ state: "",
+ city: "",
+ zipCode: "",
+ address: "",
+ address2: null,
+ };
+ }
+
+ /**
+ * Resets all state (for cleanup or re-initialization)
+ */
+ reset() {
+ this.customerInfo = null;
+ this.errors = {};
+ this.loading = false;
+ this.formLoading = false;
+ if (this.validationTimeout) {
+ clearTimeout(this.validationTimeout);
+ }
+ }
}
export const customerInfoVM = new CustomerInfoViewModel();
diff --git a/apps/frontend/src/lib/domains/passengerinfo/data/repository.ts b/apps/frontend/src/lib/domains/passengerinfo/data/repository.ts
index 444d48d..39513b4 100644
--- a/apps/frontend/src/lib/domains/passengerinfo/data/repository.ts
+++ b/apps/frontend/src/lib/domains/passengerinfo/data/repository.ts
@@ -4,7 +4,7 @@ import { getError, Logger } from "@pkg/logger";
import { ERROR_CODES, type Result } from "@pkg/result";
import {
passengerInfoModel,
- type CustomerInfo,
+ type CustomerInfoModel,
type PassengerInfo,
} from "./entities";
@@ -15,7 +15,9 @@ export class PassengerInfoRepository {
this.db = db;
}
- async createPassengerPii(payload: CustomerInfo): Promise> {
+ async createPassengerPii(
+ payload: CustomerInfoModel,
+ ): Promise> {
try {
const out = await this.db
.insert(passengerPII)
diff --git a/apps/frontend/src/lib/domains/passengerinfo/view/passenger-bag-selection.svelte b/apps/frontend/src/lib/domains/passengerinfo/view/passenger-bag-selection.svelte
deleted file mode 100644
index 07f03ed..0000000
--- a/apps/frontend/src/lib/domains/passengerinfo/view/passenger-bag-selection.svelte
+++ /dev/null
@@ -1,214 +0,0 @@
-
-
-
- {#each bagTypes as bag}
- {@const bagDetails = getBagDetails(bag.type)}
- {@const isAvailable = true}
- {@const isDisabled = false}
-
-
-
-
-
!isDisabled && !bag.included && toggleBag(bag.type)}
- onkeydown={(e) => {
- if (e.key === "Enter" || e.key === " ") {
- !isDisabled && !bag.included && toggleBag(bag.type);
- }
- }}
- >
-
-
-
-
- {bag.label}
- {#if bag.included}
-
- Included
-
- {/if}
-
- {#if bag.type === "checkedBag" && !isAvailable}
-
- Not available for this flight
-
- {/if}
-
-
{bag.description}
- {#if bagDetails}
-
- {#if bagDetails.weight > 0}
-
- Up to {bagDetails.weight}{bagDetails.unit}
-
- {/if}
- {#if formatDimensions(bagDetails)}
- {formatDimensions(bagDetails)}
- {/if}
-
- {/if}
-
-
-
-
- Free
-
-
-
- {#if isBagSelected(bag.type)}
-
-
-
- {/if}
-
- {/each}
-
diff --git a/apps/frontend/src/lib/domains/passengerinfo/view/passenger-pii-form.svelte b/apps/frontend/src/lib/domains/passengerinfo/view/passenger-pii-form.svelte
index f6bceab..f7eca33 100644
--- a/apps/frontend/src/lib/domains/passengerinfo/view/passenger-pii-form.svelte
+++ b/apps/frontend/src/lib/domains/passengerinfo/view/passenger-pii-form.svelte
@@ -6,12 +6,12 @@
import { COUNTRIES_SELECT } from "$lib/core/countries";
import type { SelectOption } from "$lib/core/data.types";
import { capitalize } from "$lib/core/string.utils";
- import type { CustomerInfo } from "$lib/domains/ticket/data/entities/create.entities";
+ import type { CustomerInfoModel } from "$lib/domains/ticket/data/entities/create.entities";
import { Gender } from "$lib/domains/ticket/data/entities/index";
import { PHONE_COUNTRY_CODES } from "@pkg/logic/core/data/phonecc";
import { passengerInfoVM } from "./passenger.info.vm.svelte";
- let { info = $bindable(), idx }: { info: CustomerInfo; idx: number } =
+ let { info = $bindable(), idx }: { info: CustomerInfoModel; idx: number } =
$props();
const genderOpts = [
diff --git a/apps/frontend/src/lib/domains/passengerinfo/view/passenger.info.vm.svelte.ts b/apps/frontend/src/lib/domains/passengerinfo/view/passenger.info.vm.svelte.ts
index a04d380..56755bf 100644
--- a/apps/frontend/src/lib/domains/passengerinfo/view/passenger.info.vm.svelte.ts
+++ b/apps/frontend/src/lib/domains/passengerinfo/view/passenger.info.vm.svelte.ts
@@ -1,7 +1,7 @@
import {
customerInfoModel,
type BagSelectionInfo,
- type CustomerInfo,
+ type CustomerInfoModel,
type PassengerInfo,
type SeatSelectionInfo,
} from "$lib/domains/ticket/data/entities/create.entities";
@@ -17,7 +17,9 @@ import { z } from "zod";
export class PassengerInfoViewModel {
passengerInfos = $state([]);
- piiErrors = $state>>>([]);
+ piiErrors = $state>>>(
+ [],
+ );
reset() {
this.passengerInfos = [];
@@ -47,7 +49,7 @@ export class PassengerInfoViewModel {
// zipCode: "123098",
// address: "address",
// address2: "",
- // } as CustomerInfo;
+ // } as CustomerInfoModel;
const _defaultPiiObj = {
firstName: "",
@@ -67,7 +69,7 @@ export class PassengerInfoViewModel {
zipCode: "",
address: "",
address2: "",
- } as CustomerInfo;
+ } as CustomerInfoModel;
const _defaultPriceObj = {
currency: "",
@@ -137,7 +139,7 @@ export class PassengerInfoViewModel {
}
}
- validatePII(info: CustomerInfo, idx: number) {
+ validatePII(info: CustomerInfoModel, idx: number) {
try {
const result = customerInfoModel.parse(info);
this.piiErrors[idx] = {};
@@ -146,11 +148,11 @@ export class PassengerInfoViewModel {
if (error instanceof z.ZodError) {
this.piiErrors[idx] = error.errors.reduce(
(acc, curr) => {
- const path = curr.path[0] as keyof CustomerInfo;
+ const path = curr.path[0] as keyof CustomerInfoModel;
acc[path] = curr.message;
return acc;
},
- {} as Record,
+ {} as Record,
);
}
return null;
diff --git a/apps/frontend/src/lib/domains/product/store.ts b/apps/frontend/src/lib/domains/product/store.ts
new file mode 100644
index 0000000..fa28268
--- /dev/null
+++ b/apps/frontend/src/lib/domains/product/store.ts
@@ -0,0 +1,4 @@
+import { writable } from "svelte/store";
+import type { ProductModel } from "./data";
+
+export const productStore = writable(null);
diff --git a/apps/frontend/src/lib/domains/ticket/view/checkout/payment-info-section/billing-details-form.svelte b/apps/frontend/src/lib/domains/ticket/view/checkout/payment-info-section/billing-details-form.svelte
index f4bd3d2..07343c8 100644
--- a/apps/frontend/src/lib/domains/ticket/view/checkout/payment-info-section/billing-details-form.svelte
+++ b/apps/frontend/src/lib/domains/ticket/view/checkout/payment-info-section/billing-details-form.svelte
@@ -4,10 +4,10 @@
import * as Select from "$lib/components/ui/select";
import { COUNTRIES_SELECT } from "$lib/core/countries";
import { capitalize } from "$lib/core/string.utils";
- import type { CustomerInfo } from "$lib/domains/ticket/data/entities/create.entities";
+ import type { CustomerInfoModel } from "$lib/domains/ticket/data/entities/create.entities";
import { billingDetailsVM } from "./billing.details.vm.svelte";
- let { info = $bindable() }: { info: CustomerInfo } = $props();
+ let { info = $bindable() }: { info: CustomerInfoModel } = $props();
function onSubmit(e: SubmitEvent) {
e.preventDefault();
diff --git a/apps/frontend/src/lib/domains/ticket/view/checkout/payment-info-section/billing.details.vm.svelte.ts b/apps/frontend/src/lib/domains/ticket/view/checkout/payment-info-section/billing.details.vm.svelte.ts
index 5d78eb1..1ca39ff 100644
--- a/apps/frontend/src/lib/domains/ticket/view/checkout/payment-info-section/billing.details.vm.svelte.ts
+++ b/apps/frontend/src/lib/domains/ticket/view/checkout/payment-info-section/billing.details.vm.svelte.ts
@@ -1,15 +1,15 @@
import {
customerInfoModel,
- type CustomerInfo,
+ type CustomerInfoModel,
} from "$lib/domains/ticket/data/entities/create.entities";
import { Gender } from "$lib/domains/ticket/data/entities/index";
import { z } from "zod";
export class BillingDetailsViewModel {
// @ts-ignore
- billingDetails = $state(undefined);
+ billingDetails = $state(undefined);
- piiErrors = $state>>({});
+ piiErrors = $state>>({});
constructor() {
this.reset();
@@ -34,15 +34,15 @@ export class BillingDetailsViewModel {
zipCode: "",
address: "",
address2: "",
- } as CustomerInfo;
+ } as CustomerInfoModel;
this.piiErrors = {};
}
- setPII(info: CustomerInfo) {
+ setPII(info: CustomerInfoModel) {
this.billingDetails = info;
}
- validatePII(info: CustomerInfo) {
+ validatePII(info: CustomerInfoModel) {
try {
const result = customerInfoModel.parse(info);
this.piiErrors = {};
@@ -51,11 +51,11 @@ export class BillingDetailsViewModel {
if (error instanceof z.ZodError) {
this.piiErrors = error.errors.reduce(
(acc, curr) => {
- const path = curr.path[0] as keyof CustomerInfo;
+ const path = curr.path[0] as keyof CustomerInfoModel;
acc[path] = curr.message;
return acc;
},
- {} as Record,
+ {} as Record,
);
}
return null;
diff --git a/apps/frontend/src/lib/trpc/router/index.ts b/apps/frontend/src/lib/trpc/router/index.ts
index dcc1b5d..7c23ff4 100644
--- a/apps/frontend/src/lib/trpc/router/index.ts
+++ b/apps/frontend/src/lib/trpc/router/index.ts
@@ -1,6 +1,7 @@
import { authRouter } from "$lib/domains/auth/domain/router";
import { ckflowRouter } from "$lib/domains/ckflow/domain/router";
import { currencyRouter } from "$lib/domains/currency/domain/router";
+import { customerInfoRouter } from "$lib/domains/customerinfo/router";
import { orderRouter } from "$lib/domains/order/domain/router";
import { productRouter } from "$lib/domains/product/router";
import { userRouter } from "$lib/domains/user/domain/router";
@@ -13,6 +14,7 @@ export const router = createTRPCRouter({
order: orderRouter,
ckflow: ckflowRouter,
product: productRouter,
+ customerInfo: customerInfoRouter,
});
export type Router = typeof router;
diff --git a/packages/logic/domains/ckflow/data/entities.ts b/packages/logic/domains/ckflow/data/entities.ts
index bd59b75..af806da 100644
--- a/packages/logic/domains/ckflow/data/entities.ts
+++ b/packages/logic/domains/ckflow/data/entities.ts
@@ -1,9 +1,6 @@
import { z } from "zod";
+import { CustomerInfoModel, customerInfoModel } from "../../customerinfo/data";
import { CheckoutStep } from "../../order/data/enums";
-import {
- CustomerInfo,
- customerInfoModel,
-} from "../../passengerinfo/data/entities";
import {
PaymentInfoPayload,
paymentInfoPayloadModel,
@@ -77,7 +74,7 @@ export const flowInfoModel = z.object({
paymentInfoLastSyncedAt: z.string().datetime().optional(),
pendingActions: pendingActionsModel.default([]),
- personalInfo: z.custom().optional(),
+ personalInfo: z.custom().optional(),
paymentInfo: z.custom().optional(),
refOids: z.array(z.number()).optional(),
diff --git a/packages/logic/domains/customerinfo/data.ts b/packages/logic/domains/customerinfo/data.ts
index 21ec85f..45b5cf0 100644
--- a/packages/logic/domains/customerinfo/data.ts
+++ b/packages/logic/domains/customerinfo/data.ts
@@ -1,5 +1,11 @@
import { z } from "zod";
+export enum Gender {
+ Male = "male",
+ Female = "female",
+ Other = "other",
+}
+
export const customerInfoModel = z.object({
id: z.number().optional(),
firstName: z.string().min(1).max(64),
diff --git a/packages/logic/domains/order/data/entities.ts b/packages/logic/domains/order/data/entities.ts
index 1a9fc14..76cc861 100644
--- a/packages/logic/domains/order/data/entities.ts
+++ b/packages/logic/domains/order/data/entities.ts
@@ -21,14 +21,20 @@ export enum OrderStatus {
CANCELLED = "CANCELLED",
}
-export const orderModel = z.object({
- id: z.coerce.number().int().positive(),
-
+export const orderPriceDetailsModel = z.object({
+ currency: z.string(),
discountAmount: z.coerce.number().min(0),
basePrice: z.coerce.number().min(0),
displayPrice: z.coerce.number().min(0),
orderPrice: z.coerce.number().min(0),
fullfilledPrice: z.coerce.number().min(0),
+});
+export type OrderPriceDetailsModel = z.infer;
+
+export const orderModel = z.object({
+ id: z.coerce.number().int().positive(),
+
+ ...orderPriceDetailsModel.shape,
status: z.nativeEnum(OrderStatus),
@@ -119,5 +125,6 @@ export const createOrderPayloadModel = z.object({
customerInfo: customerInfoModel,
paymentInfo: paymentInfoPayloadModel.optional(),
orderModel: newOrderModel,
+ flowId: z.string().optional(),
});
export type CreateOrderModel = z.infer;