mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-30 02:23:47 +00:00
feat: add channel edit in admin, hide subscribed channels in blocking screen
- Admin panel: added Edit button on channel cards with inline edit form (title, link, sort order) using existing PATCH API - Subscription screen: filter out already-subscribed channels so users only see what they still need to subscribe to - Updated channelIdHint to remove @username reference (numeric only) - Added i18n keys: edit, editing, form.save (en/ru/fa/zh)
This commit is contained in:
@@ -43,7 +43,8 @@ export default function ChannelSubscriptionScreen() {
|
|||||||
return () => clearInterval(timer);
|
return () => clearInterval(timer);
|
||||||
}, [cooldown]);
|
}, [cooldown]);
|
||||||
|
|
||||||
const channels = channelInfo?.channels ?? [];
|
const allChannels = channelInfo?.channels ?? [];
|
||||||
|
const channels = allChannels.filter((ch) => !ch.is_subscribed);
|
||||||
|
|
||||||
const checkSubscription = useCallback(async () => {
|
const checkSubscription = useCallback(async () => {
|
||||||
if (isCheckingRef.current) return;
|
if (isCheckingRef.current) return;
|
||||||
@@ -88,35 +89,23 @@ export default function ChannelSubscriptionScreen() {
|
|||||||
{channelInfo?.message || t('blocking.channel.defaultMessage')}
|
{channelInfo?.message || t('blocking.channel.defaultMessage')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Channel list */}
|
{/* Channel list (only unsubscribed channels) */}
|
||||||
{channels.length > 0 && (
|
{channels.length > 0 && (
|
||||||
<div className="mb-6 space-y-3">
|
<div className="mb-6 space-y-3">
|
||||||
{channels.map((ch) => (
|
{channels.map((ch) => (
|
||||||
<div
|
<div
|
||||||
key={ch.channel_id}
|
key={ch.channel_id}
|
||||||
className={`flex items-center justify-between rounded-xl border p-3 ${
|
className="flex items-center justify-between rounded-xl border border-red-500/30 bg-red-500/10 p-3"
|
||||||
ch.is_subscribed
|
|
||||||
? 'border-green-500/30 bg-green-500/10'
|
|
||||||
: 'border-red-500/30 bg-red-500/10'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<span className="text-sm font-medium text-white">{ch.title || ch.channel_id}</span>
|
<span className="text-sm font-medium text-white">{ch.title || ch.channel_id}</span>
|
||||||
<div className="flex items-center gap-2">
|
{ch.channel_link && (
|
||||||
{ch.is_subscribed ? (
|
<button
|
||||||
<span className="text-xs text-green-400">
|
onClick={() => safeOpenUrl(ch.channel_link)}
|
||||||
{t('blocking.channel.subscribed')}
|
className="rounded-lg bg-blue-500/20 px-3 py-1 text-xs font-medium text-blue-400 hover:bg-blue-500/30"
|
||||||
</span>
|
>
|
||||||
) : (
|
{t('blocking.channel.openChannel')}
|
||||||
ch.channel_link && (
|
</button>
|
||||||
<button
|
)}
|
||||||
onClick={() => safeOpenUrl(ch.channel_link)}
|
|
||||||
className="rounded-lg bg-blue-500/20 px-3 py-1 text-xs font-medium text-blue-400 hover:bg-blue-500/30"
|
|
||||||
>
|
|
||||||
{t('blocking.channel.openChannel')}
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1308,16 +1308,19 @@
|
|||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"enable": "Enable",
|
"enable": "Enable",
|
||||||
"disable": "Disable",
|
"disable": "Disable",
|
||||||
|
"edit": "Edit",
|
||||||
|
"editing": "Editing",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"deleteConfirm": "Delete this channel?",
|
"deleteConfirm": "Delete this channel?",
|
||||||
"sortOrder": "Order",
|
"sortOrder": "Order",
|
||||||
"form": {
|
"form": {
|
||||||
"title": "Display Name",
|
"title": "Display Name",
|
||||||
"channelId": "Channel ID",
|
"channelId": "Channel ID",
|
||||||
"channelIdHint": "@username or -100XXXXXXXXXX",
|
"channelIdHint": "-100XXXXXXXXXX",
|
||||||
"channelLink": "Channel Link",
|
"channelLink": "Channel Link",
|
||||||
"channelLinkHint": "https://t.me/channel",
|
"channelLinkHint": "https://t.me/channel",
|
||||||
"submit": "Add",
|
"submit": "Add",
|
||||||
|
"save": "Save",
|
||||||
"cancel": "Cancel"
|
"cancel": "Cancel"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1011,16 +1011,19 @@
|
|||||||
"disabled": "غیرفعال",
|
"disabled": "غیرفعال",
|
||||||
"enable": "فعالسازی",
|
"enable": "فعالسازی",
|
||||||
"disable": "غیرفعالسازی",
|
"disable": "غیرفعالسازی",
|
||||||
|
"edit": "ویرایش",
|
||||||
|
"editing": "ویرایش",
|
||||||
"delete": "حذف",
|
"delete": "حذف",
|
||||||
"deleteConfirm": "این کانال حذف شود؟",
|
"deleteConfirm": "این کانال حذف شود؟",
|
||||||
"sortOrder": "ترتیب",
|
"sortOrder": "ترتیب",
|
||||||
"form": {
|
"form": {
|
||||||
"title": "نام نمایشی",
|
"title": "نام نمایشی",
|
||||||
"channelId": "شناسه کانال",
|
"channelId": "شناسه کانال",
|
||||||
"channelIdHint": "@username یا -100XXXXXXXXXX",
|
"channelIdHint": "-100XXXXXXXXXX",
|
||||||
"channelLink": "لینک کانال",
|
"channelLink": "لینک کانال",
|
||||||
"channelLinkHint": "https://t.me/channel",
|
"channelLinkHint": "https://t.me/channel",
|
||||||
"submit": "افزودن",
|
"submit": "افزودن",
|
||||||
|
"save": "ذخیره",
|
||||||
"cancel": "لغو"
|
"cancel": "لغو"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1334,16 +1334,19 @@
|
|||||||
"disabled": "Отключён",
|
"disabled": "Отключён",
|
||||||
"enable": "Включить",
|
"enable": "Включить",
|
||||||
"disable": "Отключить",
|
"disable": "Отключить",
|
||||||
|
"edit": "Изменить",
|
||||||
|
"editing": "Редактирование",
|
||||||
"delete": "Удалить",
|
"delete": "Удалить",
|
||||||
"deleteConfirm": "Удалить этот канал?",
|
"deleteConfirm": "Удалить этот канал?",
|
||||||
"sortOrder": "Порядок",
|
"sortOrder": "Порядок",
|
||||||
"form": {
|
"form": {
|
||||||
"title": "Название",
|
"title": "Название",
|
||||||
"channelId": "ID канала",
|
"channelId": "ID канала",
|
||||||
"channelIdHint": "@username или -100XXXXXXXXXX",
|
"channelIdHint": "-100XXXXXXXXXX",
|
||||||
"channelLink": "Ссылка на канал",
|
"channelLink": "Ссылка на канал",
|
||||||
"channelLinkHint": "https://t.me/channel",
|
"channelLinkHint": "https://t.me/channel",
|
||||||
"submit": "Добавить",
|
"submit": "Добавить",
|
||||||
|
"save": "Сохранить",
|
||||||
"cancel": "Отмена"
|
"cancel": "Отмена"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1063,16 +1063,19 @@
|
|||||||
"disabled": "已禁用",
|
"disabled": "已禁用",
|
||||||
"enable": "启用",
|
"enable": "启用",
|
||||||
"disable": "禁用",
|
"disable": "禁用",
|
||||||
|
"edit": "编辑",
|
||||||
|
"editing": "编辑",
|
||||||
"delete": "删除",
|
"delete": "删除",
|
||||||
"deleteConfirm": "删除此频道?",
|
"deleteConfirm": "删除此频道?",
|
||||||
"sortOrder": "排序",
|
"sortOrder": "排序",
|
||||||
"form": {
|
"form": {
|
||||||
"title": "显示名称",
|
"title": "显示名称",
|
||||||
"channelId": "频道ID",
|
"channelId": "频道ID",
|
||||||
"channelIdHint": "@username 或 -100XXXXXXXXXX",
|
"channelIdHint": "-100XXXXXXXXXX",
|
||||||
"channelLink": "频道链接",
|
"channelLink": "频道链接",
|
||||||
"channelLinkHint": "https://t.me/channel",
|
"channelLinkHint": "https://t.me/channel",
|
||||||
"submit": "添加",
|
"submit": "添加",
|
||||||
|
"save": "保存",
|
||||||
"cancel": "取消"
|
"cancel": "取消"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
adminChannelsApi,
|
adminChannelsApi,
|
||||||
type RequiredChannel,
|
type RequiredChannel,
|
||||||
type CreateChannelRequest,
|
type CreateChannelRequest,
|
||||||
|
type UpdateChannelRequest,
|
||||||
} from '../api/adminChannels';
|
} from '../api/adminChannels';
|
||||||
import { AdminBackButton } from '../components/admin';
|
import { AdminBackButton } from '../components/admin';
|
||||||
|
|
||||||
@@ -58,6 +59,16 @@ const XIcon = () => (
|
|||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const EditIcon = () => (
|
||||||
|
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
const LinkIcon = () => (
|
const LinkIcon = () => (
|
||||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||||
<path
|
<path
|
||||||
@@ -73,10 +84,12 @@ function ChannelCard({
|
|||||||
channel,
|
channel,
|
||||||
onToggle,
|
onToggle,
|
||||||
onDelete,
|
onDelete,
|
||||||
|
onEdit,
|
||||||
}: {
|
}: {
|
||||||
channel: RequiredChannel;
|
channel: RequiredChannel;
|
||||||
onToggle: (id: number) => void;
|
onToggle: (id: number) => void;
|
||||||
onDelete: (id: number) => void;
|
onDelete: (id: number) => void;
|
||||||
|
onEdit: (channel: RequiredChannel) => void;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -137,6 +150,14 @@ function ChannelCard({
|
|||||||
|
|
||||||
{/* Action buttons */}
|
{/* Action buttons */}
|
||||||
<div className="mt-3 flex flex-wrap gap-2 border-t border-dark-700/50 pt-3">
|
<div className="mt-3 flex flex-wrap gap-2 border-t border-dark-700/50 pt-3">
|
||||||
|
<button
|
||||||
|
onClick={() => onEdit(channel)}
|
||||||
|
className="flex items-center gap-1.5 rounded-lg bg-accent-500/20 px-3 py-1.5 text-xs text-accent-400 transition-colors hover:bg-accent-500/30"
|
||||||
|
>
|
||||||
|
<EditIcon />
|
||||||
|
{t('admin.channelSubscriptions.edit')}
|
||||||
|
</button>
|
||||||
|
|
||||||
{channel.is_active ? (
|
{channel.is_active ? (
|
||||||
<button
|
<button
|
||||||
onClick={() => onToggle(channel.id)}
|
onClick={() => onToggle(channel.id)}
|
||||||
@@ -167,6 +188,95 @@ function ChannelCard({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 && (
|
||||||
|
<div>
|
||||||
|
<label className="mb-1 block text-xs font-medium text-dark-300">
|
||||||
|
{t('admin.channelSubscriptions.form.channelId')} *
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={channelId}
|
||||||
|
onChange={(e) => 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
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="mb-1 block text-xs font-medium text-dark-300">
|
||||||
|
{t('admin.channelSubscriptions.form.title')}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={title}
|
||||||
|
onChange={(e) => 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}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="mb-1 block text-xs font-medium text-dark-300">
|
||||||
|
{t('admin.channelSubscriptions.form.channelLink')}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={channelLink}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showSortOrder && (
|
||||||
|
<div>
|
||||||
|
<label className="mb-1 block text-xs font-medium text-dark-300">
|
||||||
|
{t('admin.channelSubscriptions.sortOrder')}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={sortOrder}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Add channel form component
|
// Add channel form component
|
||||||
function AddChannelForm({
|
function AddChannelForm({
|
||||||
onSubmit,
|
onSubmit,
|
||||||
@@ -194,50 +304,19 @@ function AddChannelForm({
|
|||||||
return (
|
return (
|
||||||
<div className="rounded-xl border border-accent-500/30 bg-dark-800/50 p-4">
|
<div className="rounded-xl border border-accent-500/30 bg-dark-800/50 p-4">
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{/* Channel ID (required) */}
|
<ChannelFormFields
|
||||||
<div>
|
channelId={channelId}
|
||||||
<label className="mb-1 block text-xs font-medium text-dark-300">
|
setChannelId={setChannelId}
|
||||||
{t('admin.channelSubscriptions.form.channelId')} *
|
title={title}
|
||||||
</label>
|
setTitle={setTitle}
|
||||||
<input
|
channelLink={channelLink}
|
||||||
type="text"
|
setChannelLink={setChannelLink}
|
||||||
value={channelId}
|
sortOrder=""
|
||||||
onChange={(e) => setChannelId(e.target.value)}
|
setSortOrder={() => {}}
|
||||||
placeholder={t('admin.channelSubscriptions.form.channelIdHint')}
|
showChannelId
|
||||||
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={false}
|
||||||
autoFocus
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Title (optional) */}
|
|
||||||
<div>
|
|
||||||
<label className="mb-1 block text-xs font-medium text-dark-300">
|
|
||||||
{t('admin.channelSubscriptions.form.title')}
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={title}
|
|
||||||
onChange={(e) => 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"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Channel Link (optional) */}
|
|
||||||
<div>
|
|
||||||
<label className="mb-1 block text-xs font-medium text-dark-300">
|
|
||||||
{t('admin.channelSubscriptions.form.channelLink')}
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={channelLink}
|
|
||||||
onChange={(e) => 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"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Buttons */}
|
|
||||||
<div className="flex gap-2 pt-1">
|
<div className="flex gap-2 pt-1">
|
||||||
<button
|
<button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
@@ -261,6 +340,81 @@ function AddChannelForm({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 (
|
||||||
|
<div className="rounded-xl border border-accent-500/30 bg-dark-800/50 p-4">
|
||||||
|
<p className="mb-3 text-xs text-dark-400">
|
||||||
|
{t('admin.channelSubscriptions.editing')}:{' '}
|
||||||
|
<code className="text-dark-300">{channel.channel_id}</code>
|
||||||
|
</p>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<ChannelFormFields
|
||||||
|
channelId=""
|
||||||
|
setChannelId={() => {}}
|
||||||
|
title={title}
|
||||||
|
setTitle={setTitle}
|
||||||
|
channelLink={channelLink}
|
||||||
|
setChannelLink={setChannelLink}
|
||||||
|
sortOrder={sortOrder}
|
||||||
|
setSortOrder={setSortOrder}
|
||||||
|
showChannelId={false}
|
||||||
|
showSortOrder
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex gap-2 pt-1">
|
||||||
|
<button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={isLoading}
|
||||||
|
className="flex items-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-sm text-white transition-colors hover:bg-accent-600 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<CheckIcon />
|
||||||
|
{t('admin.channelSubscriptions.form.save')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={onCancel}
|
||||||
|
disabled={isLoading}
|
||||||
|
className="flex items-center gap-2 rounded-lg bg-dark-700 px-4 py-2 text-sm text-dark-300 transition-colors hover:bg-dark-600 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<XIcon />
|
||||||
|
{t('admin.channelSubscriptions.form.cancel')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Main component
|
// Main component
|
||||||
export default function AdminChannelSubscriptions() {
|
export default function AdminChannelSubscriptions() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -268,6 +422,7 @@ export default function AdminChannelSubscriptions() {
|
|||||||
const haptic = useHaptic();
|
const haptic = useHaptic();
|
||||||
const notify = useNotify();
|
const notify = useNotify();
|
||||||
const [showAddForm, setShowAddForm] = useState(false);
|
const [showAddForm, setShowAddForm] = useState(false);
|
||||||
|
const [editingChannel, setEditingChannel] = useState<RequiredChannel | null>(null);
|
||||||
|
|
||||||
// Fetch channels
|
// Fetch channels
|
||||||
const { data, isLoading, refetch } = useQuery({
|
const { data, isLoading, refetch } = useQuery({
|
||||||
@@ -312,6 +467,20 @@ export default function AdminChannelSubscriptions() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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) => {
|
const handleToggle = (id: number) => {
|
||||||
toggleMutation.mutate(id);
|
toggleMutation.mutate(id);
|
||||||
};
|
};
|
||||||
@@ -326,6 +495,15 @@ export default function AdminChannelSubscriptions() {
|
|||||||
createMutation.mutate(data);
|
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 ?? [];
|
const channels = data?.items ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -354,7 +532,7 @@ export default function AdminChannelSubscriptions() {
|
|||||||
>
|
>
|
||||||
<RefreshIcon />
|
<RefreshIcon />
|
||||||
</button>
|
</button>
|
||||||
{!showAddForm && (
|
{!showAddForm && !editingChannel && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowAddForm(true)}
|
onClick={() => setShowAddForm(true)}
|
||||||
aria-label={t('admin.channelSubscriptions.addChannel')}
|
aria-label={t('admin.channelSubscriptions.addChannel')}
|
||||||
@@ -376,6 +554,16 @@ export default function AdminChannelSubscriptions() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Edit form */}
|
||||||
|
{editingChannel && (
|
||||||
|
<EditChannelForm
|
||||||
|
channel={editingChannel}
|
||||||
|
onSubmit={handleUpdate}
|
||||||
|
onCancel={() => setEditingChannel(null)}
|
||||||
|
isLoading={updateMutation.isPending}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Channel list */}
|
{/* Channel list */}
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="rounded-xl border border-dark-700 bg-dark-800/50 p-8 text-center text-dark-400">
|
<div className="rounded-xl border border-dark-700 bg-dark-800/50 p-8 text-center text-dark-400">
|
||||||
@@ -399,6 +587,7 @@ export default function AdminChannelSubscriptions() {
|
|||||||
channel={channel}
|
channel={channel}
|
||||||
onToggle={handleToggle}
|
onToggle={handleToggle}
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
|
onEdit={handleEdit}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user