Some checks failed
CI / Lint, Typecheck & Test (push) Failing after 2m11s
- Allow require() in next.config.js (eslint-disable) for both apps - Replace all catch (e: any) with catch (e: unknown) and proper error handling - Remove no-explicit-any: add types (PreviewProduct, ProductImage, Id<addresses>, ProductDetailReview, error payloads) across admin and storefront - Admin: use next/image in ImageUploadSection and ProductImageCarousel; remove unused layout fonts and sidebar imports; fix products page useMemo deps - Storefront: use Link for /sign-in in header actions; fix useAddressMutations and product detail types; remove unused imports/vars and fix useMemo deps Made-with: Cursor
126 lines
3.4 KiB
TypeScript
126 lines
3.4 KiB
TypeScript
"use client";
|
|
|
|
import { Button, Chip, RadioGroup, Radio, Skeleton } from "@heroui/react";
|
|
import type { CheckoutAddress } from "@/lib/checkout/types";
|
|
|
|
type AddressSelectorProps = {
|
|
addresses: CheckoutAddress[];
|
|
selectedId: string | null;
|
|
onSelect: (id: string) => void;
|
|
onAddNew: () => void;
|
|
isLoading: boolean;
|
|
};
|
|
|
|
export function AddressSelector({
|
|
addresses,
|
|
selectedId,
|
|
onSelect,
|
|
onAddNew,
|
|
isLoading,
|
|
}: AddressSelectorProps) {
|
|
if (isLoading) {
|
|
return <AddressSelectorSkeleton />;
|
|
}
|
|
|
|
if (addresses.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col gap-4">
|
|
<RadioGroup
|
|
aria-label="Select shipping address"
|
|
value={selectedId ?? undefined}
|
|
onChange={(value) => onSelect(value)}
|
|
>
|
|
<div className="grid grid-cols-1 gap-3 md:grid-cols-2">
|
|
{addresses.map((address) => (
|
|
<Radio key={address.id} value={address.id}>
|
|
{({ isSelected }) => (
|
|
<AddressCard address={address} isSelected={isSelected} />
|
|
)}
|
|
</Radio>
|
|
))}
|
|
</div>
|
|
</RadioGroup>
|
|
|
|
<Button
|
|
variant="ghost"
|
|
className="w-full md:w-auto"
|
|
onPress={onAddNew}
|
|
>
|
|
+ Add new address
|
|
</Button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function AddressCard({
|
|
address,
|
|
isSelected,
|
|
}: {
|
|
address: CheckoutAddress;
|
|
isSelected: boolean;
|
|
}) {
|
|
return (
|
|
<div
|
|
className={`
|
|
flex cursor-pointer flex-col gap-2 rounded-lg border-2 p-4 transition-colors
|
|
${isSelected ? "border-[#236f6b] bg-[#e8f7f6]/40" : "border-default-200 bg-background hover:border-default-400"}
|
|
`}
|
|
>
|
|
<div className="flex items-center justify-between gap-2">
|
|
<p className="text-sm font-semibold text-foreground">{address.fullName}</p>
|
|
<div className="flex items-center gap-1.5">
|
|
{address.isDefault && (
|
|
<Chip size="sm" variant="soft">
|
|
Default
|
|
</Chip>
|
|
)}
|
|
{address.isValidated ? (
|
|
<Chip size="sm" variant="soft" color="success">
|
|
Verified
|
|
</Chip>
|
|
) : (
|
|
<Chip size="sm" variant="soft" color="warning">
|
|
Not verified
|
|
</Chip>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="text-sm text-default-600">
|
|
<p>{address.addressLine1}</p>
|
|
{address.additionalInformation && <p>{address.additionalInformation}</p>}
|
|
<p>{address.city}</p>
|
|
<p>{address.postalCode}</p>
|
|
</div>
|
|
|
|
{address.phone && (
|
|
<p className="text-xs text-default-400">{address.phone}</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function AddressSelectorSkeleton() {
|
|
return (
|
|
<div className="grid grid-cols-1 gap-3 md:grid-cols-2">
|
|
{Array.from({ length: 2 }).map((_, i) => (
|
|
<div key={i} className="flex flex-col gap-3 rounded-lg border-2 border-default-200 p-4">
|
|
<div className="flex items-center justify-between">
|
|
<Skeleton className="h-4 w-28 rounded-md" />
|
|
<Skeleton className="h-5 w-16 rounded-full" />
|
|
</div>
|
|
<div className="flex flex-col gap-1.5">
|
|
<Skeleton className="h-3 w-40 rounded-md" />
|
|
<Skeleton className="h-3 w-32 rounded-md" />
|
|
<Skeleton className="h-3 w-20 rounded-md" />
|
|
</div>
|
|
<Skeleton className="h-3 w-24 rounded-md" />
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|