base commit
This commit is contained in:
@@ -9,7 +9,6 @@ import DialogTitle from '@mui/material/DialogTitle'
|
||||
import Divider from '@mui/material/Divider'
|
||||
import Rating from '@mui/material/Rating'
|
||||
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 axios from 'axios'
|
||||
@@ -21,10 +20,11 @@ import {
|
||||
payOrderStub,
|
||||
postOrderMessage,
|
||||
} from '@/entities/order/api/order-api'
|
||||
import { postProductReview } from '@/entities/product/api/reviews-api'
|
||||
import { postProductReview, uploadReviewImage } from '@/entities/product/api/reviews-api'
|
||||
import { markOrderMessagesRead } from '@/entities/user/api/messages-api'
|
||||
import { formatPriceRub } from '@/shared/lib/format-price'
|
||||
import { orderStatusLabelRu } from '@/shared/lib/order-status-labels'
|
||||
import { RichTextMessageEditor } from '@/shared/ui/RichTextMessageEditor'
|
||||
|
||||
function reviewSubmitErrorMessage(err: unknown): string {
|
||||
if (axios.isAxiosError(err)) {
|
||||
@@ -59,6 +59,7 @@ export function OrderDetailPage() {
|
||||
const [reviewTarget, setReviewTarget] = useState<{ productId: string; title: string } | null>(null)
|
||||
const [reviewRating, setReviewRating] = useState<number>(5)
|
||||
const [reviewText, setReviewText] = useState('')
|
||||
const [reviewImageUrl, setReviewImageUrl] = useState<string | null>(null)
|
||||
|
||||
const orderQuery = useQuery({
|
||||
queryKey: ['me', 'orders', id],
|
||||
@@ -108,16 +109,23 @@ export function OrderDetailPage() {
|
||||
await postProductReview(reviewTarget.productId, {
|
||||
rating: reviewRating,
|
||||
text: t.length ? t : null,
|
||||
imageUrl: reviewImageUrl,
|
||||
})
|
||||
},
|
||||
onSuccess: async () => {
|
||||
setReviewTarget(null)
|
||||
setReviewRating(5)
|
||||
setReviewText('')
|
||||
setReviewImageUrl(null)
|
||||
await qc.invalidateQueries({ queryKey: ['me', 'orders', id, 'review-eligibility'] })
|
||||
},
|
||||
})
|
||||
|
||||
const uploadReviewImageMut = useMutation({
|
||||
mutationFn: (file: File) => uploadReviewImage(file),
|
||||
onSuccess: ({ url }) => setReviewImageUrl(url),
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!id || orderQuery.status !== 'success' || !order) return
|
||||
void (async () => {
|
||||
@@ -326,14 +334,9 @@ export function OrderDetailPage() {
|
||||
</Stack>
|
||||
|
||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2} sx={{ alignItems: { sm: 'flex-end' } }}>
|
||||
<TextField
|
||||
label="Сообщение"
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
fullWidth
|
||||
multiline
|
||||
minRows={2}
|
||||
/>
|
||||
<Box sx={{ flexGrow: 1, width: '100%' }}>
|
||||
<RichTextMessageEditor value={text} onChange={setText} placeholder="Сообщение" />
|
||||
</Box>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => msgMut.mutate()}
|
||||
@@ -348,7 +351,13 @@ export function OrderDetailPage() {
|
||||
|
||||
<Dialog
|
||||
open={Boolean(reviewTarget)}
|
||||
onClose={() => !reviewMut.isPending && setReviewTarget(null)}
|
||||
onClose={() => {
|
||||
if (reviewMut.isPending) return
|
||||
setReviewTarget(null)
|
||||
setReviewRating(5)
|
||||
setReviewText('')
|
||||
setReviewImageUrl(null)
|
||||
}}
|
||||
fullWidth
|
||||
maxWidth="sm"
|
||||
>
|
||||
@@ -363,15 +372,60 @@ export function OrderDetailPage() {
|
||||
if (v !== null) setReviewRating(v)
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
sx={{ mt: 2 }}
|
||||
label="Комментарий (необязательно)"
|
||||
value={reviewText}
|
||||
onChange={(e) => setReviewText(e.target.value)}
|
||||
fullWidth
|
||||
multiline
|
||||
minRows={3}
|
||||
/>
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<RichTextMessageEditor
|
||||
value={reviewText}
|
||||
onChange={setReviewText}
|
||||
placeholder="Комментарий (необязательно)"
|
||||
/>
|
||||
</Box>
|
||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={1.5} sx={{ mt: 2, alignItems: { sm: 'center' } }}>
|
||||
<Button component="label" variant="outlined" disabled={uploadReviewImageMut.isPending}>
|
||||
{reviewImageUrl ? 'Заменить фото' : 'Прикрепить фото'}
|
||||
<input
|
||||
hidden
|
||||
type="file"
|
||||
accept="image/png,image/jpeg,image/webp"
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0]
|
||||
if (!file) return
|
||||
uploadReviewImageMut.mutate(file)
|
||||
e.currentTarget.value = ''
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
{reviewImageUrl && (
|
||||
<Button
|
||||
color="error"
|
||||
variant="text"
|
||||
onClick={() => setReviewImageUrl(null)}
|
||||
disabled={reviewMut.isPending}
|
||||
>
|
||||
Удалить фото
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
{reviewImageUrl && (
|
||||
<Box
|
||||
component="img"
|
||||
src={reviewImageUrl}
|
||||
alt="Фото к отзыву"
|
||||
sx={{
|
||||
mt: 1,
|
||||
width: 120,
|
||||
height: 120,
|
||||
objectFit: 'cover',
|
||||
borderRadius: 1.5,
|
||||
border: 1,
|
||||
borderColor: 'divider',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{uploadReviewImageMut.isError && (
|
||||
<Alert severity="error" sx={{ mt: 2 }}>
|
||||
Не удалось загрузить фото. Разрешены png, jpg, jpeg, webp.
|
||||
</Alert>
|
||||
)}
|
||||
{reviewMut.isError && (
|
||||
<Alert severity="error" sx={{ mt: 2 }}>
|
||||
{reviewSubmitErrorMessage(reviewMut.error)}
|
||||
@@ -379,7 +433,15 @@ export function OrderDetailPage() {
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setReviewTarget(null)} disabled={reviewMut.isPending}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setReviewTarget(null)
|
||||
setReviewRating(5)
|
||||
setReviewText('')
|
||||
setReviewImageUrl(null)
|
||||
}}
|
||||
disabled={reviewMut.isPending}
|
||||
>
|
||||
Отмена
|
||||
</Button>
|
||||
<Button variant="contained" disabled={reviewMut.isPending} onClick={() => reviewMut.mutate()}>
|
||||
|
||||
Reference in New Issue
Block a user