From 4e9713910cec2cc87a06ddf6f3ff61163d7ee80d Mon Sep 17 00:00:00 2001 From: ulitsaRaskolnikova Date: Mon, 9 Feb 2026 17:35:38 +0300 Subject: [PATCH] fix(front): buttons --- .../src/components/dashboard/DomainList.tsx | 106 +++++++++++++----- domains-frontend/src/pages/app/CartPage.tsx | 71 +++++++----- .../src/pages/app/DomainsPage.tsx | 50 ++++----- 3 files changed, 138 insertions(+), 89 deletions(-) diff --git a/domains-frontend/src/components/dashboard/DomainList.tsx b/domains-frontend/src/components/dashboard/DomainList.tsx index 12f8ae1..925856f 100644 --- a/domains-frontend/src/components/dashboard/DomainList.tsx +++ b/domains-frontend/src/components/dashboard/DomainList.tsx @@ -1,14 +1,35 @@ -import React from 'react'; +import React, { useState } from 'react'; import type { DomainResponse } from '../../api/models'; -import { Button, Grid, GridItem, HStack, Stack, Text } from '@chakra-ui/react'; +import { Button, Grid, GridItem, HStack, Text } from '@chakra-ui/react'; import DateText from '../DateText'; import { ArrowRight } from 'lucide-react'; +import { ORDER_AXIOS_INSTANCE } from '~/api/apiClientOrders'; type Props = { domains: DomainResponse[]; + onRenewed?: () => void; }; const DomainList = (props: Props) => { + const [renewingFqdn, setRenewingFqdn] = useState(null); + const [renewedFqdns, setRenewedFqdns] = useState>(new Set()); + + const handleRenew = async (fqdn: string, period: 'MONTH' | 'YEAR') => { + setRenewingFqdn(fqdn); + try { + await ORDER_AXIOS_INSTANCE.post('/domains/renew', { + l3Domains: [fqdn], + period, + }); + setRenewedFqdns((prev) => new Set(prev).add(fqdn)); + props.onRenewed?.(); + } catch { + // ignore + } finally { + setRenewingFqdn(null); + } + }; + return ( { py={2.5} borderRadius={'sm'} > - {props.domains.map((domain) => ( - - - {domain.fqdn} - - - {domain.expiresAt ? ( - - до {domain.expiresAt} - - ) : ( - - )} - - - - - - - - - ))} + {props.domains.map((domain) => { + const fqdn = domain.fqdn ?? ''; + const isRenewing = renewingFqdn === fqdn; + const wasRenewed = renewedFqdns.has(fqdn); + + return ( + + + {fqdn} + + + {domain.expiresAt ? ( + + до {domain.expiresAt} + + ) : ( + + )} + + + + {wasRenewed ? ( + + ) : ( + <> + + + + )} + + + + + ); + })} ); }; diff --git a/domains-frontend/src/pages/app/CartPage.tsx b/domains-frontend/src/pages/app/CartPage.tsx index 04ba717..20e321e 100644 --- a/domains-frontend/src/pages/app/CartPage.tsx +++ b/domains-frontend/src/pages/app/CartPage.tsx @@ -1,5 +1,4 @@ import { - Box, Button, Field, Heading, @@ -37,15 +36,15 @@ const CartPage = () => { const [totalMonthly, setTotalMonthly] = useState(0); const [totalYearly, setTotalYearly] = useState(0); const [searchResults, setSearchResults] = useState([]); - const [isCartLoading, setIsCartLoading] = useState(false); const [isSearchLoading, setIsSearchLoading] = useState(false); + const [addingFqdn, setAddingFqdn] = useState(null); + const [checkoutLoading, setCheckoutLoading] = useState(false); const [error, setError] = useState(''); const cartCount = useMemo(() => cartDomains.length, [cartDomains]); + const cartSet = useMemo(() => new Set(cartDomains), [cartDomains]); const loadCart = useCallback(async () => { - setIsCartLoading(true); - setError(''); try { const { data } = await ORDER_AXIOS_INSTANCE.get('/cart/me'); setCartDomains(data.l3Domains ?? []); @@ -53,8 +52,6 @@ const CartPage = () => { setTotalYearly(data.totalYearlyPrice ?? 0); } catch { setError('Не удалось загрузить корзину.'); - } finally { - setIsCartLoading(false); } }, []); @@ -102,7 +99,7 @@ const CartPage = () => { const handleAddToCart = useCallback( async (fqdn: string) => { - setIsCartLoading(true); + setAddingFqdn(fqdn); setError(''); try { await ORDER_AXIOS_INSTANCE.post(`/cart/${encodeURIComponent(fqdn)}`); @@ -110,7 +107,7 @@ const CartPage = () => { } catch { setError('Не удалось добавить домен в корзину.'); } finally { - setIsCartLoading(false); + setAddingFqdn(null); } }, [loadCart] @@ -119,7 +116,7 @@ const CartPage = () => { const handleCheckout = useCallback( async (period: 'MONTH' | 'YEAR') => { if (cartDomains.length === 0) return; - setIsCartLoading(true); + setCheckoutLoading(true); setError(''); try { await ORDER_AXIOS_INSTANCE.post('/cart/checkout', { period }); @@ -129,7 +126,7 @@ const CartPage = () => { } catch { setError('Не удалось оформить покупку.'); } finally { - setIsCartLoading(false); + setCheckoutLoading(false); } }, [cartDomains] @@ -161,7 +158,7 @@ const CartPage = () => { size={'sm'} colorPalette={'secondary'} onClick={() => handleCheckout('MONTH')} - loading={isCartLoading} + loading={checkoutLoading} > Купить на месяц @@ -169,7 +166,7 @@ const CartPage = () => { size={'sm'} colorPalette={'secondary'} onClick={() => handleCheckout('YEAR')} - loading={isCartLoading} + loading={checkoutLoading} > Купить на год @@ -205,26 +202,38 @@ const CartPage = () => { {searchResults.length > 0 && ( Результаты поиска - {searchResults.map((result) => ( - - {result.fqdn} - - {result.monthlyPrice}₽ / месяц - {result.free ? ( - - ) : ( - Занят - )} + {searchResults.map((result) => { + const inCart = cartSet.has(result.fqdn); + const isAdding = addingFqdn === result.fqdn; + + return ( + + {result.fqdn} + + {result.monthlyPrice}₽ / месяц + {result.free ? ( + inCart ? ( + + ) : ( + + ) + ) : ( + Занят + )} + - - ))} + ); + })} )} diff --git a/domains-frontend/src/pages/app/DomainsPage.tsx b/domains-frontend/src/pages/app/DomainsPage.tsx index 160bff6..ff983d4 100644 --- a/domains-frontend/src/pages/app/DomainsPage.tsx +++ b/domains-frontend/src/pages/app/DomainsPage.tsx @@ -1,5 +1,5 @@ import { Button, Heading, HStack, Stack, Text } from '@chakra-ui/react'; -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import type { DomainResponse } from '~/api/models/domain-order'; import { AXIOS_INSTANCE } from '~/api/apiClientDomains'; import DomainList from '../../components/dashboard/DomainList'; @@ -15,36 +15,26 @@ interface UserDomainDetailed { const DomainsPage = () => { const [domains, setDomains] = useState([]); - useEffect(() => { - let isMounted = true; - - const loadDomains = async () => { - try { - const { data } = await AXIOS_INSTANCE.get('/userDomains/detailed'); - if (isMounted) { - const mapped: DomainResponse[] = (data ?? []).map((d) => ({ - id: d.id?.toString(), - fqdn: d.fqdn, - zoneName: d.zoneName, - activatedAt: d.activatedAt, - expiresAt: d.expiresAt, - })); - setDomains(mapped); - } - } catch { - if (isMounted) { - setDomains([]); - } - } - }; - - loadDomains(); - - return () => { - isMounted = false; - }; + const loadDomains = useCallback(async () => { + try { + const { data } = await AXIOS_INSTANCE.get('/userDomains/detailed'); + const mapped: DomainResponse[] = (data ?? []).map((d) => ({ + id: d.id?.toString(), + fqdn: d.fqdn, + zoneName: d.zoneName, + activatedAt: d.activatedAt, + expiresAt: d.expiresAt, + })); + setDomains(mapped); + } catch { + setDomains([]); + } }, []); + useEffect(() => { + loadDomains(); + }, [loadDomains]); + return ( @@ -56,7 +46,7 @@ const DomainsPage = () => { - + ); };