import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { useHaptic, useNotify } from '../platform'; import { useNativeDialog } from '../platform/hooks/useNativeDialog'; import { adminChannelsApi, type RequiredChannel, type CreateChannelRequest, type UpdateChannelRequest, } from '../api/adminChannels'; import { adminSettingsApi, type SettingDefinition } from '../api/adminSettings'; import { AdminBackButton } from '../components/admin'; import { Toggle } from '../components/admin/Toggle'; // Icons const ChannelIcon = () => ( ); const PlusIcon = () => ( ); const RefreshIcon = () => ( ); const TrashIcon = () => ( ); const CheckIcon = () => ( ); const XIcon = () => ( ); const EditIcon = () => ( ); const LinkIcon = () => ( ); const SettingsIcon = () => ( ); // Setting toggle row for global settings const CHANNEL_SETTING_KEYS = [ 'CHANNEL_IS_REQUIRED_SUB', 'CHANNEL_DISABLE_TRIAL_ON_UNSUBSCRIBE', 'CHANNEL_REQUIRED_FOR_ALL', ] as const; type ChannelSettingKey = (typeof CHANNEL_SETTING_KEYS)[number]; const SETTING_I18N_MAP: Record = { CHANNEL_IS_REQUIRED_SUB: { label: 'admin.channelSubscriptions.globalSettings.channelRequired', desc: 'admin.channelSubscriptions.globalSettings.channelRequiredDesc', }, CHANNEL_DISABLE_TRIAL_ON_UNSUBSCRIBE: { label: 'admin.channelSubscriptions.globalSettings.disableTrialOnUnsub', desc: 'admin.channelSubscriptions.globalSettings.disableTrialOnUnsubDesc', }, CHANNEL_REQUIRED_FOR_ALL: { label: 'admin.channelSubscriptions.globalSettings.requiredForAll', desc: 'admin.channelSubscriptions.globalSettings.requiredForAllDesc', }, }; function GlobalSettingsSection() { const { t } = useTranslation(); const queryClient = useQueryClient(); const haptic = useHaptic(); const notify = useNotify(); const { data: settings, isLoading } = useQuery({ queryKey: ['admin-settings', 'CHANNEL'], queryFn: () => adminSettingsApi.getSettings('CHANNEL'), }); const updateSettingMutation = useMutation({ mutationFn: ({ key, value }: { key: string; value: unknown }) => adminSettingsApi.updateSetting(key, value), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-settings', 'CHANNEL'] }); haptic.impact('light'); }, onError: () => { haptic.notification('error'); notify.error(t('common.error')); }, }); const getSettingByKey = (key: string): SettingDefinition | undefined => settings?.find((s) => s.key === key); const isSettingEnabled = (key: string): boolean => { const setting = getSettingByKey(key); if (!setting) return false; return setting.current === true || setting.current === 'true'; }; const handleToggleSetting = (key: string) => { const current = isSettingEnabled(key); updateSettingMutation.mutate({ key, value: !current }); }; if (isLoading) { return (
{t('common.loading')}
); } return (

{t('admin.channelSubscriptions.globalSettings.title')}

{CHANNEL_SETTING_KEYS.map((key) => { const setting = getSettingByKey(key); const i18n = SETTING_I18N_MAP[key]; const enabled = isSettingEnabled(key); const isUpdating = updateSettingMutation.isPending; const isReadOnly = setting?.read_only ?? false; return (

{t(i18n.label)}

{t(i18n.desc)}

handleToggleSetting(key)} disabled={isUpdating || isReadOnly || !setting} />
); })}
); } // Channel card component function ChannelCard({ channel, onToggle, onDelete, onEdit, onUpdate, }: { channel: RequiredChannel; onToggle: (id: number) => void; onDelete: (id: number) => void; onEdit: (channel: RequiredChannel) => void; onUpdate: (id: number, data: UpdateChannelRequest) => void; }) { const { t } = useTranslation(); const displayName = channel.title || channel.channel_id; const hasLink = !!channel.channel_link; return (
{/* Status + sort order */}
{channel.is_active ? t('admin.channelSubscriptions.enabled') : t('admin.channelSubscriptions.disabled')} #{channel.id} {t('admin.channelSubscriptions.sortOrder')}: {channel.sort_order}
{/* Title / channel_id */}

