|
|
|
|
@@ -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<boolean> {
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|