mirror of
https://github.com/ulitsaRaskolnikova/is-coursework.git
synced 2026-09-14 14:05:36 +05:00
fix(front): buttons
This commit is contained in:
parent
7183f6e497
commit
4e9713910c
@ -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<string | null>(null);
|
||||
const [renewedFqdns, setRenewedFqdns] = useState<Set<string>>(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 (
|
||||
<Grid
|
||||
templateColumns={'50% 20% auto'}
|
||||
@ -19,32 +40,61 @@ const DomainList = (props: Props) => {
|
||||
py={2.5}
|
||||
borderRadius={'sm'}
|
||||
>
|
||||
{props.domains.map((domain) => (
|
||||
<React.Fragment key={domain.id}>
|
||||
<GridItem key={domain.id}>
|
||||
<Text>{domain.fqdn}</Text>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
{domain.expiresAt ? (
|
||||
<Text>
|
||||
до <DateText as={'span'}>{domain.expiresAt}</DateText>
|
||||
</Text>
|
||||
) : (
|
||||
<Text color="fg.muted">—</Text>
|
||||
)}
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<HStack justifyContent={'flex-end'}>
|
||||
<Button size={'sm'} colorPalette={'secondary'}>
|
||||
продлить
|
||||
</Button>
|
||||
<Button size={'sm'} colorPalette={'secondary'}>
|
||||
DNS <ArrowRight />
|
||||
</Button>
|
||||
</HStack>
|
||||
</GridItem>
|
||||
</React.Fragment>
|
||||
))}
|
||||
{props.domains.map((domain) => {
|
||||
const fqdn = domain.fqdn ?? '';
|
||||
const isRenewing = renewingFqdn === fqdn;
|
||||
const wasRenewed = renewedFqdns.has(fqdn);
|
||||
|
||||
return (
|
||||
<React.Fragment key={domain.id ?? fqdn}>
|
||||
<GridItem>
|
||||
<Text>{fqdn}</Text>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
{domain.expiresAt ? (
|
||||
<Text>
|
||||
до <DateText as={'span'}>{domain.expiresAt}</DateText>
|
||||
</Text>
|
||||
) : (
|
||||
<Text color="fg.muted">—</Text>
|
||||
)}
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<HStack justifyContent={'flex-end'}>
|
||||
{wasRenewed ? (
|
||||
<Button size={'sm'} colorPalette={'green'} variant={'solid'} disabled>
|
||||
Продлён
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
size={'sm'}
|
||||
colorPalette={'secondary'}
|
||||
onClick={() => handleRenew(fqdn, 'MONTH')}
|
||||
loading={isRenewing}
|
||||
disabled={renewingFqdn !== null && !isRenewing}
|
||||
>
|
||||
+1 мес
|
||||
</Button>
|
||||
<Button
|
||||
size={'sm'}
|
||||
colorPalette={'secondary'}
|
||||
onClick={() => handleRenew(fqdn, 'YEAR')}
|
||||
loading={isRenewing}
|
||||
disabled={renewingFqdn !== null && !isRenewing}
|
||||
>
|
||||
+1 год
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<Button size={'sm'} colorPalette={'secondary'}>
|
||||
DNS <ArrowRight />
|
||||
</Button>
|
||||
</HStack>
|
||||
</GridItem>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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<DomainSearchResult[]>([]);
|
||||
const [isCartLoading, setIsCartLoading] = useState(false);
|
||||
const [isSearchLoading, setIsSearchLoading] = useState(false);
|
||||
const [addingFqdn, setAddingFqdn] = useState<string | null>(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<CartResponse>('/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}
|
||||
>
|
||||
Купить на месяц
|
||||
</Button>
|
||||
@ -169,7 +166,7 @@ const CartPage = () => {
|
||||
size={'sm'}
|
||||
colorPalette={'secondary'}
|
||||
onClick={() => handleCheckout('YEAR')}
|
||||
loading={isCartLoading}
|
||||
loading={checkoutLoading}
|
||||
>
|
||||
Купить на год
|
||||
</Button>
|
||||
@ -205,26 +202,38 @@ const CartPage = () => {
|
||||
{searchResults.length > 0 && (
|
||||
<Stack bg={'accent.muted'} p={5} borderRadius={'md'} gap={2}>
|
||||
<Text fontWeight={'bold'}>Результаты поиска</Text>
|
||||
{searchResults.map((result) => (
|
||||
<HStack key={result.fqdn} justifyContent={'space-between'}>
|
||||
<Text>{result.fqdn}</Text>
|
||||
<HStack>
|
||||
<Text>{result.monthlyPrice}₽ / месяц</Text>
|
||||
{result.free ? (
|
||||
<Button
|
||||
size={'sm'}
|
||||
colorPalette={'secondary'}
|
||||
onClick={() => handleAddToCart(result.fqdn)}
|
||||
loading={isCartLoading}
|
||||
>
|
||||
В корзину
|
||||
</Button>
|
||||
) : (
|
||||
<Text color={'fg.muted'}>Занят</Text>
|
||||
)}
|
||||
{searchResults.map((result) => {
|
||||
const inCart = cartSet.has(result.fqdn);
|
||||
const isAdding = addingFqdn === result.fqdn;
|
||||
|
||||
return (
|
||||
<HStack key={result.fqdn} justifyContent={'space-between'}>
|
||||
<Text>{result.fqdn}</Text>
|
||||
<HStack>
|
||||
<Text>{result.monthlyPrice}₽ / месяц</Text>
|
||||
{result.free ? (
|
||||
inCart ? (
|
||||
<Button size={'sm'} colorPalette={'green'} variant={'solid'} disabled>
|
||||
В корзине
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size={'sm'}
|
||||
colorPalette={'secondary'}
|
||||
onClick={() => handleAddToCart(result.fqdn)}
|
||||
loading={isAdding}
|
||||
disabled={addingFqdn !== null && !isAdding}
|
||||
>
|
||||
В корзину
|
||||
</Button>
|
||||
)
|
||||
) : (
|
||||
<Text color={'fg.muted'}>Занят</Text>
|
||||
)}
|
||||
</HStack>
|
||||
</HStack>
|
||||
</HStack>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@ -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<DomainResponse[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
|
||||
const loadDomains = async () => {
|
||||
try {
|
||||
const { data } = await AXIOS_INSTANCE.get<UserDomainDetailed[]>('/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<UserDomainDetailed[]>('/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 (
|
||||
<Stack gap={5}>
|
||||
<HStack justifyContent={'space-between'}>
|
||||
@ -56,7 +46,7 @@ const DomainsPage = () => {
|
||||
</Button>
|
||||
</HStack>
|
||||
</HStack>
|
||||
<DomainList domains={domains} />
|
||||
<DomainList domains={domains} onRenewed={loadDomains} />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user