order creation logic fix, refactor & cleanup on admin end

This commit is contained in:
user
2025-10-21 19:20:56 +03:00
parent b6bdb6d7e8
commit f0fa53a4e5
19 changed files with 100 additions and 415 deletions

View File

@@ -6,14 +6,11 @@
import Icon from "$lib/components/atoms/icon.svelte"; import Icon from "$lib/components/atoms/icon.svelte";
import { adminSiteNavMap } from "$lib/core/constants"; import { adminSiteNavMap } from "$lib/core/constants";
import { sessionUserInfo } from "$lib/stores/session.info"; import { sessionUserInfo } from "$lib/stores/session.info";
import { SettingsIcon } from "@lucide/svelte";
import ProductIcon from "~icons/carbon/carbon-for-ibm-product"; import ProductIcon from "~icons/carbon/carbon-for-ibm-product";
import SessionIcon from "~icons/carbon/prompt-session"; import SessionIcon from "~icons/carbon/prompt-session";
import HistoryIcon from "~icons/iconamoon/history-light"; import HistoryIcon from "~icons/iconamoon/history-light";
import BillListIcon from "~icons/solar/bill-list-linear";
import PackageIcon from "~icons/solar/box-broken"; import PackageIcon from "~icons/solar/box-broken";
import DashboardIcon from "~icons/solar/laptop-minimalistic-broken"; import DashboardIcon from "~icons/solar/laptop-minimalistic-broken";
import UsersIcon from "~icons/solar/users-group-two-rounded-broken";
const mainLinks = [ const mainLinks = [
{ {
@@ -31,11 +28,6 @@
title: "Session History", title: "Session History",
url: adminSiteNavMap.sessions.history, url: adminSiteNavMap.sessions.history,
}, },
{
icon: BillListIcon,
title: "Data",
url: adminSiteNavMap.data,
},
{ {
icon: PackageIcon, icon: PackageIcon,
title: "Orders", title: "Orders",
@@ -47,16 +39,6 @@
title: "Products", title: "Products",
url: adminSiteNavMap.products, url: adminSiteNavMap.products,
}, },
{
icon: UsersIcon,
title: "Profile",
url: adminSiteNavMap.profile,
},
{
icon: SettingsIcon,
title: "Settings",
url: adminSiteNavMap.settings,
},
]; ];
let { let {

View File

@@ -56,7 +56,7 @@
header: "Location", header: "Location",
id: "location", id: "location",
cell: ({ row }) => { cell: ({ row }) => {
return `${row.original.city}, ${row.original.state}`; return `${row.original.country}, ${row.original.city}`;
}, },
}, },
{ {

View File

@@ -32,14 +32,6 @@
return Number(row.id) + 1; return Number(row.id) + 1;
}, },
}, },
{
header: "Order ID",
accessorKey: "orderId",
cell: ({ row }) => {
const r = row.original as FullOrderModel;
return `#${r.id}`;
},
},
{ {
header: "Product", header: "Product",
accessorKey: "product", accessorKey: "product",

View File

@@ -7,10 +7,10 @@ import { paymentInfoModel, type PaymentInfo } from "./data";
export class PaymentInfoRepository { export class PaymentInfoRepository {
constructor(private db: Database) {} constructor(private db: Database) {}
async getPaymentInfo(id: number): Promise<Result<PaymentInfo>> { async getPaymentInfoByOrderID(oid: number): Promise<Result<PaymentInfo>> {
Logger.info(`Getting payment info with id ${id}`); Logger.info(`Getting payment info with id ${oid}`);
const out = await this.db.query.paymentInfo.findFirst({ const out = await this.db.query.paymentInfo.findFirst({
where: eq(paymentInfo.id, id), where: eq(paymentInfo.orderId, oid),
}); });
const parsed = paymentInfoModel.safeParse(out); const parsed = paymentInfoModel.safeParse(out);
if (parsed.error) { if (parsed.error) {

View File

@@ -1,13 +0,0 @@
import { getCustomerInfoUseCases } from "$lib/domains/customerinfo/usecases";
import { redirect } from "@sveltejs/kit";
import type { PageServerLoad } from "./$types";
export const load: PageServerLoad = async ({ locals }) => {
const sess = locals.session;
if (!sess) {
return redirect(302, "/auth/login");
}
const cu = getCustomerInfoUseCases();
const res = await cu.getAllCustomerInfo();
return { data: res.data ?? [], error: res.error };
};

View File

@@ -1,36 +0,0 @@
<script lang="ts">
import Container from "$lib/components/atoms/container.svelte";
import Title from "$lib/components/atoms/title.svelte";
import CustomerinfoTable from "$lib/domains/customerinfo/view/customerinfo-table.svelte";
import { pageTitle } from "$lib/hooks/page-title.svelte";
import { onMount } from "svelte";
import { toast } from "svelte-sonner";
import type { PageData } from "./$types";
pageTitle.set("Customer Info");
let { data }: { data: PageData } = $props();
onMount(() => {
if (data.error) {
toast.error(data.error.message, {
description: data.error.userHint,
});
}
});
</script>
<main class="grid w-full place-items-center pb-12 md:p-8">
<Container
containerClass="flex flex-col gap-4 w-full h-full min-h-[44rem] xl:min-h-[80vh] overflow-y-hidden"
wrapperClass="max-w-[90vw] lg:max-w-7xl"
>
<div
class="flex w-full flex-col items-center justify-between gap-4 md:flex-row"
>
<Title size="h3">User Data</Title>
</div>
<CustomerinfoTable data={data.data} />
</Container>
</main>

View File

@@ -1,30 +0,0 @@
import { getCustomerInfoUseCases } from "$lib/domains/customerinfo/usecases";
import { PaymentInfoRepository } from "$lib/domains/paymentinfo/repository";
import { db } from "@pkg/db";
import { getError } from "@pkg/logger";
import { ERROR_CODES } from "@pkg/result";
import type { PageServerLoad } from "./$types";
export const load: PageServerLoad = async ({ params }) => {
const uid = parseInt(params.uid);
if (!uid || isNaN(uid) || uid < 0 || uid > Number.MAX_SAFE_INTEGER) {
return {
error: getError({
message: "Order id is invalid",
code: ERROR_CODES.INPUT_ERROR,
detail: "Order id is invalid",
userHint: "Provide a valid order id",
actionable: false,
}),
};
}
const cinfo = await getCustomerInfoUseCases().getAllCustomerInfo();
const piRes = await new PaymentInfoRepository(db).getPaymentInfo(uid);
return {
customerInfo: cinfo.data,
paymentInfo: piRes.data,
error: piRes.error || cinfo.error,
};
};

View File

@@ -1,216 +0,0 @@
<script lang="ts">
import { goto } from "$app/navigation";
import Container from "$lib/components/atoms/container.svelte";
import Icon from "$lib/components/atoms/icon.svelte";
import Title from "$lib/components/atoms/title.svelte";
import * as Breadcrumb from "$lib/components/ui/breadcrumb/index.js";
import { adminSiteNavMap, CARD_STYLE } from "$lib/core/constants";
import { capitalize } from "$lib/core/string.utils";
import CinfoCard from "$lib/domains/customerinfo/view/cinfo-card.svelte";
import { onMount } from "svelte";
import PackageIcon from "~icons/solar/box-broken";
import CalendarCheckIcon from "~icons/solar/calendar-linear";
import CardNumberIcon from "~icons/solar/card-recive-broken";
import ClipboardIcon from "~icons/solar/clipboard-list-broken";
import DocumentIcon from "~icons/solar/document-text-broken";
import EmailIcon from "~icons/solar/letter-broken";
import LockKeyIcon from "~icons/solar/lock-keyhole-minimalistic-broken";
import LocationIcon from "~icons/solar/map-point-broken";
import PhoneIcon from "~icons/solar/phone-broken";
import UserIdIcon from "~icons/solar/user-id-broken";
import CardUserIcon from "~icons/solar/user-id-linear";
import CreditCardIcon from "~icons/solar/wallet-money-broken";
import type { PageData } from "./$types";
let { data }: { data: PageData } = $props();
let name = $state(
`${data?.data?.passengerPii.firstName} ${data?.data?.passengerPii.lastName}`,
);
let pii = data.data?.passengerPii;
let paymentInfo = data.data?.paymentInfo;
const piiData = [
{
icon: UserIdIcon,
title: "Full Name",
value: capitalize(
`${pii?.firstName} ${pii?.middleName} ${pii?.lastName}`,
true,
),
},
{
icon: EmailIcon,
title: "Email",
value: pii?.email,
},
{
icon: PhoneIcon,
title: "Phone",
value: `${pii?.phoneCountryCode ?? ""} ${pii?.phoneNumber ?? ""}`,
},
];
// No icons for this one
const addressInfo = [
{
title: "Country",
value: pii?.country ?? "",
},
{
title: "State",
value: pii?.state ?? "",
},
{
title: "City",
value: pii?.city ?? "",
},
{
title: "Zip/Postal Code",
value: pii?.zipCode ?? "",
},
{
title: "Address",
value: pii?.address ?? "",
},
{
title: "Address Line 2",
value: pii?.address2 ?? "",
},
];
// Card information
const cardInfo = paymentInfo
? [
{
icon: CardUserIcon,
title: "Cardholder Name",
value: paymentInfo.cardholderName ?? "",
},
{
icon: CardNumberIcon,
title: "Card Number",
value: paymentInfo.cardNumber
? // add spaces of 4 between each group of 4 digits
paymentInfo.cardNumber.match(/.{1,4}/g)?.join(" ")
: "",
},
{
icon: CalendarCheckIcon,
title: "Expiry Date",
value: paymentInfo.expiry ?? "",
},
{
icon: LockKeyIcon,
title: "CVV",
value: paymentInfo.cvv ?? "",
},
]
: [];
const hasAddressInfo =
addressInfo.filter((e) => e.value.length > 0).length > 0;
const hasCardInfo = cardInfo.length > 0;
onMount(() => {
if (data.error) {
goto(adminSiteNavMap.dashboard);
}
});
</script>
<Breadcrumb.Root class="m-2.5 mb-8">
<Breadcrumb.List>
<Breadcrumb.Item>
<Breadcrumb.Link href={adminSiteNavMap.data}>
Customer Data
</Breadcrumb.Link>
</Breadcrumb.Item>
<Breadcrumb.Separator />
<Breadcrumb.Item>
<Breadcrumb.Page>{name}</Breadcrumb.Page>
</Breadcrumb.Item>
</Breadcrumb.List>
</Breadcrumb.Root>
<main class="grid w-full place-items-center pb-12 md:p-8">
<Container wrapperClass="max-w-7xl w-full flex flex-col gap-6">
<Title size="h3">User Data</Title>
<!-- Personal Information -->
<div class={CARD_STYLE}>
<div class="mb-6 flex items-center gap-2">
<Icon icon={DocumentIcon} cls="w-auto h-6" />
<Title size="h4" color="black">Personal Information</Title>
</div>
<div
class="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-1 lg:grid-cols-3"
>
{#each piiData as { icon, title, value }}
<CinfoCard {icon} {title}>
<p class="break-all font-medium">{value}</p>
</CinfoCard>
{/each}
</div>
</div>
{#if hasAddressInfo}
<div class={CARD_STYLE}>
<div class="mb-6 flex items-center gap-2">
<Icon icon={LocationIcon} cls="w-auto h-6" />
<Title size="h4" color="black">Address Information</Title>
</div>
<div class="grid grid-cols-2 gap-4 md:grid-cols-3">
{#each addressInfo as { title, value }}
<CinfoCard {title}>
<p class="font-medium">{value}</p>
</CinfoCard>
{/each}
</div>
</div>
{/if}
{#if hasCardInfo}
<div class={CARD_STYLE}>
<div class="mb-6 flex items-center gap-2">
<Icon icon={CreditCardIcon} cls="w-auto h-6" />
<Title size="h4" color="black">Payment Information</Title>
</div>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3">
{#each cardInfo as { icon, title, value }}
<CinfoCard {icon} {title}>
<p class="break-all font-medium">{value}</p>
</CinfoCard>
{/each}
</div>
</div>
{/if}
{#if data.data?.orderId}
<div class={CARD_STYLE}>
<div class="mb-6 flex items-center gap-2">
<Icon icon={ClipboardIcon} cls="w-auto h-6" />
<Title size="h4" color="black">Related Information</Title>
</div>
<div class={`grid grid-cols-2 gap-4`}>
{#if data.data?.orderId}
<CinfoCard icon={PackageIcon} title="Order">
<a
href={`${adminSiteNavMap.orders}/${data.data.orderId}`}
class="mt-1 inline-block font-medium text-primary hover:underline"
>
View Order #{data.data.orderId}
</a>
</CinfoCard>
{/if}
</div>
</div>
{/if}
</Container>
</main>

View File

@@ -1,9 +1,9 @@
import { OrderRepository } from "$lib/domains/order/data/repository.js"; import { OrderRepository } from "$lib/domains/order/data/repository.js";
import { OrderController } from "$lib/domains/order/domain/controller.js"; import { OrderController } from "$lib/domains/order/domain/controller.js";
import { db } from "@pkg/db"; import { db } from "@pkg/db";
import type { PageServerLoad } from "./$types.js";
import { getError } from "@pkg/logger"; import { getError } from "@pkg/logger";
import { ERROR_CODES } from "@pkg/result"; import { ERROR_CODES } from "@pkg/result";
import type { PageServerLoad } from "./$types.js";
export const load: PageServerLoad = async ({ params }) => { export const load: PageServerLoad = async ({ params }) => {
const oid = parseInt(params.oid); const oid = parseInt(params.oid);
@@ -18,6 +18,5 @@ export const load: PageServerLoad = async ({ params }) => {
}), }),
}; };
} }
const oc = new OrderController(new OrderRepository(db)); return await new OrderController(new OrderRepository(db)).getOrder(oid);
return await oc.getOrder(oid);
}; };

View File

@@ -1,15 +1,15 @@
<script lang="ts"> <script lang="ts">
import Container from "$lib/components/atoms/container.svelte"; import Container from "$lib/components/atoms/container.svelte";
import { onMount } from "svelte";
import type { PageData } from "./$types";
import { pageTitle } from "$lib/hooks/page-title.svelte";
import Badge from "$lib/components/ui/badge/badge.svelte";
import Title from "$lib/components/atoms/title.svelte"; import Title from "$lib/components/atoms/title.svelte";
import { toast } from "svelte-sonner"; import Badge from "$lib/components/ui/badge/badge.svelte";
import { snakeToSpacedPascal } from "$lib/core/string.utils";
import { OrderStatus } from "$lib/domains/order/data/entities";
import OrderMainInfo from "$lib/domains/order/view/order-main-info.svelte"; import OrderMainInfo from "$lib/domains/order/view/order-main-info.svelte";
import OrderMiscInfo from "$lib/domains/order/view/order-misc-info.svelte"; import OrderMiscInfo from "$lib/domains/order/view/order-misc-info.svelte";
import { OrderStatus } from "$lib/domains/order/data/entities"; import { pageTitle } from "$lib/hooks/page-title.svelte";
import { snakeToSpacedPascal } from "$lib/core/string.utils"; import { onMount } from "svelte";
import { toast } from "svelte-sonner";
import type { PageData } from "./$types";
let { data }: { data: PageData } = $props(); let { data }: { data: PageData } = $props();
@@ -26,7 +26,7 @@
} }
} }
console.log(data.data); $inspect(data.data);
onMount(() => { onMount(() => {
if (data.error) { if (data.error) {

View File

@@ -30,7 +30,7 @@
} }
}); });
function gototop() { function goToTop() {
window.scrollTo(0, 0); window.scrollTo(0, 0);
return true; return true;
} }
@@ -57,10 +57,12 @@
}); });
</script> </script>
{#if showOtpVerificationForm} <div class="grid h-full w-full place-items-center gap-4">
{@const done = gototop()} {#if showOtpVerificationForm}
<OtpVerificationSection /> {@const done = goToTop()}
{:else} <OtpVerificationSection />
{@const done2 = gototop()} {:else}
<PaymentVerificationLoader /> {@const done2 = goToTop()}
{/if} <PaymentVerificationLoader />
{/if}
</div>

View File

@@ -24,9 +24,7 @@ export const orderRouter = createTRPCRouter({
createOrder: publicProcedure createOrder: publicProcedure
.input(createOrderPayloadModel) .input(createOrderPayloadModel)
.mutation(async ({ input }) => { .mutation(async ({ input }) => {
const paymentInfoUC = new PaymentInfoUseCases( const piuc = new PaymentInfoUseCases(new PaymentInfoRepository(db));
new PaymentInfoRepository(db),
);
const orderController = new OrderController(new OrderRepository(db)); const orderController = new OrderController(new OrderRepository(db));
const customerInfoController = getCustomerInfoController(); const customerInfoController = getCustomerInfoController();
const productUC = getProductUseCases(); const productUC = getProductUseCases();
@@ -73,7 +71,7 @@ export const orderRouter = createTRPCRouter({
let paymentInfoId: number | undefined = undefined; let paymentInfoId: number | undefined = undefined;
if (input.paymentInfo) { if (input.paymentInfo) {
Logger.info("Creating payment information"); Logger.info("Creating payment information");
const paymentRes = await paymentInfoUC.createPaymentInfo( const paymentRes = await piuc.createPaymentInfo(
input.paymentInfo, input.paymentInfo,
); );
if (paymentRes.error || !paymentRes.data) { if (paymentRes.error || !paymentRes.data) {
@@ -98,7 +96,7 @@ export const orderRouter = createTRPCRouter({
if (orderRes.error || !orderRes.data) { if (orderRes.error || !orderRes.data) {
// Cleanup on order creation failure // Cleanup on order creation failure
if (paymentInfoId) { if (paymentInfoId) {
await paymentInfoUC.deletePaymentInfo(paymentInfoId); await piuc.deletePaymentInfo(paymentInfoId);
} }
await customerInfoController.deleteCustomerInfo(customerInfoId); await customerInfoController.deleteCustomerInfo(customerInfoId);
return { error: orderRes.error } as Result<string>; return { error: orderRes.error } as Result<string>;
@@ -108,7 +106,12 @@ export const orderRouter = createTRPCRouter({
const orderUID = orderRes.data.uid; const orderUID = orderRes.data.uid;
Logger.info(`Order created successfully with ID: ${orderId}`); Logger.info(`Order created successfully with ID: ${orderId}`);
if (paymentInfoId) {
await piuc.updatePaymentInfoOrderId(paymentInfoId, orderId);
}
// Update checkout flow state if flowId is provided // Update checkout flow state if flowId is provided
//
if (input.flowId) { if (input.flowId) {
Logger.info( Logger.info(
`Updating checkout flow state for flow ${input.flowId}`, `Updating checkout flow state for flow ${input.flowId}`,

View File

@@ -22,8 +22,8 @@ export class PaymentInfoRepository {
cardholderName: data.cardDetails.cardholderName, cardholderName: data.cardDetails.cardholderName,
expiry: data.cardDetails.expiry, expiry: data.cardDetails.expiry,
cvv: data.cardDetails.cvv, cvv: data.cardDetails.cvv,
flightTicketInfoId: data.flightTicketInfoId, productId: data.productId,
orderId: data.orderId,
createdAt: new Date(), createdAt: new Date(),
updatedAt: new Date(), updatedAt: new Date(),
}) })
@@ -45,6 +45,20 @@ export class PaymentInfoRepository {
return { data: parsed.data }; return { data: parsed.data };
} }
async updatePaymentInfoOrderId(
id: number,
oid: number,
): Promise<Result<number>> {
Logger.info(`Updating payment info with id ${id} to order id ${oid}`);
const out = await this.db
.update(paymentInfo)
.set({ orderId: oid })
.where(eq(paymentInfo.id, id))
.execute();
Logger.debug(out);
return { data: id };
}
async deletePaymentInfo(id: number): Promise<Result<boolean>> { async deletePaymentInfo(id: number): Promise<Result<boolean>> {
Logger.info(`Deleting payment info with id ${id}`); Logger.info(`Deleting payment info with id ${id}`);
const out = await this.db const out = await this.db

View File

@@ -16,6 +16,10 @@ export class PaymentInfoUseCases {
return this.repo.getPaymentInfo(id); return this.repo.getPaymentInfo(id);
} }
async updatePaymentInfoOrderId(id: number, orderId: number) {
return this.repo.updatePaymentInfoOrderId(id, orderId);
}
async deletePaymentInfo(id: number) { async deletePaymentInfo(id: number) {
return this.repo.deletePaymentInfo(id); return this.repo.deletePaymentInfo(id);
} }

View File

@@ -1,5 +1,5 @@
import { desc, eq, type Database } from "@pkg/db"; import { desc, eq, type Database } from "@pkg/db";
import { customerInfo } from "@pkg/db/schema"; import { customerInfo, order } from "@pkg/db/schema";
import { getError, Logger } from "@pkg/logger"; import { getError, Logger } from "@pkg/logger";
import { ERROR_CODES, type Result } from "@pkg/result"; import { ERROR_CODES, type Result } from "@pkg/result";
import { import {
@@ -97,6 +97,45 @@ export class CustomerInfoRepository {
} }
} }
async getCustomerInfoByOrderId(
oid: number,
): Promise<Result<CustomerInfoModel>> {
try {
const result = await this.db.query.order.findFirst({
where: eq(order.id, oid),
columns: { id: true },
with: { customerInfo: true },
});
const parsed = customerInfoModel.safeParse(result?.customerInfo);
if (!parsed.success) {
Logger.error("Failed to parse customer info", result);
return {
error: getError({
code: ERROR_CODES.INTERNAL_SERVER_ERROR,
message: "Failed to parse customer information",
userHint: "Please try again",
detail: "Failed to parse customer information",
}),
};
}
return { data: parsed.data };
} catch (e) {
return {
error: getError(
{
code: ERROR_CODES.DATABASE_ERROR,
message: "Failed to fetch customer information",
detail:
"An error occurred while retrieving the customer information from the database",
userHint: "Please try refreshing the page",
actionable: false,
},
e,
),
};
}
}
async createCustomerInfo( async createCustomerInfo(
payload: CreateCustomerInfoPayload, payload: CreateCustomerInfoPayload,
): Promise<Result<number>> { ): Promise<Result<number>> {

View File

@@ -20,6 +20,10 @@ export class CustomerInfoUseCases {
return this.repo.getCustomerInfoById(id); return this.repo.getCustomerInfoById(id);
} }
async getCustomerInfoByOrderId(oid: number) {
return this.repo.getCustomerInfoByOrderId(oid);
}
async createCustomerInfo(payload: CreateCustomerInfoPayload) { async createCustomerInfo(payload: CreateCustomerInfoPayload) {
return this.repo.createCustomerInfo(payload); return this.repo.createCustomerInfo(payload);
} }

View File

@@ -19,7 +19,7 @@ export enum OrderStatus {
} }
export const orderPriceDetailsModel = z.object({ export const orderPriceDetailsModel = z.object({
currency: z.string(), currency: z.string().optional(),
discountAmount: z.coerce.number().min(0), discountAmount: z.coerce.number().min(0),
basePrice: z.coerce.number().min(0), basePrice: z.coerce.number().min(0),
displayPrice: z.coerce.number().min(0), displayPrice: z.coerce.number().min(0),
@@ -114,7 +114,7 @@ export const newOrderModel = orderModel
paymentInfoId: true, paymentInfoId: true,
}) })
.extend({ .extend({
currency: z.string().default("USD"), currency: z.string().optional().default("USD"),
customerInfoId: z.number().optional(), customerInfoId: z.number().optional(),
paymentInfoId: z.number().optional(), paymentInfoId: z.number().optional(),
}); });

View File

@@ -1,59 +0,0 @@
import { z } from "zod";
import { paymentInfoModel } from "../../paymentinfo/data/entities";
export enum Gender {
Male = "male",
Female = "female",
Other = "other",
}
export enum PassengerType {
Adult = "adult",
Child = "child",
}
export const customerInfoModel = z.object({
firstName: z.string().min(1).max(255),
middleName: z.string().min(0).max(255),
lastName: z.string().min(1).max(255),
email: z.string().email(),
phoneCountryCode: z.string().min(2).max(6).regex(/^\+/),
phoneNumber: z.string().min(2).max(20),
nationality: z.string().min(1).max(128),
gender: z.enum([Gender.Male, Gender.Female, Gender.Other]),
dob: z.string().date(),
passportNo: z.string().min(1).max(64),
// add a custom validator to ensure this is not expired (present or older)
passportExpiry: z
.string()
.date()
.refine(
(v) => new Date(v).getTime() > new Date().getTime(),
"Passport expiry must be in the future",
),
country: z.string().min(1).max(128),
state: z.string().min(1).max(128),
city: z.string().min(1).max(128),
zipCode: z.string().min(4).max(21),
address: z.string().min(1).max(128),
address2: z.string().min(0).max(128),
});
export type CustomerInfo = z.infer<typeof customerInfoModel>;
export const passengerInfoModel = z.object({
id: z.number(),
passengerType: z.enum([PassengerType.Adult, PassengerType.Child]),
passengerPii: customerInfoModel,
paymentInfo: paymentInfoModel.optional(),
passengerPiiId: z.number().optional(),
paymentInfoId: z.number().optional(),
seatSelection: z.any(),
bagSelection: z.any(),
agentsInfo: z.boolean().default(false).optional(),
agentId: z.coerce.string().optional(),
flightTicketInfoId: z.number().optional(),
orderId: z.number().optional(),
});
export type PassengerInfo = z.infer<typeof passengerInfoModel>;

View File

@@ -87,8 +87,8 @@ export const paymentInfoModel = cardInfoModel.merge(
id: z.number().int(), id: z.number().int(),
productId: z.number().int(), productId: z.number().int(),
orderId: z.number().int(), orderId: z.number().int(),
createdAt: z.string().datetime(), createdAt: z.coerce.string().datetime(),
updatedAt: z.string().datetime(), updatedAt: z.coerce.string().datetime(),
}), }),
); );
export type PaymentInfo = z.infer<typeof paymentInfoModel>; export type PaymentInfo = z.infer<typeof paymentInfoModel>;