diff --git a/src/api/adminChannels.ts b/src/api/adminChannels.ts index 6749a86..6eb11ef 100644 --- a/src/api/adminChannels.ts +++ b/src/api/adminChannels.ts @@ -8,6 +8,8 @@ export interface RequiredChannel { title: string | null; is_active: boolean; sort_order: number; + disable_trial_on_leave: boolean; + disable_paid_on_leave: boolean; } export interface ChannelListResponse { @@ -19,6 +21,8 @@ export interface CreateChannelRequest { channel_id: string; channel_link?: string; title?: string; + disable_trial_on_leave?: boolean; + disable_paid_on_leave?: boolean; } export interface UpdateChannelRequest { @@ -27,6 +31,8 @@ export interface UpdateChannelRequest { title?: string; is_active?: boolean; sort_order?: number; + disable_trial_on_leave?: boolean; + disable_paid_on_leave?: boolean; } export const adminChannelsApi = { diff --git a/src/locales/en.json b/src/locales/en.json index 2b6ffcd..d4aba78 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1322,6 +1322,21 @@ "submit": "Add", "save": "Save", "cancel": "Cancel" + }, + "globalSettings": { + "title": "Channel Settings", + "channelRequired": "Require subscription", + "channelRequiredDesc": "Users must subscribe to channels to use the bot", + "disableTrialOnUnsub": "Disable trial on unsubscribe", + "disableTrialOnUnsubDesc": "Revoke trial access when user unsubscribes from any channel", + "requiredForAll": "Required for all users", + "requiredForAllDesc": "Apply channel subscription check to all users, including paid" + }, + "perChannel": { + "disableTrial": "Disable trial on leave", + "disableTrialDesc": "Revoke trial when user leaves this channel", + "disablePaid": "Disable paid on leave", + "disablePaidDesc": "Revoke paid access when user leaves this channel" } }, "settings": { diff --git a/src/locales/fa.json b/src/locales/fa.json index 61e7267..8352ad8 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -1025,6 +1025,21 @@ "submit": "افزودن", "save": "ذخیره", "cancel": "لغو" + }, + "globalSettings": { + "title": "تنظیمات کانال", + "channelRequired": "الزام عضویت", + "channelRequiredDesc": "کاربران باید در کانال‌ها عضو شوند تا از ربات استفاده کنند", + "disableTrialOnUnsub": "غیرفعال‌سازی آزمایشی هنگام لغو عضویت", + "disableTrialOnUnsubDesc": "لغو دسترسی آزمایشی هنگام لغو عضویت از هر کانال", + "requiredForAll": "اجباری برای همه", + "requiredForAllDesc": "بررسی عضویت برای همه کاربران، از جمله پولی" + }, + "perChannel": { + "disableTrial": "غیرفعال‌سازی آزمایشی هنگام خروج", + "disableTrialDesc": "لغو دسترسی آزمایشی هنگام خروج از این کانال", + "disablePaid": "غیرفعال‌سازی پولی هنگام خروج", + "disablePaidDesc": "لغو دسترسی پولی هنگام خروج از این کانال" } }, "payments": { diff --git a/src/locales/ru.json b/src/locales/ru.json index e10b230..a4f6914 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -1348,6 +1348,21 @@ "submit": "Добавить", "save": "Сохранить", "cancel": "Отмена" + }, + "globalSettings": { + "title": "Настройки каналов", + "channelRequired": "Требовать подписку", + "channelRequiredDesc": "Пользователи должны подписаться на каналы для использования бота", + "disableTrialOnUnsub": "Отключать триал при отписке", + "disableTrialOnUnsubDesc": "Отзывать пробный доступ при отписке от любого канала", + "requiredForAll": "Обязательно для всех", + "requiredForAllDesc": "Проверять подписку для всех пользователей, включая платных" + }, + "perChannel": { + "disableTrial": "Отключать триал при выходе", + "disableTrialDesc": "Отзывать пробный доступ при выходе из этого канала", + "disablePaid": "Отключать платный при выходе", + "disablePaidDesc": "Отзывать платный доступ при выходе из этого канала" } }, "settings": { diff --git a/src/locales/zh.json b/src/locales/zh.json index b493a0d..db83042 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -1077,6 +1077,21 @@ "submit": "添加", "save": "保存", "cancel": "取消" + }, + "globalSettings": { + "title": "频道设置", + "channelRequired": "要求订阅", + "channelRequiredDesc": "用户必须订阅频道才能使用机器人", + "disableTrialOnUnsub": "取消订阅时禁用试用", + "disableTrialOnUnsubDesc": "用户取消订阅任何频道时撤销试用权限", + "requiredForAll": "对所有用户强制", + "requiredForAllDesc": "对所有用户(包括付费用户)检查频道订阅" + }, + "perChannel": { + "disableTrial": "退出时禁用试用", + "disableTrialDesc": "用户退出此频道时撤销试用权限", + "disablePaid": "退出时禁用付费", + "disablePaidDesc": "用户退出此频道时撤销付费权限" } }, "settings": { diff --git a/src/pages/AdminChannelSubscriptions.tsx b/src/pages/AdminChannelSubscriptions.tsx index ba37fca..6d0f1d1 100644 --- a/src/pages/AdminChannelSubscriptions.tsx +++ b/src/pages/AdminChannelSubscriptions.tsx @@ -8,7 +8,9 @@ import { 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 = () => ( @@ -79,17 +81,146 @@ 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(); @@ -148,6 +279,46 @@ function ChannelCard({ + {/* 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 */}
+ {/* Global channel settings */} + + {/* Add form */} {showAddForm && ( ))}