155 lines
5.6 KiB
TypeScript
155 lines
5.6 KiB
TypeScript
'use client';
|
||
|
||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||
import { ChevronRight, Home, MapPin } from 'lucide-react';
|
||
import { AddressFormDialog } from '@/components/addresses/address-form-dialog';
|
||
import { ActionTile, SectionTitle } from '@/components/id/action-tile';
|
||
import { useAuth } from '@/components/id/auth-provider';
|
||
import { useToast } from '@/components/id/toast-provider';
|
||
import {
|
||
ADDRESS_LABELS,
|
||
formatAddressLine,
|
||
getOtherAddresses,
|
||
getPrimaryAddress,
|
||
type AddressLabel
|
||
} from '@/lib/address-catalog';
|
||
import { fetchUserAddresses, getApiErrorMessage, UserAddress } from '@/lib/api';
|
||
|
||
function AddressRow({ title, text, onClick }: { title: string; text: string; onClick: () => void }) {
|
||
return (
|
||
<button
|
||
type="button"
|
||
onClick={onClick}
|
||
className="flex w-full items-center gap-4 border-b border-[#eceef4] px-4 py-4 text-left transition last:border-b-0 hover:bg-[#fafbfd]"
|
||
>
|
||
<div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-white">
|
||
<MapPin className="h-5 w-5" />
|
||
</div>
|
||
<div className="min-w-0 flex-1">
|
||
<div className="font-medium">{title}</div>
|
||
<div className="truncate text-sm text-[#667085]">{text}</div>
|
||
</div>
|
||
<ChevronRight className="h-5 w-5 shrink-0 text-[#a8adbc]" />
|
||
</button>
|
||
);
|
||
}
|
||
|
||
export function AddressQuickSection({
|
||
layout,
|
||
isPinLocked = false,
|
||
isReady = true
|
||
}: {
|
||
layout: 'home' | 'data';
|
||
isPinLocked?: boolean;
|
||
isReady?: boolean;
|
||
}) {
|
||
const { user, token } = useAuth();
|
||
const { showToast } = useToast();
|
||
const [addresses, setAddresses] = useState<UserAddress[]>([]);
|
||
const [formOpen, setFormOpen] = useState(false);
|
||
const [activeLabel, setActiveLabel] = useState<AddressLabel>('HOME');
|
||
const [editingAddress, setEditingAddress] = useState<UserAddress | null>(null);
|
||
|
||
const loadAddresses = useCallback(async () => {
|
||
if (!user || !token || isPinLocked || !isReady) return;
|
||
try {
|
||
const response = await fetchUserAddresses(user.id, token);
|
||
setAddresses(response.addresses ?? []);
|
||
} catch (error) {
|
||
const message = getApiErrorMessage(error, 'Не удалось загрузить адреса');
|
||
if (message) showToast(message);
|
||
}
|
||
}, [isPinLocked, isReady, showToast, token, user]);
|
||
|
||
useEffect(() => {
|
||
if (isReady && user && !isPinLocked) void loadAddresses();
|
||
}, [isPinLocked, isReady, loadAddresses, user]);
|
||
|
||
const homeAddress = useMemo(() => getPrimaryAddress(addresses, 'HOME') as UserAddress | null, [addresses]);
|
||
const workAddress = useMemo(() => getPrimaryAddress(addresses, 'WORK') as UserAddress | null, [addresses]);
|
||
const otherAddresses = useMemo(() => getOtherAddresses(addresses), [addresses]);
|
||
|
||
function openAddress(label: AddressLabel, existing: UserAddress | null = null) {
|
||
setActiveLabel(label);
|
||
if (label === 'HOME') setEditingAddress(existing ?? homeAddress);
|
||
else if (label === 'WORK') setEditingAddress(existing ?? workAddress);
|
||
else setEditingAddress(existing);
|
||
setFormOpen(true);
|
||
}
|
||
|
||
const tiles = (
|
||
<div className={layout === 'home' ? 'grid grid-cols-3 gap-4' : 'mt-4 grid grid-cols-3 gap-3'}>
|
||
<ActionTile
|
||
icon={Home}
|
||
label={ADDRESS_LABELS.HOME.title}
|
||
description={homeAddress ? formatAddressLine(homeAddress) : layout === 'data' ? 'Не указан' : undefined}
|
||
className={layout === 'home' ? 'min-h-[76px]' : 'min-h-[76px]'}
|
||
onClick={() => openAddress('HOME')}
|
||
/>
|
||
<ActionTile
|
||
icon={MapPin}
|
||
label={ADDRESS_LABELS.WORK.title}
|
||
description={workAddress ? formatAddressLine(workAddress) : layout === 'data' ? 'Не указан' : undefined}
|
||
className="min-h-[76px]"
|
||
onClick={() => openAddress('WORK')}
|
||
/>
|
||
<ActionTile
|
||
icon={MapPin}
|
||
label={ADDRESS_LABELS.OTHER.shortTitle}
|
||
description={
|
||
otherAddresses.length > 0
|
||
? `${otherAddresses.length} ${otherAddresses.length === 1 ? 'адрес' : otherAddresses.length < 5 ? 'адреса' : 'адресов'}`
|
||
: layout === 'data'
|
||
? 'Добавить'
|
||
: undefined
|
||
}
|
||
className="min-h-[76px]"
|
||
onClick={() => openAddress('OTHER')}
|
||
/>
|
||
</div>
|
||
);
|
||
|
||
return (
|
||
<>
|
||
{layout === 'home' ? (
|
||
<>
|
||
<SectionTitle>Адреса</SectionTitle>
|
||
{tiles}
|
||
</>
|
||
) : (
|
||
<section className="mt-10">
|
||
<h2 className="text-2xl font-medium">Адреса</h2>
|
||
<p className="mt-1 text-sm text-[#667085]">Укажите адреса на карте или введите их вручную.</p>
|
||
{tiles}
|
||
<div className="mt-4 overflow-hidden rounded-[24px] bg-[#f4f5f8]">
|
||
{addresses.length === 0 ? (
|
||
<p className="px-4 py-6 text-center text-sm text-[#667085]">Адреса пока не добавлены</p>
|
||
) : (
|
||
addresses.map((address) => (
|
||
<AddressRow
|
||
key={address.id}
|
||
title={ADDRESS_LABELS[address.label as AddressLabel]?.title ?? address.label}
|
||
text={formatAddressLine(address)}
|
||
onClick={() => openAddress(address.label as AddressLabel, address)}
|
||
/>
|
||
))
|
||
)}
|
||
</div>
|
||
</section>
|
||
)}
|
||
|
||
{user ? (
|
||
<AddressFormDialog
|
||
open={formOpen}
|
||
onOpenChange={setFormOpen}
|
||
label={activeLabel}
|
||
userId={user.id}
|
||
token={token}
|
||
existing={editingAddress}
|
||
onSaved={loadAddresses}
|
||
/>
|
||
) : null}
|
||
</>
|
||
);
|
||
}
|