feat: Added AdminAuditLog page with paginated impersonation log table,…
- "frontend/src/pages/AdminAuditLog.tsx" - "frontend/src/pages/AdminAuditLog.module.css" - "frontend/src/App.tsx" - "frontend/src/components/AdminDropdown.tsx" GSD-Task: S07/T03
This commit is contained in:
parent
4969935c76
commit
4bda29705d
8 changed files with 346 additions and 2 deletions
|
|
@ -81,7 +81,7 @@
|
||||||
- Estimate: 1h
|
- Estimate: 1h
|
||||||
- Files: frontend/src/components/ConfirmModal.tsx, frontend/src/components/ConfirmModal.module.css, frontend/src/api/auth.ts, frontend/src/context/AuthContext.tsx, frontend/src/pages/AdminUsers.tsx, frontend/src/components/ImpersonationBanner.tsx, frontend/src/components/ImpersonationBanner.module.css
|
- Files: frontend/src/components/ConfirmModal.tsx, frontend/src/components/ConfirmModal.module.css, frontend/src/api/auth.ts, frontend/src/context/AuthContext.tsx, frontend/src/pages/AdminUsers.tsx, frontend/src/components/ImpersonationBanner.tsx, frontend/src/components/ImpersonationBanner.module.css
|
||||||
- Verify: cd frontend && npm run build 2>&1 | tail -5
|
- Verify: cd frontend && npm run build 2>&1 | tail -5
|
||||||
- [ ] **T03: Frontend: audit log admin page, route, and nav link** — Add an admin page displaying paginated impersonation audit log entries.
|
- [x] **T03: Added AdminAuditLog page with paginated impersonation log table, /admin/audit-log route, and Audit Log link in admin dropdown** — Add an admin page displaying paginated impersonation audit log entries.
|
||||||
|
|
||||||
## Steps
|
## Steps
|
||||||
|
|
||||||
|
|
|
||||||
16
.gsd/milestones/M021/slices/S07/tasks/T02-VERIFY.json
Normal file
16
.gsd/milestones/M021/slices/S07/tasks/T02-VERIFY.json
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"taskId": "T02",
|
||||||
|
"unitId": "M021/S07/T02",
|
||||||
|
"timestamp": 1775284058050,
|
||||||
|
"passed": true,
|
||||||
|
"discoverySource": "task-plan",
|
||||||
|
"checks": [
|
||||||
|
{
|
||||||
|
"command": "cd frontend",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 5,
|
||||||
|
"verdict": "pass"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
81
.gsd/milestones/M021/slices/S07/tasks/T03-SUMMARY.md
Normal file
81
.gsd/milestones/M021/slices/S07/tasks/T03-SUMMARY.md
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
---
|
||||||
|
id: T03
|
||||||
|
parent: S07
|
||||||
|
milestone: M021
|
||||||
|
provides: []
|
||||||
|
requires: []
|
||||||
|
affects: []
|
||||||
|
key_files: ["frontend/src/pages/AdminAuditLog.tsx", "frontend/src/pages/AdminAuditLog.module.css", "frontend/src/App.tsx", "frontend/src/components/AdminDropdown.tsx"]
|
||||||
|
key_decisions: ["Disabled Next button when current page returns zero entries as simple end-of-data signal"]
|
||||||
|
patterns_established: []
|
||||||
|
drill_down_paths: []
|
||||||
|
observability_surfaces: []
|
||||||
|
duration: ""
|
||||||
|
verification_result: "Vite build succeeds (exit 0). tsc -b output filtered for new files shows zero errors — all reported errors are pre-existing in ChapterReview.tsx."
|
||||||
|
completed_at: 2026-04-04T06:29:43.845Z
|
||||||
|
blocker_discovered: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# T03: Added AdminAuditLog page with paginated impersonation log table, /admin/audit-log route, and Audit Log link in admin dropdown
|
||||||
|
|
||||||
|
> Added AdminAuditLog page with paginated impersonation log table, /admin/audit-log route, and Audit Log link in admin dropdown
|
||||||
|
|
||||||
|
## What Happened
|
||||||
|
---
|
||||||
|
id: T03
|
||||||
|
parent: S07
|
||||||
|
milestone: M021
|
||||||
|
key_files:
|
||||||
|
- frontend/src/pages/AdminAuditLog.tsx
|
||||||
|
- frontend/src/pages/AdminAuditLog.module.css
|
||||||
|
- frontend/src/App.tsx
|
||||||
|
- frontend/src/components/AdminDropdown.tsx
|
||||||
|
key_decisions:
|
||||||
|
- Disabled Next button when current page returns zero entries as simple end-of-data signal
|
||||||
|
duration: ""
|
||||||
|
verification_result: passed
|
||||||
|
completed_at: 2026-04-04T06:29:43.845Z
|
||||||
|
blocker_discovered: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# T03: Added AdminAuditLog page with paginated impersonation log table, /admin/audit-log route, and Audit Log link in admin dropdown
|
||||||
|
|
||||||
|
**Added AdminAuditLog page with paginated impersonation log table, /admin/audit-log route, and Audit Log link in admin dropdown**
|
||||||
|
|
||||||
|
## What Happened
|
||||||
|
|
||||||
|
Created AdminAuditLog.tsx following AdminUsers patterns: useDocumentTitle, token-authenticated fetch via fetchImpersonationLog, loading/error/empty states, and a six-column table (Date/Time, Admin, Target User, Action, Write Mode, IP Address). Badge styling uses data-attributes for action (start=cyan, stop=slate) and write mode (yes=red, no=muted). Pagination uses Previous/Next buttons with page state. Created matching CSS module. Added lazy import and /admin/audit-log route in App.tsx. Added Audit Log link in AdminDropdown after Users.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
Vite build succeeds (exit 0). tsc -b output filtered for new files shows zero errors — all reported errors are pre-existing in ChapterReview.tsx.
|
||||||
|
|
||||||
|
## Verification Evidence
|
||||||
|
|
||||||
|
| # | Command | Exit Code | Verdict | Duration |
|
||||||
|
|---|---------|-----------|---------|----------|
|
||||||
|
| 1 | `cd frontend && npx vite build` | 0 | ✅ pass | 2020ms |
|
||||||
|
| 2 | `cd frontend && npx tsc -b 2>&1 | grep -v ChapterReview` | 0 | ✅ pass (no new errors) | 3000ms |
|
||||||
|
|
||||||
|
|
||||||
|
## Deviations
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
|
||||||
|
Pre-existing tsc -b errors in ChapterReview.tsx cause npm run build to exit non-zero. Unrelated to this task.
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
- `frontend/src/pages/AdminAuditLog.tsx`
|
||||||
|
- `frontend/src/pages/AdminAuditLog.module.css`
|
||||||
|
- `frontend/src/App.tsx`
|
||||||
|
- `frontend/src/components/AdminDropdown.tsx`
|
||||||
|
|
||||||
|
|
||||||
|
## Deviations
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
Pre-existing tsc -b errors in ChapterReview.tsx cause npm run build to exit non-zero. Unrelated to this task.
|
||||||
|
|
@ -20,6 +20,7 @@ const CreatorSettings = React.lazy(() => import("./pages/CreatorSettings"));
|
||||||
const ConsentDashboard = React.lazy(() => import("./pages/ConsentDashboard"));
|
const ConsentDashboard = React.lazy(() => import("./pages/ConsentDashboard"));
|
||||||
const WatchPage = React.lazy(() => import("./pages/WatchPage"));
|
const WatchPage = React.lazy(() => import("./pages/WatchPage"));
|
||||||
const AdminUsers = React.lazy(() => import("./pages/AdminUsers"));
|
const AdminUsers = React.lazy(() => import("./pages/AdminUsers"));
|
||||||
|
const AdminAuditLog = React.lazy(() => import("./pages/AdminAuditLog"));
|
||||||
const ChatPage = React.lazy(() => import("./pages/ChatPage"));
|
const ChatPage = React.lazy(() => import("./pages/ChatPage"));
|
||||||
const ChapterReview = React.lazy(() => import("./pages/ChapterReview"));
|
const ChapterReview = React.lazy(() => import("./pages/ChapterReview"));
|
||||||
import AdminDropdown from "./components/AdminDropdown";
|
import AdminDropdown from "./components/AdminDropdown";
|
||||||
|
|
@ -187,6 +188,7 @@ function AppShell() {
|
||||||
<Route path="/admin/pipeline" element={<Suspense fallback={<LoadingFallback />}><AdminPipeline /></Suspense>} />
|
<Route path="/admin/pipeline" element={<Suspense fallback={<LoadingFallback />}><AdminPipeline /></Suspense>} />
|
||||||
<Route path="/admin/techniques" element={<Suspense fallback={<LoadingFallback />}><AdminTechniquePages /></Suspense>} />
|
<Route path="/admin/techniques" element={<Suspense fallback={<LoadingFallback />}><AdminTechniquePages /></Suspense>} />
|
||||||
<Route path="/admin/users" element={<Suspense fallback={<LoadingFallback />}><AdminUsers /></Suspense>} />
|
<Route path="/admin/users" element={<Suspense fallback={<LoadingFallback />}><AdminUsers /></Suspense>} />
|
||||||
|
<Route path="/admin/audit-log" element={<Suspense fallback={<LoadingFallback />}><AdminAuditLog /></Suspense>} />
|
||||||
|
|
||||||
{/* Info routes */}
|
{/* Info routes */}
|
||||||
<Route path="/about" element={<Suspense fallback={<LoadingFallback />}><About /></Suspense>} />
|
<Route path="/about" element={<Suspense fallback={<LoadingFallback />}><About /></Suspense>} />
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,14 @@ export default function AdminDropdown() {
|
||||||
>
|
>
|
||||||
Users
|
Users
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/admin/audit-log"
|
||||||
|
className="admin-dropdown__item"
|
||||||
|
role="menuitem"
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
>
|
||||||
|
Audit Log
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
133
frontend/src/pages/AdminAuditLog.module.css
Normal file
133
frontend/src/pages/AdminAuditLog.module.css
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
.page {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
color: var(--text-primary, #e2e8f0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 0.6rem 0.75rem;
|
||||||
|
border-bottom: 2px solid var(--color-border, #2d2d3d);
|
||||||
|
color: var(--text-secondary, #828291);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table td {
|
||||||
|
padding: 0.6rem 0.75rem;
|
||||||
|
border-bottom: 1px solid var(--color-border, #2d2d3d);
|
||||||
|
color: var(--text-primary, #e2e8f0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.actionBadge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.15rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actionBadge[data-action="start"] {
|
||||||
|
background: rgba(34, 211, 238, 0.15);
|
||||||
|
color: #22d3ee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actionBadge[data-action="stop"] {
|
||||||
|
background: rgba(148, 163, 184, 0.15);
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.writeBadge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.15rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.writeBadge[data-write="yes"] {
|
||||||
|
background: rgba(239, 68, 68, 0.15);
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.writeBadge[data-write="no"] {
|
||||||
|
background: rgba(148, 163, 184, 0.1);
|
||||||
|
color: #828291;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ip {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pageBtn {
|
||||||
|
padding: 0.35rem 0.75rem;
|
||||||
|
border: 1px solid var(--color-border, #2d2d3d);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-primary, #e2e8f0);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 150ms, border-color 150ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pageBtn:hover:not(:disabled) {
|
||||||
|
border-color: var(--color-accent, #22d3ee);
|
||||||
|
background: rgba(34, 211, 238, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pageBtn:disabled {
|
||||||
|
opacity: 0.35;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pageNum {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--text-secondary, #828291);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading,
|
||||||
|
.error,
|
||||||
|
.empty {
|
||||||
|
text-align: center;
|
||||||
|
padding: 3rem 1rem;
|
||||||
|
color: var(--text-secondary, #828291);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.table th:nth-child(6),
|
||||||
|
.table td:nth-child(6) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
104
frontend/src/pages/AdminAuditLog.tsx
Normal file
104
frontend/src/pages/AdminAuditLog.tsx
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useAuth } from "../context/AuthContext";
|
||||||
|
import { fetchImpersonationLog, type ImpersonationLogEntry } from "../api/auth";
|
||||||
|
import { useDocumentTitle } from "../hooks/useDocumentTitle";
|
||||||
|
import styles from "./AdminAuditLog.module.css";
|
||||||
|
|
||||||
|
export default function AdminAuditLog() {
|
||||||
|
useDocumentTitle("Audit Log — Admin");
|
||||||
|
const { token } = useAuth();
|
||||||
|
const [entries, setEntries] = useState<ImpersonationLogEntry[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!token) return;
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
fetchImpersonationLog(token, page)
|
||||||
|
.then(setEntries)
|
||||||
|
.catch((e) => setError(e.message || "Failed to load audit log"))
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
}, [token, page]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className={styles.page}>
|
||||||
|
<h1 className={styles.title}>Audit Log</h1>
|
||||||
|
<p className={styles.loading}>Loading audit log…</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className={styles.page}>
|
||||||
|
<h1 className={styles.title}>Audit Log</h1>
|
||||||
|
<p className={styles.error}>{error}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.page}>
|
||||||
|
<h1 className={styles.title}>Audit Log</h1>
|
||||||
|
{entries.length === 0 ? (
|
||||||
|
<p className={styles.empty}>No impersonation log entries found.</p>
|
||||||
|
) : (
|
||||||
|
<table className={styles.table}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date/Time</th>
|
||||||
|
<th>Admin</th>
|
||||||
|
<th>Target User</th>
|
||||||
|
<th>Action</th>
|
||||||
|
<th>Write Mode</th>
|
||||||
|
<th>IP Address</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{entries.map((entry) => (
|
||||||
|
<tr key={entry.id}>
|
||||||
|
<td>{new Date(entry.created_at).toLocaleString()}</td>
|
||||||
|
<td>{entry.admin_name}</td>
|
||||||
|
<td>{entry.target_name}</td>
|
||||||
|
<td>
|
||||||
|
<span className={styles.actionBadge} data-action={entry.action}>
|
||||||
|
{entry.action}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span
|
||||||
|
className={styles.writeBadge}
|
||||||
|
data-write={entry.write_mode ? "yes" : "no"}
|
||||||
|
>
|
||||||
|
{entry.write_mode ? "Yes" : "No"}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className={styles.ip}>{entry.ip_address ?? "—"}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)}
|
||||||
|
<div className={styles.pagination}>
|
||||||
|
<button
|
||||||
|
className={styles.pageBtn}
|
||||||
|
disabled={page <= 1}
|
||||||
|
onClick={() => setPage((p) => Math.max(1, p - 1))}
|
||||||
|
>
|
||||||
|
← Previous
|
||||||
|
</button>
|
||||||
|
<span className={styles.pageNum}>Page {page}</span>
|
||||||
|
<button
|
||||||
|
className={styles.pageBtn}
|
||||||
|
disabled={entries.length === 0}
|
||||||
|
onClick={() => setPage((p) => p + 1)}
|
||||||
|
>
|
||||||
|
Next →
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/admin-pipeline.ts","./src/api/admin-techniques.ts","./src/api/auth.ts","./src/api/chat.ts","./src/api/client.ts","./src/api/consent.ts","./src/api/creator-dashboard.ts","./src/api/creators.ts","./src/api/index.ts","./src/api/reports.ts","./src/api/search.ts","./src/api/stats.ts","./src/api/techniques.ts","./src/api/topics.ts","./src/api/videos.ts","./src/components/AdminDropdown.tsx","./src/components/AppFooter.tsx","./src/components/AudioWaveform.tsx","./src/components/CategoryIcons.tsx","./src/components/ChapterMarkers.tsx","./src/components/ConfirmModal.tsx","./src/components/CopyLinkButton.tsx","./src/components/CreatorAvatar.tsx","./src/components/ImpersonationBanner.tsx","./src/components/PlayerControls.tsx","./src/components/ProtectedRoute.tsx","./src/components/ReportIssueModal.tsx","./src/components/SearchAutocomplete.tsx","./src/components/SocialIcons.tsx","./src/components/SortDropdown.tsx","./src/components/TableOfContents.tsx","./src/components/TagList.tsx","./src/components/ToggleSwitch.tsx","./src/components/TranscriptSidebar.tsx","./src/components/VideoPlayer.tsx","./src/context/AuthContext.tsx","./src/hooks/useCountUp.ts","./src/hooks/useDocumentTitle.ts","./src/hooks/useMediaSync.ts","./src/hooks/useSortPreference.ts","./src/pages/About.tsx","./src/pages/AdminPipeline.tsx","./src/pages/AdminReports.tsx","./src/pages/AdminTechniquePages.tsx","./src/pages/AdminUsers.tsx","./src/pages/ChapterReview.tsx","./src/pages/ChatPage.tsx","./src/pages/ConsentDashboard.tsx","./src/pages/CreatorDashboard.tsx","./src/pages/CreatorDetail.tsx","./src/pages/CreatorSettings.tsx","./src/pages/CreatorsBrowse.tsx","./src/pages/Home.tsx","./src/pages/Login.tsx","./src/pages/Register.tsx","./src/pages/SearchResults.tsx","./src/pages/SubTopicPage.tsx","./src/pages/TechniquePage.tsx","./src/pages/TopicsBrowse.tsx","./src/pages/WatchPage.tsx","./src/utils/catSlug.ts","./src/utils/citations.tsx"],"errors":true,"version":"5.6.3"}
|
{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/admin-pipeline.ts","./src/api/admin-techniques.ts","./src/api/auth.ts","./src/api/chat.ts","./src/api/client.ts","./src/api/consent.ts","./src/api/creator-dashboard.ts","./src/api/creators.ts","./src/api/index.ts","./src/api/reports.ts","./src/api/search.ts","./src/api/stats.ts","./src/api/techniques.ts","./src/api/topics.ts","./src/api/videos.ts","./src/components/AdminDropdown.tsx","./src/components/AppFooter.tsx","./src/components/AudioWaveform.tsx","./src/components/CategoryIcons.tsx","./src/components/ChapterMarkers.tsx","./src/components/ConfirmModal.tsx","./src/components/CopyLinkButton.tsx","./src/components/CreatorAvatar.tsx","./src/components/ImpersonationBanner.tsx","./src/components/PlayerControls.tsx","./src/components/ProtectedRoute.tsx","./src/components/ReportIssueModal.tsx","./src/components/SearchAutocomplete.tsx","./src/components/SocialIcons.tsx","./src/components/SortDropdown.tsx","./src/components/TableOfContents.tsx","./src/components/TagList.tsx","./src/components/ToggleSwitch.tsx","./src/components/TranscriptSidebar.tsx","./src/components/VideoPlayer.tsx","./src/context/AuthContext.tsx","./src/hooks/useCountUp.ts","./src/hooks/useDocumentTitle.ts","./src/hooks/useMediaSync.ts","./src/hooks/useSortPreference.ts","./src/pages/About.tsx","./src/pages/AdminAuditLog.tsx","./src/pages/AdminPipeline.tsx","./src/pages/AdminReports.tsx","./src/pages/AdminTechniquePages.tsx","./src/pages/AdminUsers.tsx","./src/pages/ChapterReview.tsx","./src/pages/ChatPage.tsx","./src/pages/ConsentDashboard.tsx","./src/pages/CreatorDashboard.tsx","./src/pages/CreatorDetail.tsx","./src/pages/CreatorSettings.tsx","./src/pages/CreatorsBrowse.tsx","./src/pages/Home.tsx","./src/pages/Login.tsx","./src/pages/Register.tsx","./src/pages/SearchResults.tsx","./src/pages/SubTopicPage.tsx","./src/pages/TechniquePage.tsx","./src/pages/TopicsBrowse.tsx","./src/pages/WatchPage.tsx","./src/utils/catSlug.ts","./src/utils/citations.tsx"],"errors":true,"version":"5.6.3"}
|
||||||
Loading…
Add table
Reference in a new issue