feat: migrate frontend to Astro SSG with design system

- Replace monolithic React SPA (App.tsx) with 7 Astro pages (+ routing)
- Implement full design system from design/ (OKLCH tokens, Tailwind 4 @theme)
- Add 12 Astro components (Button, Badge, Card, Sidebar, Header, states)
- Add 11 React islands (AuthGuard, MessageFeed, VoiceControls, AudioVisualizer, etc.)
- Add 3 Zustand stores (UI, voice, message state)
- Add CSS architecture: dark/light themes, glassmorphism, fluid typography, animations
- Add 404, login, settings, recordings pages
- Remove 40+ legacy React SPA files
- Add Particles background and MascotChat deferred islands
- Wire WebSocket bridge for real-time messages and voice events

Build: 7 pages in ~900ms
Typecheck: clean (0 errors)
Lint: clean (0 errors)
This commit is contained in:
asepharyana
2026-07-02 01:18:45 +07:00
parent 5e40b1ad8c
commit 8ad888da28
110 changed files with 3396 additions and 4980 deletions
@@ -1,67 +0,0 @@
import { User } from "lucide-react";
import type { DashboardUser } from "../../../entities/dashboard/types.js";
import type { SummaryItem } from "../../../shared/ui";
import { SummaryList } from "../../../shared/ui";
interface UserSummaryListProps {
users: DashboardUser[];
loading: boolean;
error: string | null;
search: string;
onSearchChange: (value: string) => void;
onLoadMore: () => void;
hasMore: boolean;
onRefetch: () => void;
onSelectUser: (userId: string) => void;
}
export function UserSummaryList({
users,
loading,
error,
search,
onSearchChange,
onLoadMore,
hasMore,
onRefetch,
onSelectUser,
}: UserSummaryListProps) {
const items: SummaryItem[] = users.map((u) => ({
id: u.user_id,
label: u.username ?? u.user_id,
subtitle: u.trust_score !== null ? `Trust: ${u.trust_score}` : undefined,
summaryText: u.profile_summary ?? `${u.total_messages} messages`,
onClick: () => onSelectUser(u.user_id),
}));
const avatarMap = new Map(users.map((u) => [u.user_id, u.avatar_url]));
return (
<SummaryList
items={items}
loading={loading}
error={error}
searchValue={search}
onSearchChange={onSearchChange}
onRetry={onRefetch}
hasMore={hasMore}
onLoadMore={onLoadMore}
loadingMore={loading}
renderIcon={(item) => {
const avatarUrl = avatarMap.get(item.id);
return avatarUrl ? (
<img
src={avatarUrl}
alt=""
className="h-10 w-10 rounded-full object-cover"
/>
) : (
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-muted">
<User className="h-5 w-5 text-muted-foreground" />
</div>
);
}}
emptyMessage="No users found."
/>
);
}