style fix

main
Stepan Pilipenko 1 month ago
parent f83a5bf1e3
commit 012cfa0a06

@ -18,6 +18,16 @@ async def shop(request):
except Exception as error: except Exception as error:
return JsonResponse({"error": format(error)}, status=500) return JsonResponse({"error": format(error)}, status=500)
initialUser = {
"id": -1,
"nickname": '',
"email": '',
"token": '',
"token_expiry_date": '',
"money": '',
"histories_id": []
}
@csrf_exempt @csrf_exempt
async def user(request): async def user(request):
try: try:
@ -37,9 +47,11 @@ async def user(request):
elif "unregister" in body.keys(): elif "unregister" in body.keys():
token = format_token(request.headers.get("Authorization")) token = format_token(request.headers.get("Authorization"))
api.unregister(token) api.unregister(token)
return JsonResponse({"success": [initialUser]}, status=200)
elif "logout" in body.keys(): elif "logout" in body.keys():
token = format_token(request.headers.get("Authorization")) token = format_token(request.headers.get("Authorization"))
api.logout(token) api.logout(token)
return JsonResponse({"success": [initialUser]}, status=200)
elif "add_money" in body.keys(): elif "add_money" in body.keys():
token = format_token(request.headers.get("Authorization")) token = format_token(request.headers.get("Authorization"))
api.add_money(token, body["add_money"]["money"]) api.add_money(token, body["add_money"]["money"])

@ -23,19 +23,24 @@ export const ProductCard = ({ productId }: ProductCardProps) => {
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 4 }}> <Grid size={{ xs: 12, sm: 6, md: 4, lg: 4 }}>
<Card elevation={3}> <Card elevation={3}>
<CardHeader <CardHeader
avatar={ title={
<Avatar sx={{ bgcolor: 'info.main' }} aria-label="recipe"> <Typography
{name[0]} variant="h6" // или 'h5', 'subtitle1' — под ваш вкус
</Avatar> sx={{
fontWeight: 'normal',
textAlign: 'left', // хотя по умолчанию и так слева
fontSize: '1.25rem', // можно уточнить размер, если нужно
}}
>
{name}
</Typography>
} }
title={name}
subheader={'2025 год'}
/> />
<Link to={`/product/${id}`}> <Link to={`/product/${id}`}>
<CardMedia <CardMedia
component="img" component="img"
height="194" height="194"
onError={(e: SyntheticEvent<HTMLImageElement>) => { onError={(e: React.SyntheticEvent<HTMLImageElement>) => {
e.currentTarget.src = e.currentTarget.src =
'https://react-learning.ru/image-compressed/default-image.jpg' 'https://react-learning.ru/image-compressed/default-image.jpg'
}} }}
@ -44,13 +49,13 @@ export const ProductCard = ({ productId }: ProductCardProps) => {
/> />
</Link> </Link>
<CardContent> <CardContent>
<Typography noWrap={true} variant="body2" color="text.secondary"> <Typography noWrap variant="body2" color="text.secondary">
{description} {description}
</Typography> </Typography>
<Typography noWrap={true} variant="subtitle1" color="success.main"> <Typography noWrap variant="subtitle1" color="success.main">
{`Цена: ${cost}`} {`Цена: ${cost}`}
</Typography> </Typography>
<Typography noWrap={true} variant="subtitle1" color="text.primary"> <Typography noWrap variant="subtitle1" color="text.primary">
{`Доступно: ${count} шт.`} {`Доступно: ${count} шт.`}
</Typography> </Typography>
</CardContent> </CardContent>

@ -42,6 +42,7 @@ export const ProductDetail = withProtection(({ productId }: ProductDetailProps)
toast.error(error || 'Не известная ошибка при аутентификации пользователя', { toast.error(error || 'Не известная ошибка при аутентификации пользователя', {
toastId: 'error-toast', toastId: 'error-toast',
}) })
dispatch(basket.action.clearError())
} }
return ( return (
@ -58,13 +59,18 @@ export const ProductDetail = withProtection(({ productId }: ProductDetailProps)
}} }}
> >
<CardHeader <CardHeader
avatar={ title={
<Avatar sx={{ bgcolor: 'info.main' }} aria-label="recipe"> <Typography
{product?.name[0]} variant="h6" // или 'h5', 'subtitle1' — под ваш вкус
</Avatar> sx={{
fontWeight: 'normal',
textAlign: 'left', // хотя по умолчанию и так слева
fontSize: '2rem', // можно уточнить размер, если нужно
}}
>
{product?.name}
</Typography>
} }
title={product?.name}
subheader={'2025 год'}
/> />
<CardMedia <CardMedia
component="img" component="img"

