ыввы
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user