{displayName}

{/* Channel ID (if title exists, show ID separately) */} {channel.title &&

{channel.channel_id}

} {/* Link */} {hasLink && (
{channel.channel_link}
)}
{/* Per-channel disable toggles */}

{t('admin.channelSubscriptions.perChannel.disableTrial')}

{t('admin.channelSubscriptions.perChannel.disableTrialDesc')}

onUpdate(channel.id, { disable_trial_on_leave: !channel.disable_trial_on_leave, }) } />

{t('admin.channelSubscriptions.perChannel.disablePaid')}

{t('admin.channelSubscriptions.perChannel.disablePaidDesc')}

onUpdate(channel.id, { disable_paid_on_leave: !channel.disable_paid_on_leave, }) } />
{/* Action buttons */}
{channel.is_active ? ( ) : ( )}
); } // Shared form fields component function ChannelFormFields({ channelId, setChannelId, title, setTitle, channelLink, setChannelLink, sortOrder, setSortOrder, showChannelId, showSortOrder, }: { channelId: string; setChannelId: (v: string) => void; title: string; setTitle: (v: string) => void; channelLink: string; setChannelLink: (v: string) => void; sortOrder: string; setSortOrder: (v: string) => void; showChannelId: boolean; showSortOrder: boolean; }) { const { t } = useTranslation(); return ( <> {showChannelId && (
setChannelId(e.target.value)} placeholder={t('admin.channelSubscriptions.form.channelIdHint')} className="w-full rounded-lg border border-dark-600 bg-dark-700 px-3 py-2 text-sm text-dark-100 placeholder-dark-500 outline-none transition-colors focus:border-accent-500" autoFocus />
)}
setTitle(e.target.value)} placeholder={t('admin.channelSubscriptions.form.title')} className="w-full rounded-lg border border-dark-600 bg-dark-700 px-3 py-2 text-sm text-dark-100 placeholder-dark-500 outline-none transition-colors focus:border-accent-500" autoFocus={!showChannelId} />
setChannelLink(e.target.value)} placeholder={t('admin.channelSubscriptions.form.channelLinkHint')} className="w-full rounded-lg border border-dark-600 bg-dark-700 px-3 py-2 text-sm text-dark-100 placeholder-dark-500 outline-none transition-colors focus:border-accent-500" />
{showSortOrder && (
setSortOrder(e.target.value)} placeholder="0" className="w-full rounded-lg border border-dark-600 bg-dark-700 px-3 py-2 text-sm text-dark-100 placeholder-dark-500 outline-none transition-colors focus:border-accent-500" />
)} ); } // Add channel form component function AddChannelForm({ onSubmit, onCancel, isLoading, }: { onSubmit: (data: CreateChannelRequest) => void; onCancel: () => void; isLoading: boolean; }) { const { t } = useTranslation(); const [channelId, setChannelId] = useState(''); const [channelLink, setChannelLink] = useState(''); const [title, setTitle] = useState(''); const handleSubmit = () => { if (!channelId.trim()) return; onSubmit({ channel_id: channelId.trim(), channel_link: channelLink.trim() || undefined, title: title.trim() || undefined, }); }; return (
{}} showChannelId showSortOrder={false} />
); } // Edit channel form component function EditChannelForm({ channel, onSubmit, onCancel, isLoading, }: { channel: RequiredChannel; onSubmit: (id: number, data: UpdateChannelRequest) => void; onCancel: () => void; isLoading: boolean; }) { const { t } = useTranslation(); const [title, setTitle] = useState(channel.title ?? ''); const [channelLink, setChannelLink] = useState(channel.channel_link ?? ''); const [sortOrder, setSortOrder] = useState(String(channel.sort_order)); const handleSubmit = () => { const updates: UpdateChannelRequest = {}; const newTitle = title.trim() || undefined; const newLink = channelLink.trim() || undefined; const newSort = parseInt(sortOrder, 10); if (newTitle !== (channel.title ?? undefined)) updates.title = newTitle ?? (null as unknown as string); if (newLink !== (channel.channel_link ?? undefined)) updates.channel_link = newLink ?? (null as unknown as string); if (!isNaN(newSort) && newSort !== channel.sort_order) updates.sort_order = newSort; onSubmit(channel.id, updates); }; return (

