Форматтера у фронтенда не было: стиль держался вручную и успел разъехаться в 50 файлах. Ставим Prettier с настройками под уже сложившийся стиль (без точек с запятой, одинарные кавычки, ширина 100 — подобрана замером: при 100 расходится меньше файлов, чем при 96 или 110) и прогоняем его по коду. `src/routeTree.gen.ts` исключён — его переписывает плагин роутера. Чтобы форматирование больше не расходилось незаметно, добавлены проверки в CI: `csharpier check` для бэкенда (его отсутствие и позволило накопиться 79 неотформатированным файлам) и `prettier --check` для фронтенда. Версии форматтеров прибиты точно, без кареток: минорка меняет вывод и красит CI на файлах, которых никто не трогал. `.editorconfig` задаёт редакторам те же отступы и LF ещё до форматтера; значения совпадают с настройками csharpier и Prettier намеренно — оба его читают. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
81 lines
2.5 KiB
TypeScript
81 lines
2.5 KiB
TypeScript
import { useMutation } from '@tanstack/react-query'
|
|
import { useState } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { imageUrl } from '@/features/admin/images/api'
|
|
import { ImageGallery } from '@/features/admin/images/ImageGallery'
|
|
import { Button } from '@/shared/ui/button'
|
|
import { Label } from '@/shared/ui/label'
|
|
import { clearBumperTemplateBackground, setBumperTemplateBackground } from '../api'
|
|
|
|
export function BumperBackgroundField({
|
|
channelId,
|
|
templateId,
|
|
backgroundImageId,
|
|
onChanged,
|
|
onError,
|
|
}: Readonly<{
|
|
channelId: string
|
|
templateId: string
|
|
backgroundImageId: string | null
|
|
onChanged: () => void
|
|
onError: (e: unknown) => void
|
|
}>) {
|
|
const { t } = useTranslation()
|
|
const [galleryOpen, setGalleryOpen] = useState(false)
|
|
|
|
const setBg = useMutation({
|
|
mutationFn: (imageId: string) => setBumperTemplateBackground(channelId, templateId, imageId),
|
|
onSuccess: onChanged,
|
|
onError,
|
|
})
|
|
const clearBg = useMutation({
|
|
mutationFn: () => clearBumperTemplateBackground(channelId, templateId),
|
|
onSuccess: onChanged,
|
|
onError,
|
|
})
|
|
|
|
return (
|
|
<div className="flex flex-col gap-1.5">
|
|
<Label>
|
|
{t('admin.channels.bumperBackground')}{' '}
|
|
<span className={backgroundImageId ? 'text-emerald-500' : 'text-muted-foreground'}>
|
|
{backgroundImageId
|
|
? `· ${t('admin.channels.bumperFileLoaded')}`
|
|
: `· ${t('admin.channels.bumperFileDefault')}`}
|
|
</span>
|
|
</Label>
|
|
<span className="text-xs text-muted-foreground">
|
|
{t('admin.channels.bumperBackgroundHint')}
|
|
</span>
|
|
<div className="flex items-center gap-2">
|
|
{backgroundImageId && (
|
|
<img
|
|
src={imageUrl(backgroundImageId)}
|
|
alt=""
|
|
className="h-10 w-16 shrink-0 rounded border border-border object-cover"
|
|
/>
|
|
)}
|
|
<Button size="sm" variant="outline" onClick={() => setGalleryOpen(true)}>
|
|
{t('admin.channels.bumperBackgroundPick')}
|
|
</Button>
|
|
{backgroundImageId && (
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
disabled={clearBg.isPending}
|
|
onClick={() => clearBg.mutate()}
|
|
>
|
|
{t('admin.channels.bumperReset')}
|
|
</Button>
|
|
)}
|
|
</div>
|
|
<ImageGallery
|
|
open={galleryOpen}
|
|
onOpenChange={setGalleryOpen}
|
|
category="BumperBackground"
|
|
onSelect={(img) => setBg.mutate(img.id)}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|