fix: resolve bugs and improve frontend performance

- Fix register not resetting isLoading on success (causing login page to hang)
- Fix leaderboard streaks 400 error by forcing all_time timeframe
- Reorder routes so static paths match before dynamic practice/:sheetId
- Lazy-load QuestMap + Three.js (saves ~350KB gzip on initial load)
- Move KaTeX CSS to lazy import (only loads on math pages)
- Remove 28 duplicate Google Font @import lines from component CSS
- Add font preconnect + single stylesheet link in index.html
- Replace 8 unsafe JSON.parse(localStorage) calls with Zustand selectors
- Add global ErrorBoundary to prevent full-app crashes
- Extract arcTheme utilities to break static import cycle with QuestMap
- Merge Three.js + Troika into single chunk to fix circular dependency

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 08:41:13 +06:00
parent ebbad9bc9e
commit e4c86d473c
34 changed files with 259 additions and 206 deletions

View File

@ -31,7 +31,6 @@ const DOTS = [
];
const STYLES = `
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;800;900&family=Nunito+Sans:wght@400;600;700&display=swap');
:root { --content-max: 1100px; }
@ -415,6 +414,7 @@ const EmptyState = () => (
export const Rewards = () => {
const user = useAuthStore((state) => state.user);
const token = useAuthStore((state) => state.token);
const [time, setTime] = useState("today");
const [activeTab, setActiveTab] = useState<TabId>("xp");
const [leaderboard, setLeaderboard] = useState<Leaderboard | undefined>();
@ -431,20 +431,15 @@ export const Rewards = () => {
useEffect(() => {
const fetchData = async () => {
if (!user) return;
const authStorage = localStorage.getItem("auth-storage");
if (!authStorage) return;
const parsed = JSON.parse(authStorage) as {
state?: { token?: string };
} | null;
const token = parsed?.state?.token;
if (!token) return;
if (!user || !token) return;
try {
setLoading(true);
const timeframe =
activeTab === "streaks" ? "all_time" : (TIME_MAP[time] ?? "daily");
const response = await api.fetchLeaderboard(
token,
activeTab,
TIME_MAP[time] ?? "daily",
timeframe,
);
setLeaderboard(response);
// ✅ FIX 1: Guard against null user_rank before accessing its properties
@ -563,9 +558,13 @@ export const Rewards = () => {
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="rw-filter-btn">
{formatTimeLabel(time)} <ChevronDown size={13} />
<DropdownMenuTrigger asChild disabled={activeTab === "streaks"}>
<button
className="rw-filter-btn"
style={activeTab === "streaks" ? { opacity: 0.5, cursor: "not-allowed" } : undefined}
>
{activeTab === "streaks" ? "All Time" : formatTimeLabel(time)}{" "}
<ChevronDown size={13} />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent