diff --git a/Makefile b/Makefile index a3e0a7b94..055d76aa4 100644 --- a/Makefile +++ b/Makefile @@ -130,11 +130,19 @@ deploy-docker: build-all ## [HIGH] Deploy all services with Docker + HAProxy @$(MAKE) -s wait-for-services @$(ECHO_CMD) "${GREEN}✅ Deployment complete! Access via http://localhost:$(PORT_HAPROXY)${NC}" -deploy-incus: build-all ## [HIGH] Deploy all services with Incus containers - @$(ECHO_CMD) "${BOLD}${BLUE}📦 Deploying with Incus...${NC}" +deploy-incus: build-all-native ## [HIGH] Deploy all services with Incus containers (native, no Docker) + @$(ECHO_CMD) "${BOLD}${BLUE}📦 Deploying with Incus (native)...${NC}" @$(MAKE) -s incus-setup-network - @$(MAKE) -s incus-deploy-all + @$(MAKE) -s incus-deploy-infra + @$(MAKE) -s incus-deploy-all-native + @$(MAKE) -s incus-start-all @$(ECHO_CMD) "${GREEN}✅ Incus deployment complete!${NC}" + @$(ECHO_CMD) "${BLUE}Access services at:${NC}" + @$(ECHO_CMD) " Backend API: http://10.10.10.2:8080" + @$(ECHO_CMD) " Chat Server: http://10.10.10.3:8081" + @$(ECHO_CMD) " Stream Server: http://10.10.10.4:3002" + @$(ECHO_CMD) " Web Frontend: http://10.10.10.5:80" + @$(ECHO_CMD) " HAProxy: http://10.10.10.6:80" status-full: ## [HIGH] Show complete system status @$(ECHO_CMD) "${BOLD}${CYAN}📊 SYSTEM STATUS${NC}" @@ -196,7 +204,7 @@ build-service: ## [MID] Build a specific service (usage: make build-service SERV @$(MAKE) -s build-$(SERVICE) @$(ECHO_CMD) "${GREEN}✅ $(SERVICE) built.${NC}" -build-all: ## [MID] Build all services +build-all: ## [MID] Build all services (Docker images) @$(ECHO_CMD) "${BLUE}🔨 Building all services...${NC}" @$(MAKE) -s build-backend-api @$(MAKE) -s build-chat-server @@ -204,6 +212,11 @@ build-all: ## [MID] Build all services @$(MAKE) -s build-web @$(ECHO_CMD) "${GREEN}✅ All services built.${NC}" +build-all-native: ## [MID] Build all services natively (for Incus) + @$(ECHO_CMD) "${BLUE}🔨 Building all services natively...${NC}" + @$(PROJECT_ROOT)/config/incus/build-native.sh all + @$(ECHO_CMD) "${GREEN}✅ All services built natively.${NC}" + # ============================================================================== # LOW LEVEL / DEBUG COMMANDS # ============================================================================== @@ -216,6 +229,14 @@ check-tools: ## [LOW] Check required tools done @$(ECHO_CMD) "${GREEN}✅ All tools present.${NC}" +check-tools-incus: ## [LOW] Check required tools for Incus deployment + @$(ECHO_CMD) "${BLUE}Checking Incus deployment requirements...${NC}" + @command -v incus >/dev/null 2>&1 || { $(ECHO_CMD) "${RED}❌ incus is missing! Install with: sudo snap install incus${NC}"; exit 1; } + @command -v go >/dev/null 2>&1 || { $(ECHO_CMD) "${RED}❌ go is missing!${NC}"; exit 1; } + @command -v cargo >/dev/null 2>&1 || { $(ECHO_CMD) "${RED}❌ cargo is missing!${NC}"; exit 1; } + @command -v npm >/dev/null 2>&1 || { $(ECHO_CMD) "${RED}❌ npm is missing!${NC}"; exit 1; } + @$(ECHO_CMD) "${GREEN}✅ All Incus tools present.${NC}" + install-tools: ## [LOW] Install Power User tools (Hot Reload, Linters) @$(ECHO_CMD) "${BLUE}🛠️ Installing Dev Tools...${NC}" @command -v air >/dev/null 2>&1 || go install github.com/air-verse/air@latest @@ -378,7 +399,7 @@ build-web: ## [LOW] Build web frontend # ============================================================================== # INCUS / LXD DEPLOYMENT # ============================================================================== -.PHONY: incus-setup-network incus-deploy-all incus-deploy-service incus-stop-all incus-logs +.PHONY: incus-setup-network incus-deploy-all incus-deploy-all-native incus-deploy-service incus-deploy-service-native incus-deploy-infra incus-start-all incus-stop-all incus-logs incus-setup-network: ## [LOW] Setup Incus network profile @$(ECHO_CMD) "${BLUE}📦 Setting up Incus network...${NC}" @@ -389,8 +410,8 @@ incus-setup-network: ## [LOW] Setup Incus network profile incus profile device add $(INCUS_PROFILE) eth0 nic network=$(INCUS_NETWORK) @$(ECHO_CMD) "${GREEN}✅ Incus network ready.${NC}" -incus-deploy-all: incus-setup-network ## [MID] Deploy all services to Incus - @$(ECHO_CMD) "${BLUE}📦 Deploying all services to Incus...${NC}" +incus-deploy-all: incus-setup-network ## [MID] Deploy all services to Incus (legacy Docker method) + @$(ECHO_CMD) "${BLUE}📦 Deploying all services to Incus (Docker)...${NC}" @$(MAKE) -s incus-deploy-service SERVICE=backend-api @$(MAKE) -s incus-deploy-service SERVICE=chat-server @$(MAKE) -s incus-deploy-service SERVICE=stream-server @@ -398,12 +419,21 @@ incus-deploy-all: incus-setup-network ## [MID] Deploy all services to Incus @$(MAKE) -s incus-deploy-service SERVICE=haproxy @$(ECHO_CMD) "${GREEN}✅ All services deployed to Incus.${NC}" -incus-deploy-service: ## [LOW] Deploy a service to Incus (usage: make incus-deploy-service SERVICE=backend-api) +incus-deploy-all-native: incus-setup-network ## [MID] Deploy all services to Incus (native, no Docker) + @$(ECHO_CMD) "${BLUE}📦 Deploying all services to Incus (native)...${NC}" + @$(MAKE) -s incus-deploy-service-native SERVICE=backend-api + @$(MAKE) -s incus-deploy-service-native SERVICE=chat-server + @$(MAKE) -s incus-deploy-service-native SERVICE=stream-server + @$(MAKE) -s incus-deploy-service-native SERVICE=web + @$(MAKE) -s incus-deploy-service-native SERVICE=haproxy + @$(ECHO_CMD) "${GREEN}✅ All services deployed to Incus.${NC}" + +incus-deploy-service: ## [LOW] Deploy a service to Incus with Docker (usage: make incus-deploy-service SERVICE=backend-api) @if [ -z "$(SERVICE)" ]; then \ $(ECHO_CMD) "${RED}❌ Please specify SERVICE=name${NC}"; \ exit 1; \ fi - @$(ECHO_CMD) "${BLUE}📦 Deploying $(SERVICE) to Incus...${NC}" + @$(ECHO_CMD) "${BLUE}📦 Deploying $(SERVICE) to Incus (Docker)...${NC}" @if incus list -c n --format csv | grep -q "^veza-$(SERVICE)$$"; then \ $(ECHO_CMD) "${YELLOW}Container exists, removing...${NC}"; \ incus delete veza-$(SERVICE) --force; \ @@ -414,6 +444,44 @@ incus-deploy-service: ## [LOW] Deploy a service to Incus (usage: make incus-depl @incus exec veza-$(SERVICE) -- bash -c "apt-get update && apt-get install -y docker.io docker-compose && systemctl enable docker && systemctl start docker" || true @$(ECHO_CMD) "${GREEN}✅ $(SERVICE) deployed.${NC}" +incus-deploy-service-native: ## [LOW] Deploy a service to Incus natively (usage: make incus-deploy-service-native SERVICE=backend-api) + @if [ -z "$(SERVICE)" ]; then \ + $(ECHO_CMD) "${RED}❌ Please specify SERVICE=name${NC}"; \ + exit 1; \ + fi + @$(ECHO_CMD) "${BLUE}📦 Deploying $(SERVICE) to Incus (native)...${NC}" + @$(shell pwd)/config/incus/deploy-service-native.sh $(SERVICE) + +incus-deploy-infra: incus-setup-network ## [LOW] Deploy infrastructure services (PostgreSQL, Redis) + @$(ECHO_CMD) "${BLUE}📦 Deploying infrastructure services...${NC}" + @$(MAKE) -s incus-deploy-service-native SERVICE=infra + @$(ECHO_CMD) "${BLUE}Waiting for infrastructure to be ready...${NC}" + @sleep 10 + @$(ECHO_CMD) "${GREEN}✅ Infrastructure deployed.${NC}" + +incus-start-all: ## [MID] Start all Incus services + @$(ECHO_CMD) "${BLUE}🚀 Starting all Incus services...${NC}" + @for service in backend-api chat-server stream-server; do \ + if incus list -c n --format csv | grep -q "^veza-$$service$$"; then \ + $(ECHO_CMD) "Starting veza-$$service..."; \ + incus exec veza-$$service -- systemctl start veza-$$service 2>/dev/null || true; \ + fi; \ + done + @if incus list -c n --format csv | grep -q "^veza-web$$"; then \ + $(ECHO_CMD) "Starting veza-web..."; \ + incus exec veza-web -- systemctl start apache2 2>/dev/null || true; \ + fi + @if incus list -c n --format csv | grep -q "^veza-haproxy$$"; then \ + $(ECHO_CMD) "Starting veza-haproxy..."; \ + incus exec veza-haproxy -- systemctl start haproxy 2>/dev/null || true; \ + fi + @if incus list -c n --format csv | grep -q "^veza-infra$$"; then \ + $(ECHO_CMD) "Starting infrastructure services..."; \ + incus exec veza-infra -- systemctl start postgresql 2>/dev/null || true; \ + incus exec veza-infra -- systemctl start redis-server 2>/dev/null || true; \ + fi + @$(ECHO_CMD) "${GREEN}✅ All services started.${NC}" + incus-stop-all: ## [MID] Stop all Incus containers @$(ECHO_CMD) "${YELLOW}🛑 Stopping all Incus containers...${NC}" @for container in $$(incus list -c n --format csv | grep veza-); do \ diff --git a/apps/web/src/__tests__/accessibility.test.tsx b/apps/web/src/__tests__/accessibility.test.tsx index 8c28ec221..c2a76f71b 100644 --- a/apps/web/src/__tests__/accessibility.test.tsx +++ b/apps/web/src/__tests__/accessibility.test.tsx @@ -1,7 +1,7 @@ /** * Accessibility Tests * FE-TEST-013: Test keyboard navigation, screen reader support - * + * * This test suite covers: * - Keyboard navigation (Tab, Shift+Tab, Enter, Space, Arrow keys) * - Screen reader support (ARIA labels, roles, states) @@ -20,7 +20,12 @@ const LoginForm = () => ( - + ); @@ -30,13 +35,31 @@ const RegisterForm = () => ( - + - + ); -const SimpleDialog = ({ open, onClose, children }: { open: boolean; onClose: () => void; children: React.ReactNode }) => { +const SimpleDialog = ({ + open, + onClose, + children, +}: { + open: boolean; + onClose: () => void; + children: React.ReactNode; +}) => { if (!open) return null; return (
@@ -55,7 +78,9 @@ describe('Accessibility Tests', () => { const emailInput = screen.getByLabelText(/email/i); const passwordInput = screen.getByLabelText(/mot de passe/i); - const submitButton = screen.getByRole('button', { name: /se connecter/i }); + const submitButton = screen.getByRole('button', { + name: /se connecter/i, + }); // Focus should start on email input emailInput.focus(); @@ -90,9 +115,7 @@ describe('Accessibility Tests', () => { const user = userEvent.setup(); const handleClick = vi.fn(); - render( - , - ); + render(); const button = screen.getByRole('button', { name: /test button/i }); button.focus(); @@ -107,9 +130,7 @@ describe('Accessibility Tests', () => { const user = userEvent.setup(); const handleClick = vi.fn(); - render( - , - ); + render(); const button = screen.getByRole('button', { name: /test button/i }); button.focus(); @@ -184,7 +205,9 @@ describe('Accessibility Tests', () => { const dialog = screen.getByRole('dialog'); const firstButton = screen.getByRole('button', { name: /first button/i }); - const secondButton = screen.getByRole('button', { name: /second button/i }); + const secondButton = screen.getByRole('button', { + name: /second button/i, + }); const closeButton = screen.getByRole('button', { name: /close/i }); // Verify dialog and buttons are present @@ -325,9 +348,7 @@ describe('Accessibility Tests', () => { describe('Focus Management', () => { it('should show visible focus indicators', () => { - render( - , - ); + render(); const button = screen.getByRole('button'); button.focus(); @@ -397,7 +418,9 @@ describe('Accessibility Tests', () => {
, ); - const skipLink = screen.getByRole('link', { name: /skip to main content/i }); + const skipLink = screen.getByRole('link', { + name: /skip to main content/i, + }); expect(skipLink).toBeInTheDocument(); expect(skipLink).toHaveAttribute('href', '#main-content'); }); @@ -453,4 +476,3 @@ describe('Accessibility Tests', () => { }); }); }); - diff --git a/apps/web/src/app/App.tsx b/apps/web/src/app/App.tsx index 983217b72..ac4b68847 100644 --- a/apps/web/src/app/App.tsx +++ b/apps/web/src/app/App.tsx @@ -50,7 +50,7 @@ export function App() { // (refreshUser() est asynchrone, donc on vérifie après un court délai) const checkAndFetchCSRF = async () => { // Attendre un peu pour que refreshUser() se termine - await new Promise(resolve => setTimeout(resolve, 100)); + await new Promise((resolve) => setTimeout(resolve, 100)); const { isAuthenticated } = useAuthStore.getState(); if (isAuthenticated) { csrfService.refreshToken().catch((error) => { @@ -67,7 +67,10 @@ export function App() { // Forcer dark mode par défaut si pas encore défini if (!theme || theme === 'system') { const root = document.documentElement; - if (!root.classList.contains('dark') && !root.classList.contains('light')) { + if ( + !root.classList.contains('dark') && + !root.classList.contains('light') + ) { setTheme('dark'); } else { setTheme(theme); diff --git a/apps/web/src/components/ErrorBoundary.tsx b/apps/web/src/components/ErrorBoundary.tsx index 39f4ecefc..77bf84d06 100644 --- a/apps/web/src/components/ErrorBoundary.tsx +++ b/apps/web/src/components/ErrorBoundary.tsx @@ -32,7 +32,7 @@ export class ErrorBoundary extends Component { // Action 3.3.1.4: Enhanced error boundary logging for monitoring const logContext = getLogContext(); - + // Gather additional context for monitoring const monitoringContext = { ...logContext, @@ -42,7 +42,8 @@ export class ErrorBoundary extends Component { stack: error.stack, componentStack: errorInfo.componentStack, url: typeof window !== 'undefined' ? window.location.href : undefined, - userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined, + userAgent: + typeof navigator !== 'undefined' ? navigator.userAgent : undefined, timestamp: new Date().toISOString(), }; @@ -65,7 +66,9 @@ export class ErrorBoundary extends Component { tags: { error_boundary: true, error_type: error.name || 'Error', - ...(logContext.request_id ? { request_id: String(logContext.request_id) } : {}), + ...(logContext.request_id + ? { request_id: String(logContext.request_id) } + : {}), }, level: 'error', }); @@ -87,7 +90,10 @@ export class ErrorBoundary extends Component {
{ onRetry={this.handleReset} actions={[ { - label: 'Retour à l\'accueil', + label: "Retour à l'accueil", onClick: () => { window.location.href = '/'; }, diff --git a/apps/web/src/components/OfflineIndicator.tsx b/apps/web/src/components/OfflineIndicator.tsx index 0af27b43d..c6197d6cb 100644 --- a/apps/web/src/components/OfflineIndicator.tsx +++ b/apps/web/src/components/OfflineIndicator.tsx @@ -76,7 +76,8 @@ export function OfflineIndicator() { Mode hors ligne {queueSize > 0 && ( - - {queueSize} {queueSize === 1 ? 'requête' : 'requêtes'} en attente + - {queueSize} {queueSize === 1 ? 'requête' : 'requêtes'} en + attente )} @@ -93,7 +94,8 @@ export function OfflineIndicator() { Synchronisation en cours {queueSize > 0 && ( - - {queueSize} {queueSize === 1 ? 'requête' : 'requêtes'} restante{queueSize > 1 ? 's' : ''} + - {queueSize} {queueSize === 1 ? 'requête' : 'requêtes'} restante + {queueSize > 1 ? 's' : ''} )} diff --git a/apps/web/src/components/admin/AdminDashboardView.tsx b/apps/web/src/components/admin/AdminDashboardView.tsx index 4772935cc..086af559f 100644 --- a/apps/web/src/components/admin/AdminDashboardView.tsx +++ b/apps/web/src/components/admin/AdminDashboardView.tsx @@ -1,9 +1,18 @@ - import React, { useState, useEffect } from 'react'; import { Card } from '../ui/card'; import { Button } from '../ui/button'; import { StatCard } from '../dashboard/StatCard'; -import { Users, DollarSign, Activity, AlertTriangle, HardDrive, ShoppingBag, ShieldAlert, CheckCircle, Loader2 } from 'lucide-react'; +import { + Users, + DollarSign, + Activity, + AlertTriangle, + HardDrive, + ShoppingBag, + ShieldAlert, + CheckCircle, + Loader2, +} from 'lucide-react'; import { adminService } from '../../services/adminService'; import { Report } from '../../types'; import { useToast } from '../../context/ToastContext'; @@ -17,148 +26,272 @@ export const AdminDashboardView: React.FC = () => { const [loading, setLoading] = useState(true); useEffect(() => { - const fetchData = async () => { - setLoading(true); - try { - const [statsData, reportsData, uploadsData] = await Promise.all([ - adminService.getDashboardStats(), - adminService.getModerationQueue('pending'), - adminService.getRecentUploads() - ]); - setStats(statsData); - setReports(reportsData); - setUploads(uploadsData); - } catch (e) { - logger.error('Error loading admin dashboard data', { - error: e instanceof Error ? e.message : String(e), - stack: e instanceof Error ? e.stack : undefined, - }); - } finally { - setLoading(false); - } - }; - fetchData(); + const fetchData = async () => { + setLoading(true); + try { + const [statsData, reportsData, uploadsData] = await Promise.all([ + adminService.getDashboardStats(), + adminService.getModerationQueue('pending'), + adminService.getRecentUploads(), + ]); + setStats(statsData); + setReports(reportsData); + setUploads(uploadsData); + } catch (e) { + logger.error('Error loading admin dashboard data', { + error: e instanceof Error ? e.message : String(e), + stack: e instanceof Error ? e.stack : undefined, + }); + } finally { + setLoading(false); + } + }; + fetchData(); }, []); const handleAction = async (id: string, action: string) => { - await adminService.resolveReport(id, action); - setReports(reports.filter(r => r.id !== id)); - addToast(`Report ${action}`, 'success'); + await adminService.resolveReport(id, action); + setReports(reports.filter((r) => r.id !== id)); + addToast(`Report ${action}`, 'success'); }; - if (loading) return
; + if (loading) + return ( +
+ +
+ ); return (
-

SYSTEM OVERVIEW

+

+ SYSTEM OVERVIEW +

- {/* Stats Grid */} -
- } trend={stats.trends?.users} color="cyan" /> - } trend={stats.trends?.revenue} color="gold" /> - } trend={stats.trends?.sessions} color="lime" /> - } trend={stats.trends?.reports} color="red" /> -
+ {/* Stats Grid */} +
+ } + trend={stats.trends?.users} + color="cyan" + /> + } + trend={stats.trends?.revenue} + color="gold" + /> + } + trend={stats.trends?.sessions} + color="lime" + /> + } + trend={stats.trends?.reports} + color="red" + /> +
-
- - {/* Main Chart Area (Mock) */} - -
-

Traffic & Server Load

-
-
Traffic
-
CPU
-
-
-
- {Array.from({length: 40}).map((_, i) => ( -
-
-
-
- ))} -
-
- - {/* Quick Actions */} -
- -

Quick Actions

-
- - - - -
-
- - -

System Health

-
-
- Database - Healthy -
-
- Storage - 65% Used -
-
- API Latency - 45ms -
-
-
+
+ {/* Main Chart Area (Mock) */} + +
+

Traffic & Server Load

+
+ +
{' '} + Traffic +
+ +
CPU +
-
+
+
+ {Array.from({ length: 40 }).map((_, i) => ( +
+
+
+
+ ))} +
+ -
- {/* Recent Reports */} - -
-

Recent Reports

- -
-
- {reports.map(report => ( -
-
-
{report.targetName}
-
{report.targetType} • {report.reason}
-
-
- - -
-
- ))} - {reports.length === 0 &&
No pending reports.
} -
-
+ {/* Quick Actions */} +
+ +

+ Quick Actions +

+
+ + + + +
+
- {/* Recent Uploads */} - -
-

Moderation Queue

- -
-
- {uploads.map(upload => ( -
-
-
{upload.name}
-
{upload.user} • {upload.size}
-
-
- - -
-
- ))} -
-
+ +

+ System Health +

+
+
+ Database + Healthy +
+
+ Storage + 65% Used +
+
+ API Latency + 45ms +
+
+
+
+ +
+ {/* Recent Reports */} + +
+

+ Recent Reports +

+ +
+
+ {reports.map((report) => ( +
+
+
+ {report.targetName} +
+
+ {report.targetType} • {report.reason} +
+
+
+ + +
+
+ ))} + {reports.length === 0 && ( +
+ No pending reports. +
+ )} +
+
+ + {/* Recent Uploads */} + +
+

+ Moderation Queue +

+ +
+
+ {uploads.map((upload) => ( +
+
+
+ {upload.name} +
+
+ {upload.user} • {upload.size} +
+
+
+ + +
+
+ ))} +
+
+
); }; diff --git a/apps/web/src/components/admin/AdminModerationView.tsx b/apps/web/src/components/admin/AdminModerationView.tsx index 2b1b07e31..1e9d14be5 100644 --- a/apps/web/src/components/admin/AdminModerationView.tsx +++ b/apps/web/src/components/admin/AdminModerationView.tsx @@ -1,10 +1,16 @@ - import React, { useState, useEffect } from 'react'; import { Card } from '../ui/card'; import { Button } from '../ui/button'; import { Badge } from '../ui/badge'; import { Report } from '../../types'; -import { ShieldAlert, CheckCircle, Ban, MessageSquare, Clock, Loader2 } from 'lucide-react'; +import { + ShieldAlert, + CheckCircle, + Ban, + MessageSquare, + Clock, + Loader2, +} from 'lucide-react'; import { useToast } from '../../context/ToastContext'; import { adminService } from '../../services/adminService'; import { logger } from '@/utils/logger'; @@ -12,105 +18,169 @@ import { logger } from '@/utils/logger'; export const AdminModerationView: React.FC = () => { const { addToast } = useToast(); const [queue, setQueue] = useState([]); - const [activeTab, setActiveTab] = useState<'pending' | 'reviewed' | 'resolved'>('pending'); + const [activeTab, setActiveTab] = useState< + 'pending' | 'reviewed' | 'resolved' + >('pending'); const [loading, setLoading] = useState(true); useEffect(() => { - const loadQueue = async () => { - setLoading(true); - try { - const data = await adminService.getModerationQueue('all'); - setQueue(data); - } catch (e) { - logger.error('Error loading moderation queue', { - error: e instanceof Error ? e.message : String(e), - stack: e instanceof Error ? e.stack : undefined, - }); - } finally { - setLoading(false); - } - }; - loadQueue(); + const loadQueue = async () => { + setLoading(true); + try { + const data = await adminService.getModerationQueue('all'); + setQueue(data); + } catch (e) { + logger.error('Error loading moderation queue', { + error: e instanceof Error ? e.message : String(e), + stack: e instanceof Error ? e.stack : undefined, + }); + } finally { + setLoading(false); + } + }; + loadQueue(); }, []); - const filteredQueue = queue.filter(r => - activeTab === 'pending' ? r.status === 'pending' : - activeTab === 'reviewed' ? r.status === 'reviewed' : - r.status === 'resolved' || r.status === 'dismissed' + const filteredQueue = queue.filter((r) => + activeTab === 'pending' + ? r.status === 'pending' + : activeTab === 'reviewed' + ? r.status === 'reviewed' + : r.status === 'resolved' || r.status === 'dismissed', ); const handleAction = async (id: string, action: string) => { - try { - await adminService.resolveReport(id, action); - addToast(`Report ${action}`, 'success'); - setQueue(queue.map(r => r.id === id ? { ...r, status: action === 'dismissed' ? 'dismissed' : 'resolved' } as any : r)); - } catch (e) { - addToast("Action failed", "error"); - } + try { + await adminService.resolveReport(id, action); + addToast(`Report ${action}`, 'success'); + setQueue( + queue.map((r) => + r.id === id + ? ({ + ...r, + status: action === 'dismissed' ? 'dismissed' : 'resolved', + } as any) + : r, + ), + ); + } catch (e) { + addToast('Action failed', 'error'); + } }; return (
-

MODERATION QUEUE

+

+ MODERATION QUEUE +

-
- {['pending', 'reviewed', 'resolved'].map(tab => ( - - ))} -
+
+ {['pending', 'reviewed', 'resolved'].map((tab) => ( + + ))} +
-
- {loading &&
} - - {!loading && filteredQueue.length === 0 && ( -
- -

All caught up! No reports in this queue.

-
- )} +
+ {loading && ( +
+ +
+ )} - {!loading && filteredQueue.map(report => ( - -
-
-
- - {report.targetName} - - {report.timestamp} - -
-
-
Reason: {report.reason}
-

{report.description}

-
-
Reported by: {report.reportedBy}
-
+ {!loading && filteredQueue.length === 0 && ( +
+ +

All caught up! No reports in this queue.

+
+ )} -
- - - - -
+ {!loading && + filteredQueue.map((report) => ( + +
+
+
+ + + {report.targetName} + + + {report.timestamp} + +
+
+
+ Reason: {report.reason}
- - ))} -
+

+ {report.description} +

+
+
+ Reported by:{' '} + {report.reportedBy} +
+
+ +
+ + + + +
+
+
+ ))} +
); }; diff --git a/apps/web/src/components/admin/AdminSettingsView.tsx b/apps/web/src/components/admin/AdminSettingsView.tsx index 5a80e731d..c4aed2567 100644 --- a/apps/web/src/components/admin/AdminSettingsView.tsx +++ b/apps/web/src/components/admin/AdminSettingsView.tsx @@ -1,4 +1,3 @@ - import React, { useState } from 'react'; import { Card } from '../ui/card'; import { Button } from '../ui/button'; @@ -12,94 +11,121 @@ export const AdminSettingsView: React.FC = () => { const [announcement, setAnnouncement] = useState(''); const handleSave = () => { - addToast("System settings updated", "success"); + addToast('System settings updated', 'success'); }; return (
-
-

SYSTEM SETTINGS

- +
+

+ SYSTEM SETTINGS +

+ +
+ + {/* General Config */} + +

+ General Configuration +

+ +
+
+ + setUploadLimit(Number(e.target.value))} + /> +

+ Maximum file size for standard users. +

+
+
+ + +
+
- {/* General Config */} - -

- General Configuration -

- -
-
- - setUploadLimit(Number(e.target.value))} - /> -

Maximum file size for standard users.

-
-
- - -
+ {/* Feature Flags */} + +

+ Feature Flags +

+
+ {[ + 'Live Streaming', + 'Marketplace Transactions', + 'AI Mastering', + 'Public Registrations', + ].map((feature) => ( +
+ {feature} +
+
+
- + ))} +
+
- {/* Feature Flags */} - -

- Feature Flags -

-
- {['Live Streaming', 'Marketplace Transactions', 'AI Mastering', 'Public Registrations'].map(feature => ( -
- {feature} -
-
-
-
- ))} + {/* Maintenance */} + +

+ Emergency & + Maintenance +

+ +
+
+
+
Maintenance Mode
+
+ Disable access for non-admin users +
- - - {/* Maintenance */} - -

- Emergency & Maintenance -

- -
-
-
-
Maintenance Mode
-
Disable access for non-admin users
-
-
setMaintenance(!maintenance)} - className={`w-12 h-6 rounded-full relative cursor-pointer transition-colors ${maintenance ? 'bg-kodo-red' : 'bg-gray-600'}`} - > -
-
-
- -
- -