- |
+ |
No milestone submissions found.
@@ -760,9 +819,13 @@ const MilestoneQueue: React.FC = () => {
className="border-b border-white/5 hover:bg-white/3 transition-colors"
>
|
-
- {shortenContractId(milestone.learnerAddress, 8, 4)}
-
+
|
{milestone.course}
@@ -773,6 +836,10 @@ const MilestoneQueue: React.FC = () => {
|
|
+
+ +{milestone.peerApprovalCount} / −
+ {milestone.peerRejectionCount}
+ |
{
)
}
+export default Admin
+
const UserLookup: React.FC = () => {
const [search, setSearch] = useState("")
const [submittedAddress, setSubmittedAddress] = useState(null)
@@ -1510,422 +1579,124 @@ const WikiManagement: React.FC = () => {
)
}
-interface FlaggedContent {
- id: number
- content_type: "comment" | "proposal"
- content_id: number
- reporter_address: string
- reason: string
- flag_count: number
- status: "pending" | "reviewed" | "dismissed"
- admin_action?: "deleted" | "dismissed" | "warned"
- admin_address?: string
- admin_notes?: string
- is_hidden: boolean
- created_at: string
- reviewed_at?: string
-}
-
-interface FlagDetails {
- flag: FlaggedContent
- content: {
- id: number
- content: string
- author_address: string
- created_at: string
- } | null
- auditLog: Array<{
- id: number
- action: string
- actor_address: string
- notes: string
- created_at: string
- }>
+interface ScholarshipMetricsData {
+ active_scholarships: number
+ total_scholars: number
+ completion_rate: number
+ avg_milestones_per_scholar: number
+ dropout_rate: number
+ total_usdc_disbursed: number
}
-const ModerationQueue: React.FC = () => {
- const [statusFilter, setStatusFilter] = useState<"pending" | "reviewed">("pending")
- const [flags, setFlags] = useState([])
- const [loading, setLoading] = useState(false)
+const ScholarshipMetrics: React.FC = () => {
+ const [metrics, setMetrics] = useState(null)
+ const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
- const [selectedFlagId, setSelectedFlagId] = useState(null)
- const [selectedDetails, setSelectedDetails] = useState(null)
- const [detailsLoading, setDetailsLoading] = useState(false)
- const [adminNotes, setAdminNotes] = useState("")
- const [actionInProgress, setActionInProgress] = useState(false)
-
- const loadFlags = async () => {
- try {
- setLoading(true)
- setError(null)
- const res = await apiFetchJson<{ data: FlaggedContent[] }>(
- `/api/admin/moderation?status=${statusFilter}`,
- { auth: true },
- )
- setFlags(res.data || [])
- } catch (err) {
- setError(err instanceof Error ? err.message : "Failed to load flags")
- } finally {
- setLoading(false)
- }
- }
-
useEffect(() => {
- void loadFlags()
- setSelectedFlagId(null)
- setSelectedDetails(null)
- }, [statusFilter])
-
- const handleSelectFlag = async (flagId: number) => {
- try {
- setSelectedFlagId(flagId)
- setDetailsLoading(true)
- const res = await apiFetchJson<{ data: FlagDetails }>(
- `/api/admin/moderation/${flagId}`,
- { auth: true },
- )
- setSelectedDetails(res.data)
- setAdminNotes("")
- } catch (err) {
- console.error("Failed to load flag details", err)
- } finally {
- setDetailsLoading(false)
+ const fetchMetrics = async () => {
+ try {
+ const res = await fetch(`${API_BASE}/api/scholarships/metrics`)
+ if (!res.ok) throw new Error(`HTTP ${res.status}`)
+ const data = await res.json()
+ setMetrics(data)
+ } catch (err) {
+ setError("Failed to load metrics")
+ } finally {
+ setLoading(false)
+ }
}
- }
+ void fetchMetrics()
+ }, [])
- const handleTakeAction = async (action: "delete" | "dismiss" | "warn") => {
- if (!selectedFlagId) return
- try {
- setActionInProgress(true)
- await apiFetchJson(`/api/admin/moderation/${selectedFlagId}/action`, {
- method: "POST",
- auth: true,
- headers: {
- "Content-Type": "application/json",
+ const chartData = metrics
+ ? [
+ {
+ name: "Completion Rate",
+ value: metrics.completion_rate,
+ fill: "#00d2ff",
},
- body: JSON.stringify({ action, adminNotes }),
- })
- await loadFlags()
- setSelectedFlagId(null)
- setSelectedDetails(null)
- } catch (err) {
- console.error("Action failed", err)
- } finally {
- setActionInProgress(false)
- }
- }
+ {
+ name: "Dropout Rate",
+ value: metrics.dropout_rate,
+ fill: "#ff4d4d",
+ },
+ ]
+ : []
return (
-
-
-
- Content Moderation Queue
-
-
- Review flags submitted by users and take corrective action.
-
-
-
-
-
-
-
+
+ Scholarship Program Health
- {error && (
-
- Error: {error}
-
+ {loading && (
+
+ {Array.from({ length: 6 }).map((_, i) => (
+
+ ))}
+
)}
-
-
-
-
-
-
- | Content |
- Flags |
- Reporter |
- Reason |
-
-
-
- {loading && (
-
- |
- Loading flagged items…
- |
-
- )}
-
- {!loading && flags.length === 0 && (
-
- |
-
- All Clean!
-
-
- No content in this queue requires moderation.
-
- |
-
- )}
-
- {!loading &&
- flags.map((flag) => (
- void handleSelectFlag(flag.id)}
- className={`border-b border-white/5 hover:bg-white/5 transition-all cursor-pointer ${
- selectedFlagId === flag.id ? "bg-white/10" : ""
- }`}
- >
- |
- {flag.content_type} #{flag.content_id}
- |
-
-
- {flag.flag_count}
-
- |
-
- {flag.reporter_address.slice(0, 6)}...
- {flag.reporter_address.slice(-4)}
- |
-
- {flag.reason}
- |
-
- ))}
-
-
+ {error && {error} }
+
+ {metrics && (
+ <>
+
+ {[
+ {
+ label: "Active Scholarships",
+ value: metrics.active_scholarships,
+ },
+ { label: "Total Scholars", value: metrics.total_scholars },
+ {
+ label: "Completion Rate",
+ value: `${metrics.completion_rate}%`,
+ },
+ {
+ label: "Avg Milestones / Scholar",
+ value: metrics.avg_milestones_per_scholar,
+ },
+ { label: "Dropout Rate", value: `${metrics.dropout_rate}%` },
+ {
+ label: "Total USDC Disbursed",
+ value: `$${(metrics.total_usdc_disbursed / 1e7).toLocaleString(undefined, { maximumFractionDigits: 2 })}`,
+ },
+ ].map(({ label, value }) => (
+
+
+ {label}
+
+ {value}
+
+ ))}
-
-
-
- {selectedFlagId === null ? (
-
-
- 🔎
-
-
- Select an Item
-
-
- Click any item in the moderation queue to view details and take
- actions.
-
-
- ) : detailsLoading ? (
-
- Loading details…
-
- ) : selectedDetails ? (
-
-
-
-
-
-
- {selectedDetails.content ? (
-
-
- Comment by:{" "}
-
- {selectedDetails.content.author_address.slice(0, 6)}...
- {selectedDetails.content.author_address.slice(-4)}
-
-
- {selectedDetails.content.content}
-
- ) : (
-
- Content has been soft-deleted or does not exist.
-
- )}
-
-
-
-
-
- Reporter
-
-
- {selectedDetails.flag.reporter_address}
-
-
-
-
- Reason Given
-
-
- {selectedDetails.flag.reason}
-
-
-
- {statusFilter === "pending" && selectedDetails.content && (
-
-
-
-
-
-
-
-
-
-
-
- )}
-
- {statusFilter === "reviewed" && (
-
-
-
- Moderation Outcome
-
-
-
-
- Action Taken
-
-
- {selectedDetails.flag.admin_action || "Reviewed"}
-
-
-
-
- Moderator
-
-
- {selectedDetails.flag.admin_address
- ? `${selectedDetails.flag.admin_address.slice(0, 6)}...${selectedDetails.flag.admin_address.slice(-4)}`
- : "System"}
-
-
-
- {selectedDetails.flag.admin_notes && (
-
-
- Audit Notes
-
-
- {selectedDetails.flag.admin_notes}
-
-
- )}
-
-
- {selectedDetails.auditLog.length > 0 && (
-
-
-
- {selectedDetails.auditLog.map((log) => (
-
-
-
- {log.action}
-
-
- {new Date(log.created_at).toLocaleDateString()}
-
-
-
- {log.notes || "No notes provided."}
-
-
- ))}
-
-
- )}
-
- )}
-
-
- ) : null}
-
-
+
+ Completion vs Dropout
+
+
+
+
+ `${value}%`} />
+
+
+
+ >
+ )}
)
}
-
-export default Admin
diff --git a/src/pages/DaoProposals.tsx b/src/pages/DaoProposals.tsx
index 9d5f70fd..47eb6277 100644
--- a/src/pages/DaoProposals.tsx
+++ b/src/pages/DaoProposals.tsx
@@ -23,6 +23,7 @@ import {
type FilterType =
| "Voting Open"
| "Voting Closed"
+ | "Queued"
| "Passed"
| "Rejected"
| "All"
@@ -36,20 +37,25 @@ const shortenAddress = (address: string) => {
return `${address.slice(0, 6)}...${address.slice(-4)}`
}
-const formatCountdown = (deadline: string | null, now: number) => {
+const formatCountdown = (
+ deadline: string | null,
+ now: number,
+ { queued = false }: { queued?: boolean } = {},
+) => {
if (!deadline) return "No deadline set"
const diff = new Date(deadline).getTime() - now
- if (diff <= 0) return "Voting closed"
+ if (diff <= 0) return queued ? "Ready for execution" : "Voting closed"
const minutes = Math.floor(diff / (1000 * 60))
const days = Math.floor(minutes / (60 * 24))
const hours = Math.floor((minutes % (60 * 24)) / 60)
const mins = minutes % 60
- if (days > 0) return `${days}d ${hours}h remaining`
- if (hours > 0) return `${hours}h ${mins}m remaining`
- return `${Math.max(mins, 1)}m remaining`
+ const prefix = queued ? "Execution in " : ""
+ if (days > 0) return `${prefix}${days}d ${hours}h remaining`
+ if (hours > 0) return `${prefix}${hours}h ${mins}m remaining`
+ return `${prefix}${Math.max(mins, 1)}m remaining`
}
const formatTokenAmount = (value: bigint) => value.toString()
@@ -57,6 +63,7 @@ const formatTokenAmount = (value: bigint) => value.toString()
const getFilterValue = (proposal: ProposalRecord): FilterType => {
if (proposal.displayStatus === "Voting Open") return "Voting Open"
if (proposal.displayStatus === "Voting Closed") return "Voting Closed"
+ if (proposal.displayStatus === "Queued") return "Queued"
if (proposal.displayStatus === "Passed") return "Passed"
return "Rejected"
}
@@ -351,6 +358,7 @@ const DaoProposals: React.FC = () => {
[
"Voting Open",
"Voting Closed",
+ "Queued",
"Passed",
"Rejected",
"All",
@@ -370,30 +378,6 @@ const DaoProposals: React.FC = () => {
))}
- {showCancelConfirm && selectedProposal && (
- void handleCancelProposal()}
- onCancel={() => setShowCancelConfirm(false)}
- isDestructive
- />
- )}
-
- {showDeleteDraftConfirm && (
- setShowDeleteDraftConfirm(false)}
- isDestructive
- />
- )}
-
{selectedProposal && (
@@ -412,7 +396,12 @@ const DaoProposals: React.FC = () => {
ID #{selectedProposal.id}
- {formatCountdown(selectedProposal.deadline, now)}
+ {formatCountdown(
+ selectedProposal.executionReadyAt ??
+ selectedProposal.deadline,
+ now,
+ { queued: selectedProposal.status === "queued" },
+ )}
@@ -496,7 +485,14 @@ const DaoProposals: React.FC = () => {
Total voting power cast: {formatTokenAmount(totalVotes)} GOV
- {formatCountdown(selectedProposal.deadline, now)}
+
+ {formatCountdown(
+ selectedProposal.executionReadyAt ??
+ selectedProposal.deadline,
+ now,
+ { queued: selectedProposal.status === "queued" },
+ )}
+
@@ -651,7 +647,13 @@ const DaoProposals: React.FC = () => {
Yes: {formatTokenAmount(proposal.votesFor)}
No: {formatTokenAmount(proposal.votesAgainst)}
- {formatCountdown(proposal.deadline, now)}
+
+ {formatCountdown(
+ proposal.executionReadyAt ?? proposal.deadline,
+ now,
+ { queued: proposal.status === "queued" },
+ )}
+
View details
diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx
index 015ca31f..0784d6d8 100644
--- a/src/pages/Dashboard.tsx
+++ b/src/pages/Dashboard.tsx
@@ -5,7 +5,7 @@ import ActivityFeed from "../components/ActivityFeed"
import AddressDisplay from "../components/AddressDisplay"
import CourseCard from "../components/CourseCard"
import LRNBalanceWidget from "../components/LRNBalanceWidget"
-import MyBookmarks from "../components/MyBookmarks"
+import { DashboardStatsSkeleton } from "../components/SkeletonLoader"
import { useCourse } from "../hooks/useCourse"
import { useLearnerProfile } from "../hooks/useLearnerProfile"
import { useLearnToken } from "../hooks/useLearnToken"
@@ -61,10 +61,7 @@ const Dashboard: React.FC = () => {
if (isInitializing && !address) {
return (
-
+
)
diff --git a/src/pages/Leaderboard.tsx b/src/pages/Leaderboard.tsx
index 19594c30..58490deb 100644
--- a/src/pages/Leaderboard.tsx
+++ b/src/pages/Leaderboard.tsx
@@ -2,6 +2,7 @@ import { ChevronLeft, ChevronRight, Trophy } from "lucide-react"
import React, { useMemo, useState } from "react"
import { useTranslation } from "react-i18next"
import AddressDisplay from "../components/AddressDisplay"
+import { LeaderboardRowSkeleton } from "../components/SkeletonLoader"
import { EmptyState } from "../components/states/emptyState"
import { ErrorState } from "../components/states/errorState"
import { useLeaderboard } from "../hooks/useLeaderboard"
@@ -74,7 +75,7 @@ const Leaderboard: React.FC = () => {
}
return (
-
+
{t("pages.leaderboard.title")}
@@ -85,14 +86,7 @@ const Leaderboard: React.FC = () => {
{isLoading ? (
-
- {[...Array(3)].map((_, i) => (
-
- ))}
-
+
) : error ? (
{
{course.title}
- {course.hasUpdatedContent && (
-
- Updated content is available. You are currently on version{" "}
- {course.enrollmentContentVersion ?? 1}, while the
- latest is {course.latestContentVersion ?? 1}.
-
- )}
+
+ {lesson.title}
+
+
+
{currentTab === "forum" ? "Community Forum" : lesson.title}
-
- {hasPrerequisiteData ? (
-
-
-
- Prerequisites
-
-
-
-
-
- ) : null}
-
{/* Course progress bar */}
{allLessons.length > 0 &&
(() => {
@@ -457,24 +410,36 @@ const LessonView: React.FC = () => {
+
+
{currentTab === "forum" ? (
) : (
- markLessonRead(lessonId)}
- prevLessonId={prevLessonId}
- nextLessonId={nextLessonId}
- isNextLocked={isNextLocked}
- />
- )}
+ <>
+ markLessonRead(lessonId)}
+ prevLessonId={prevLessonId}
+ nextLessonId={nextLessonId}
+ isNextLocked={isNextLocked}
+ />
{lesson?.isMilestone && !isLoadingCourse && !isLoadingContent && (
diff --git a/src/pages/NotFound.module.css b/src/pages/NotFound.module.css
index 817f4c5f..878964b0 100644
--- a/src/pages/NotFound.module.css
+++ b/src/pages/NotFound.module.css
@@ -57,9 +57,17 @@
font-weight: 500;
}
+.actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1rem;
+ justify-content: center;
+ align-items: center;
+ margin-top: 0.5rem;
+}
+
.buttonLink {
text-decoration: none;
- margin-top: 0.5rem;
}
@keyframes fadeInUp {
diff --git a/src/pages/NotFound.tsx b/src/pages/NotFound.tsx
index 13033492..287f461c 100644
--- a/src/pages/NotFound.tsx
+++ b/src/pages/NotFound.tsx
@@ -4,7 +4,7 @@ import styles from "./NotFound.module.css"
const NotFound: React.FC = () => {
return (
-
+
@@ -13,11 +13,26 @@ const NotFound: React.FC = () => {
This page doesn't exist — but your learning journey does.
-
-
)
diff --git a/src/pages/Profile.tsx b/src/pages/Profile.tsx
index 0436a745..bdef6e1f 100644
--- a/src/pages/Profile.tsx
+++ b/src/pages/Profile.tsx
@@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next"
import { Link, useParams } from "react-router-dom"
import { ActivityFeed } from "../components/ActivityFeed"
import AddressDisplay from "../components/AddressDisplay"
-import { FollowButton } from "../components/FollowButton"
+
import IdenticonAvatar from "../components/IdenticonAvatar"
import LRNHistoryChart from "../components/LRNHistoryChart"
import ProfileEditForm, {
@@ -169,6 +169,38 @@ const Profile: React.FC = () => {
void fetchProfileData()
}, [fetchProfileData])
+ // Determine which address to view (from URL param or connected wallet)
+ useEffect(() => {
+ const pathMatch = window.location.pathname.match(/\/profile\/(.+)/)
+ if (pathMatch?.[1]) {
+ setViewAddress(pathMatch[1])
+ } else if (walletAddress) {
+ setViewAddress(walletAddress)
+ }
+ }, [walletAddress])
+
+ // Fetch rich profile data
+ const fetchProfileData = useCallback(async () => {
+ if (!viewAddress) return
+
+ try {
+ const response = await fetch(`/api/profile/${viewAddress}`)
+ if (!response.ok) {
+ const errorData = await response.json().catch(() => ({}))
+ throw new Error(errorData.error || "Failed to load profile")
+ }
+ const data = await response.json()
+ setUserProfile(data.profile)
+ setStats(data.stats)
+ } catch (err) {
+ console.error("[profile] Error loading profile data:", err)
+ }
+ }, [viewAddress])
+
+ useEffect(() => {
+ void fetchProfileData()
+ }, [fetchProfileData])
+
useEffect(() => {
void fetchCredentials()
}, [fetchCredentials])
@@ -201,14 +233,12 @@ const Profile: React.FC = () => {
})
if (!response.ok) {
- const errorData = (await response.json().catch(() => ({}))) as {
- error?: string
- }
+ const errorData = await response.json().catch(() => ({}))
throw new Error(errorData.error || "Failed to save profile")
}
- const data = (await response.json()) as { profile?: UserProfile }
- setUserProfile(data.profile ?? null)
+ const data = await response.json()
+ setUserProfile(data.profile)
setIsEditing(false)
} catch (err) {
console.error("[profile] Error saving profile:", err)
@@ -230,10 +260,7 @@ const Profile: React.FC = () => {
Object.values(userProfile.socialLinks).some(Boolean)
const siteUrl = "https://learnvault.app"
- const lrnBalance =
- stats?.lrnBalance?.toLocaleString() ||
- profile?.lrn_balance?.toLocaleString() ||
- "0"
+ const lrnBalance = stats?.lrnBalance.toLocaleString() ?? "0"
const coursesCompleted = stats?.coursesCompleted ?? nfts.length
const title = `${displayName} — ${lrnBalance} LRN · ${coursesCompleted} Course${
coursesCompleted !== 1 ? "s" : ""
@@ -303,36 +330,25 @@ const Profile: React.FC = () => {
{bio}
)}
-
- {displayAddress ? (
-
-
- {!isOwnProfile && (
-
- )}
-
+
+ {viewAddress ? (
+
) : (
{t("wallet.connect")}
)}
-
-
-
-
- {profile?.follower_count || 0}
-
-
- Followers
-
+
+ {viewAddress ? (
+
+ ) : (
+
+ {t("wallet.connect")}
@@ -366,6 +382,16 @@ const Profile: React.FC = () => {
)}
+ {stats?.reputationRank && (
+
+ Rank #{stats.reputationRank}
+
+ )}
+ {stats?.percentile && (
+
+ Top {stats.percentile}%
+
+ )}
{/* Social Links */}
@@ -466,6 +492,8 @@ const Profile: React.FC = () => {
)}
+
+
diff --git a/src/pages/Treasury.tsx b/src/pages/Treasury.tsx
index 52a71591..1b052fcf 100644
--- a/src/pages/Treasury.tsx
+++ b/src/pages/Treasury.tsx
@@ -13,6 +13,7 @@ import TreasuryHealthChart, {
type TreasuryPoint,
} from "../components/treasury/TreasuryHealthChart"
import TxHashLink from "../components/TxHashLink"
+import { ActivityFeedSkeleton } from "../components/SkeletonLoader"
import { useContractIds } from "../hooks/useContractIds"
import { useTreasury } from "../hooks/useTreasury"
import { useUSDC } from "../hooks/useUSDC"
@@ -258,10 +259,7 @@ const Treasury: React.FC = () => {
const description = `LearnVault's decentralized scholarship treasury holds ${displayStats.totalTreasury} and has funded ${displayStats.scholarsFunded} scholars. View real-time inflows and disbursements.`
return (
-
+
{title}
@@ -411,6 +409,33 @@ const Treasury: React.FC = () => {
Donate to Treasury
+
+ {/* Scholarship Program Metrics */}
+
+
+ Scholarship Program
+
+
+ Real-time health metrics for the active scholarship cohort.
+
+
+ {isLoading && (
+
+ {Array.from({ length: 6 }).map((_, i) => (
+
+ ))}
+
+ )}
+
+ {!isLoading && (
+
+ Scholarship metrics unavailable
+
+ )}
+
)
}
@@ -575,44 +600,36 @@ const ActivityFeed: React.FC<{
loadingMore = false,
onLoadMore,
}) => (
-
-
- {title}
-
-
- {loading ? (
-
- {Array.from({ length: 3 }).map((_, index) => (
-
- ))}
-
- ) : error ? (
- {error}
- ) : items.length === 0 ? (
-
- ) : (
- <>
- {items.map((item, i) => (
-
-
-
+
+ {title}
+
+
+ {loading ? (
+
+ ) : error ? (
+ {error}
+ ) : items.length === 0 ? (
+ {emptyMessage}
+ ) : (
+ <>
+ {items.map((item, i) => (
+
+
+
+
+ {item.user}
+
+ {item.time}
+
+
{item.user}
diff --git a/src/types/contracts.ts b/src/types/contracts.ts
index 80de2273..e62e6b14 100644
--- a/src/types/contracts.ts
+++ b/src/types/contracts.ts
@@ -57,7 +57,7 @@ export interface Vote {
proposalTitle: string
voteChoice: "for" | "against"
votePower: number
- status: "active" | "passed" | "rejected"
+ status: "active" | "queued" | "passed" | "rejected"
}
export interface Scholar {
diff --git a/src/types/governance.ts b/src/types/governance.ts
index 49c60989..8fe39829 100644
--- a/src/types/governance.ts
+++ b/src/types/governance.ts
@@ -15,7 +15,7 @@ export interface Proposal {
title: string
description: string
author: string
- status: "Active" | "Passed" | "Rejected"
+ status: "Active" | "Queued" | "Passed" | "Rejected" | "Executed"
votesFor: bigint
votesAgainst: bigint
endDate: number // ledger sequence or unix timestamp
diff --git a/tsconfig.app.tsbuildinfo b/tsconfig.app.tsbuildinfo
index 302ae58a..1917a5da 100644
--- a/tsconfig.app.tsbuildinfo
+++ b/tsconfig.app.tsbuildinfo
@@ -1 +1 @@
-{"root":["./src/App.tsx","./src/i18n.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/ActivityFeed.tsx","./src/components/AddressDisplay.tsx","./src/components/ComingSoon.tsx","./src/components/CommentCard.tsx","./src/components/CommentSection.tsx","./src/components/ConnectAccount.tsx","./src/components/ConnectWalletGuard.tsx","./src/components/CourseCard.tsx","./src/components/CourseFilter.tsx","./src/components/CourseProgressBar.tsx","./src/components/DeferredSection.tsx","./src/components/ErrorBoundary.tsx","./src/components/Footer.tsx","./src/components/FundAccountButton.tsx","./src/components/GetTestUSDCButton.tsx","./src/components/GuessTheNumber.tsx","./src/components/IpfsUpload.tsx","./src/components/LRNBalanceWidget.tsx","./src/components/LanguageSelector.tsx","./src/components/LessonContent.tsx","./src/components/LessonSidebar.tsx","./src/components/MilestoneCelebration.tsx","./src/components/MilestoneReportForm.tsx","./src/components/MilestoneSubmitPanel.tsx","./src/components/MilestoneTracker.tsx","./src/components/NavBar.tsx","./src/components/NetworkBanner.tsx","./src/components/NetworkPill.tsx","./src/components/NetworkPreconnect.tsx","./src/components/OnboardingWizard.tsx","./src/components/Pagination.tsx","./src/components/ProposalCard.tsx","./src/components/ProposalCountdown.test.ts","./src/components/ProposalCountdown.tsx","./src/components/QuizEngine.tsx","./src/components/ReputationBadge.tsx","./src/components/SkeletonLoader.tsx","./src/components/ThemeToggle.tsx","./src/components/TreasuryStatsBar.tsx","./src/components/TxHashLink.tsx","./src/components/WalletAddressPill.tsx","./src/components/WalletButton.tsx","./src/components/WalletToastWatcher.tsx","./src/components/Toast/ToastProvider.tsx","./src/components/debug/ContractExplorerPanel.tsx","./src/components/donor/ActiveVotes.tsx","./src/components/donor/DepositMore.tsx","./src/components/donor/EmptyState.tsx","./src/components/donor/GovernancePower.tsx","./src/components/donor/MyContributions.tsx","./src/components/donor/ScholarsFunded.tsx","./src/components/skeletons/CourseCardSkeleton.tsx","./src/components/skeletons/LessonListSkeleton.tsx","./src/components/treasury/TreasuryHealthChart.tsx","./src/contracts/governance_token.ts","./src/contracts/guess_the_number.ts","./src/contracts/scholarship_treasury.ts","./src/contracts/util.ts","./src/data/courses.ts","./src/data/lessons.ts","./src/hooks/useActivityFeed.ts","./src/hooks/useContractIds.ts","./src/hooks/useCourse.ts","./src/hooks/useDonor.test.tsx","./src/hooks/useDonor.ts","./src/hooks/useGovernance.test.tsx","./src/hooks/useGovernance.ts","./src/hooks/useLearnToken.test.tsx","./src/hooks/useLearnToken.ts","./src/hooks/useNotification.ts","./src/hooks/useScholarshipApplication.ts","./src/hooks/useSubscription.ts","./src/hooks/useWallet.test.tsx","./src/hooks/useWallet.ts","./src/lib/ipfs.ts","./src/pages/Admin.tsx","./src/pages/Courses.tsx","./src/pages/Credential.tsx","./src/pages/Dao.tsx","./src/pages/DaoProposals.tsx","./src/pages/DaoPropose.tsx","./src/pages/Dashboard.tsx","./src/pages/Debug.tsx","./src/pages/Donor.tsx","./src/pages/Home.tsx","./src/pages/Leaderboard.tsx","./src/pages/Learn.tsx","./src/pages/LessonView.tsx","./src/pages/NotFound.tsx","./src/pages/Profile.tsx","./src/pages/ScholarMilestones.tsx","./src/pages/ScholarshipApply.tsx","./src/pages/Treasury.tsx","./src/providers/NotificationProvider.tsx","./src/providers/WalletProvider.tsx","./src/test/setup.ts","./src/test/mocks/contracts.ts","./src/test/mocks/wallet.ts","./src/tests/setup.ts","./src/types/errors.ts","./src/types/governance.ts","./src/types/milestone.ts","./src/util/contract.test.ts","./src/util/contract.ts","./src/util/error.ts","./src/util/friendbot.test.ts","./src/util/friendbot.ts","./src/util/mockLeaderboardData.ts","./src/util/profileData.test.ts","./src/util/profileData.ts","./src/util/reputationRank.test.ts","./src/util/reputationRank.ts","./src/util/scholarshipApplications.ts","./src/util/scholarshipTreasury.ts","./src/util/storage.test.ts","./src/util/storage.ts","./src/util/theme.test.ts","./src/util/theme.ts","./src/util/tokenFormat.test.ts","./src/util/tokenFormat.ts","./src/util/usdc.ts","./src/util/wallet.test.ts","./src/util/wallet.ts","./src/utils/errors.ts","./e2e/critical-flows.spec.ts","./e2e/fixtures/mock-horizon.ts","./e2e/fixtures/mock-wallet.ts","./playwright.config.ts","./reset.d.ts"],"errors":true,"version":"5.9.3"}
\ No newline at end of file
+{"root":["./src/App.test.tsx","./src/App.tsx","./src/i18n.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/ActivityFeed.tsx","./src/components/AddressDisplay.tsx","./src/components/ComingSoon.tsx","./src/components/CommentCard.tsx","./src/components/CommentSection.tsx","./src/components/ConnectAccount.tsx","./src/components/ConnectWalletGuard.tsx","./src/components/CourseCard.tsx","./src/components/CourseFilter.tsx","./src/components/CourseProgressBar.tsx","./src/components/DeferredSection.tsx","./src/components/ErrorBoundary.tsx","./src/components/Footer.tsx","./src/components/FundAccountButton.tsx","./src/components/GetTestUSDCButton.tsx","./src/components/GlobalSearch.tsx","./src/components/GuessTheNumber.tsx","./src/components/IdenticonAvatar.tsx","./src/components/IpfsUpload.tsx","./src/components/LRNBalanceWidget.tsx","./src/components/LRNHistoryChart.tsx","./src/components/LanguageSelector.tsx","./src/components/LessonContent.tsx","./src/components/LessonSidebar.tsx","./src/components/MilestoneCelebration.tsx","./src/components/MilestoneReportForm.tsx","./src/components/MilestoneSubmitPanel.tsx","./src/components/MilestoneTracker.tsx","./src/components/NavBar.tsx","./src/components/NetworkBanner.tsx","./src/components/NetworkPill.tsx","./src/components/NetworkPreconnect.tsx","./src/components/NotificationBell.tsx","./src/components/OnboardingWizard.tsx","./src/components/Pagination.tsx","./src/components/ProfileEditForm.tsx","./src/components/ProfileLinkedWallets.tsx","./src/components/ProposalCard.test.tsx","./src/components/ProposalCard.tsx","./src/components/ProposalCountdown.test.ts","./src/components/ProposalCountdown.tsx","./src/components/QuizEngine.tsx","./src/components/ReputationBadge.tsx","./src/components/SkeletonLoader.tsx","./src/components/ThemeToggle.tsx","./src/components/TreasuryStatsBar.tsx","./src/components/TxHashLink.tsx","./src/components/WalletAddressPill.tsx","./src/components/WalletButton.tsx","./src/components/WalletInfoModal.tsx","./src/components/WalletToastWatcher.tsx","./src/components/Toast/ToastProvider.tsx","./src/components/debug/ContractExplorerPanel.tsx","./src/components/donor/ActiveVotes.tsx","./src/components/donor/DepositMore.tsx","./src/components/donor/EmptyState.tsx","./src/components/donor/GovernancePower.tsx","./src/components/donor/MyContributions.tsx","./src/components/donor/ScholarsFunded.tsx","./src/components/forum/CourseForum.tsx","./src/components/forum/ThreadDetail.tsx","./src/components/forum/ThreadList.tsx","./src/components/skeletons/CourseCardSkeleton.tsx","./src/components/skeletons/LessonListSkeleton.tsx","./src/components/states/emptyState.tsx","./src/components/states/errorState.tsx","./src/components/treasury/TreasuryHealthChart.tsx","./src/constants/contracts.ts","./src/constants/network.ts","./src/constants/reputation.test.ts","./src/constants/reputation.ts","./src/contracts/governance_token.ts","./src/contracts/guess_the_number.ts","./src/contracts/scholarship_treasury.ts","./src/contracts/util.ts","./src/data/courses.ts","./src/data/lessons.ts","./src/hooks/useActivityFeed.ts","./src/hooks/useAdmin.test.tsx","./src/hooks/useAdmin.ts","./src/hooks/useAdminContracts.test.tsx","./src/hooks/useAdminContracts.ts","./src/hooks/useContractIds.ts","./src/hooks/useCourse.ts","./src/hooks/useCourses.ts","./src/hooks/useDelegation.ts","./src/hooks/useDonor.test.tsx","./src/hooks/useDonor.ts","./src/hooks/useForum.ts","./src/hooks/useGovernance.test.tsx","./src/hooks/useGovernance.ts","./src/hooks/useLeaderboard.ts","./src/hooks/useLearnToken.test.tsx","./src/hooks/useLearnToken.ts","./src/hooks/useLearnerProfile.ts","./src/hooks/useNotification.ts","./src/hooks/useNotifications.ts","./src/hooks/useProposals.test.tsx","./src/hooks/useProposals.ts","./src/hooks/useScholarCredentials.ts","./src/hooks/useScholarMilestones.ts","./src/hooks/useScholarNft.ts","./src/hooks/useScholarshipApplication.ts","./src/hooks/useSubscription.ts","./src/hooks/useTheme.ts","./src/hooks/useTreasury.ts","./src/hooks/useUSDC.ts","./src/hooks/useWallet.test.tsx","./src/hooks/useWallet.ts","./src/hooks/useWiki.ts","./src/lib/api.ts","./src/lib/ipfs.ts","./src/pages/Admin.tsx","./src/pages/Community.tsx","./src/pages/Courses.tsx","./src/pages/Credential.tsx","./src/pages/Dao.tsx","./src/pages/DaoProposals.tsx","./src/pages/DaoPropose.test.tsx","./src/pages/DaoPropose.tsx","./src/pages/Dashboard.tsx","./src/pages/Debug.tsx","./src/pages/Donor.tsx","./src/pages/History.tsx","./src/pages/Home.tsx","./src/pages/Leaderboard.tsx","./src/pages/Learn.tsx","./src/pages/LessonView.tsx","./src/pages/NotFound.tsx","./src/pages/Profile.tsx","./src/pages/ScholarMilestones.tsx","./src/pages/ScholarshipApply.tsx","./src/pages/Treasury.tsx","./src/pages/Wiki.tsx","./src/pages/WikiPage.tsx","./src/providers/NotificationProvider.tsx","./src/providers/WalletProvider.tsx","./src/test/setup.ts","./src/test/mocks/contracts.ts","./src/test/mocks/wallet.ts","./src/tests/setup.ts","./src/types/contracts.ts","./src/types/courses.ts","./src/types/errors.ts","./src/types/forum.ts","./src/types/governance.ts","./src/types/milestone.ts","./src/util/auth.ts","./src/util/contract.test.ts","./src/util/contract.ts","./src/util/error.ts","./src/util/friendbot.test.ts","./src/util/friendbot.ts","./src/util/learningTime.ts","./src/util/mockLeaderboardData.ts","./src/util/profileData.test.ts","./src/util/profileData.ts","./src/util/reputationRank.test.ts","./src/util/reputationRank.ts","./src/util/scholarshipApplications.ts","./src/util/scholarshipTreasury.ts","./src/util/sorobanAdmin.ts","./src/util/storage.test.ts","./src/util/storage.ts","./src/util/theme.test.ts","./src/util/theme.ts","./src/util/tokenFormat.test.ts","./src/util/tokenFormat.ts","./src/util/usdc.test.ts","./src/util/usdc.ts","./src/util/wallet.test.ts","./src/util/wallet.ts","./src/utils/errors.ts","./src/utils/logger.ts","./e2e/a11y.spec.ts","./e2e/critical-flows.spec.ts","./e2e/wallet-auth.spec.ts","./e2e/fixtures/mock-horizon.ts","./e2e/fixtures/mock-wallet.ts","./playwright.config.ts","./reset.d.ts"],"errors":true,"version":"5.9.3"}
|