From 0403813b8954be68283f4b48b3f6b43e799c17d0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=80=9Ckirukib=E2=80=9D?= <“kirubeljkl679@gmail.com”>
Date: Fri, 27 Feb 2026 16:58:05 +0300
Subject: [PATCH 04/10] -
---
.env | 2 +-
package-lock.json | 19 +------------------
2 files changed, 2 insertions(+), 19 deletions(-)
diff --git a/.env b/.env
index ff1a1d8..9eb5bee 100644
--- a/.env
+++ b/.env
@@ -1,2 +1,2 @@
-VITE_API_BASE_URL=http://localhost:8080/api/v1
+VITE_API_BASE_URL= https://api.yimaru.yaltopia.com/
VITE_GOOGLE_CLIENT_ID=google_client_id
diff --git a/package-lock.json b/package-lock.json
index 6fc436d..c725334 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -88,7 +88,6 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@@ -2754,7 +2753,6 @@
"integrity": "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"undici-types": "~7.16.0"
}
@@ -2765,7 +2763,6 @@
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
"devOptional": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -2776,7 +2773,6 @@
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"devOptional": true,
"license": "MIT",
- "peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -2832,7 +2828,6 @@
"integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.50.0",
"@typescript-eslint/types": "8.50.0",
@@ -3084,7 +3079,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -3323,7 +3317,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -3900,7 +3893,6 @@
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -5015,7 +5007,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -5063,7 +5054,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -5252,7 +5242,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -5262,7 +5251,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -5282,7 +5270,6 @@
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0"
@@ -5488,8 +5475,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/redux-thunk": {
"version": "3.1.0",
@@ -5893,7 +5879,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -6061,7 +6046,6 @@
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@@ -6199,7 +6183,6 @@
"integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==",
"dev": true,
"license": "MIT",
- "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
From d0e694bc079fb6a183a8c64e516e5b558fb6bc24 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=80=9Ckirukib=E2=80=9D?= <“kirubeljkl679@gmail.com”>
Date: Fri, 27 Feb 2026 17:08:11 +0300
Subject: [PATCH 05/10] t
---
.env | 4 ++--
src/pages/auth/LoginPage.tsx | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.env b/.env
index 86dd9a4..e5a2141 100644
--- a/.env
+++ b/.env
@@ -1,3 +1,3 @@
-VITE_API_BASE_URL=http://localhost:8080/api/v1
-VITE_GOOGLE_CLIENT_ID=google_client_id
+VITE_API_BASE_URL= http://api.yimaru.yaltopia.com/
+VITE_GOOGLE_CLIENT_ID= google_client_id
VERSION=1.0.0
diff --git a/src/pages/auth/LoginPage.tsx b/src/pages/auth/LoginPage.tsx
index 3b1d522..0de60fd 100644
--- a/src/pages/auth/LoginPage.tsx
+++ b/src/pages/auth/LoginPage.tsx
@@ -216,7 +216,7 @@ export function LoginPage() {
- Yimaru Academy
+ Yimaru Academy Test Mode
Manage your academy, track student progress, and streamline
From e35defe48a7b43690d81205d08da6a8fe0fd5734 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=80=9Ckirukib=E2=80=9D?= <“kirubeljkl679@gmail.com”>
Date: Fri, 27 Feb 2026 19:03:47 +0300
Subject: [PATCH 06/10] ui-fixes
---
.env | 2 +-
src/pages/DashboardPage.tsx | 163 +++---
src/pages/ProfilePage.tsx | 515 ++++++++++--------
src/pages/analytics/AnalyticsPage.tsx | 254 +++++----
src/pages/issues/IssuesPage.tsx | 2 +-
src/pages/user-log/UserLogPage.tsx | 2 +-
.../UserManagementDashboard.tsx | 20 +-
7 files changed, 571 insertions(+), 387 deletions(-)
diff --git a/.env b/.env
index 9eb5bee..a4657ce 100644
--- a/.env
+++ b/.env
@@ -1,2 +1,2 @@
-VITE_API_BASE_URL= https://api.yimaru.yaltopia.com/
+VITE_API_BASE_URL= https://api.yimaru.yaltopia.com/api/v1
VITE_GOOGLE_CLIENT_ID=google_client_id
diff --git a/src/pages/DashboardPage.tsx b/src/pages/DashboardPage.tsx
index aae2257..dc0b049 100644
--- a/src/pages/DashboardPage.tsx
+++ b/src/pages/DashboardPage.tsx
@@ -27,7 +27,7 @@ import {
} from "recharts"
import { StatCard } from "../components/dashboard/StatCard"
import { Card, CardContent, CardHeader, CardTitle } from "../components/ui/card"
-// import { cn } from "../lib/utils"
+import { cn } from "../lib/utils"
import { getTeamMemberById } from "../api/team.api"
import { getDashboard } from "../api/analytics.api"
import { useEffect, useState } from "react"
@@ -44,6 +44,7 @@ export function DashboardPage() {
const [userFirstName, setUserFirstName] = useState("")
const [dashboard, setDashboard] = useState(null)
const [loading, setLoading] = useState(true)
+ const [activeStatTab, setActiveStatTab] = useState<"primary" | "secondary">("primary")
useEffect(() => {
const fetchUser = async () => {
@@ -115,69 +116,109 @@ export function DashboardPage() {
Failed to load dashboard data.
) : (
<>
- {/* Stat Cards */}
-
-
0}
- />
- 0}
- />
- 0}
- />
- 0.5}
- />
+ {/* Stat tabs */}
+
+
+
+
+
+ {/* Stat Cards */}
+ {activeStatTab === "primary" && (
+
+ 0}
+ />
+ 0}
+ />
+ 0}
+ />
+ 0.5}
+ />
+
+ )}
+
{/* Secondary Stats */}
-
-
-
-
-
-
+ {activeStatTab === "secondary" && (
+
+
+
+
+
+
+ )}
{/* User Registrations Chart */}
diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx
index c632bce..b86c816 100644
--- a/src/pages/ProfilePage.tsx
+++ b/src/pages/ProfilePage.tsx
@@ -44,7 +44,7 @@ function formatDateTime(dateStr: string | null | undefined): string {
function LoadingSkeleton() {
return (
-
+
{/* Hero skeleton */}
@@ -93,15 +93,15 @@ function InfoRow({
extra?: React.ReactNode;
}) {
return (
-
+
-
-
{value || "—"}
+
+ {value || "—"}
{extra}
@@ -121,13 +121,13 @@ function VerifiedIcon({ verified }: { verified: boolean }) {
}
function ProgressRing({ percent }: { percent: number }) {
- const radius = 18;
+ const radius = 14;
const circumference = 2 * Math.PI * radius;
const offset = circumference - (percent / 100) * circumference;
return (
-
);
}
@@ -179,7 +179,7 @@ export function ProfilePage() {
if (error || !profile) {
return (
-
+
@@ -203,227 +203,310 @@ export function ProfilePage() {
const initials = `${profile.first_name?.[0] ?? ""}${profile.last_name?.[0] ?? ""}`.toUpperCase();
const completionPct = profile.profile_completion_percentage ?? 0;
- const sectionCardIcons: Record
= {
- personal: { icon: User, color: "from-brand-500 to-brand-600" },
- contact: { icon: Mail, color: "from-brand-400 to-brand-500" },
- account: { icon: Shield, color: "from-brand-600 to-brand-500" },
- };
-
return (
-
- {/* Hero Card */}
-
- {/* Banner gradient */}
-
- {/* Decorative pattern overlay */}
-
-
+
+ {/* Page header (no tabs) */}
+
+
+ {/* Main profile layout card */}
+
+ {/* Header strip */}
+
+
+
+
Overview
+
+ Personal, job and account details for this team member.
+
+
- {/* Bottom fade */}
-
-
-
- {/* Avatar */}
-
-
-
- {initials}
-
-
-
- {/* Name */}
-
- {fullName}
-
-
- {/* Role badge */}
-
-
- {profile.role}
-
-
- {/* Status pills */}
-
- {/* Active status */}
-
-
- {profile.status}
+
+
+ {/* Left column: About & details */}
+
+ {/* Identity */}
+
+
+
+
+ {initials}
+
+
+
+
+
{fullName}
+
+ #{profile.id}
+
+
+
+
+
+ {profile.role}
+
+
+
+ Joined {formatDate(profile.created_at)}
+
+
+
+
+
+ {profile.status}
+
+
+
+
- {/* Email verification */}
-
- {profile.email_verified ? (
-
- ) : (
-
- )}
- Email {profile.email_verified ? "Verified" : "Unverified"}
+ {/* About / Contact */}
+
+
+ About
+
+
+ } />
+ } />
+
+
- {/* Phone verification */}
-
- {profile.phone_verified ? (
-
- ) : (
-
- )}
- Phone {profile.phone_verified ? "Verified" : "Unverified"}
+ {/* Employee details */}
+
+
+ Employee details
+
+
+
+
- Date of birth
+ -
+ {formatDate(profile.birth_day)}
+
+
+
+
- Age
+ -
+ {profile.age ? `${profile.age} years` : "—"}
+
+
+
+
- Gender
+ -
+ {profile.gender || "Not specified"}
+
+
+
+
- Age group
+ -
+ {profile.age_group || "—"}
+
+
+
+
- Occupation
+ -
+ {profile.occupation || "—"}
+
+
+
+
- Preferred language
+ -
+ {profile.preferred_language || "—"}
+
+
+
+
+
+
+ {/* Middle column: Job information */}
+
+
+
+ Job information
+
+
+
+
+
+ | Title |
+ Team |
+ Division |
+ Manager |
+ Hire date |
+ Location |
+
+
+
+
+ | {profile.occupation || profile.role} |
+ {profile.role} |
+ {profile.preferred_language || "—"} |
+ — |
+ {formatDate(profile.created_at)} |
+
+ {[profile.region, profile.country].filter(Boolean).join(", ") || "—"}
+ |
+
+
+
+
- {/* Profile completion ring */}
-
-
-
Profile Complete
+ {/* Learning & goals */}
+
+
+
+
+ Learning goal
+
+
+
+
+ {profile.learning_goal || "No learning goal specified."}
+
+
+
+
+
+
+
+ Language goal
+
+
+
+
+ {profile.language_goal || "No language goal specified."}
+
+
+
+
+
+
+ {/* Right column: Activity & account summary */}
+
+ {/* Activity */}
+
+
+ Activity
+
+
+
+
+
+
+
+
+
+
+ Last login
+
+
+ {formatDateTime(profile.last_login)}
+
+
+
+
+
+
+
+
+
+
+ Account created
+
+
+ {formatDateTime(profile.created_at)}
+
+
+
+
+
+
+
+ {/* Account summary */}
+
+
+ Account
+
+
+
+
+ Role
+ {profile.role}
+
+
+ Status
+
+
+ {profile.status}
+
+
+
+ Email
+
+
+ {profile.email}
+
+
+
+
+
+ Phone
+
+
+ {profile.phone_number || "—"}
+
+
+
+
+
+
-
-
-
- {/* Info Cards */}
-
- {/* Personal Information */}
-
-
-
-
-
-
-
-
- Personal Information
-
-
-
-
-
-
-
-
-
-
-
-
- {/* Contact & Location */}
-
-
-
-
-
-
-
-
- Contact & Location
-
-
-
-
- }
- />
- }
- />
-
-
-
-
-
- {/* Account Details */}
-
-
-
-
-
-
-
-
- Account Details
-
-
-
-
-
-
-
-
-
- }
- />
-
-
+
);
diff --git a/src/pages/analytics/AnalyticsPage.tsx b/src/pages/analytics/AnalyticsPage.tsx
index 882fd0c..d4d37d2 100644
--- a/src/pages/analytics/AnalyticsPage.tsx
+++ b/src/pages/analytics/AnalyticsPage.tsx
@@ -282,6 +282,7 @@ export function AnalyticsPage() {
const [dashboard, setDashboard] = useState
(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(false)
+ const [activeSummaryTab, setActiveSummaryTab] = useState<"key" | "content" | "operations">("key")
const fetchData = async () => {
setLoading(true)
@@ -384,107 +385,166 @@ export function AnalyticsPage() {
+ {/* Summary Tabs */}
+
+
+
+
+
+
+
+
- {/* ─── Key Metrics ─── */}
-
-
- 0 ? "up" : "neutral"}
- />
- 0 ? "up" : "neutral"}
- />
- 0 ? "up" : "neutral"}
- />
- = 0.5 ? "up" : "down"}
- />
-
-
+ {activeSummaryTab === "key" && (
+ <>
+ {/* ─── Key Metrics ─── */}
+
+
+ 0 ? "up" : "neutral"}
+ />
+ 0 ? "up" : "neutral"}
+ />
+ 0 ? "up" : "neutral"}
+ />
+ = 0.5 ? "up" : "down"}
+ />
+
+
+ >
+ )}
- {/* ─── Content & Platform ─── */}
-
-
-
-
+ {/* ─── Content & Platform ─── */}
+
-
-
-
-
+ count={courses.total_courses + content.total_questions}
+ defaultOpen
+ >
+
+
+
+
+
+
+
+ >
+ )}
- {/* ─── Operations ─── */}
-
-
-
-
- 0 ? "up" : "neutral"}
- />
- `${q.count} ${q.label.toLowerCase()}`).join(" · ")}
- trend="neutral"
- />
-
-
+ {activeSummaryTab === "operations" && (
+ <>
+ {/* ─── Operations ─── */}
+
+
+
+
+ 0 ? "up" : "neutral"}
+ />
+ `${q.count} ${q.label.toLowerCase()}`).join(" · ")}
+ trend="neutral"
+ />
+
+
+ >
+ )}
{/* ─── User Analytics ─── */}
diff --git a/src/pages/issues/IssuesPage.tsx b/src/pages/issues/IssuesPage.tsx
index d16a2e7..7241c29 100644
--- a/src/pages/issues/IssuesPage.tsx
+++ b/src/pages/issues/IssuesPage.tsx
@@ -512,7 +512,7 @@ export function IssuesPage() {
-
+
diff --git a/src/pages/user-log/UserLogPage.tsx b/src/pages/user-log/UserLogPage.tsx
index c3ebe60..fc4da43 100644
--- a/src/pages/user-log/UserLogPage.tsx
+++ b/src/pages/user-log/UserLogPage.tsx
@@ -404,7 +404,7 @@ export function UserLogPage() {
-
-
Total Users
-
1,248
+
Total Users
+
1,248
-
+
-
+
-
Active Users
-
1,180
+
Active Users
+
1,180
-
+
-
+
-
New This Month
-
64
+
New This Month
+
64
From cd2ed669607d632d236bdc8744a55af0fd987368 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=80=9Ckirukib=E2=80=9D?= <“kirubeljkl679@gmail.com”>
Date: Fri, 27 Feb 2026 19:31:41 +0300
Subject: [PATCH 07/10] ui+plus
---
src/api/courses.api.ts | 4 +
src/api/users.api.ts | 14 +
src/pages/ProfilePage.tsx | 66 +-
.../content-management/AddQuestionPage.tsx | 23 +-
.../content-management/CourseCategoryPage.tsx | 106 ++-
src/pages/issues/IssuesPage.tsx | 122 +++-
src/pages/notifications/NotificationsPage.tsx | 675 ++++++++++++++++--
.../user-management/RegisterUserPage.tsx | 113 ++-
src/types/course.types.ts | 4 +
9 files changed, 979 insertions(+), 148 deletions(-)
diff --git a/src/api/courses.api.ts b/src/api/courses.api.ts
index 7bf5ce7..c831e5a 100644
--- a/src/api/courses.api.ts
+++ b/src/api/courses.api.ts
@@ -37,11 +37,15 @@ import type {
CreateQuestionRequest,
CreateQuestionResponse,
CreateVimeoVideoRequest,
+ CreateCourseCategoryRequest,
} from "../types/course.types"
export const getCourseCategories = () =>
http.get
("/course-management/categories")
+export const createCourseCategory = (data: CreateCourseCategoryRequest) =>
+ http.post("/course-management/categories", data)
+
export const getCoursesByCategory = (categoryId: number) =>
http.get(`/course-management/categories/${categoryId}/courses`)
diff --git a/src/api/users.api.ts b/src/api/users.api.ts
index 89e3ca4..30936b6 100644
--- a/src/api/users.api.ts
+++ b/src/api/users.api.ts
@@ -14,3 +14,17 @@ export const getUserById = (id: number) =>
export const getMyProfile = () =>
http.get("/team/me");
+
+// Best-guess API for creating a new user (admin-side).
+// Adjust payload shape or endpoint if backend differs.
+export interface CreateUserRequest {
+ first_name: string;
+ last_name: string;
+ email: string;
+ phone_number: string;
+ role: string;
+ notes?: string;
+}
+
+export const createUser = (payload: CreateUserRequest) =>
+ http.post("/users", payload);
diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx
index b86c816..933fa24 100644
--- a/src/pages/ProfilePage.tsx
+++ b/src/pages/ProfilePage.tsx
@@ -226,7 +226,7 @@ export function ProfilePage() {
-
+
{/* Left column: About & details */}
{/* Identity */}
@@ -348,70 +348,6 @@ export function ProfilePage() {
- {/* Middle column: Job information */}
-
-
-
- Job information
-
-
-
-
-
- | Title |
- Team |
- Division |
- Manager |
- Hire date |
- Location |
-
-
-
-
- | {profile.occupation || profile.role} |
- {profile.role} |
- {profile.preferred_language || "—"} |
- — |
- {formatDate(profile.created_at)} |
-
- {[profile.region, profile.country].filter(Boolean).join(", ") || "—"}
- |
-
-
-
-
-
-
- {/* Learning & goals */}
-
-
-
-
- Learning goal
-
-
-
-
- {profile.learning_goal || "No learning goal specified."}
-
-
-
-
-
-
-
- Language goal
-
-
-
-
- {profile.language_goal || "No language goal specified."}
-
-
-
-
-
-
{/* Right column: Activity & account summary */}
{/* Activity */}
diff --git a/src/pages/content-management/AddQuestionPage.tsx b/src/pages/content-management/AddQuestionPage.tsx
index 13ec07c..85f7251 100644
--- a/src/pages/content-management/AddQuestionPage.tsx
+++ b/src/pages/content-management/AddQuestionPage.tsx
@@ -1,6 +1,7 @@
import { useState } from "react"
import { useNavigate, useParams } from "react-router-dom"
import { ArrowLeft, Plus, X } from "lucide-react"
+import { toast } from "sonner"
import { Button } from "../../components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "../../components/ui/card"
import { Input } from "../../components/ui/input"
@@ -105,32 +106,44 @@ export function AddQuestionPage() {
// Validation
if (!formData.question.trim()) {
- alert("Please enter a question")
+ toast.error("Missing question", {
+ description: "Please enter a question before saving.",
+ })
return
}
if (formData.type === "multiple-choice" || formData.type === "true-false") {
if (!formData.correctAnswer) {
- alert("Please select a correct answer")
+ toast.error("Missing correct answer", {
+ description: "Select the correct answer for this question.",
+ })
return
}
if (formData.type === "multiple-choice") {
const hasEmptyOptions = formData.options.some((opt) => !opt.trim())
if (hasEmptyOptions) {
- alert("Please fill in all options")
+ toast.error("Incomplete options", {
+ description: "Fill in all answer options for this multiple choice question.",
+ })
return
}
}
} else if (formData.type === "short-answer") {
if (!formData.correctAnswer.trim()) {
- alert("Please enter a correct answer")
+ toast.error("Missing correct answer", {
+ description: "Enter the expected correct answer.",
+ })
return
}
}
// In a real app, save the question here
console.log("Saving question:", formData)
- alert(isEditing ? "Question updated successfully!" : "Question created successfully!")
+ toast.success(isEditing ? "Question updated" : "Question created", {
+ description: isEditing
+ ? "The question has been updated successfully."
+ : "Your new question has been created.",
+ })
navigate("/content/questions")
}
diff --git a/src/pages/content-management/CourseCategoryPage.tsx b/src/pages/content-management/CourseCategoryPage.tsx
index fd44f88..6804ef6 100644
--- a/src/pages/content-management/CourseCategoryPage.tsx
+++ b/src/pages/content-management/CourseCategoryPage.tsx
@@ -1,14 +1,27 @@
import { useEffect, useState } from "react"
import { Link } from "react-router-dom"
-import { FolderOpen, RefreshCw, AlertCircle, BookOpen } from "lucide-react"
+import { FolderOpen, RefreshCw, AlertCircle, BookOpen, Plus } from "lucide-react"
import { Card, CardContent, CardHeader, CardTitle } from "../../components/ui/card"
-import { getCourseCategories } from "../../api/courses.api"
+import { Button } from "../../components/ui/button"
+import { Input } from "../../components/ui/input"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from "../../components/ui/dialog"
+import { getCourseCategories, createCourseCategory } from "../../api/courses.api"
import type { CourseCategory } from "../../types/course.types"
+import { toast } from "sonner"
export function CourseCategoryPage() {
const [categories, setCategories] = useState
([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
+ const [createOpen, setCreateOpen] = useState(false)
+ const [newCategoryName, setNewCategoryName] = useState("")
+ const [creating, setCreating] = useState(false)
const fetchCategories = async () => {
setLoading(true)
@@ -68,11 +81,21 @@ export function CourseCategoryPage() {
return (
{/* Page header */}
-
-
Course Categories
-
- Browse and manage your course categories below
-
+
+
+
Course Categories
+
+ Browse and manage your course categories below
+
+
+
{categories.length === 0 ? (
@@ -126,6 +149,75 @@ export function CourseCategoryPage() {
))}
)}
+
+ {/* Create category dialog */}
+
)
}
diff --git a/src/pages/issues/IssuesPage.tsx b/src/pages/issues/IssuesPage.tsx
index 7241c29..891f63c 100644
--- a/src/pages/issues/IssuesPage.tsx
+++ b/src/pages/issues/IssuesPage.tsx
@@ -20,6 +20,7 @@ import {
CheckCircle2,
XCircle,
ArrowUpCircle,
+ MessageCircle,
} from "lucide-react";
import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input";
@@ -201,6 +202,12 @@ export function IssuesPage() {
// Status update
const [statusUpdating, setStatusUpdating] = useState(null);
+ // Create issue dialog (admin-created)
+ const [createOpen, setCreateOpen] = useState(false);
+ const [createSubject, setCreateSubject] = useState("");
+ const [createType, setCreateType] = useState("bug");
+ const [createDescription, setCreateDescription] = useState("");
+
const fetchIssues = useCallback(async () => {
setLoading(true);
try {
@@ -345,17 +352,26 @@ export function IssuesPage() {
Review and manage user-reported issues across the platform.
-
+
+
+
+
{/* Stats cards */}
@@ -840,6 +856,90 @@ export function IssuesPage() {
+ {/* Create Issue Dialog */}
+
+
{/* Delete Confirmation Dialog */}