🎉👏 done
This commit is contained in:
@@ -1,16 +1,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Icon from "$lib/components/atoms/icon.svelte";
|
import Icon from "$lib/components/atoms/icon.svelte";
|
||||||
|
import Title from "$lib/components/atoms/title.svelte";
|
||||||
|
|
||||||
let { icon, title, children }: { icon?: any; title: string; children: any } =
|
let { icon, title, children }: { icon?: any; title: string; children: any } =
|
||||||
$props();
|
$props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2 rounded-lg border bg-gray-50 p-2 md:p-4">
|
<div
|
||||||
|
class="flex flex-col gap-4 rounded-lg border border-gray-200 bg-white p-6 shadow-md"
|
||||||
|
>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
{#if icon}
|
{#if icon}
|
||||||
<Icon {icon} cls="w-5 h-5" />
|
<Icon {icon} cls="w-5 h-5" />
|
||||||
{/if}
|
{/if}
|
||||||
<span class="text-gray-500">{title}</span>
|
<Title size="h5" color="black">{title}</Title>
|
||||||
</div>
|
</div>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<span class="text-xs text-gray-500">Full Name</span>
|
<span class="text-xs text-gray-500">Full Name</span>
|
||||||
<p>
|
<p class="mt-1 font-medium">
|
||||||
{customerInfo.firstName}
|
{customerInfo.firstName}
|
||||||
{#if customerInfo.middleName}
|
{#if customerInfo.middleName}
|
||||||
{customerInfo.middleName}
|
{customerInfo.middleName}
|
||||||
@@ -24,33 +24,36 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="text-xs text-gray-500">Email</span>
|
<span class="text-xs text-gray-500">Email</span>
|
||||||
<p>{customerInfo.email}</p>
|
<p class="mt-1">{customerInfo.email}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="text-xs text-gray-500">Phone Number</span>
|
<span class="text-xs text-gray-500">Phone Number</span>
|
||||||
<p>{customerInfo.phoneCountryCode} {customerInfo.phoneNumber}</p>
|
<p class="mt-1">
|
||||||
|
{customerInfo.phoneCountryCode}
|
||||||
|
{customerInfo.phoneNumber}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="text-xs text-gray-500">City</span>
|
<span class="text-xs text-gray-500">City</span>
|
||||||
<p>{customerInfo.city}</p>
|
<p class="mt-1">{customerInfo.city}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="text-xs text-gray-500">State</span>
|
<span class="text-xs text-gray-500">State</span>
|
||||||
<p>{customerInfo.state}</p>
|
<p class="mt-1">{customerInfo.state}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="text-xs text-gray-500">Country</span>
|
<span class="text-xs text-gray-500">Country</span>
|
||||||
<p>{customerInfo.country}</p>
|
<p class="mt-1">{customerInfo.country}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="text-xs text-gray-500">Zip Code</span>
|
<span class="text-xs text-gray-500">Zip Code</span>
|
||||||
<p>{customerInfo.zipCode}</p>
|
<p class="mt-1">{customerInfo.zipCode}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="md:col-span-2">
|
<div class="md:col-span-2">
|
||||||
<span class="text-xs text-gray-500">Address</span>
|
<span class="text-xs text-gray-500">Address</span>
|
||||||
<p>{customerInfo.address}</p>
|
<p class="mt-1">{customerInfo.address}</p>
|
||||||
{#if customerInfo.address2}
|
{#if customerInfo.address2}
|
||||||
<p class="text-sm text-gray-600">{customerInfo.address2}</p>
|
<p class="mt-1 text-sm text-gray-600">{customerInfo.address2}</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export class OrderRepository {
|
|||||||
});
|
});
|
||||||
const out = [] as FullOrderModel[];
|
const out = [] as FullOrderModel[];
|
||||||
for (const each of res) {
|
for (const each of res) {
|
||||||
|
console.log(each);
|
||||||
const parsed = fullOrderModel.safeParse({
|
const parsed = fullOrderModel.safeParse({
|
||||||
...each,
|
...each,
|
||||||
});
|
});
|
||||||
@@ -58,6 +59,7 @@ export class OrderRepository {
|
|||||||
where: eq(order.id, oid),
|
where: eq(order.id, oid),
|
||||||
with: { customerInfo: true, product: true, paymentInfo: true },
|
with: { customerInfo: true, product: true, paymentInfo: true },
|
||||||
});
|
});
|
||||||
|
console.log(out?.paymentInfo);
|
||||||
if (!out) return {};
|
if (!out) return {};
|
||||||
const parsed = fullOrderModel.safeParse({
|
const parsed = fullOrderModel.safeParse({
|
||||||
...out,
|
...out,
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import CInfoCard from "$lib/domains/customerinfo/view/cinfo-card.svelte";
|
||||||
|
import type { PaymentInfo } from "@pkg/logic/domains/paymentinfo/data/entities";
|
||||||
|
import CreditCardIcon from "~icons/solar/card-broken";
|
||||||
|
|
||||||
|
let {
|
||||||
|
paymentInfo,
|
||||||
|
}: {
|
||||||
|
paymentInfo: PaymentInfo;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
function maskCardNumber(cardNumber: string): string {
|
||||||
|
const cleaned = cardNumber.replace(/\s/g, "");
|
||||||
|
const lastFour = cleaned.slice(-4);
|
||||||
|
return `•••• •••• •••• ${lastFour}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function maskCVV(cvv: string): string {
|
||||||
|
return "•".repeat(cvv.length);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CInfoCard icon={CreditCardIcon} title="Billing Information">
|
||||||
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
|
<div class="md:col-span-2">
|
||||||
|
<span class="text-xs text-gray-500">Cardholder Name</span>
|
||||||
|
<p class="font-medium">{paymentInfo.cardholderName}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="text-xs text-gray-500">Card Number</span>
|
||||||
|
<p class="font-mono text-sm tracking-wider">
|
||||||
|
{maskCardNumber(paymentInfo.cardNumber)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="text-xs text-gray-500">Expiry Date</span>
|
||||||
|
<p class="font-mono">{paymentInfo.expiry}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="text-xs text-gray-500">CVV</span>
|
||||||
|
<p class="font-mono">{maskCVV(paymentInfo.cvv)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CInfoCard>
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
import type { FullOrderModel } from "$lib/domains/order/data/entities";
|
import type { FullOrderModel } from "$lib/domains/order/data/entities";
|
||||||
import ProductIcon from "~icons/solar/box-broken";
|
import ProductIcon from "~icons/solar/box-broken";
|
||||||
import CreditCardIcon from "~icons/solar/card-broken";
|
import CreditCardIcon from "~icons/solar/card-broken";
|
||||||
|
import BillingDetailsCard from "./billing-details-card.svelte";
|
||||||
|
|
||||||
let { order }: { order: FullOrderModel } = $props();
|
let { order }: { order: FullOrderModel } = $props();
|
||||||
|
|
||||||
@@ -29,26 +30,28 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-4">
|
||||||
<div>
|
<div>
|
||||||
<span class="text-sm text-gray-500">Product Name</span>
|
<span class="text-sm text-gray-500">Product Name</span>
|
||||||
<p class="font-medium">{order.product.title}</p>
|
<p class="mt-1 font-medium">{order.product.title}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span class="text-sm text-gray-500">Description</span>
|
<span class="text-sm text-gray-500">Description</span>
|
||||||
<p class="text-gray-700">{order.product.description}</p>
|
<p class="mt-1 text-gray-700">{order.product.description}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<span class="text-sm text-gray-500">Regular Price</span>
|
<span class="text-sm text-gray-500">Regular Price</span>
|
||||||
<p class="font-medium">${order.product.price.toFixed(2)}</p>
|
<p class="mt-1 font-semibold">
|
||||||
|
${order.product.price.toFixed(2)}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{#if order.product.discountPrice > 0}
|
{#if order.product.discountPrice > 0}
|
||||||
<div>
|
<div>
|
||||||
<span class="text-sm text-gray-500">Discount Price</span>
|
<span class="text-sm text-gray-500">Discount Price</span>
|
||||||
<p class="font-medium text-green-600">
|
<p class="mt-1 font-semibold text-green-600">
|
||||||
${order.product.discountPrice.toFixed(2)}
|
${order.product.discountPrice.toFixed(2)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -64,34 +67,40 @@
|
|||||||
<Title size="h5" color="black">Price Summary</Title>
|
<Title size="h5" color="black">Price Summary</Title>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-3">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span>Base Price</span>
|
<span class="text-gray-600">Base Price</span>
|
||||||
<span>${order.basePrice.toFixed(2)}</span>
|
<span class="font-medium">${order.basePrice.toFixed(2)}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span>Display Price</span>
|
<span class="text-gray-600">Display Price</span>
|
||||||
<span>${order.displayPrice.toFixed(2)}</span>
|
<span class="font-medium">${order.displayPrice.toFixed(2)}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if discAmt > 0}
|
{#if discAmt > 0}
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span>Discount</span>
|
<span class="text-gray-600">Discount</span>
|
||||||
<span class="text-green-600">
|
<span class="font-semibold text-green-600">
|
||||||
-${discAmt.toFixed(2)}
|
-${discAmt.toFixed(2)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="mt-2 flex items-center justify-between border-t pt-2">
|
<div
|
||||||
<span class="font-medium">Order Price</span>
|
class="mt-2 flex items-center justify-between border-t border-gray-200 pt-3"
|
||||||
<span class="font-medium">${order.orderPrice.toFixed(2)}</span>
|
>
|
||||||
|
<span class="font-semibold">Order Price</span>
|
||||||
|
<span class="text-lg font-bold"
|
||||||
|
>${order.orderPrice.toFixed(2)}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="text-sm text-gray-500">Fulfilled</span>
|
<span class="text-sm text-gray-500">Fulfilled Amount</span>
|
||||||
<span class="text-sm">${order.fullfilledPrice.toFixed(2)}</span>
|
<span class="text-sm font-medium">
|
||||||
|
${order.fullfilledPrice.toFixed(2)}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -100,4 +109,9 @@
|
|||||||
<!-- Customer Information -->
|
<!-- Customer Information -->
|
||||||
<CustomerDetailsCard customerInfo={order.customerInfo} />
|
<CustomerDetailsCard customerInfo={order.customerInfo} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if order.paymentInfo}
|
||||||
|
<!-- Billing Information -->
|
||||||
|
<BillingDetailsCard paymentInfo={order.paymentInfo} />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
const cardStyle =
|
const cardStyle =
|
||||||
"flex flex-col gap-4 rounded-lg border border-gray-200 bg-white p-6 shadow-md";
|
"flex flex-col gap-4 rounded-lg border border-gray-200 bg-white p-6 shadow-md";
|
||||||
|
const stickyContainerStyle = "sticky top-4 flex flex-col gap-6";
|
||||||
|
|
||||||
function getStatusVariant(status: OrderStatus) {
|
function getStatusVariant(status: OrderStatus) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
@@ -46,7 +47,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col gap-6">
|
<div class={stickyContainerStyle}>
|
||||||
<!-- Order Status -->
|
<!-- Order Status -->
|
||||||
<div class={cardStyle}>
|
<div class={cardStyle}>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
@@ -54,16 +55,16 @@
|
|||||||
<Title size="h5" color="black">Order Status</Title>
|
<Title size="h5" color="black">Order Status</Title>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="text-gray-600">Current Status</span>
|
<span class="text-sm text-gray-500">Current Status</span>
|
||||||
<Badge variant={getStatusVariant(order.status)}>
|
<Badge variant={getStatusVariant(order.status)}>
|
||||||
{formatStatus(order.status)}
|
{formatStatus(order.status)}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2 text-sm">
|
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="text-gray-600">Order ID</span>
|
<span class="text-sm text-gray-500">Order ID</span>
|
||||||
<span class="font-medium">#{order.id}</span>
|
<span class="font-medium">#{order.id}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -79,12 +80,12 @@
|
|||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
<div>
|
<div>
|
||||||
<span class="text-sm text-gray-500">Created At</span>
|
<span class="text-sm text-gray-500">Created At</span>
|
||||||
<p class="font-medium">{formatDate(order.createdAt)}</p>
|
<p class="mt-1 font-medium">{formatDate(order.createdAt)}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span class="text-sm text-gray-500">Last Updated</span>
|
<span class="text-sm text-gray-500">Last Updated</span>
|
||||||
<p class="font-medium">{formatDate(order.updatedAt)}</p>
|
<p class="mt-1 font-medium">{formatDate(order.updatedAt)}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -40,20 +40,35 @@
|
|||||||
|
|
||||||
{#if data.data}
|
{#if data.data}
|
||||||
<main class="grid w-full place-items-center gap-4">
|
<main class="grid w-full place-items-center gap-4">
|
||||||
<Container containerClass="w-full flex flex-col gap-8">
|
<Container containerClass="w-full flex flex-col gap-6">
|
||||||
<div class="flex items-center justify-between gap-4 md:flex-row">
|
<!-- Header Section -->
|
||||||
|
<div
|
||||||
|
class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
<Title size="h3">Order Details</Title>
|
<Title size="h3">Order Details</Title>
|
||||||
<Badge variant={getStatusVariant(data.data?.status)}>
|
<p class="text-sm text-gray-500">Order #{data.data.id}</p>
|
||||||
|
</div>
|
||||||
|
<Badge
|
||||||
|
variant={getStatusVariant(data.data?.status)}
|
||||||
|
class="w-fit"
|
||||||
|
>
|
||||||
{snakeToSpacedPascal(data.data?.status.toLowerCase())}
|
{snakeToSpacedPascal(data.data?.status.toLowerCase())}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<!-- Main Content Grid -->
|
||||||
class="grid h-full w-full grid-cols-1 gap-4 md:gap-6 lg:grid-cols-2 lg:gap-8"
|
<div class="grid h-full w-full grid-cols-1 gap-6 lg:grid-cols-3">
|
||||||
>
|
<!-- Left Column - Main Info (2/3 width on large screens) -->
|
||||||
|
<div class="lg:col-span-2">
|
||||||
<OrderMainInfo order={data.data} />
|
<OrderMainInfo order={data.data} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Column - Misc Info (1/3 width on large screens) -->
|
||||||
|
<div class="lg:col-span-1">
|
||||||
<OrderMiscInfo order={data.data} />
|
<OrderMiscInfo order={data.data} />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
</main>
|
</main>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import { z } from "zod";
|
|||||||
import { paginationModel } from "../../../core/pagination.utils";
|
import { paginationModel } from "../../../core/pagination.utils";
|
||||||
import { encodeCursor } from "../../../core/string.utils";
|
import { encodeCursor } from "../../../core/string.utils";
|
||||||
import { customerInfoModel } from "../../customerinfo/data";
|
import { customerInfoModel } from "../../customerinfo/data";
|
||||||
import { paymentInfoPayloadModel } from "../../paymentinfo/data/entities";
|
import {
|
||||||
|
paymentInfoModel,
|
||||||
|
paymentInfoPayloadModel,
|
||||||
|
} from "../../paymentinfo/data/entities";
|
||||||
import { productModel } from "../../product/data";
|
import { productModel } from "../../product/data";
|
||||||
|
|
||||||
export enum OrderCreationStep {
|
export enum OrderCreationStep {
|
||||||
@@ -72,6 +75,7 @@ export const fullOrderModel = orderModel.merge(
|
|||||||
z.object({
|
z.object({
|
||||||
product: productModel,
|
product: productModel,
|
||||||
customerInfo: customerInfoModel.optional().nullable(),
|
customerInfo: customerInfoModel.optional().nullable(),
|
||||||
|
paymentInfo: paymentInfoModel.optional().nullable(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
export type FullOrderModel = z.infer<typeof fullOrderModel>;
|
export type FullOrderModel = z.infer<typeof fullOrderModel>;
|
||||||
|
|||||||
@@ -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.coerce.string().datetime(),
|
createdAt: z.coerce.string(),
|
||||||
updatedAt: z.coerce.string().datetime(),
|
updatedAt: z.coerce.string(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
export type PaymentInfo = z.infer<typeof paymentInfoModel>;
|
export type PaymentInfo = z.infer<typeof paymentInfoModel>;
|
||||||
|
|||||||
Reference in New Issue
Block a user