style fix

main
Stepan Pilipenko 1 month ago
parent f83a5bf1e3
commit 012cfa0a06

@ -18,6 +18,16 @@ async def shop(request):
except Exception as error:
return JsonResponse({"error": format(error)}, status=500)
initialUser = {
"id": -1,
"nickname": '',
"email": '',
"token": '',
"token_expiry_date": '',
"money": '',
"histories_id": []
}
@csrf_exempt
async def user(request):
try:
@ -37,9 +47,11 @@ async def user(request):
elif "unregister" in body.keys():
token = format_token(request.headers.get("Authorization"))
api.unregister(token)
return JsonResponse({"success": [initialUser]}, status=200)
elif "logout" in body.keys():
token = format_token(request.headers.get("Authorization"))
api.logout(token)
return JsonResponse({"success": [initialUser]}, status=200)
elif "add_money" in body.keys():
token = format_token(request.headers.get("Authorization"))
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 }}>
<Card elevation={3}>
<CardHeader
avatar={
<Avatar sx={{ bgcolor: 'info.main' }} aria-label="recipe">
{name[0]}
</Avatar>
title={
<Typography
variant="h6" // или 'h5', 'subtitle1' — под ваш вкус
sx={{
fontWeight: 'normal',
textAlign: 'left', // хотя по умолчанию и так слева
fontSize: '1.25rem', // можно уточнить размер, если нужно
}}
>
{name}
</Typography>
}
title={name}
subheader={'2025 год'}
/>
<Link to={`/product/${id}`}>
<CardMedia
component="img"
height="194"
onError={(e: SyntheticEvent<HTMLImageElement>) => {
onError={(e: React.SyntheticEvent<HTMLImageElement>) => {
e.currentTarget.src =
'https://react-learning.ru/image-compressed/default-image.jpg'
}}
@ -44,13 +49,13 @@ export const ProductCard = ({ productId }: ProductCardProps) => {
/>
</Link>
<CardContent>
<Typography noWrap={true} variant="body2" color="text.secondary">
<Typography noWrap variant="body2" color="text.secondary">
{description}
</Typography>
<Typography noWrap={true} variant="subtitle1" color="success.main">
<Typography noWrap variant="subtitle1" color="success.main">
{`Цена: ${cost}`}
</Typography>
<Typography noWrap={true} variant="subtitle1" color="text.primary">
<Typography noWrap variant="subtitle1" color="text.primary">
{`Доступно: ${count} шт.`}
</Typography>
</CardContent>

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save