This commit is contained in:
Kirill
2026-05-26 12:30:09 +05:00
parent e092299a11
commit c01070cb09
5 changed files with 119 additions and 92 deletions
@@ -59,7 +59,7 @@ export function GalleryImagePicker({
return (
<Dialog open={open} onClose={handleClose} fullWidth maxWidth="sm">
<DialogTitle>Изображения из галереи</DialogTitle>
<DialogContent dividers>
<DialogContent dividers>
{galleryQuery.isLoading && <Typography color="text.secondary">Загрузка списка</Typography>}
{galleryQuery.isError && <Alert severity="error">Не удалось загрузить галерею. Попробуйте ещё раз.</Alert>}
{galleryQuery.data?.items.length === 0 && !galleryQuery.isLoading && (
@@ -13,6 +13,7 @@ import ToggleButton from '@mui/material/ToggleButton'
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'
import Typography from '@mui/material/Typography'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { Grid3x3, List } from 'lucide-react'
import {
deleteGalleryImage,
fetchAdminGallery,
@@ -24,7 +25,6 @@ import type { GalleryImageItem } from '@/entities/gallery'
import { formatAdminImageMaxSizeHint } from '@/shared/constants/upload-limits'
import { invalidateQueryKeys } from '@/shared/lib/invalidate-query-keys'
import type { AxiosError } from 'axios'
import { Grid3x3, List } from 'lucide-react'
function getApiErrorMessage(error: unknown): string | null {
const e = error as AxiosError<{ error?: string }>
@@ -197,12 +197,7 @@ export function AdminGalleryPage() {
}}
/>
</Button>
<ToggleButtonGroup
value={viewMode}
exclusive
onChange={(_, v) => v && setViewMode(v)}
size="small"
>
<ToggleButtonGroup value={viewMode} exclusive onChange={(_, v) => v && setViewMode(v)} size="small">
<ToggleButton value="grid" aria-label="Сетка">
<Grid3x3 size={16} />
</ToggleButton>
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'
import { useMemo, useState } from 'react'
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'
import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'
@@ -21,36 +21,17 @@ import { invalidateQueryKeys } from '@/shared/lib/invalidate-query-keys'
type SlideDraft = { galleryImageId: string; caption: string; textColor: string }
export function AdminSliderPage() {
function SliderEditor({
initialSlides,
galleryItems,
}: {
initialSlides: SlideDraft[]
galleryItems: GalleryImageItem[]
}) {
const queryClient = useQueryClient()
const sliderQuery = useQuery({
queryKey: ['admin', 'catalog-slider'],
queryFn: fetchAdminCatalogSlider,
})
const galleryQuery = useQuery({
queryKey: ['admin', 'gallery'],
queryFn: fetchAdminGallery,
})
const [sliderDraft, setSliderDraft] = useState<SlideDraft[]>([])
const [sliderDraft, setSliderDraft] = useState<SlideDraft[]>(initialSlides)
const [pickOpen, setPickOpen] = useState(false)
useEffect(() => {
if (sliderQuery.isSuccess && sliderDraft.length === 0) {
setSliderDraft(
sliderQuery.data.slides.map((s) => ({
galleryImageId: s.galleryImageId,
caption: s.caption,
textColor: s.textColor || '#ffffff',
})),
)
}
}, [sliderQuery.isSuccess, sliderQuery.dataUpdatedAt])
const galleryItems: GalleryImageItem[] = galleryQuery.data?.items ?? []
const usedIds = new Set(sliderDraft.map((s) => s.galleryImageId))
const pickCandidates = galleryItems.filter((i) => !usedIds.has(i.id) && i.isResized)
@@ -88,24 +69,8 @@ export function AdminSliderPage() {
})
}
if (sliderQuery.isLoading || galleryQuery.isLoading) {
return <Typography color="text.secondary">Загрузка</Typography>
}
if (sliderQuery.isError) {
return <Typography color="error">Не удалось загрузить слайдер.</Typography>
}
return (
<Box>
<Typography variant="h4" gutterBottom>
Слайдер
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
Изображения для карусели на главной странице. Сначала загрузите фото в Галерею и обработайте их (Resize), затем
добавьте в слайдер. Порядок строк = порядок показа.
</Typography>
<>
<Paper variant="outlined" sx={{ p: 2, borderRadius: 2 }}>
<Stack spacing={1.5} sx={{ mb: 2 }}>
{sliderDraft.length === 0 && (
@@ -236,6 +201,51 @@ export function AdminSliderPage() {
<Button onClick={() => setPickOpen(false)}>Закрыть</Button>
</DialogActions>
</Dialog>
</>
)
}
export function AdminSliderPage() {
const sliderQuery = useQuery({
queryKey: ['admin', 'catalog-slider'],
queryFn: fetchAdminCatalogSlider,
})
const galleryQuery = useQuery({
queryKey: ['admin', 'gallery'],
queryFn: fetchAdminGallery,
})
const galleryItems: GalleryImageItem[] = galleryQuery.data?.items ?? []
const initialSlides = useMemo<SlideDraft[]>(() => {
if (!sliderQuery.isSuccess) return []
return sliderQuery.data.slides.map((s) => ({
galleryImageId: s.galleryImageId,
caption: s.caption,
textColor: s.textColor || '#ffffff',
}))
}, [sliderQuery.isSuccess, sliderQuery.data?.slides])
if (sliderQuery.isLoading || galleryQuery.isLoading) {
return <Typography color="text.secondary">Загрузка</Typography>
}
if (sliderQuery.isError) {
return <Typography color="error">Не удалось загрузить слайдер.</Typography>
}
return (
<Box>
<Typography variant="h4" gutterBottom>
Слайдер
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
Изображения для карусели на главной странице. Сначала загрузите фото в Галерею и обработайте их (Resize), затем
добавьте в слайдер. Порядок строк = порядок показа.
</Typography>
<SliderEditor key={sliderQuery.dataUpdatedAt} initialSlides={initialSlides} galleryItems={galleryItems} />
</Box>
)
}
+15 -3
View File
@@ -13,12 +13,24 @@ import { AuthForgotForm } from '@/features/auth-forgot'
import { OAuthButtons } from '@/features/auth-oauth'
import { AuthPasswordForm } from '@/features/auth-password'
import { $user } from '@/shared/model/auth'
import { useThemeController } from '@/app/providers/theme-controller'
import type { ColorScheme } from '@/shared/model/theme'
import { BearLogo } from '@/shared/ui/BearLogo'
function readStoredScheme(): ColorScheme {
try {
const raw = localStorage.getItem('craftshop_theme')
if (!raw) return 'craft'
const parsed = JSON.parse(raw)
const scheme = parsed?.scheme
return scheme === 'forest' || scheme === 'ocean' || scheme === 'berry' ? scheme : 'craft'
} catch {
return 'craft'
}
}
export function AuthPage() {
const theme = useTheme()
const { scheme } = useThemeController()
const scheme = readStoredScheme()
const [message, setMessage] = useState<string | null>(null)
const [oauthError, setOauthError] = useState<string | null>(null)
const [tab, setTab] = useState(0)
@@ -40,7 +52,7 @@ export function AuthPage() {
setSearchParams({}, { replace: true })
}, 0)
return () => clearTimeout(timeoutId)
}, [])
}, [searchParams, setSearchParams])
if (showForgot) {
return (