Files
IdP/apps/frontend/components/addresses/address-quick-section.tsx
2026-06-24 14:37:15 +03:00

155 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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) return;
try {
const response = await fetchUserAddresses(user.id, token);
setAddresses(response.addresses ?? []);
} catch (error) {
const message = getApiErrorMessage(error, 'Не удалось загрузить адреса');
if (message) showToast(message);
}
}, [isPinLocked, 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}
</>
);
}