mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
Add CABINET_GIFT_ENABLED toggle in Admin Settings > Branding > Interface Options, alongside existing fullscreen and email auth toggles. Includes updateGiftEnabled API method and i18n keys for all 4 locales (ru/en/zh/fa).
257 lines
9.3 KiB
TypeScript
257 lines
9.3 KiB
TypeScript
import { useState, useRef } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
import { brandingApi, setCachedBranding } from '../../api/branding';
|
|
import { setCachedFullscreenEnabled } from '../../hooks/useTelegramSDK';
|
|
import { UploadIcon, TrashIcon, PencilIcon, CheckIcon, CloseIcon } from './icons';
|
|
import { Toggle } from './Toggle';
|
|
import { BackgroundEditor } from './BackgroundEditor';
|
|
|
|
interface BrandingTabProps {
|
|
accentColor?: string;
|
|
}
|
|
|
|
export function BrandingTab({ accentColor = '#3b82f6' }: BrandingTabProps) {
|
|
const { t } = useTranslation();
|
|
const queryClient = useQueryClient();
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
|
|
const [editingName, setEditingName] = useState(false);
|
|
const [newName, setNewName] = useState('');
|
|
|
|
// Queries
|
|
const { data: branding } = useQuery({
|
|
queryKey: ['branding'],
|
|
queryFn: brandingApi.getBranding,
|
|
});
|
|
|
|
const { data: fullscreenSettings } = useQuery({
|
|
queryKey: ['fullscreen-enabled'],
|
|
queryFn: brandingApi.getFullscreenEnabled,
|
|
});
|
|
|
|
const { data: emailAuthSettings } = useQuery({
|
|
queryKey: ['email-auth-enabled'],
|
|
queryFn: brandingApi.getEmailAuthEnabled,
|
|
});
|
|
|
|
const { data: giftSettings } = useQuery({
|
|
queryKey: ['gift-enabled'],
|
|
queryFn: brandingApi.getGiftEnabled,
|
|
});
|
|
|
|
// Mutations
|
|
const updateBrandingMutation = useMutation({
|
|
mutationFn: brandingApi.updateName,
|
|
onSuccess: (data) => {
|
|
setCachedBranding(data);
|
|
queryClient.invalidateQueries({ queryKey: ['branding'] });
|
|
setEditingName(false);
|
|
},
|
|
});
|
|
|
|
const uploadLogoMutation = useMutation({
|
|
mutationFn: brandingApi.uploadLogo,
|
|
onSuccess: (data) => {
|
|
setCachedBranding(data);
|
|
queryClient.invalidateQueries({ queryKey: ['branding'] });
|
|
},
|
|
});
|
|
|
|
const deleteLogoMutation = useMutation({
|
|
mutationFn: brandingApi.deleteLogo,
|
|
onSuccess: (data) => {
|
|
setCachedBranding(data);
|
|
queryClient.invalidateQueries({ queryKey: ['branding'] });
|
|
},
|
|
});
|
|
|
|
const updateFullscreenMutation = useMutation({
|
|
mutationFn: (enabled: boolean) => brandingApi.updateFullscreenEnabled(enabled),
|
|
onSuccess: (data) => {
|
|
setCachedFullscreenEnabled(data.enabled);
|
|
queryClient.invalidateQueries({ queryKey: ['fullscreen-enabled'] });
|
|
},
|
|
});
|
|
|
|
const updateEmailAuthMutation = useMutation({
|
|
mutationFn: (enabled: boolean) => brandingApi.updateEmailAuthEnabled(enabled),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['email-auth-enabled'] });
|
|
},
|
|
});
|
|
|
|
const updateGiftMutation = useMutation({
|
|
mutationFn: (enabled: boolean) => brandingApi.updateGiftEnabled(enabled),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['gift-enabled'] });
|
|
},
|
|
});
|
|
|
|
const handleLogoUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = e.target.files?.[0];
|
|
if (file) {
|
|
uploadLogoMutation.mutate(file);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Logo & Name */}
|
|
<div className="rounded-2xl border border-dark-700/50 bg-dark-800/50 p-6">
|
|
<h3 className="mb-4 text-lg font-semibold text-dark-100">
|
|
{t('admin.settings.logoAndName')}
|
|
</h3>
|
|
|
|
<div className="flex items-start gap-6">
|
|
{/* Logo */}
|
|
<div className="flex-shrink-0">
|
|
<div
|
|
className="flex h-20 w-20 items-center justify-center overflow-hidden rounded-2xl text-3xl font-bold text-white"
|
|
style={{
|
|
background: `linear-gradient(135deg, ${accentColor}, ${accentColor}dd)`,
|
|
}}
|
|
>
|
|
{branding?.has_custom_logo ? (
|
|
<img
|
|
src={brandingApi.getLogoUrl(branding) ?? undefined}
|
|
alt="Logo"
|
|
className="h-full w-full object-cover"
|
|
/>
|
|
) : (
|
|
branding?.logo_letter || 'V'
|
|
)}
|
|
</div>
|
|
|
|
<div className="mt-3 flex gap-2">
|
|
<input
|
|
ref={fileInputRef}
|
|
type="file"
|
|
accept="image/*"
|
|
onChange={handleLogoUpload}
|
|
className="hidden"
|
|
/>
|
|
<button
|
|
onClick={() => fileInputRef.current?.click()}
|
|
disabled={uploadLogoMutation.isPending}
|
|
className="flex flex-1 items-center justify-center gap-1 rounded-xl bg-dark-700 px-3 py-2 text-sm text-dark-200 transition-colors hover:bg-dark-600 disabled:opacity-50"
|
|
>
|
|
<UploadIcon />
|
|
</button>
|
|
{branding?.has_custom_logo && (
|
|
<button
|
|
onClick={() => deleteLogoMutation.mutate()}
|
|
disabled={deleteLogoMutation.isPending}
|
|
className="rounded-xl bg-dark-700 px-3 py-2 text-dark-400 transition-colors hover:bg-error-500/20 hover:text-error-400 disabled:opacity-50"
|
|
>
|
|
<TrashIcon />
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Name */}
|
|
<div className="flex-1">
|
|
<label className="mb-2 block text-sm font-medium text-dark-300">
|
|
{t('admin.settings.projectName')}
|
|
</label>
|
|
{editingName ? (
|
|
<div className="flex gap-2">
|
|
<input
|
|
type="text"
|
|
value={newName}
|
|
onChange={(e) => setNewName(e.target.value)}
|
|
className="flex-1 rounded-xl border border-dark-600 bg-dark-700 px-4 py-2 text-dark-100 focus:border-accent-500 focus:outline-none"
|
|
maxLength={50}
|
|
/>
|
|
<button
|
|
onClick={() => updateBrandingMutation.mutate(newName)}
|
|
disabled={updateBrandingMutation.isPending}
|
|
className="rounded-xl bg-accent-500 px-4 py-2 text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
|
|
>
|
|
<CheckIcon />
|
|
</button>
|
|
<button
|
|
onClick={() => setEditingName(false)}
|
|
className="rounded-xl bg-dark-700 px-4 py-2 text-dark-300 transition-colors hover:bg-dark-600"
|
|
>
|
|
<CloseIcon />
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-lg text-dark-100">
|
|
{branding?.name || t('admin.settings.notSpecified')}
|
|
</span>
|
|
<button
|
|
onClick={() => {
|
|
setNewName(branding?.name ?? '');
|
|
setEditingName(true);
|
|
}}
|
|
className="rounded-lg p-1.5 text-dark-400 transition-colors hover:bg-dark-700 hover:text-dark-200"
|
|
>
|
|
<PencilIcon />
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Animated Background Editor */}
|
|
<div className="rounded-2xl border border-dark-700/50 bg-dark-800/50 p-6">
|
|
<BackgroundEditor />
|
|
</div>
|
|
|
|
{/* Fullscreen & Email toggles */}
|
|
<div className="rounded-2xl border border-dark-700/50 bg-dark-800/50 p-6">
|
|
<h3 className="mb-4 text-lg font-semibold text-dark-100">
|
|
{t('admin.settings.interfaceOptions')}
|
|
</h3>
|
|
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between rounded-xl bg-dark-700/30 p-4">
|
|
<div>
|
|
<span className="font-medium text-dark-100">
|
|
{t('admin.settings.autoFullscreen')}
|
|
</span>
|
|
<p className="text-sm text-dark-400">{t('admin.settings.autoFullscreenDesc')}</p>
|
|
</div>
|
|
<Toggle
|
|
checked={fullscreenSettings?.enabled ?? false}
|
|
onChange={() =>
|
|
updateFullscreenMutation.mutate(!(fullscreenSettings?.enabled ?? false))
|
|
}
|
|
disabled={updateFullscreenMutation.isPending}
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between rounded-xl bg-dark-700/30 p-4">
|
|
<div>
|
|
<span className="font-medium text-dark-100">{t('admin.settings.emailAuth')}</span>
|
|
<p className="text-sm text-dark-400">{t('admin.settings.emailAuthDesc')}</p>
|
|
</div>
|
|
<Toggle
|
|
checked={emailAuthSettings?.enabled ?? true}
|
|
onChange={() => updateEmailAuthMutation.mutate(!(emailAuthSettings?.enabled ?? true))}
|
|
disabled={updateEmailAuthMutation.isPending}
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between rounded-xl bg-dark-700/30 p-4">
|
|
<div>
|
|
<span className="font-medium text-dark-100">{t('admin.settings.giftEnabled')}</span>
|
|
<p className="text-sm text-dark-400">{t('admin.settings.giftEnabledDesc')}</p>
|
|
</div>
|
|
<Toggle
|
|
checked={giftSettings?.enabled ?? false}
|
|
onChange={() => updateGiftMutation.mutate(!(giftSettings?.enabled ?? false))}
|
|
disabled={updateGiftMutation.isPending}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|