Files
the-pet-loft/apps/storefront/src/components/checkout/content/AddressSelector.tsx
ianshaloom 23efcab80c
Some checks failed
CI / Lint, Typecheck & Test (push) Failing after 2m11s
fix: resolve CI and workspace lint errors (admin + storefront)
- 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
2026-03-08 00:45:57 +03:00

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>
);
}