mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-30 02:23:47 +00:00
Phase 0: Remove ~920 lines of dead code (ThemeBentoPicker, PromoDiscountBadge, AdminLayout, SettingsSidebar, MovingGradient, EmptyState, miniapp API, unused types/functions/transitions/skeleton helpers). Fix API barrel file (add 20 missing exports). Phase 1: Add ErrorBoundary (app/page/widget levels), centralize constants, add axios timeout, fix staleTime:0 in React Query. Phase 2: Consolidate hexToHsl, extract email validation, fix duplicate code. Phase 3: Fix auth race condition (await checkAdminStatus), memoize useCurrency, add WebSocket message validation. Phase 4: Extract useBranding, useFeatureFlags, useScrollRestoration from AppShell. Phase 5: Remove all eslint-disable react-hooks/exhaustive-deps (14 total), simplify logger, remove deprecated hooks (useBackButton, useTelegramDnd, useTelegramWebApp). Dependencies: React 19, react-router 7, zustand 5, i18next 25, react-i18next 16, eslint-plugin-react-refresh 0.5. Remove unused @lottiefiles/dotlottie-react. Convert vite manualChunks to function-based approach for react-router v7 compat. Lint: Fix logger.ts no-unused-expressions, fix react-refresh/only-export-components in 6 Radix primitive files (const re-exports → direct re-exports), fix WebSocketProvider exhaustive-deps. Result: 0 errors, 0 warnings. Add CLAUDE.md to .gitignore.
120 lines
4.3 KiB
TypeScript
120 lines
4.3 KiB
TypeScript
import { useNavigate } from 'react-router';
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { adminAppsApi } from '../api/adminApps';
|
|
import { usePlatform } from '../platform/hooks/usePlatform';
|
|
|
|
export default function AdminApps() {
|
|
const { t } = useTranslation();
|
|
const navigate = useNavigate();
|
|
const queryClient = useQueryClient();
|
|
const { capabilities } = usePlatform();
|
|
|
|
// RemnaWave status
|
|
const { data: status } = useQuery({
|
|
queryKey: ['remnawave-status'],
|
|
queryFn: adminAppsApi.getRemnaWaveStatus,
|
|
staleTime: 60000,
|
|
});
|
|
|
|
// Available configs
|
|
const { data: configs, isLoading: isLoadingConfigs } = useQuery({
|
|
queryKey: ['remnawave-configs-list'],
|
|
queryFn: adminAppsApi.listRemnaWaveConfigs,
|
|
staleTime: 30000,
|
|
});
|
|
|
|
// Set UUID mutation
|
|
const setUuidMutation = useMutation({
|
|
mutationFn: adminAppsApi.setRemnaWaveUuid,
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['remnawave-status'] });
|
|
queryClient.invalidateQueries({ queryKey: ['remnawave-config'] });
|
|
queryClient.invalidateQueries({ queryKey: ['appConfig'] });
|
|
},
|
|
});
|
|
|
|
const currentUuid = status?.config_uuid || '';
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header */}
|
|
<div className="flex items-center gap-3">
|
|
{!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"
|
|
>
|
|
<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>
|
|
</button>
|
|
)}
|
|
<h1 className="text-2xl font-bold text-dark-50 sm:text-3xl">{t('admin.apps.title')}</h1>
|
|
</div>
|
|
|
|
{/* Status card */}
|
|
<div className="card p-4">
|
|
<div className="flex items-center gap-3">
|
|
<div
|
|
className={`h-3 w-3 rounded-full ${status?.enabled ? 'bg-success-400' : 'bg-dark-600'}`}
|
|
/>
|
|
<span className="text-sm font-medium text-dark-200">
|
|
{status?.enabled
|
|
? t('admin.apps.remnaWaveConnected', 'RemnaWave connected')
|
|
: t('admin.apps.remnaWaveDisconnected', 'RemnaWave not connected')}
|
|
</span>
|
|
</div>
|
|
{status?.config_uuid && (
|
|
<div className="mt-2 truncate font-mono text-xs text-dark-500">
|
|
UUID: {status.config_uuid}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Available configs */}
|
|
<div className="space-y-3">
|
|
<h2 className="text-sm font-semibold text-dark-300">
|
|
{t('admin.apps.availableConfigs', 'Available configs')}
|
|
</h2>
|
|
{isLoadingConfigs ? (
|
|
<div className="flex items-center justify-center py-8">
|
|
<div className="h-6 w-6 animate-spin rounded-full border-2 border-accent-500 border-t-transparent" />
|
|
</div>
|
|
) : configs && configs.length > 0 ? (
|
|
<div className="space-y-2">
|
|
{configs.map((config) => (
|
|
<button
|
|
key={config.uuid}
|
|
onClick={() => {
|
|
if (config.uuid !== currentUuid) {
|
|
setUuidMutation.mutate(config.uuid);
|
|
}
|
|
}}
|
|
className={`w-full rounded-lg border p-4 text-left transition-colors ${
|
|
currentUuid === config.uuid
|
|
? 'border-accent-500 bg-accent-500/10'
|
|
: 'border-dark-700 bg-dark-800/50 hover:border-dark-600'
|
|
}`}
|
|
>
|
|
<div className="font-medium text-dark-100">{config.name}</div>
|
|
<div className="mt-1 font-mono text-xs text-dark-500">{config.uuid}</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<div className="card py-8 text-center text-sm text-dark-500">
|
|
{t('admin.apps.noConfigs', 'No configs available')}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|