base commit

This commit is contained in:
@kirill.komarov
2026-04-29 19:14:34 +05:00
parent c1773e5c57
commit bfc9661d22
25 changed files with 1885 additions and 3 deletions
+1
View File
@@ -0,0 +1 @@
export { CheckoutPage } from './ui/CheckoutPage'
@@ -0,0 +1,132 @@
import Alert from '@mui/material/Alert'
import Box from '@mui/material/Box'
import Button from '@mui/material/Button'
import FormControl from '@mui/material/FormControl'
import InputLabel from '@mui/material/InputLabel'
import MenuItem from '@mui/material/MenuItem'
import Select from '@mui/material/Select'
import Stack from '@mui/material/Stack'
import TextField from '@mui/material/TextField'
import Typography from '@mui/material/Typography'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useState } from 'react'
import { Link as RouterLink, useNavigate } from 'react-router-dom'
import { useUnit } from 'effector-react'
import { fetchMyCart } from '@/entities/cart/api/cart-api'
import { createOrder } from '@/entities/order/api/order-api'
import { fetchMyAddresses } from '@/entities/user/api/address-api'
import { formatPriceRub } from '@/shared/lib/format-price'
import { $user } from '@/shared/model/auth'
export function CheckoutPage() {
const user = useUnit($user)
const qc = useQueryClient()
const navigate = useNavigate()
const [addressId, setAddressId] = useState('')
const [comment, setComment] = useState('')
const cartQuery = useQuery({
queryKey: ['me', 'cart'],
queryFn: fetchMyCart,
enabled: Boolean(user),
})
const addressesQuery = useQuery({
queryKey: ['me', 'addresses'],
queryFn: fetchMyAddresses,
enabled: Boolean(user),
})
const createMut = useMutation({
mutationFn: () => createOrder({ addressId, comment: comment.trim() || null }),
onSuccess: async (res) => {
await qc.invalidateQueries({ queryKey: ['me', 'cart'] })
navigate(`/me/orders/${res.orderId}`, { replace: true })
},
})
if (!user) {
return (
<Alert severity="info">
Чтобы оформить заказ, нужно войти. Перейдите на страницу{' '}
<Typography component={RouterLink} to="/auth" sx={{ textDecoration: 'underline' }}>
Вход
</Typography>
.
</Alert>
)
}
const items = cartQuery.data?.items ?? []
const total = items.reduce((s, x) => s + x.product.priceCents * x.qty, 0)
const addresses = addressesQuery.data?.items ?? []
const defaultAddr = addresses.find((a) => a.isDefault)
return (
<Box>
<Typography variant="h4" gutterBottom>
Оформление заказа
</Typography>
{cartQuery.isSuccess && items.length === 0 && (
<Alert severity="info" sx={{ mb: 2 }}>
Корзина пуста. Вернитесь в{' '}
<Typography component={RouterLink} to="/" sx={{ textDecoration: 'underline' }}>
каталог
</Typography>
.
</Alert>
)}
<Stack spacing={2} sx={{ maxWidth: 720 }}>
<FormControl size="small" fullWidth>
<InputLabel id="addr-label">Адрес доставки</InputLabel>
<Select
labelId="addr-label"
label="Адрес доставки"
value={addressId || (defaultAddr?.id ?? '')}
onChange={(e) => setAddressId(String(e.target.value))}
>
{addresses.map((a) => (
<MenuItem key={a.id} value={a.id}>
{(a.label?.trim() ? `${a.label}: ` : '') + a.addressLine}
</MenuItem>
))}
</Select>
</FormControl>
{addresses.length === 0 && (
<Alert severity="warning">
У вас нет адресов доставки. Добавьте адрес в{' '}
<Typography component={RouterLink} to="/me/addresses" sx={{ textDecoration: 'underline' }}>
кабинете
</Typography>
.
</Alert>
)}
<TextField
label="Комментарий к заказу (необязательно)"
value={comment}
onChange={(e) => setComment(e.target.value)}
fullWidth
multiline
minRows={2}
/>
<Typography variant="h6">Итого: {formatPriceRub(total)}</Typography>
<Button
variant="contained"
disabled={items.length === 0 || addresses.length === 0 || createMut.isPending}
onClick={() => createMut.mutate()}
>
Создать заказ
</Button>
{createMut.isError && <Alert severity="error">{(createMut.error as Error).message}</Alert>}
</Stack>
</Box>
)
}