mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
Complete back button logic for all admin pages in Telegram Mini App
Main sections navigate to /admin (15 pages) Detail/edit pages navigate to parent lists (16 pages) All pages use useBackButton hook with proper navigation paths Native Telegram back button in Mini App, UI button on web Email template pages keep UI-only back buttons as required
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useState, useRef } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@@ -12,10 +13,23 @@ import {
|
||||
RemnawaveConfig,
|
||||
importFromRemnawaveFormat as convertRemnawave,
|
||||
} from '../api/adminApps';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// Icons
|
||||
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
className="h-5 w-5 text-dark-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const AppsIcon = () => (
|
||||
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
<path
|
||||
@@ -571,7 +585,13 @@ function AppEditorModal({ app, platform, isNew, onSave, onClose }: AppEditorModa
|
||||
|
||||
export default function AdminApps() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
const [selectedPlatform, setSelectedPlatform] = useState<string>('ios');
|
||||
const [editingApp, setEditingApp] = useState<{ app: AppDefinition; isNew: boolean } | null>(null);
|
||||
const [copyTarget, setCopyTarget] = useState<{ appId: string; platform: string } | null>(null);
|
||||
@@ -805,7 +825,15 @@ export default function AdminApps() {
|
||||
<div className="space-y-6">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<AdminBackButton />
|
||||
{/* Show back button only on web, not in Telegram Mini App */}
|
||||
{!capabilities.hasBackButton && (
|
||||
<button
|
||||
onClick={() => navigate('/admin')}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-xl border border-dark-700 bg-dark-800 transition-colors hover:border-dark-600"
|
||||
>
|
||||
<BackIcon />
|
||||
</button>
|
||||
)}
|
||||
<h1 className="text-2xl font-bold text-dark-50 sm:text-3xl">{t('admin.apps.title')}</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
CombinedBroadcastCreateRequest,
|
||||
} from '../api/adminBroadcasts';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// Icons
|
||||
const BroadcastIcon = () => (
|
||||
@@ -118,6 +119,8 @@ export default function AdminBroadcastCreate() {
|
||||
const queryClient = useQueryClient();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useBackButton(() => navigate('/admin/broadcasts'));
|
||||
|
||||
// Channel selection
|
||||
const [channel, setChannel] = useState<BroadcastChannel>('telegram');
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { adminBroadcastsApi, type BroadcastChannel } from '../api/adminBroadcasts';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// Icons
|
||||
|
||||
@@ -158,6 +159,8 @@ export default function AdminBroadcastDetail() {
|
||||
|
||||
const broadcastId = id ? parseInt(id, 10) : null;
|
||||
|
||||
useBackButton(() => navigate('/admin/broadcasts'));
|
||||
|
||||
// Fetch broadcast details
|
||||
const {
|
||||
data: broadcast,
|
||||
|
||||
@@ -3,10 +3,23 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { adminBroadcastsApi } from '../api/adminBroadcasts';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// Icons
|
||||
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
className="h-5 w-5 text-dark-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const BroadcastIcon = () => (
|
||||
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
<path
|
||||
@@ -116,6 +129,10 @@ function StatusBadge({ status }: { status: string }) {
|
||||
export default function AdminBroadcasts() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
const [page, setPage] = useState(0);
|
||||
const limit = 20;
|
||||
@@ -136,7 +153,15 @@ export default function AdminBroadcasts() {
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<AdminBackButton />
|
||||
{/* Show back button only on web, not in Telegram Mini App */}
|
||||
{!capabilities.hasBackButton && (
|
||||
<button
|
||||
onClick={() => navigate('/admin')}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-xl border border-dark-700 bg-dark-800 transition-colors hover:border-dark-600"
|
||||
>
|
||||
<BackIcon />
|
||||
</button>
|
||||
)}
|
||||
<div>
|
||||
<h1 className="text-xl font-bold text-dark-100">{t('admin.broadcasts.title')}</h1>
|
||||
<p className="text-sm text-dark-400">{t('admin.broadcasts.subtitle')}</p>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from '../api/campaigns';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { createNumberInputHandler, toNumber } from '../utils/inputHelpers';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// Icons
|
||||
const CampaignIcon = () => (
|
||||
@@ -155,6 +156,8 @@ export default function AdminCampaignCreate() {
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useBackButton(() => navigate('/admin/campaigns'));
|
||||
|
||||
// Form state
|
||||
const [name, setName] = useState('');
|
||||
const [startParameter, setStartParameter] = useState('');
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { CheckIcon, CampaignIcon } from '../components/icons';
|
||||
import { createNumberInputHandler, toNumber } from '../utils/inputHelpers';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// Bonus type config
|
||||
const bonusTypeConfig: Record<
|
||||
@@ -132,6 +133,8 @@ export default function AdminCampaignEdit() {
|
||||
|
||||
const campaignId = parseInt(id || '0');
|
||||
|
||||
useBackButton(() => navigate('/admin/campaigns'));
|
||||
|
||||
// Fetch campaign
|
||||
const {
|
||||
data: campaign,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { campaignsApi, CampaignBonusType } from '../api/campaigns';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// Icons
|
||||
const CopyIcon = () => (
|
||||
@@ -97,6 +98,8 @@ export default function AdminCampaignStats() {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [showUsers, setShowUsers] = useState(false);
|
||||
|
||||
useBackButton(() => navigate('/admin/campaigns'));
|
||||
|
||||
// Fetch stats
|
||||
const {
|
||||
data: stats,
|
||||
|
||||
@@ -4,8 +4,9 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import i18n from '../i18n';
|
||||
import { campaignsApi, CampaignListItem, CampaignBonusType } from '../api/campaigns';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { PlusIcon, EditIcon, TrashIcon, CheckIcon, XIcon, ChartIcon } from '../components/icons';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// Bonus type labels and colors
|
||||
const bonusTypeConfig: Record<
|
||||
@@ -34,6 +35,19 @@ const bonusTypeConfig: Record<
|
||||
},
|
||||
};
|
||||
|
||||
// Icons
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
className="h-5 w-5 text-dark-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
// Locale mapping for formatting
|
||||
const localeMap: Record<string, string> = { ru: 'ru-RU', en: 'en-US', zh: 'zh-CN', fa: 'fa-IR' };
|
||||
|
||||
@@ -51,6 +65,10 @@ export default function AdminCampaigns() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
const [deleteConfirm, setDeleteConfirm] = useState<number | null>(null);
|
||||
|
||||
@@ -89,7 +107,15 @@ export default function AdminCampaigns() {
|
||||
{/* Header */}
|
||||
<div className="mb-6 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<AdminBackButton />
|
||||
{/* Show back button only on web, not in Telegram Mini App */}
|
||||
{!capabilities.hasBackButton && (
|
||||
<button
|
||||
onClick={() => navigate('/admin')}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-xl border border-dark-700 bg-dark-800 transition-colors hover:border-dark-600"
|
||||
>
|
||||
<BackIcon />
|
||||
</button>
|
||||
)}
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold text-dark-100">{t('admin.campaigns.title')}</h1>
|
||||
<p className="text-sm text-dark-400">{t('admin.campaigns.subtitle')}</p>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
statsApi,
|
||||
@@ -9,10 +10,23 @@ import {
|
||||
type RecentPaymentsResponse,
|
||||
} from '../api/admin';
|
||||
import { useCurrency } from '../hooks/useCurrency';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// Icons - styled like main navigation
|
||||
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
className="h-5 w-5 text-dark-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const ServerIcon = () => (
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
<path
|
||||
@@ -347,7 +361,13 @@ function RevenueChart({ data }: { data: { date: string; amount_rubles: number }[
|
||||
|
||||
export default function AdminDashboard() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { formatAmount, currencySymbol } = useCurrency();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
const [stats, setStats] = useState<DashboardStats | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -450,7 +470,15 @@ export default function AdminDashboard() {
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<AdminBackButton />
|
||||
{/* Show back button only on web, not in Telegram Mini App */}
|
||||
{!capabilities.hasBackButton && (
|
||||
<button
|
||||
onClick={() => navigate('/admin')}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-xl border border-dark-700 bg-dark-800 transition-colors hover:border-dark-600"
|
||||
>
|
||||
<BackIcon />
|
||||
</button>
|
||||
)}
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-dark-100">{t('adminDashboard.title')}</h1>
|
||||
<p className="text-dark-400">{t('adminDashboard.subtitle')}</p>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate, Link } from 'react-router-dom';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { adminPaymentMethodsApi } from '../api/adminPaymentMethods';
|
||||
import type { PromoGroupSimple } from '../types';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
@@ -68,6 +70,10 @@ export default function AdminPaymentMethodEdit() {
|
||||
const { methodId } = useParams<{ methodId: string }>();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin/payment-methods'));
|
||||
|
||||
// Fetch payment methods
|
||||
const { data: methods, isLoading } = useQuery({
|
||||
@@ -174,12 +180,15 @@ export default function AdminPaymentMethodEdit() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<Link
|
||||
to="/admin/payment-methods"
|
||||
className="flex h-10 w-10 items-center justify-center rounded-xl border border-dark-700 bg-dark-800 transition-colors hover:border-dark-600"
|
||||
>
|
||||
<BackIcon />
|
||||
</Link>
|
||||
{/* Show back button only on web, not in Telegram Mini App */}
|
||||
{!capabilities.hasBackButton && (
|
||||
<button
|
||||
onClick={() => navigate('/admin/payment-methods')}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-xl border border-dark-700 bg-dark-800 transition-colors hover:border-dark-600"
|
||||
>
|
||||
<BackIcon />
|
||||
</button>
|
||||
)}
|
||||
<h1 className="text-2xl font-bold text-dark-50">
|
||||
{t('admin.paymentMethods.notFound', 'Payment method not found')}
|
||||
</h1>
|
||||
@@ -195,12 +204,15 @@ export default function AdminPaymentMethodEdit() {
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-3">
|
||||
<Link
|
||||
to="/admin/payment-methods"
|
||||
className="flex h-10 w-10 items-center justify-center rounded-xl border border-dark-700 bg-dark-800 transition-colors hover:border-dark-600"
|
||||
>
|
||||
<BackIcon />
|
||||
</Link>
|
||||
{/* Show back button only on web, not in Telegram Mini App */}
|
||||
{!capabilities.hasBackButton && (
|
||||
<button
|
||||
onClick={() => navigate('/admin/payment-methods')}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-xl border border-dark-700 bg-dark-800 transition-colors hover:border-dark-600"
|
||||
>
|
||||
<BackIcon />
|
||||
</button>
|
||||
)}
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-dark-700/50 text-xl">
|
||||
{icon}
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,35 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { adminPaymentsApi } from '../api/adminPayments';
|
||||
import { useCurrency } from '../hooks/useCurrency';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import type { PendingPayment, PaginatedResponse } from '../types';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// BackIcon
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
className="h-5 w-5 text-dark-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default function AdminPayments() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const { formatAmount, currencySymbol } = useCurrency();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
const [page, setPage] = useState(1);
|
||||
const [methodFilter, setMethodFilter] = useState<string>('');
|
||||
@@ -68,7 +88,15 @@ export default function AdminPayments() {
|
||||
{/* Header */}
|
||||
<div className="flex flex-wrap items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<AdminBackButton />
|
||||
{/* Show back button only on web, not in Telegram Mini App */}
|
||||
{!capabilities.hasBackButton && (
|
||||
<button
|
||||
onClick={() => navigate('/admin')}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-xl border border-dark-700 bg-dark-800 transition-colors hover:border-dark-600"
|
||||
>
|
||||
<BackIcon />
|
||||
</button>
|
||||
)}
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-dark-50">{t('admin.payments.title')}</h1>
|
||||
<p className="text-sm text-dark-400">{t('admin.payments.description')}</p>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
PromoGroupUpdateRequest,
|
||||
} from '../api/promocodes';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// Icons
|
||||
const PlusIcon = () => (
|
||||
@@ -55,6 +56,8 @@ export default function AdminPromoGroupCreate() {
|
||||
const queryClient = useQueryClient();
|
||||
const isEdit = !!id;
|
||||
|
||||
useBackButton(() => navigate('/admin/promo-groups'));
|
||||
|
||||
// Form state
|
||||
const [name, setName] = useState('');
|
||||
const [serverDiscount, setServerDiscount] = useState<number | ''>(0);
|
||||
|
||||
@@ -3,9 +3,22 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { promocodesApi, PromoGroup } from '../api/promocodes';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// Icons
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
className="h-5 w-5 text-dark-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const PlusIcon = () => (
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
@@ -46,6 +59,10 @@ export default function AdminPromoGroups() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
const [deleteConfirm, setDeleteConfirm] = useState<number | null>(null);
|
||||
|
||||
@@ -71,7 +88,15 @@ export default function AdminPromoGroups() {
|
||||
{/* Header */}
|
||||
<div className="mb-6 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<AdminBackButton />
|
||||
{/* Show back button only on web, not in Telegram Mini App */}
|
||||
{!capabilities.hasBackButton && (
|
||||
<button
|
||||
onClick={() => navigate('/admin')}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-xl border border-dark-700 bg-dark-800 transition-colors hover:border-dark-600"
|
||||
>
|
||||
<BackIcon />
|
||||
</button>
|
||||
)}
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold text-dark-100">{t('admin.promoGroups.title')}</h1>
|
||||
<p className="text-sm text-dark-400">{t('admin.promoGroups.subtitle')}</p>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from '../api/promoOffers';
|
||||
import { adminUsersApi, UserListItem } from '../api/adminUsers';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// Icons
|
||||
const SendIcon = () => (
|
||||
@@ -75,6 +76,8 @@ export default function AdminPromoOfferSend() {
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useBackButton(() => navigate('/admin/promo-offers'));
|
||||
|
||||
const [selectedTemplateId, setSelectedTemplateId] = useState<number | null>(null);
|
||||
const [sendMode, setSendMode] = useState<'segment' | 'user'>('segment');
|
||||
const [selectedTarget, setSelectedTarget] = useState<TargetSegment>('active');
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from '../api/promoOffers';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { createNumberInputHandler, toNumber } from '../utils/inputHelpers';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
const getOfferTypeIcon = (offerType: string): string => {
|
||||
return OFFER_TYPE_CONFIG[offerType as OfferType]?.icon || '🎁';
|
||||
@@ -21,6 +22,8 @@ export default function AdminPromoOfferTemplateEdit() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useBackButton(() => navigate('/admin/promo-offers'));
|
||||
|
||||
// Form state
|
||||
const [name, setName] = useState('');
|
||||
const [messageText, setMessageText] = useState('');
|
||||
|
||||
@@ -4,9 +4,22 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import i18n from '../i18n';
|
||||
import { promoOffersApi, PromoOfferLog, OFFER_TYPE_CONFIG, OfferType } from '../api/promoOffers';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// Icons
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
className="h-5 w-5 text-dark-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const EditIcon = () => (
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path
|
||||
@@ -93,6 +106,10 @@ const getOfferTypeLabel = (offerType: string): string => {
|
||||
export default function AdminPromoOffers() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
const [activeTab, setActiveTab] = useState<'templates' | 'logs'>('templates');
|
||||
|
||||
@@ -116,7 +133,15 @@ export default function AdminPromoOffers() {
|
||||
{/* Header */}
|
||||
<div className="mb-6 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<AdminBackButton />
|
||||
{/* Show back button only on web, not in Telegram Mini App */}
|
||||
{!capabilities.hasBackButton && (
|
||||
<button
|
||||
onClick={() => navigate('/admin')}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-xl border border-dark-700 bg-dark-800 transition-colors hover:border-dark-600"
|
||||
>
|
||||
<BackIcon />
|
||||
</button>
|
||||
)}
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold text-dark-100">{t('admin.promoOffers.title')}</h1>
|
||||
<p className="text-sm text-dark-400">{t('admin.promoOffers.subtitle')}</p>
|
||||
|
||||
@@ -10,9 +10,22 @@ import {
|
||||
PromoCodeUpdateRequest,
|
||||
PromoGroup,
|
||||
} from '../api/promocodes';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// Icons
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
className="h-5 w-5 text-dark-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const RefreshIcon = () => (
|
||||
<svg
|
||||
className="h-4 w-4 animate-spin"
|
||||
@@ -34,8 +47,12 @@ export default function AdminPromocodeCreate() {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const queryClient = useQueryClient();
|
||||
const { capabilities } = usePlatform();
|
||||
const isEdit = !!id;
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin/promocodes'));
|
||||
|
||||
// Form state
|
||||
const [code, setCode] = useState('');
|
||||
const [type, setType] = useState<PromoCodeType>('balance');
|
||||
@@ -186,7 +203,15 @@ export default function AdminPromocodeCreate() {
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-3">
|
||||
<AdminBackButton to="/admin/promocodes" />
|
||||
{/* Show back button only on web, not in Telegram Mini App */}
|
||||
{!capabilities.hasBackButton && (
|
||||
<button
|
||||
onClick={() => navigate('/admin/promocodes')}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-xl border border-dark-700 bg-dark-800 transition-colors hover:border-dark-600"
|
||||
>
|
||||
<BackIcon />
|
||||
</button>
|
||||
)}
|
||||
<div>
|
||||
<h1 className="text-xl font-bold text-dark-100">
|
||||
{isEdit
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import i18n from '../i18n';
|
||||
import { promocodesApi, PromoCodeType } from '../api/promocodes';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// Icons
|
||||
const EditIcon = () => (
|
||||
@@ -88,6 +89,8 @@ export default function AdminPromocodeStats() {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
||||
useBackButton(() => navigate('/admin/promocodes'));
|
||||
|
||||
const {
|
||||
data: promocode,
|
||||
isLoading,
|
||||
|
||||
@@ -4,10 +4,23 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import i18n from '../i18n';
|
||||
import { promocodesApi, PromoCode, PromoCodeType } from '../api/promocodes';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// Icons
|
||||
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
className="h-5 w-5 text-dark-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const PlusIcon = () => (
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
@@ -98,6 +111,10 @@ export default function AdminPromocodes() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
const [deleteConfirm, setDeleteConfirm] = useState<number | null>(null);
|
||||
const [copiedCode, setCopiedCode] = useState<string | null>(null);
|
||||
@@ -130,7 +147,15 @@ export default function AdminPromocodes() {
|
||||
{/* Header */}
|
||||
<div className="mb-6 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<AdminBackButton />
|
||||
{/* Show back button only on web, not in Telegram Mini App */}
|
||||
{!capabilities.hasBackButton && (
|
||||
<button
|
||||
onClick={() => navigate('/admin')}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-xl border border-dark-700 bg-dark-800 transition-colors hover:border-dark-600"
|
||||
>
|
||||
<BackIcon />
|
||||
</button>
|
||||
)}
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold text-dark-100">{t('admin.promocodes.title')}</h1>
|
||||
<p className="text-sm text-dark-400">{t('admin.promocodes.subtitle')}</p>
|
||||
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
SystemStatsResponse,
|
||||
AutoSyncStatus,
|
||||
} from '../api/adminRemnawave';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
import {
|
||||
ServerIcon,
|
||||
ChartIcon,
|
||||
@@ -23,6 +24,20 @@ import {
|
||||
RemnawaveIcon,
|
||||
} from '../components/icons';
|
||||
|
||||
// ============ Icons ============
|
||||
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
className="h-5 w-5 text-dark-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
// ============ Helpers ============
|
||||
|
||||
const formatBytes = (bytes: number): string => {
|
||||
@@ -861,6 +876,10 @@ export default function AdminRemnawave() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
// State
|
||||
const [activeTab, setActiveTab] = useState<TabType>('overview');
|
||||
@@ -996,7 +1015,15 @@ export default function AdminRemnawave() {
|
||||
{/* Header */}
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<AdminBackButton />
|
||||
{/* Show back button only on web, not in Telegram Mini App */}
|
||||
{!capabilities.hasBackButton && (
|
||||
<button
|
||||
onClick={() => navigate('/admin')}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-xl border border-dark-700 bg-dark-800 transition-colors hover:border-dark-600"
|
||||
>
|
||||
<BackIcon />
|
||||
</button>
|
||||
)}
|
||||
<div className="rounded-lg bg-accent-500/20 p-2">
|
||||
<RemnawaveIcon className="h-6 w-6 text-accent-400" />
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { adminRemnawaveApi, SquadWithLocalInfo } from '../api/adminRemnawave';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { ServerIcon, UsersIcon, CheckIcon, XIcon } from '../components/icons';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// Country flags helper
|
||||
const getCountryFlag = (code: string | null | undefined): string => {
|
||||
@@ -54,6 +55,8 @@ export default function AdminRemnawaveSquadDetail() {
|
||||
const { uuid } = useParams<{ uuid: string }>();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useBackButton(() => navigate('/admin/remnawave'));
|
||||
|
||||
// Fetch all squads and find the one we need
|
||||
const {
|
||||
data: squadsData,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { serversApi, ServerUpdateRequest } from '../api/servers';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { ServerIcon } from '../components/icons';
|
||||
import { createNumberInputHandler, toNumber } from '../utils/inputHelpers';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// Country flags (simple emoji mapping)
|
||||
const getCountryFlag = (code: string | null): string => {
|
||||
@@ -48,6 +49,8 @@ export default function AdminServerEdit() {
|
||||
|
||||
const serverId = parseInt(id || '0');
|
||||
|
||||
useBackButton(() => navigate('/admin/servers'));
|
||||
|
||||
const {
|
||||
data: server,
|
||||
isLoading,
|
||||
|
||||
@@ -2,8 +2,22 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { serversApi, ServerListItem } from '../api/servers';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { SyncIcon, EditIcon, CheckIcon, XIcon, UsersIcon, GiftIcon } from '../components/icons';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// BackIcon
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
className="h-5 w-5 text-dark-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
// Country flags (simple emoji mapping)
|
||||
const getCountryFlag = (code: string | null): string => {
|
||||
@@ -42,6 +56,10 @@ export default function AdminServers() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
// Queries
|
||||
const { data: serversData, isLoading } = useQuery({
|
||||
@@ -79,7 +97,15 @@ export default function AdminServers() {
|
||||
{/* Header */}
|
||||
<div className="mb-6 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<AdminBackButton />
|
||||
{/* Show back button only on web, not in Telegram Mini App */}
|
||||
{!capabilities.hasBackButton && (
|
||||
<button
|
||||
onClick={() => navigate('/admin')}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-xl border border-dark-700 bg-dark-800 transition-colors hover:border-dark-600"
|
||||
>
|
||||
<BackIcon />
|
||||
</button>
|
||||
)}
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold text-dark-100">{t('admin.servers.title')}</h1>
|
||||
<p className="text-sm text-dark-400">{t('admin.servers.subtitle')}</p>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { useState, useMemo, useEffect, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { adminSettingsApi, SettingDefinition } from '../api/adminSettings';
|
||||
import { themeColorsApi } from '../api/themeColors';
|
||||
import { useFavoriteSettings } from '../hooks/useFavoriteSettings';
|
||||
import { MENU_SECTIONS, MenuItem, formatSettingKey, AdminBackButton } from '../components/admin';
|
||||
import { MENU_SECTIONS, MenuItem, formatSettingKey } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
import { AnalyticsTab } from '../components/admin/AnalyticsTab';
|
||||
import { BrandingTab } from '../components/admin/BrandingTab';
|
||||
import { ThemeTab } from '../components/admin/ThemeTab';
|
||||
@@ -17,6 +20,19 @@ import {
|
||||
SettingsSearchResults,
|
||||
} from '../components/admin/SettingsSearch';
|
||||
|
||||
// BackIcon
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
className="h-5 w-5 text-dark-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
// Find section ID by category key
|
||||
function findSectionByCategory(categoryKey: string): string | null {
|
||||
for (const section of MENU_SECTIONS) {
|
||||
@@ -31,6 +47,11 @@ function findSectionByCategory(categoryKey: string): string | null {
|
||||
|
||||
export default function AdminSettings() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
// State
|
||||
const [activeSection, setActiveSection] = useState('branding');
|
||||
@@ -216,7 +237,15 @@ export default function AdminSettings() {
|
||||
<div className="w-64 shrink-0 overflow-y-auto border-r border-dark-700/50">
|
||||
<div className="border-b border-dark-700/50 p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<AdminBackButton />
|
||||
{/* Show back button only on web, not in Telegram Mini App */}
|
||||
{!capabilities.hasBackButton && (
|
||||
<button
|
||||
onClick={() => navigate('/admin')}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-xl border border-dark-700 bg-dark-800 transition-colors hover:border-dark-600"
|
||||
>
|
||||
<BackIcon />
|
||||
</button>
|
||||
)}
|
||||
<h1 className="text-lg font-bold text-dark-100">{t('admin.settings.title')}</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from '../api/tariffs';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { createNumberInputHandler, toNumber } from '../utils/inputHelpers';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// Icons
|
||||
const PlusIcon = () => (
|
||||
@@ -91,6 +92,8 @@ export default function AdminTariffCreate() {
|
||||
const queryClient = useQueryClient();
|
||||
const isEdit = !!id;
|
||||
|
||||
useBackButton(() => navigate('/admin/tariffs'));
|
||||
|
||||
// Step: null = type selection, 'period' or 'daily' = form
|
||||
const [tariffType, setTariffType] = useState<TariffType>(null);
|
||||
|
||||
|
||||
@@ -2,8 +2,9 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { tariffsApi, TariffListItem } from '../api/tariffs';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useDestructiveConfirm, useNotify } from '@/platform';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// Icons
|
||||
const PlusIcon = () => (
|
||||
@@ -54,12 +55,29 @@ const GiftIcon = () => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
// BackIcon
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
className="h-5 w-5 text-dark-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default function AdminTariffs() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const confirmDelete = useDestructiveConfirm();
|
||||
const notify = useNotify();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
// Queries
|
||||
const { data: tariffsData, isLoading } = useQuery({
|
||||
@@ -117,7 +135,15 @@ export default function AdminTariffs() {
|
||||
{/* Header */}
|
||||
<div className="mb-6 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<AdminBackButton />
|
||||
{/* Show back button only on web, not in Telegram Mini App */}
|
||||
{!capabilities.hasBackButton && (
|
||||
<button
|
||||
onClick={() => navigate('/admin')}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-xl border border-dark-700 bg-dark-800 transition-colors hover:border-dark-600"
|
||||
>
|
||||
<BackIcon />
|
||||
</button>
|
||||
)}
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold text-dark-100">{t('admin.tariffs.title')}</h1>
|
||||
<p className="text-sm text-dark-400">{t('admin.tariffs.subtitle')}</p>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { adminApi } from '../api/admin';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
const SettingsIcon = () => (
|
||||
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
@@ -21,6 +22,8 @@ export default function AdminTicketSettings() {
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useBackButton(() => navigate('/admin/tickets'));
|
||||
|
||||
const {
|
||||
data: settings,
|
||||
isLoading,
|
||||
|
||||
@@ -4,7 +4,8 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { adminApi, AdminTicket, AdminTicketDetail, AdminTicketMessage } from '../api/admin';
|
||||
import { ticketsApi } from '../api/tickets';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
function AdminMessageMedia({
|
||||
message,
|
||||
@@ -103,10 +104,28 @@ function AdminMessageMedia({
|
||||
);
|
||||
}
|
||||
|
||||
// BackIcon
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
className="h-5 w-5 text-dark-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default function AdminTickets() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
const [selectedTicketId, setSelectedTicketId] = useState<number | null>(null);
|
||||
const [statusFilter, setStatusFilter] = useState<string>('');
|
||||
const [replyText, setReplyText] = useState('');
|
||||
@@ -209,7 +228,15 @@ export default function AdminTickets() {
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<AdminBackButton />
|
||||
{/* Show back button only on web, not in Telegram Mini App */}
|
||||
{!capabilities.hasBackButton && (
|
||||
<button
|
||||
onClick={() => navigate('/admin')}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-xl border border-dark-700 bg-dark-800 transition-colors hover:border-dark-600"
|
||||
>
|
||||
<BackIcon />
|
||||
</button>
|
||||
)}
|
||||
<h1 className="text-2xl font-bold text-dark-50 sm:text-3xl">
|
||||
{t('admin.tickets.title')}
|
||||
</h1>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
type UpdateSubscriptionRequest,
|
||||
} from '../api/adminUsers';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
|
||||
// ============ Icons ============
|
||||
|
||||
@@ -81,6 +82,8 @@ export default function AdminUserDetail() {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
||||
useBackButton(() => navigate('/admin/users'));
|
||||
|
||||
const localeMap: Record<string, string> = { ru: 'ru-RU', en: 'en-US', zh: 'zh-CN', fa: 'fa-IR' };
|
||||
const locale = localeMap[i18n.language] || 'ru-RU';
|
||||
|
||||
|
||||
@@ -4,10 +4,23 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useCurrency } from '../hooks/useCurrency';
|
||||
import { useToast } from '../components/Toast';
|
||||
import { adminUsersApi, type UserListItem, type UsersStatsResponse } from '../api/adminUsers';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// ============ Icons ============
|
||||
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
className="h-5 w-5 text-dark-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const SearchIcon = () => (
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path
|
||||
@@ -383,6 +396,10 @@ export default function AdminUsers() {
|
||||
const { formatWithCurrency } = useCurrency();
|
||||
const navigate = useNavigate();
|
||||
const { showToast } = useToast();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
const [users, setUsers] = useState<UserListItem[]>([]);
|
||||
const [stats, setStats] = useState<UsersStatsResponse | null>(null);
|
||||
@@ -551,7 +568,15 @@ export default function AdminUsers() {
|
||||
{/* Header */}
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<AdminBackButton />
|
||||
{/* Show back button only on web, not in Telegram Mini App */}
|
||||
{!capabilities.hasBackButton && (
|
||||
<button
|
||||
onClick={() => navigate('/admin')}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-xl border border-dark-700 bg-dark-800 transition-colors hover:border-dark-600"
|
||||
>
|
||||
<BackIcon />
|
||||
</button>
|
||||
)}
|
||||
<div>
|
||||
<h1 className="text-xl font-bold text-dark-100">{t('admin.users.title')}</h1>
|
||||
<p className="text-sm text-dark-400">{t('admin.users.subtitle')}</p>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@@ -20,12 +21,25 @@ import {
|
||||
} from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { adminWheelApi, type WheelPrizeAdmin, type CreateWheelPrizeData } from '../api/wheel';
|
||||
import { AdminBackButton } from '../components/admin';
|
||||
import { useDestructiveConfirm } from '@/platform';
|
||||
import FortuneWheel from '../components/wheel/FortuneWheel';
|
||||
import { useBackButton } from '../platform/hooks/useBackButton';
|
||||
import { usePlatform } from '../platform/hooks/usePlatform';
|
||||
|
||||
// Icons
|
||||
|
||||
const BackIcon = () => (
|
||||
<svg
|
||||
className="h-5 w-5 text-dark-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const CogIcon = () => (
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
<path
|
||||
@@ -272,8 +286,14 @@ function SortablePrizeCard({
|
||||
|
||||
export default function AdminWheel() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const confirmDelete = useDestructiveConfirm();
|
||||
const { capabilities } = usePlatform();
|
||||
|
||||
// Use native Telegram back button in Mini App
|
||||
useBackButton(() => navigate('/admin'));
|
||||
|
||||
const [activeTab, setActiveTab] = useState<Tab>('settings');
|
||||
const [expandedPrizeId, setExpandedPrizeId] = useState<number | null>(null);
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
@@ -487,7 +507,15 @@ export default function AdminWheel() {
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<AdminBackButton />
|
||||
{/* Show back button only on web, not in Telegram Mini App */}
|
||||
{!capabilities.hasBackButton && (
|
||||
<button
|
||||
onClick={() => navigate('/admin')}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-xl border border-dark-700 bg-dark-800 transition-colors hover:border-dark-600"
|
||||
>
|
||||
<BackIcon />
|
||||
</button>
|
||||
)}
|
||||
<h1 className="text-xl font-bold text-dark-50 sm:text-2xl">{t('admin.wheel.title')}</h1>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
Reference in New Issue
Block a user