veza/apps/web/src/features/chat/pages/ChatPage.tsx
senke 6fad0ad68d fix: stabilize frontend — 98 TS errors to 0, align API endpoints, optimize bundle
- Fix 98 TypeScript errors across 37 files:
  - Service layer double-unwrapping (subscriptionService, distributionService, gearService)
  - Self-referencing variables in SearchPageResults
  - FeedView/ExploreView .posts→.items alignment
  - useQueueSync Zustand subscribe API
  - AdminAuditLogsView missing interface fields
  - Toast proxy type, interceptor type narrowing
  - 22 unused imports/variables removed
  - 5 storybook mock data fixes

- Align frontend API calls with backend endpoints:
  - Analytics: useAnalyticsView now calls /creator/analytics/dashboard (was /analytics)
  - Chat: chatService uses /conversations (was mock data), WS URL from backend token
  - Dashboard StatsSection: uses real /dashboard API data (was hardcoded zeros)
  - Settings: suppress 2FA toast error when endpoint unavailable

- Fix marketplace products: seed uses 'active' status (was 'published')
- Enrich seed: admin follows all creators (feed has content)

- Optimize bundle: vendor catch-all 793KB→318KB gzip (-60%)
  Split into vendor-charts, vendor-emoji, vendor-swagger, vendor-media, etc.

- Clean repo: remove ~100 orphaned screenshots, audit reports, logs from root

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 21:18:49 +01:00

126 lines
5.5 KiB
TypeScript

import React, { useEffect } from 'react';
import { ChatSidebar } from '../components/ChatSidebar';
import { ChatRoom } from '../components/ChatRoom';
import { ChatInput } from '../components/ChatInput';
import { useChatStore } from '../store/chatStore';
import { useAuthStore } from '@/features/auth/store/authStore';
import { useUser } from '@/features/auth/hooks/useUser';
import { useQuery } from '@tanstack/react-query';
import { apiClient } from '@/services/api/client';
import { AlertCircle } from 'lucide-react';
import { env } from '@/config/env';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
export const ChatPage: React.FC = () => {
const { isAuthenticated } = useAuthStore();
const { data: user } = useUser();
const userId = user?.id;
const { setWsToken, currentConversationId, wsStatus } = useChatStore();
const {
data: wsTokenResponse,
isLoading: isTokenLoading,
error: tokenError,
} = useQuery({
queryKey: ['chatWsToken', userId],
queryFn: async () => {
if (!isAuthenticated || !userId) return null;
const response = await apiClient.post('/chat/token', {});
return response.data;
},
enabled: isAuthenticated && !!userId && wsStatus === 'disconnected',
refetchOnWindowFocus: false,
retry: false,
staleTime: 5 * 60 * 1000,
gcTime: 10 * 60 * 1000,
});
useEffect(() => {
if (wsTokenResponse?.token) {
// Derive the absolute WS URL from the backend-provided ws_url path
// (e.g. "/api/v1/ws") or fall back to the env-derived WS_URL.
let wsUrl = env.WS_URL;
if (wsTokenResponse.ws_url) {
const backendPath: string = wsTokenResponse.ws_url;
if (backendPath.startsWith('ws://') || backendPath.startsWith('wss://')) {
wsUrl = backendPath;
} else {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
wsUrl = `${protocol}//${window.location.host}${backendPath}`;
}
}
const needsUpdate =
wsTokenResponse.token !== useChatStore.getState().wsToken ||
wsUrl !== useChatStore.getState().wsUrl;
if (needsUpdate) {
setWsToken(wsTokenResponse.token, wsUrl);
}
}
}, [wsTokenResponse, setWsToken]);
if (!isAuthenticated) return (
<div className="flex flex-col items-center justify-center h-layout-chat">
<Card variant="glass" className="p-8 text-center max-w-md border-primary/20">
<AlertCircle className="w-12 h-12 text-primary mx-auto mb-4 opacity-50" />
<h2 className="text-xl font-bold text-foreground mb-2">Access Restricted</h2>
<p className="mb-6 text-muted-foreground">Encrypted channel access requires authorization.</p>
<Button onClick={() => (window.location.href = '/login')}>Initialize Handshake</Button>
</Card>
</div>
);
if (isTokenLoading || wsStatus === 'connecting') return (
<div className="flex flex-col items-center justify-center h-layout-chat">
<div className="relative mb-6">
<div className="w-16 h-16 border-2 border-primary/20 border-t-primary rounded-full animate-spin" />
<div className="absolute inset-0 flex items-center justify-center">
<div className="w-2 h-2 bg-primary rounded-full animate-pulse shadow-status-dot-cyan" />
</div>
</div>
<p className="font-mono text-sm text-primary animate-pulse tracking-widest">ESTABLISHING UPLINK...</p>
</div>
);
if (tokenError) return (
<div className="flex flex-col items-center justify-center h-layout-chat">
<Card variant="glass" className="p-8 text-center max-w-md border-destructive/30">
<AlertCircle className="w-12 h-12 text-destructive mb-4" />
<h2 className="text-xl font-bold text-foreground mb-2">Connection Terminated</h2>
<p className="text-destructive/80 mb-4">{tokenError instanceof Error ? tokenError.message : 'Secure handshake failed.'}</p>
<Button variant="outline" onClick={() => window.location.reload()}>Retry Connection</Button>
</Card>
</div>
);
return (
<div className="h-layout-chat-main flex gap-6 overflow-hidden p-4 container mx-auto max-w-layout-content">
{/* Sidebar - Glass Panel */}
<Card variant="glass" className="w-80 shrink-0 flex flex-col overflow-hidden p-0 border-white/5 bg-black/40 backdrop-blur-2xl">
<div className="p-4 border-b border-white/5 flex items-center justify-between">
<h3 className="font-bold text-sm tracking-widest text-muted-foreground uppercase">Channels</h3>
<div className={cn("w-2 h-2 rounded-full", wsStatus === 'connected' ? 'bg-success shadow-status-dot-lime' : 'bg-destructive')} />
</div>
<ChatSidebar />
</Card>
{/* Main Chat Area - Holographic Container */}
<Card variant="glass" className="flex-1 flex flex-col overflow-hidden relative p-0 border-white/5 bg-black/40 backdrop-blur-2xl">
{/* Background Grid */}
<div className="absolute inset-0 opacity-[0.03] pointer-events-none"
style={{ backgroundImage: 'linear-gradient(var(--color-primary) 1px, transparent 1px), linear-gradient(90deg, var(--color-primary) 1px, transparent 1px)', backgroundSize: '40px 40px' }} />
<div className="flex-1 overflow-hidden flex flex-col relative z-10">
<ChatRoom conversationId={currentConversationId || ''} />
</div>
<div className="p-4 border-t border-white/5 bg-black/40 relative z-20 backdrop-blur-xl">
<ChatInput />
</div>
</Card>
</div>
);
};