{t('admin.channelSubscriptions.editing')}:{' '} {channel.channel_id}

{}} title={title} setTitle={setTitle} channelLink={channelLink} setChannelLink={setChannelLink} sortOrder={sortOrder} setSortOrder={setSortOrder} showChannelId={false} showSortOrder />
); } // Main component export default function AdminChannelSubscriptions() { const { t } = useTranslation(); const queryClient = useQueryClient(); const haptic = useHaptic(); const notify = useNotify(); const [showAddForm, setShowAddForm] = useState(false); const [editingChannel, setEditingChannel] = useState(null); // Fetch channels const { data, isLoading, refetch } = useQuery({ queryKey: ['admin-channels'], queryFn: adminChannelsApi.list, }); const toggleMutation = useMutation({ mutationFn: (id: number) => adminChannelsApi.toggle(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-channels'] }); haptic.impact('light'); }, onError: () => { haptic.notification('error'); notify.error(t('common.error')); }, }); const deleteMutation = useMutation({ mutationFn: (id: number) => adminChannelsApi.delete(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-channels'] }); haptic.impact('medium'); }, onError: () => { haptic.notification('error'); notify.error(t('common.error')); }, }); const createMutation = useMutation({ mutationFn: (req: CreateChannelRequest) => adminChannelsApi.create(req), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-channels'] }); setShowAddForm(false); haptic.impact('light'); }, onError: () => { haptic.notification('error'); notify.error(t('common.error')); }, }); const updateMutation = useMutation({ mutationFn: ({ id, data }: { id: number; data: UpdateChannelRequest }) => adminChannelsApi.update(id, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-channels'] }); setEditingChannel(null); haptic.impact('light'); }, onError: () => { haptic.notification('error'); notify.error(t('common.error')); }, }); const handleToggle = (id: number) => { toggleMutation.mutate(id); }; const { confirm: confirmDialog } = useNativeDialog(); const handleDelete = async (id: number) => { if (await confirmDialog(t('admin.channelSubscriptions.deleteConfirm'))) { deleteMutation.mutate(id); } }; const handleCreate = (data: CreateChannelRequest) => { createMutation.mutate(data); }; const handleUpdate = (id: number, data: UpdateChannelRequest) => { updateMutation.mutate({ id, data }); }; const handleEdit = (channel: RequiredChannel) => { setEditingChannel(channel); setShowAddForm(false); }; const channels = data?.items ?? []; return (
{/* Header */}

{t('admin.channelSubscriptions.title')}

{t('admin.channelSubscriptions.subtitle')}

{!showAddForm && !editingChannel && ( )}
{/* Global channel settings */} {/* Add form */} {showAddForm && ( setShowAddForm(false)} isLoading={createMutation.isPending} /> )} {/* Edit form */} {editingChannel && ( setEditingChannel(null)} isLoading={updateMutation.isPending} /> )} {/* Channel list */} {isLoading ? (

{t('common.loading')}

) : channels.length === 0 ? (

{t('admin.channelSubscriptions.empty')}

) : (
{channels.map((channel: RequiredChannel) => ( ))}
)}
); }