first commit

This commit is contained in:
lendry
2026-06-24 14:37:15 +03:00
commit 995adeedd4
188 changed files with 28810 additions and 0 deletions

View File

@@ -0,0 +1,154 @@
'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}
</>
);
}