feat(frontend): Phase 2/3 hardening release

Roadmap P0
- Ship /logo.svg fallback and rewire asset references (superseded later by
  the project-manager PNG in a separate commit).

Roadmap P1
- Response envelope alignment ({items,total,page,size}) across services
  and query hooks.
- Approval/ticket UX updates tied to the new backend rollback semantics.

Roadmap P2
- React.lazy + Vite manualChunks to split vendor-radix/charts/icons/forms
  from the main bundle and cut initial JS.
- Fix the 181 tsc errors (PaginationParams class + per-service generics).
- Auto-refresh flow for JWT refresh tokens wired into api-client.ts.

Roadmap P3
- i18n: i18next + language detector, en/ar locales, RTL auto-switch,
  LanguageToggle component in RoleLayout and AdminLmsLayout.
- GDPR: PrivacyCenter page for data export and right-to-erasure, backed
  by gdpr.service.ts; logout via AuthContext after erasure.
- Human-in-the-loop exam review UI: admin ExamReviewQueue + ExamReviewDetail
  pages, useExamReview hooks, exam-review.service.ts.
- AI quality loop: AIPromptEditor (versioning + render preview),
  AIFeedbackButtons for students, AIFeedbackTriage admin page, dedicated
  services, hooks, and types.
- Dark mode: ThemeProvider + ThemeToggle + chart color tokens.
- Accessibility: DialogDescription on every DialogContent; alert-dialog
  and sheet updates.
- Retire dead pages: ExamPage marketing, Index, ProfilePage import.
- Playwright e2e scaffolding: playwright.config.ts + e2e/login.spec.ts
  smoke tests, npm scripts test:e2e / test:e2e:install.

Made-with: Cursor
This commit is contained in:
Yamen Ahmad
2026-04-19 14:16:32 +04:00
parent 9b20d6ce66
commit b02c2e7526
75 changed files with 3727 additions and 551 deletions

View File

@@ -50,7 +50,7 @@ export default function TaxonomyManager() {
const [showAddTopic, setShowAddTopic] = useState<number | null>(null);
const [newTopicName, setNewTopicName] = useState("");
const [newTopicDifficulty, setNewTopicDifficulty] = useState("medium");
const [newTopicDifficulty, setNewTopicDifficulty] = useState<"easy" | "medium" | "hard">("medium");
const [newTopicHours, setNewTopicHours] = useState("1");
const [editingSubjectId, setEditingSubjectId] = useState<number | null>(null);
@@ -62,7 +62,7 @@ export default function TaxonomyManager() {
const [editingTopicId, setEditingTopicId] = useState<number | null>(null);
const [editTopicName, setEditTopicName] = useState("");
const [editTopicDifficulty, setEditTopicDifficulty] = useState("medium");
const [editTopicDifficulty, setEditTopicDifficulty] = useState<"easy" | "medium" | "hard">("medium");
const [editTopicHours, setEditTopicHours] = useState("1");
const [editTopicDesc, setEditTopicDesc] = useState("");
@@ -142,7 +142,7 @@ export default function TaxonomyManager() {
const startEditTopic = (t: { id: number; name: string; difficulty_level?: string; estimated_hours?: number; description?: string }) => {
setEditingTopicId(t.id);
setEditTopicName(t.name);
setEditTopicDifficulty(t.difficulty_level ?? "medium");
setEditTopicDifficulty((t.difficulty_level as "easy" | "medium" | "hard") ?? "medium");
setEditTopicHours(String(t.estimated_hours ?? 1));
setEditTopicDesc(t.description ?? "");
};
@@ -333,7 +333,7 @@ export default function TaxonomyManager() {
{showAddTopic === domain.id && (
<div className="flex items-center gap-2 p-2 rounded border bg-muted/50">
<Input placeholder="Topic name" value={newTopicName} onChange={e => setNewTopicName(e.target.value)} className="h-8 text-sm" />
<Select value={newTopicDifficulty} onValueChange={setNewTopicDifficulty}>
<Select value={newTopicDifficulty} onValueChange={(v) => setNewTopicDifficulty(v as "easy" | "medium" | "hard")}>
<SelectTrigger className="w-28 h-8"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="easy">Easy</SelectItem>
@@ -352,7 +352,7 @@ export default function TaxonomyManager() {
<div className="space-y-2">
<div className="flex items-center gap-2">
<Input value={editTopicName} onChange={e => setEditTopicName(e.target.value)} className="h-7 text-sm flex-1" placeholder="Topic name" />
<Select value={editTopicDifficulty} onValueChange={setEditTopicDifficulty}>
<Select value={editTopicDifficulty} onValueChange={(v) => setEditTopicDifficulty(v as "easy" | "medium" | "hard")}>
<SelectTrigger className="w-28 h-7"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="easy">Easy</SelectItem>