From 94bb51bdc7fea305aa417f4c3fb923dae2478ebe Mon Sep 17 00:00:00 2001 From: user Date: Tue, 21 Oct 2025 16:26:57 +0300 Subject: [PATCH] refactor: create order vm | remove: order email account id thingy admin-side --- .../domains/order/view/order-main-info.svelte | 12 -- .../domains/order/view/order-misc-info.svelte | 24 --- .../order/view/orders-table-full.svelte | 8 - .../view/create/create.order.vm.svelte.ts | 199 ++++++++++++------ packages/logic/domains/order/data/entities.ts | 11 +- 5 files changed, 139 insertions(+), 115 deletions(-) diff --git a/apps/admin/src/lib/domains/order/view/order-main-info.svelte b/apps/admin/src/lib/domains/order/view/order-main-info.svelte index d571fce..d8a0431 100644 --- a/apps/admin/src/lib/domains/order/view/order-main-info.svelte +++ b/apps/admin/src/lib/domains/order/view/order-main-info.svelte @@ -6,7 +6,6 @@ import type { FullOrderModel } from "$lib/domains/order/data/entities"; import ProductIcon from "~icons/solar/box-broken"; import CreditCardIcon from "~icons/solar/card-broken"; - import EmailIcon from "~icons/solar/letter-broken"; let { order }: { order: FullOrderModel } = $props(); @@ -17,17 +16,6 @@
- {#if order.emailAccountId} - -
-
- - Account Information -
-

Email Account ID: #{order.emailAccountId}

-
- {/if} -
diff --git a/apps/admin/src/lib/domains/order/view/order-misc-info.svelte b/apps/admin/src/lib/domains/order/view/order-misc-info.svelte index 87fb671..3cdfb24 100644 --- a/apps/admin/src/lib/domains/order/view/order-misc-info.svelte +++ b/apps/admin/src/lib/domains/order/view/order-misc-info.svelte @@ -88,28 +88,4 @@
- - - {#if order.emailAccountId} -
-
- - Additional Information -
- -
-
- Email Account ID - #{order.emailAccountId} -
- - {#if order.paymentInfoId} -
- Payment Info ID - #{order.paymentInfoId} -
- {/if} -
-
- {/if} diff --git a/apps/admin/src/lib/domains/order/view/orders-table-full.svelte b/apps/admin/src/lib/domains/order/view/orders-table-full.svelte index 8f78703..3e56b7b 100644 --- a/apps/admin/src/lib/domains/order/view/orders-table-full.svelte +++ b/apps/admin/src/lib/domains/order/view/orders-table-full.svelte @@ -81,14 +81,6 @@ return snakeToSpacedPascal(r.status.toLowerCase()); }, }, - { - header: "Order Type", - accessorKey: "ordertype", - cell: ({ row }) => { - const r = row.original as FullOrderModel; - return r.emailAccountId ? "Agent" : "Customer"; - }, - }, { header: "Action", id: "actions", diff --git a/apps/frontend/src/lib/domains/order/view/create/create.order.vm.svelte.ts b/apps/frontend/src/lib/domains/order/view/create/create.order.vm.svelte.ts index 061726d..0bb7768 100644 --- a/apps/frontend/src/lib/domains/order/view/create/create.order.vm.svelte.ts +++ b/apps/frontend/src/lib/domains/order/view/create/create.order.vm.svelte.ts @@ -1,4 +1,5 @@ import { billingDetailsVM } from "$lib/domains/checkout/payment-info-section/billing.details.vm.svelte"; +import { calculateFinalPrices } from "$lib/domains/checkout/utils"; import { ckFlowVM } from "$lib/domains/ckflow/view/ckflow.vm.svelte"; import { customerInfoVM } from "$lib/domains/customerinfo/view/customerinfo.vm.svelte"; import { @@ -10,104 +11,176 @@ import { trpcApiStore } from "$lib/stores/api"; import { toast } from "svelte-sonner"; import { get } from "svelte/store"; +/** + * CreateOrderViewModel manages the order creation flow for product checkout. + * Handles step progression, validation, and order submission. + */ export class CreateOrderViewModel { - orderStep = $state(OrderCreationStep.ACCOUNT_SELECTION); + // Current step in the order creation flow + orderStep = $state(OrderCreationStep.CUSTOMER_INFO); - passengerInfosOk = $state(false); - - loading = $state(true); + // Loading state + loading = $state(false); + /** + * Sets the current order creation step + * @param step - The step to navigate to + */ setStep(step: OrderCreationStep) { - if (step === OrderCreationStep.ACCOUNT_SELECTION && this.accountInfoOk) { - this.orderStep = step; - } else if ( - step === OrderCreationStep.TICKET_SELECTION && - this.ticketInfoOk - ) { - this.orderStep = step; - } else { - this.orderStep = step; - } + this.orderStep = step; } + /** + * Advances to the next step in the order creation flow + */ setNextStep() { - if (this.orderStep === OrderCreationStep.ACCOUNT_SELECTION) { - this.orderStep = OrderCreationStep.TICKET_SELECTION; - } else if (this.orderStep === OrderCreationStep.TICKET_SELECTION) { - this.orderStep = OrderCreationStep.CUSTOMER_INFO; - } else if (this.orderStep === OrderCreationStep.CUSTOMER_INFO) { + if (this.orderStep === OrderCreationStep.CUSTOMER_INFO) { + // Validate customer info before proceeding + if (!this.isCustomerInfoValid()) { + toast.error("Please complete customer information"); + return; + } + this.orderStep = OrderCreationStep.PAYMENT; + } else if (this.orderStep === OrderCreationStep.PAYMENT) { this.orderStep = OrderCreationStep.SUMMARY; - } else { - this.orderStep = OrderCreationStep.ACCOUNT_SELECTION; } } + /** + * Goes back to the previous step + */ setPrevStep() { if (this.orderStep === OrderCreationStep.SUMMARY) { + this.orderStep = OrderCreationStep.PAYMENT; + } else if (this.orderStep === OrderCreationStep.PAYMENT) { this.orderStep = OrderCreationStep.CUSTOMER_INFO; - } else if (this.orderStep === OrderCreationStep.CUSTOMER_INFO) { - this.orderStep = OrderCreationStep.TICKET_SELECTION; - } else { - this.orderStep = OrderCreationStep.ACCOUNT_SELECTION; } } - async createOrder() { + /** + * Validates if customer information is complete + * @returns true if customer info is valid, false otherwise + */ + isCustomerInfoValid(): boolean { + if (!customerInfoVM.customerInfo) { + return false; + } + return customerInfoVM.isValid(); + } + + /** + * Validates if product is selected + * @returns true if product exists, false otherwise + */ + isProductValid(): boolean { + const product = get(productStore); + return product !== null && product.id !== undefined; + } + + /** + * Checks if order can be submitted (all validations pass) + * @returns true if order is ready to submit, false otherwise + */ + canSubmitOrder(): boolean { + return this.isProductValid() && this.isCustomerInfoValid(); + } + + /** + * Creates and submits the order + * @returns true if successful, false otherwise + */ + async createOrder(): Promise { const api = get(trpcApiStore); if (!api) { - return; + toast.error("API client not initialized"); + return false; } - let basePrice = 0; - let displayPrice = 0; - let discountAmount = 0; - if (this.ticketInfo) { - basePrice = this.ticketInfo.priceDetails.basePrice; - displayPrice = this.ticketInfo.priceDetails.displayPrice; - discountAmount = this.ticketInfo.priceDetails.discountAmount; + const product = get(productStore); + if (!product || !customerInfoVM.customerInfo) { + toast.error("Missing required information", { + description: "Product or customer information is incomplete", + }); + return false; } + // Calculate price details from product + const priceDetails = calculateFinalPrices( + product, + customerInfoVM.customerInfo, + ); + + // Build the order payload const parsed = createOrderPayloadModel.safeParse({ - product: get(productStore), - productId: get(productStore)?.id, + product: product, + productId: product.id, customerInfo: customerInfoVM.customerInfo, paymentInfo: billingDetailsVM.billingDetails, orderModel: { - basePrice, - displayPrice, - discountAmount, - flightTicketInfoId: 0, - emailAccountId: 0, + ...priceDetails, + productId: product.id, + customerInfoId: customerInfoVM.customerInfo.id, + paymentInfoId: undefined, }, flowId: ckFlowVM.flowId, }); + if (parsed.error) { - console.log(parsed.error.errors); + console.error("Order payload validation error:", parsed.error.errors); const msg = parsed.error.errors[0].message; - return toast.error(msg); + toast.error("Invalid order data", { + description: msg, + }); + return false; } + this.loading = true; - const out = await api.order.createOrder.mutate(parsed.data); + + try { + const out = await api.order.createOrder.mutate(parsed.data); + + if (out.error) { + toast.error(out.error.message, { + description: out.error.userHint, + }); + return false; + } + + if (!out.data) { + toast.error("Order creation failed", { + description: + "Please try again, or contact support if the issue persists", + }); + return false; + } + + toast.success("Order created successfully", { + description: "Please wait, redirecting...", + }); + + // Redirect to success page after a short delay + setTimeout(() => { + window.location.href = `/order/success?id=${out.data}`; + }, 1000); + + return true; + } catch (e) { + console.error("Order creation error:", e); + toast.error("An unexpected error occurred", { + description: "Please try again later", + }); + return false; + } finally { + this.loading = false; + } + } + + /** + * Resets the view model state + */ + reset() { + this.orderStep = OrderCreationStep.CUSTOMER_INFO; this.loading = false; - - console.log(out); - - if (out.error) { - return toast.error(out.error.message, { - description: out.error.userHint, - }); - } - if (!out.data) { - return toast.error("Order likely failed to create", { - description: - "Please try again, or contact us to resolve the issue", - }); - } - - toast.success("Order created successfully, redirecting"); - setTimeout(() => { - window.location.replace("/"); - }, 1000); } } diff --git a/packages/logic/domains/order/data/entities.ts b/packages/logic/domains/order/data/entities.ts index 76cc861..f4cafd6 100644 --- a/packages/logic/domains/order/data/entities.ts +++ b/packages/logic/domains/order/data/entities.ts @@ -6,12 +6,9 @@ import { paymentInfoPayloadModel } from "../../paymentinfo/data/entities"; import { productModel } from "../../product/data"; export enum OrderCreationStep { - ACCOUNT_SELECTION = 0, - TICKET_SELECTION = 1, - // TODO: only keep these remove the above 2 steps - CUSTOMER_INFO = 2, - PAYMENT = 2, - SUMMARY = 3, + CUSTOMER_INFO = 0, + PAYMENT = 1, + SUMMARY = 2, } export enum OrderStatus { @@ -40,7 +37,6 @@ export const orderModel = z.object({ productId: z.number(), customerInfoId: z.number().nullish().optional(), - emailAccountId: z.number().nullish().optional(), paymentInfoId: z.number().nullish().optional(), createdAt: z.coerce.string(), @@ -115,7 +111,6 @@ export const newOrderModel = orderModel.pick({ productId: true, customerInfoId: true, paymentInfoId: true, - emailAccountId: true, }); export type NewOrderModel = z.infer;