@ -4,12 +4,16 @@ import { user, useAppDispatch, useAppSelector } from '../storage'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { Box } from '@mui/material' import { Box } from '@mui/material'
import { Spinner } from '../components' import { Spinner } from '../components'
import type { UnknownAction } from '@reduxjs/toolkit'
type StorageType = { type StorageType = {
selector: { selector: {
loading: any loading: any
error: any error: any
} }
action: {
clearError: () => UnknownAction
}
} }
export const withResponse = <P extends object, T extends StorageType>( export const withResponse = <P extends object, T extends StorageType>(
@ -34,6 +38,7 @@ export const withResponse = <P extends object, T extends StorageType>(
toast.error(error || 'Не известная ошибка при аутентификации пользователя', { toast.error(error || 'Не известная ошибка при аутентификации пользователя', {
toastId: 'error-toast', toastId: 'error-toast',
}) })
dispatch(storage.action.clearError())
} }
if (loading) { if (loading) {

@ -1,13 +1,22 @@
import { Fragment, useCallback, useMemo, useState } from 'react' import { Fragment, useCallback, useEffect, useMemo, useState } from 'react'
import { import {
Avatar,
Box, Box,
Button, Button,
Divider, Divider,
IconButton, IconButton,
List, List,
ListItem, ListItem,
ListItemIcon,
ListItemText, ListItemText,
Paper,
Stack, Stack,
styled,
Table,
TableBody,
TableCell,
TableContainer,
TableRow,
Typography, Typography,
} from '@mui/material' } from '@mui/material'
import { AddCircleOutline, RemoveCircleOutline } from '@mui/icons-material' import { AddCircleOutline, RemoveCircleOutline } from '@mui/icons-material'
@ -26,6 +35,13 @@ const getText = (str?: string): string => {
return (str?.length || 0) <= MAX_LEN_STR ? str || '' : str?.substring(0, MAX_LEN_STR) + '...' return (str?.length || 0) <= MAX_LEN_STR ? str || '' : str?.substring(0, MAX_LEN_STR) + '...'
} }
const StyledTableRow = styled(TableRow)(({ theme }) => ({
cursor: 'pointer',
'&:hover': {
backgroundColor: theme.palette.action.hover,
},
}))
type LasFetchType = 'addProduct' | 'deleteProduct' | 'buyProducts' | 'none' type LasFetchType = 'addProduct' | 'deleteProduct' | 'buyProducts' | 'none'
export const BasketPage = withProtection(() => { export const BasketPage = withProtection(() => {
@ -52,6 +68,10 @@ export const BasketPage = withProtection(() => {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
useEffect(() => {
dispatch(basket.fetch.basket())
}, [dispatch])
const handleUpdateQuantity = useCallback( const handleUpdateQuantity = useCallback(
(productId: number, quantity: number) => { (productId: number, quantity: number) => {
if (quantity > 0) { if (quantity > 0) {
@ -86,6 +106,7 @@ export const BasketPage = withProtection(() => {
toast.error(error || 'Не известная ошибка при аутентификации пользователя', { toast.error(error || 'Не известная ошибка при аутентификации пользователя', {
toastId: 'error-toast', toastId: 'error-toast',
}) })
dispatch(basket.action.clearError())
} }
if (loading && lasFetch === 'buyProducts') { if (loading && lasFetch === 'buyProducts') {
@ -104,65 +125,83 @@ export const BasketPage = withProtection(() => {
Продуктовая корзина Продуктовая корзина
</Typography> </Typography>
</Stack> </Stack>
<List component="nav" aria-label="cart items"> <TableContainer component={Paper}>
<Fragment> <Table>
{sortedProducts.length ? ( <TableBody>
<Stack direction="row" alignItems="center" spacing={2} sx={{ pb: 2 }}> {products.map(product => {
<Typography variant="h6" gutterBottom> const prod = getProduct(product.id)
{`Заказ №: ${1}`} if (!prod) return null
</Typography>
</Stack> return (
) : ( <StyledTableRow
<></> key={product.id}
)} onClick={() => handleProduct(product.id)}
{sortedProducts?.map(product => ( >
<ListItem key={product.id}> {/* Квадратное изображение */}
<ListItemText <TableCell sx={{ width: 64, padding: '8px' }}>
onClick={() => handleProduct(product.id)} <Avatar
primary={`Название: ${getText(getProduct(product.id)?.name)}`} variant="square"
/> src={prod.picture_url || ''}
<ListItemText alt={prod.name || 'Товар'}
onClick={() => handleProduct(product.id)} sx={{
primary={`Цена: ${Number(product.cost) * product.user_count}`} width: 48,
/> height: 48,
<ListItemText objectFit: 'cover',
onClick={() => handleProduct(product.id)} }}
primary={`На складе: ${getProduct(product.id)?.count} `} />
/> </TableCell>
<ListItemText
onClick={() => handleProduct(product.id)} {/* Название */}
primary={`${product.user_count} шт.`} <TableCell>{getText(prod.name)}</TableCell>
/>
<IconButton {/* Цена */}
edge="start" <TableCell>{`Цена: ${Number(product.cost) * product.user_count}`}</TableCell>
aria-label="add"
onClick={() => handleUpdateQuantity(product.id, 1)} {/* На складе */}
> <TableCell>{`Доступно: ${prod.count}`}</TableCell>
<AddCircleOutline />
</IconButton> {/* Количество у пользователя */}
<IconButton <TableCell>{`${product.user_count} шт.`}</TableCell>
edge="start"
aria-label="remove" {/* Кнопки управления */}
onClick={() => handleUpdateQuantity(product.id, -1)} <TableCell padding="none" sx={{ width: 'auto' }}>
> <IconButton
<RemoveCircleOutline /> onClick={e => {
</IconButton> e.stopPropagation() // предотвращает срабатывание onClick строки
</ListItem> handleUpdateQuantity(product.id, 1)
))} }}
<Box sx={{ pb: 3 }}> size="small"
<Divider /> >
</Box> <AddCircleOutline />
{sortedProducts.length ? ( </IconButton>
<Button color="success" variant="contained" onClick={handleBuy}> <IconButton
{'КУПИТЬ'} onClick={e => {
</Button> e.stopPropagation()
) : ( handleUpdateQuantity(product.id, -1)
<Typography variant="button" sx={{ fontSize: 16, pt: 2, pb: 2 }}> }}
Корзина пуста size="small"
</Typography> >
)} <RemoveCircleOutline />
</Fragment> </IconButton>
</List> </TableCell>
</StyledTableRow>
)
})}
</TableBody>
</Table>
</TableContainer>
<Box sx={{ pb: 3 }}>
<Divider />
</Box>
{sortedProducts.length ? (
<Button color="success" variant="contained" onClick={handleBuy}>
{`КУПИТЬ ЗА ${products.reduce((total, product) => total + Number(product.cost) * product.user_count, 0)}`}
</Button>
) : (
<Typography variant="button" sx={{ fontSize: 16, pt: 2, pb: 2 }}>
Корзина пуста
</Typography>
)}
</div> </div>
) )
}) })

@ -30,13 +30,14 @@ export const LoginPage = (): JSX.Element => {
useEffect(() => { useEffect(() => {
if (token && !error) { if (token && !error) {
toast.success('Вы успешно вошли в систему!', { toastId: 'success-toast' }) toast.success('Вы успешно вошли в систему!', { toastId: 'success-toast' })
navigate('/') navigate('/user')
} }
if (error) { if (error) {
console.log({ error }) console.log({ error })
toast.error(error, { toastId: 'error-toast' }) toast.error(error, { toastId: 'error-toast' })
dispatch(user.action.clearError())
} }
}, [token, error, navigate]) }, [token, error, navigate, dispatch])
// инициализируем react-hook-form // инициализируем react-hook-form
const { const {

@ -32,15 +32,13 @@ export const RegisterPage = (): JSX.Element => {
useEffect(() => { useEffect(() => {
if (token && !error) { if (token && !error) {
toast.success('Вы успешно зарегистрировались!', { toastId: 'success-toast' }) toast.success('Вы успешно зарегистрировались!', { toastId: 'success-toast' })
// переходит туда откуда выпали на логин navigate('/user')
if (state?.from) {
navigate(state?.from)
}
} }
if (error) { if (error) {
toast.error(error, { toastId: 'error-toast' }) toast.error(error, { toastId: 'error-toast' })
dispatch(user.action.clearError())
} }
}, [token, error, navigate, state]) }, [token, error, navigate, state, dispatch])
// инициализируем react-hook-form // инициализируем react-hook-form
const { const {

@ -18,11 +18,14 @@ const UserWithProtection = withProtection(() => {
const handleLogout = useCallback(() => { const handleLogout = useCallback(() => {
navigate('/logout') navigate('/logout')
}, []) }, [navigate])
const handleAddMoney = useCallback((value: number) => { const handleAddMoney = useCallback(
dispatch(user.fetch.addMoney({ money: String(value) })) (value: number) => {
}, []) dispatch(user.fetch.addMoney({ money: String(value) }))
},
[dispatch]
)
const handleAddMoneyOpen = useCallback(() => { const handleAddMoneyOpen = useCallback(() => {
setOpenAddMoney(true) setOpenAddMoney(true)
@ -33,8 +36,10 @@ const UserWithProtection = withProtection(() => {
}, []) }, [])
const handleDeleteAccout = useCallback(() => { const handleDeleteAccout = useCallback(() => {
navigate('/')
dispatch(user.fetch.unregister()) dispatch(user.fetch.unregister())
}, []) dispatch(user.action.clearUser())
}, [dispatch, navigate])
const handleDeleteAccoutOpen = useCallback(() => { const handleDeleteAccoutOpen = useCallback(() => {
setOpenDeleteAccout(true) setOpenDeleteAccout(true)

@ -24,7 +24,11 @@ const initialState: BasketState = {
const basketSlice = createSlice({ const basketSlice = createSlice({
name: 'basket', name: 'basket',
initialState, initialState,
reducers: {}, reducers: {
clearError: state => {
state.error = ''
},
},
extraReducers: builder => { extraReducers: builder => {
builder builder
.addCase(fetchBasket.fulfilled, (state, action) => { .addCase(fetchBasket.fulfilled, (state, action) => {

@ -29,6 +29,9 @@ export const shopSlice = createSlice({
product.count += action.payload.count product.count += action.payload.count
} }
}, },
clearError: state => {
state.error = ''
},
}, },
extraReducers: builder => { extraReducers: builder => {
builder builder

@ -16,32 +16,39 @@ type UserState = {
error?: string error?: string
} }
const defaultUser: UserType = { const defaultUser = (): UserType => {
id: -1, return {
nickname: '', id: -1,
email: '', nickname: '',
token: token.load(), email: '',
token_expiry_date: '', token: token.load(),
money: '', token_expiry_date: '',
histories_id: [], money: '',
histories_id: [],
}
} }
const initialState: UserState = { const initialState = (): UserState => {
user: defaultUser, return {
loading: false, user: defaultUser(),
error: undefined, loading: false,
error: undefined,
}
} }
const userSlice = createSlice({ const userSlice = createSlice({
name: 'user', name: 'user',
initialState, initialState: initialState(),
reducers: { reducers: {
setUser: (state, action: PayloadAction<UserType>) => { setUser: (state, action: PayloadAction<UserType>) => {
state.user = action.payload state.user = action.payload
}, },
clearUser() { clearUser() {
token.save('') token.save('')
return initialState return initialState()
},
clearError: state => {
state.error = ''
}, },
}, },
extraReducers: builder => { extraReducers: builder => {

Loading…
Cancel
Save