diff --git a/package-lock.json b/package-lock.json
index 0fb2f56..3a96594 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,6 +23,7 @@
"lucide-react": "^0.561.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
+ "react-is": "^19.2.5",
"react-router-dom": "^7.10.1",
"recharts": "^3.6.0",
"sonner": "^2.0.7",
@@ -5315,11 +5316,10 @@
}
},
"node_modules/react-is": {
- "version": "19.2.3",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.3.tgz",
- "integrity": "sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA==",
- "license": "MIT",
- "peer": true
+ "version": "19.2.5",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.5.tgz",
+ "integrity": "sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==",
+ "license": "MIT"
},
"node_modules/react-redux": {
"version": "9.2.0",
diff --git a/package.json b/package.json
index eba2940..57f998e 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"lucide-react": "^0.561.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
+ "react-is": "^19.2.5",
"react-router-dom": "^7.10.1",
"recharts": "^3.6.0",
"sonner": "^2.0.7",
diff --git a/src/app/AppRoutes.tsx b/src/app/AppRoutes.tsx
index 5c75232..7502414 100644
--- a/src/app/AppRoutes.tsx
+++ b/src/app/AppRoutes.tsx
@@ -1,52 +1,59 @@
-import { Navigate, Route, Routes } from "react-router-dom"
-import { AppLayout } from "../layouts/AppLayout"
-import { DashboardPage } from "../pages/DashboardPage"
-import { AnalyticsPage } from "../pages/analytics/AnalyticsPage"
-import { ContentManagementLayout } from "../pages/content-management/ContentManagementLayout"
-import { CourseCategoryPage } from "../pages/content-management/CourseCategoryPage"
-import { AllCoursesPage } from "../pages/content-management/AllCoursesPage"
-import { CourseFlowBuilderPage } from "../pages/content-management/CourseFlowBuilderPage"
-import { ContentOverviewPage } from "../pages/content-management/ContentOverviewPage"
-import { CoursesPage } from "../pages/content-management/CoursesPage"
-import { PracticeQuestionsPage } from "../pages/content-management/PracticeQuestionsPage"
-import { AddNewPracticePage } from "../pages/content-management/AddNewPracticePage"
-import { SubModulesPage } from "../pages/content-management/SubCoursesPage"
-import { SubModuleContentPage } from "../pages/content-management/SubCourseContentPage"
-import { SpeakingPage } from "../pages/content-management/SpeakingPage"
-import { AddVideoPage } from "../pages/content-management/AddVideoPage"
-import { AddPracticePage } from "../pages/content-management/AddPracticePage"
-import { NotFoundPage } from "../pages/NotFoundPage"
-import { NotificationsPage } from "../pages/notifications/NotificationsPage"
-import { CreateNotificationPage } from "../pages/notifications/CreateNotificationPage"
-import { UserDetailPage } from "../pages/user-management/UserDetailPage"
-import { UserManagementLayout } from "../pages/user-management/UserManagementLayout"
-import { UsersListPage } from "../pages/user-management/UsersListPage"
-import { UserManagementDashboard } from "../pages/user-management/UserManagementDashboard"
-import { UserGroupsPage } from "../pages/user-management/UserGroupsPage"
-import { DeletionRequestsPage } from "../pages/user-management/DeletionRequestsPage"
-import { RoleManagementLayout } from "../pages/role-management/RoleManagementLayout"
-import { RolesListPage } from "../pages/role-management/RolesListPage"
-import { AddRolePage } from "../pages/role-management/AddRolePage"
-import { PracticeDetailsPage } from "../pages/content-management/PracticeDetailsPage"
-import { PracticeMembersPage } from "../pages/content-management/PracticeMembersPage"
-import { QuestionsPage } from "../pages/content-management/QuestionsPage"
-import { AddQuestionPage } from "../pages/content-management/AddQuestionPage"
-import { HumanLanguagePage } from "../pages/content-management/HumanLanguagePage"
-import { HumanLanguageSubModulePage } from "../pages/content-management/HumanLanguageSubModulePage"
-import { UserLogPage } from "../pages/user-log/UserLogPage"
-import { IssuesPage } from "../pages/issues/IssuesPage"
-import { ProfilePage } from "../pages/ProfilePage"
-import { SettingsPage } from "../pages/SettingsPage"
-import { TeamManagementPage } from "../pages/team/TeamManagementPage"
-import { AddTeamMemberPage } from "../pages/team/AddTeamMemberPage"
-import { TeamMemberDetailPage } from "../pages/team/TeamMemberDetailPage"
-import { LoginPage } from "../pages/auth/LoginPage"
-import { ForgotPasswordPage } from "../pages/auth/ForgotPasswordPage"
-import { VerificationPage } from "../pages/auth/VerificationPage"
-import { AboutPage } from "../pages/AboutPage"
-import { TermsPage } from "../pages/TermsPage"
-import { PrivacyPage } from "../pages/PrivacyPage"
-import { AccountDeletionPage } from "../pages/AccountDeletionPage"
+import { Navigate, Route, Routes } from "react-router-dom";
+import { AppLayout } from "../layouts/AppLayout";
+import { DashboardPage } from "../pages/DashboardPage";
+import { AnalyticsPage } from "../pages/analytics/AnalyticsPage";
+import { ContentManagementLayout } from "../pages/content-management/ContentManagementLayout";
+import { CourseCategoryPage } from "../pages/content-management/CourseCategoryPage";
+import { AllCoursesPage } from "../pages/content-management/AllCoursesPage";
+import { CourseFlowBuilderPage } from "../pages/content-management/CourseFlowBuilderPage";
+import { ContentOverviewPage } from "../pages/content-management/ContentOverviewPage";
+import { CoursesPage } from "../pages/content-management/CoursesPage";
+import { PracticeQuestionsPage } from "../pages/content-management/PracticeQuestionsPage";
+import { AddNewPracticePage } from "../pages/content-management/AddNewPracticePage";
+import { SubModulesPage } from "../pages/content-management/SubCoursesPage";
+import { SubModuleContentPage } from "../pages/content-management/SubCourseContentPage";
+import { SpeakingPage } from "../pages/content-management/SpeakingPage";
+import { AddVideoPage } from "../pages/content-management/AddVideoPage";
+import { AddPracticePage } from "../pages/content-management/AddPracticePage";
+import { NewContentPage } from "../pages/content-management/NewContentPage";
+import { LearnEnglishPage } from "../pages/content-management/LearnEnglishPage";
+import { ProgramCoursesPage } from "../pages/content-management/ProgramCoursesPage";
+import { CourseDetailPage } from "../pages/content-management/CourseDetailPage";
+import { ModuleDetailPage } from "../pages/content-management/ModuleDetailPage";
+import { AddVideoFlow } from "../pages/content-management/AddVideoFlow";
+import { AddPracticeFlow } from "../pages/content-management/AddPracticeFlow";
+import { NotFoundPage } from "../pages/NotFoundPage";
+import { NotificationsPage } from "../pages/notifications/NotificationsPage";
+import { CreateNotificationPage } from "../pages/notifications/CreateNotificationPage";
+import { UserDetailPage } from "../pages/user-management/UserDetailPage";
+import { UserManagementLayout } from "../pages/user-management/UserManagementLayout";
+import { UsersListPage } from "../pages/user-management/UsersListPage";
+import { UserManagementDashboard } from "../pages/user-management/UserManagementDashboard";
+import { UserGroupsPage } from "../pages/user-management/UserGroupsPage";
+import { DeletionRequestsPage } from "../pages/user-management/DeletionRequestsPage";
+import { RoleManagementLayout } from "../pages/role-management/RoleManagementLayout";
+import { RolesListPage } from "../pages/role-management/RolesListPage";
+import { AddRolePage } from "../pages/role-management/AddRolePage";
+import { PracticeDetailsPage } from "../pages/content-management/PracticeDetailsPage";
+import { PracticeMembersPage } from "../pages/content-management/PracticeMembersPage";
+import { QuestionsPage } from "../pages/content-management/QuestionsPage";
+import { AddQuestionPage } from "../pages/content-management/AddQuestionPage";
+import { HumanLanguagePage } from "../pages/content-management/HumanLanguagePage";
+import { HumanLanguageSubModulePage } from "../pages/content-management/HumanLanguageSubModulePage";
+import { UserLogPage } from "../pages/user-log/UserLogPage";
+import { IssuesPage } from "../pages/issues/IssuesPage";
+import { ProfilePage } from "../pages/ProfilePage";
+import { SettingsPage } from "../pages/SettingsPage";
+import { TeamManagementPage } from "../pages/team/TeamManagementPage";
+import { AddTeamMemberPage } from "../pages/team/AddTeamMemberPage";
+import { TeamMemberDetailPage } from "../pages/team/TeamMemberDetailPage";
+import { LoginPage } from "../pages/auth/LoginPage";
+import { ForgotPasswordPage } from "../pages/auth/ForgotPasswordPage";
+import { VerificationPage } from "../pages/auth/VerificationPage";
+import { AboutPage } from "../pages/AboutPage";
+import { TermsPage } from "../pages/TermsPage";
+import { PrivacyPage } from "../pages/PrivacyPage";
+import { AccountDeletionPage } from "../pages/AccountDeletionPage";
export function AppRoutes() {
return (
@@ -91,19 +98,52 @@ export function AppRoutes() {
path="human-language/:categoryId/:courseId/sub-module/:subModuleId"
element={}
/>
- } />
- } />
+ }
+ />
+ }
+ />
{/* Course → Sub-module → Lesson/Practice */}
- } />
- } />
- } />
- } />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
{/* Legacy aliases */}
- } />
- } />
- } />
- } />
- } />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
} />
} />
} />
@@ -113,8 +153,37 @@ export function AppRoutes() {
} />
+ } />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+
} />
- } />
+ }
+ />
} />
} />
} />
@@ -128,7 +197,5 @@ export function AppRoutes() {
} />
- )
+ );
}
-
-
diff --git a/src/assets/icons/upload.png b/src/assets/icons/upload.png
new file mode 100644
index 0000000..44342e3
Binary files /dev/null and b/src/assets/icons/upload.png differ
diff --git a/src/components/sidebar/Sidebar.tsx b/src/components/sidebar/Sidebar.tsx
index 10bd6da..ce361c2 100644
--- a/src/components/sidebar/Sidebar.tsx
+++ b/src/components/sidebar/Sidebar.tsx
@@ -13,57 +13,65 @@ import {
Users,
Users2,
X,
-} from "lucide-react"
-import { type ComponentType, useEffect, useState } from "react"
-import { NavLink } from "react-router-dom"
-import { cn } from "../../lib/utils"
-import { BrandLogo } from "../brand/BrandLogo"
-import { getUnreadCount } from "../../api/notifications.api"
+} from "lucide-react";
+import { type ComponentType, useEffect, useState } from "react";
+import { NavLink } from "react-router-dom";
+import { cn } from "../../lib/utils";
+import { BrandLogo } from "../brand/BrandLogo";
+import { getUnreadCount } from "../../api/notifications.api";
type NavItem = {
- label: string
- to: string
- icon: ComponentType<{ className?: string }>
-}
+ label: string;
+ to: string;
+ icon: ComponentType<{ className?: string }>;
+};
const navItems: NavItem[] = [
{ label: "Dashboard", to: "/dashboard", icon: LayoutDashboard },
{ label: "User Management", to: "/users", icon: Users },
{ label: "Role Management", to: "/roles", icon: Shield },
{ label: "Content Management", to: "/content", icon: BookOpen },
+ { label: "New Content", to: "/new-content", icon: BookOpen },
+
{ label: "Notifications", to: "/notifications", icon: Bell },
{ label: "User Log", to: "/user-log", icon: ClipboardList },
{ label: "Issue Reports", to: "/issues", icon: CircleAlert },
{ label: "Analytics", to: "/analytics", icon: BarChart3 },
{ label: "Team Management", to: "/team", icon: Users2 },
{ label: "Profile", to: "/profile", icon: UserCircle2 },
-]
+];
type SidebarProps = {
- isOpen: boolean
- isCollapsed: boolean
- onToggleCollapse: () => void
- onClose: () => void
-}
+ isOpen: boolean;
+ isCollapsed: boolean;
+ onToggleCollapse: () => void;
+ onClose: () => void;
+};
-export function Sidebar({ isOpen, isCollapsed, onToggleCollapse, onClose }: SidebarProps) {
- const [unreadCount, setUnreadCount] = useState(0)
+export function Sidebar({
+ isOpen,
+ isCollapsed,
+ onToggleCollapse,
+ onClose,
+}: SidebarProps) {
+ const [unreadCount, setUnreadCount] = useState(0);
useEffect(() => {
const fetchUnread = async () => {
try {
- const res = await getUnreadCount()
- setUnreadCount(res.data.unread)
+ const res = await getUnreadCount();
+ setUnreadCount(res.data.unread);
} catch {
// silently fail
}
- }
+ };
- fetchUnread()
+ fetchUnread();
- window.addEventListener("notifications-updated", fetchUnread)
- return () => window.removeEventListener("notifications-updated", fetchUnread)
- }, [])
+ window.addEventListener("notifications-updated", fetchUnread);
+ return () =>
+ window.removeEventListener("notifications-updated", fetchUnread);
+ }, []);
return (
<>
@@ -86,7 +94,12 @@ export function Sidebar({ isOpen, isCollapsed, onToggleCollapse, onClose }: Side
isOpen ? "translate-x-0" : "-translate-x-full",
)}
>
-
+
{isCollapsed ? (
@@ -103,7 +116,11 @@ export function Sidebar({ isOpen, isCollapsed, onToggleCollapse, onClose }: Side
onClick={onToggleCollapse}
aria-label={isCollapsed ? "Expand sidebar" : "Collapse sidebar"}
>
- {isCollapsed ? : }
+ {isCollapsed ? (
+
+ ) : (
+
+ )}
- {!isCollapsed && {item.label}}
- {!isCollapsed && item.to === "/notifications" && unreadCount > 0 && (
-
- {unreadCount > 99 ? "99+" : unreadCount}
-
+ {!isCollapsed && (
+ {item.label}
)}
- {!isCollapsed && item.to !== "/notifications" && isActive ? (
+ {!isCollapsed &&
+ item.to === "/notifications" &&
+ unreadCount > 0 && (
+
+ {unreadCount > 99 ? "99+" : unreadCount}
+
+ )}
+ {!isCollapsed &&
+ item.to !== "/notifications" &&
+ isActive ? (
- ) : !isCollapsed && item.to === "/notifications" && unreadCount === 0 && isActive ? (
+ ) : !isCollapsed &&
+ item.to === "/notifications" &&
+ unreadCount === 0 &&
+ isActive ? (
) : null}
>
)}
- )
+ );
})}
@@ -169,8 +197,8 @@ export function Sidebar({ isOpen, isCollapsed, onToggleCollapse, onClose }: Side
>
- )
+ );
}
diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx
index c46b5cd..83cec90 100644
--- a/src/components/ui/input.tsx
+++ b/src/components/ui/input.tsx
@@ -1,21 +1,21 @@
-import * as React from "react"
-import { cn } from "../../lib/utils"
+import * as React from "react";
+import { cn } from "../../lib/utils";
export interface InputProps extends React.InputHTMLAttributes
{}
-export const Input = React.forwardRef(({ className, type, ...props }, ref) => {
- return (
-
- )
-})
-Input.displayName = "Input"
-
-
+export const Input = React.forwardRef(
+ ({ className, type, ...props }, ref) => {
+ return (
+
+ );
+ },
+);
+Input.displayName = "Input";
diff --git a/src/components/ui/stepper.tsx b/src/components/ui/stepper.tsx
index a4627aa..a0c87a2 100644
--- a/src/components/ui/stepper.tsx
+++ b/src/components/ui/stepper.tsx
@@ -1,61 +1,52 @@
-import * as React from "react"
-import { Check } from "lucide-react"
-import { cn } from "../../lib/utils"
+import { cn } from "../../lib/utils";
export interface StepperProps {
- steps: string[]
- currentStep: number
- className?: string
+ steps: string[];
+ currentStep: number;
+ className?: string;
}
export function Stepper({ steps, currentStep, className }: StepperProps) {
return (
-
+
{steps.map((step, index) => {
- const stepNumber = index + 1
- const isCompleted = stepNumber < currentStep
- const isCurrent = stepNumber === currentStep
+ const stepNumber = index + 1;
+ const isCurrent = stepNumber === currentStep;
return (
-
-
-
-
- {isCompleted ? : stepNumber}
-
-
- {step}
-
-
-
+
+ {/* Connector Line (Behind) */}
{index < steps.length - 1 && (
-
+
)}
-
- )
+
+ {/* Circle */}
+
+ {stepNumber}
+
+
+ {/* Label */}
+
+ {step}
+
+
+ );
})}
- )
+ );
}
-
diff --git a/src/pages/content-management/AddPracticeFlow.tsx b/src/pages/content-management/AddPracticeFlow.tsx
new file mode 100644
index 0000000..7e3b742
--- /dev/null
+++ b/src/pages/content-management/AddPracticeFlow.tsx
@@ -0,0 +1,259 @@
+import { useState } from "react";
+import {
+ Link,
+ useNavigate,
+ useParams,
+ useSearchParams,
+} from "react-router-dom";
+import { ArrowLeft } from "lucide-react";
+import { Button } from "../../components/ui/button";
+import { Stepper } from "../../components/ui/stepper";
+import successIcon from "../../assets/success.svg";
+
+import { ContextStep } from "./components/practice-steps/ContextStep";
+import { ScenarioStep } from "./components/practice-steps/ScenarioStep";
+import { PersonaStep } from "./components/practice-steps/PersonaStep";
+import { QuestionsStep } from "./components/practice-steps/QuestionsStep";
+import { ReviewStep } from "./components/practice-steps/ReviewStep";
+
+export function AddPracticeFlow() {
+ const navigate = useNavigate();
+ const { level } = useParams<{ level: string }>();
+ const [searchParams] = useSearchParams();
+ const backTo = searchParams.get("backTo");
+ const courseId = searchParams.get("courseId");
+ const moduleId = searchParams.get("moduleId");
+
+ const isModuleContext = backTo === "module";
+
+ const backLabel =
+ backTo === "module"
+ ? "Back to Module"
+ : backTo === "modules"
+ ? "Back to Modules"
+ : "Back to Courses";
+ const backPath =
+ backTo === "module" && courseId && moduleId
+ ? `/new-content/learn-english/${level}/courses/${courseId}/modules/${moduleId}`
+ : backTo === "modules" && courseId
+ ? `/new-content/learn-english/${level}/courses/${courseId}`
+ : `/new-content/learn-english/${level}/courses`;
+
+ const flowSteps = isModuleContext
+ ? ["Context", "Persona", "Questions", "Review"]
+ : ["Context", "Scenario", "Persona", "Questions", "Review"];
+
+ const [currentStep, setCurrentStep] = useState(1);
+ const [selectedPersona, setSelectedPersona] = useState
(
+ "dawit",
+ );
+ const [isPublished, setIsPublished] = useState(false);
+
+ const [formData, setFormData] = useState({
+ program: "Intermediate",
+ course: "A2",
+ title: "",
+ description: "",
+ selectedVideo: "",
+ tips: "Focus on using the present perfect continuous tense to describe an action that started in the past and continues now.",
+ questions: [
+ {
+ id: "q1",
+ text: "How long have you been studying English?",
+ type: "Speaking",
+ voicePrompt: "prompt_q1_en.mp3",
+ sampleAnswer: "prompt_q1_en.mp3",
+ },
+ ],
+ });
+
+ const nextStep = () =>
+ setCurrentStep((prev) => Math.min(prev + 1, flowSteps.length));
+ const prevStep = () => setCurrentStep((prev) => Math.max(prev - 1, 1));
+
+ if (isPublished) {
+ return (
+
+
+
+

+
+
+ Practice Published Successfully!
+
+
+ Your speaking practice is now active and available inside the module.
+
+
+
+
+
+
+ );
+ }
+
+ // Helper to map currentStep to the actual component for the module flow
+ const renderStep = () => {
+ if (!isModuleContext) {
+ switch (currentStep) {
+ case 1:
+ return (
+
+ );
+ case 2:
+ return (
+
+ );
+ case 3:
+ return (
+
+ );
+ case 4:
+ return (
+
+ );
+ case 5:
+ return (
+
+ );
+ default:
+ return null;
+ }
+ } else {
+ // Module Context Flow (Skips Scenario)
+ switch (currentStep) {
+ case 1:
+ return (
+
+ );
+ case 2:
+ return (
+
+ );
+ case 3:
+ return (
+
+ );
+ case 4:
+ return (
+
+ );
+ default:
+ return null;
+ }
+ }
+ };
+
+ return (
+
+ {/* Header */}
+
+
+
+
+ {backLabel}
+
+
+
+
+
+
+ Add New Practice
+
+
+ Create a new immersive practice session for students.
+
+
+
+
+
+
+
+
{renderStep()}
+
+
+ );
+}
diff --git a/src/pages/content-management/AddVideoFlow.tsx b/src/pages/content-management/AddVideoFlow.tsx
new file mode 100644
index 0000000..7086bd4
--- /dev/null
+++ b/src/pages/content-management/AddVideoFlow.tsx
@@ -0,0 +1,155 @@
+import { useState } from "react";
+import { Link, useNavigate, useParams } from "react-router-dom";
+import { ArrowLeft, Check } from "lucide-react";
+import { Button } from "../../components/ui/button";
+import { Stepper } from "../../components/ui/stepper";
+
+import { VideoDetailStep } from "./components/video-steps/VideoDetailStep";
+import { ReviewPublishStep } from "./components/video-steps/ReviewPublishStep";
+
+const STEPS = [
+ { id: 1, label: "Video Detail" },
+ { id: 2, label: "Review & Publish" },
+];
+
+export function AddVideoFlow() {
+ const navigate = useNavigate();
+ const { level, courseId, moduleId } = useParams<{
+ level: string;
+ courseId: string;
+ moduleId: string;
+ }>();
+ const [currentStep, setCurrentStep] = useState(1);
+ const [isPublished, setIsPublished] = useState(false);
+
+ const [formData, setFormData] = useState({
+ title: "",
+ order: "1",
+ description: "",
+ thumbnail: null,
+ videoFile: null,
+ });
+
+ const nextStep = () => setCurrentStep((prev) => Math.min(prev + 1, 2));
+ const prevStep = () => setCurrentStep((prev) => Math.max(prev - 1, 1));
+
+ const backPath = `/new-content/learn-english/${level}/courses/${courseId}/modules/${moduleId}`;
+
+ if (isPublished) {
+ return (
+
+ {/* Success Icon Wrapper (Jagged Circle Style) */}
+
+
+
+
+
+
+ {/* Sub-Jagged layer for depth if needed */}
+
+
+
+
+
+ Video Published Successfully!
+
+
+ Your video is now live and available inside the selected module.
+
+
+
+
+
+
+
+ );
+ }
+
+ return (
+
+ {/* Header */}
+
+
+
+
+ Back to Modules
+
+
+
+
+
+ Add New Video
+
+
+
+ s.label)}
+ currentStep={currentStep}
+ />
+
+
+ {/* Step Content */}
+
+ {currentStep === 1 && (
+
+ )}
+
+ {currentStep === 2 && (
+
+ )}
+
+
+
+ );
+}
diff --git a/src/pages/content-management/CourseDetailPage.tsx b/src/pages/content-management/CourseDetailPage.tsx
new file mode 100644
index 0000000..52896fd
--- /dev/null
+++ b/src/pages/content-management/CourseDetailPage.tsx
@@ -0,0 +1,160 @@
+import { useState } from "react";
+import { ArrowLeft, Plus, Calendar, Plane, Clock, Hand } from "lucide-react";
+import { Link, useNavigate, useParams } from "react-router-dom";
+import { Button } from "../../components/ui/button";
+import { Card } from "../../components/ui/card";
+import { cn } from "../../lib/utils";
+
+const MODULES = [
+ {
+ id: "m1",
+ title: "Introduction Basics",
+ description: "Learn basic English words, phrases, and simple sentences.",
+ icon: Hand,
+ status: "Published",
+ gradient: "from-[#8E44AD] to-[#C39BD3]",
+ },
+ {
+ id: "m2",
+ title: "Daily Routines",
+ description: "Vocabulary related to waking up, and evening activities.",
+ icon: Clock,
+ status: "Draft",
+ gradient: "from-[#8E44AD] to-[#C39BD3]",
+ },
+ {
+ id: "m3",
+ title: "Travel Essentials",
+ description:
+ "Key phrases for airports, hotels, and asking for help while abroad.",
+ icon: Plane,
+ status: "Draft",
+ gradient: "from-[#8E44AD] to-[#C39BD3]",
+ },
+];
+
+import { AddModuleModal } from "./components/AddModuleModal";
+
+export function CourseDetailPage() {
+ const navigate = useNavigate();
+ const { level, courseId } = useParams<{ level: string; courseId: string }>();
+ const [isAddModuleOpen, setIsAddModuleOpen] = useState(false);
+
+ return (
+
+ {/* Header Navigation */}
+
+
+ {/* Hero Section */}
+
+
+
+ {courseId?.toUpperCase() || "A1"}
+
+
+ Learn basic English words, phrases, and simple sentences for daily
+ situations.
+
+
+
+
+
+
+
+
+
setIsAddModuleOpen(false)}
+ />
+
+ {/* Gradient Grid */}
+
+ {MODULES.map((module) => (
+
+ {/* Gradient Banner */}
+
+
+
+
+ {/* Icon Circle */}
+
+
+
+
+ {/* Content */}
+
+
+ {module.title}
+
+
+ {module.description}
+
+
+
+
+ {/* Actions */}
+
+
+ {module.status === "Published" ? (
+
+ ) : (
+
+ )}
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/pages/content-management/LearnEnglishPage.tsx b/src/pages/content-management/LearnEnglishPage.tsx
new file mode 100644
index 0000000..05e5070
--- /dev/null
+++ b/src/pages/content-management/LearnEnglishPage.tsx
@@ -0,0 +1,214 @@
+import { Plus, ArrowRight } from "lucide-react";
+import { Link } from "react-router-dom";
+import { Card, CardContent } from "../../components/ui/card";
+import { Button } from "../../components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogTrigger,
+ DialogClose,
+} from "../../components/ui/dialog";
+import { Input } from "../../components/ui/input";
+import { Select } from "../../components/ui/select";
+import uploadIcon from "../../assets/icons/upload.png";
+
+export function LearnEnglishPage() {
+ const levels = [
+ {
+ id: "beginner",
+ title: "Beginner",
+ description:
+ "Designed for learners starting from scratch. Focuses on simple grammar, and everyday communication.",
+ },
+ {
+ id: "intermediate",
+ title: "Intermediate",
+ description:
+ "For learners who can communicate at a basic level and want to improve fluency, accuracy, and confidence.",
+ },
+ {
+ id: "advanced",
+ title: "Advanced",
+ description:
+ "Targets advanced learners aiming for professional, academic, and complex conversational English.",
+ },
+ ];
+
+ return (
+
+ {/* Header section */}
+
+
+
+ Learn English
+
+
+ Manage learning content by level
+
+
+
+
+
+
+ {/* Gradient Divider */}
+
+
+ {/* Cards Grid */}
+
+ {levels.map((level) => (
+
+ {/* Gradient Header */}
+
+
+
+ {level.title}
+
+
+ {level.description}
+
+
+
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/pages/content-management/ModuleDetailPage.tsx b/src/pages/content-management/ModuleDetailPage.tsx
new file mode 100644
index 0000000..834a4b9
--- /dev/null
+++ b/src/pages/content-management/ModuleDetailPage.tsx
@@ -0,0 +1,321 @@
+import { useState } from "react";
+import {
+ ArrowLeft,
+ Video,
+ Calendar,
+ Mic,
+ Layers,
+ Edit2,
+ Trash2,
+} from "lucide-react";
+import { Link, useNavigate, useParams } from "react-router-dom";
+import { Button } from "../../components/ui/button";
+import { cn } from "../../lib/utils";
+import { VideoCard } from "./components/VideoCard";
+
+const MOCK_VIDEOS = [
+ {
+ id: "v1",
+ title: "1.1 Introduction to Formal Greetings",
+ duration: "08:45",
+ status: "Draft",
+ thumbnailGradient: "from-[#CBD5E1] to-[#94A3B8]",
+ },
+ {
+ id: "v2",
+ title: "1.2 Understanding Email Structure",
+ duration: "08:45",
+ status: "Published",
+ thumbnailGradient: "from-[#DBEAFE] to-[#93C5FD]",
+ },
+ {
+ id: "v3",
+ title: "1.3 Common Business Idioms",
+ duration: "08:45",
+ status: "Published",
+ thumbnailGradient: "from-[#FEF3C7] to-[#FCD34D]",
+ },
+ {
+ id: "v4",
+ title: "1.4 Video Conference Etiquette",
+ duration: "08:45",
+ status: "Published",
+ thumbnailGradient: "from-[#FCE7F3] to-[#F9A8D4]",
+ },
+];
+
+const MOCK_PRACTICES = [
+ {
+ id: "p1",
+ title: "Describe a Photo",
+ level: "IELTS",
+ variations: 12,
+ status: "Draft",
+ },
+ {
+ id: "p2",
+ title: "Describe a Photo",
+ level: "IELTS",
+ variations: 12,
+ status: "Draft",
+ },
+ {
+ id: "p3",
+ title: "Describe a Photo",
+ level: "IELTS",
+ variations: 12,
+ status: "Draft",
+ },
+ {
+ id: "p4",
+ title: "Describe a Photo",
+ level: "IELTS",
+ variations: 12,
+ status: "Draft",
+ },
+];
+
+export function ModuleDetailPage() {
+ const navigate = useNavigate();
+ const { level, courseId, moduleId } = useParams<{
+ level: string;
+ courseId: string;
+ moduleId: string;
+ }>();
+ const [activeTab, setActiveTab] = useState<"video" | "practice">("video");
+ const [activeFilter, setActiveFilter] = useState("Draft");
+ const [videos] = useState(MOCK_VIDEOS);
+ const [practices] = useState(MOCK_PRACTICES);
+
+ const moduleTitle =
+ moduleId
+ ?.split("-")
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
+ .join(" ") || "Business English Fundamentals";
+
+ return (
+
+ {/* Header Navigation */}
+
+
+
+ Back to Modules
+
+
+
+ {/* Hero Section */}
+
+
+
+ Module 3: {moduleTitle}
+
+
+ This module covers essential vocabulary and phrases used in modern
+ business environments, including email etiquette and meeting
+ protocols.
+
+
+
+
+
+
+
+
+ {/* Tabs */}
+
+
+
+
+
+
+
+ {/* Content */}
+
+ {activeTab === "video" ? (
+ videos.length > 0 ? (
+
+ {videos.map((video) => (
+ console.log("Edit", video.id)}
+ onPublish={() => console.log("Publish", video.id)}
+ />
+ ))}
+
+ ) : (
+
+
+
+ No videos added to this module yet
+
+
+ Videos are a great way to engage students. Start building your
+ module by adding your first video lesson now.
+
+
+
+ )
+ ) : (
+
+ {/* Practice Tab Filter Bar */}
+
+
+ STATUS:
+
+
+ {["All", "Published", "Draft", "Archived"].map((label) => (
+
+ ))}
+
+
+
+ {/* Practice Cards Grid */}
+
+ {practices.map((practice) => (
+
+ ))}
+
+
+ )}
+
+
+ );
+}
+
+function PracticeCard({
+ title,
+ level,
+ variations,
+ status,
+}: {
+ title: string;
+ level: string;
+ variations: number;
+ status: string;
+}) {
+ return (
+
+
+
+
+ {title}
+
+
+
+
+
+ {level}
+
+
+
+ Speaking
+
+
+
+
+
+ {variations} Variations
+
+
+
+
+ {status}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/content-management/NewContentPage.tsx b/src/pages/content-management/NewContentPage.tsx
new file mode 100644
index 0000000..d382ebd
--- /dev/null
+++ b/src/pages/content-management/NewContentPage.tsx
@@ -0,0 +1,81 @@
+import { Link } from "react-router-dom";
+import { Mic } from "lucide-react";
+import { Card, CardContent } from "../../components/ui/card";
+import { Button } from "../../components/ui/button";
+
+export function NewContentPage() {
+ return (
+
+ {/* Header section */}
+
+
+ Content Management
+
+
+ Upload, organize, and manage learning content across programs and
+ courses
+
+
+
+ {/* Gradient Divider */}
+
+
+ {/* Cards Grid */}
+
+ {/* Learn English Card */}
+
+
+
+
+ Learn English
+
+
+ Manage structured English learning content based on levels and
+ modules.
+
+
+
+
+
+
+
+ {/* Courses Card */}
+
+
+
+ Courses
+
+ Manage skill-based and exam preparation courses such as Duolingo
+ and IELTS.
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/content-management/ProgramCoursesPage.tsx b/src/pages/content-management/ProgramCoursesPage.tsx
new file mode 100644
index 0000000..68586b8
--- /dev/null
+++ b/src/pages/content-management/ProgramCoursesPage.tsx
@@ -0,0 +1,265 @@
+import { ArrowLeft, Plus, FileText } from "lucide-react";
+import { Link, useNavigate, useParams } from "react-router-dom";
+import { Card, CardContent } from "../../components/ui/card";
+import { Button } from "../../components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogTrigger,
+ DialogClose,
+} from "../../components/ui/dialog";
+import { Input } from "../../components/ui/input";
+import { Select } from "../../components/ui/select";
+import uploadIcon from "../../assets/icons/upload.png";
+
+export function ProgramCoursesPage() {
+ const navigate = useNavigate();
+ const { level } = useParams<{ level: string }>();
+
+ const courses = [
+ {
+ id: "a1",
+ title: "A1",
+ description:
+ "Learn basic English words, phrases, and simple sentences for daily situations.",
+ stats: {
+ modules: 3,
+ videos: 15,
+ practices: 18,
+ },
+ },
+ {
+ id: "a2",
+ title: "A2",
+ description:
+ "Build on basic skills with longer sentences, and practical conversations.",
+ stats: {
+ modules: 3,
+ videos: 15,
+ practices: 18,
+ },
+ },
+ ];
+
+ return (
+
+ {/* Navigation */}
+
+
+ Back to Programs
+
+
+ {/* Header section */}
+
+
+
+ {level || "Program"}
+
+
+ Designed for learners starting from scratch. Focuses on simple
+ grammar, and everyday communication.
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Cards Grid */}
+
+ {courses.map((course) => (
+
+ {/* Gradient Header */}
+
+
+
+ {course.title}
+
+
+ {course.description}
+
+
+ {/* Stats */}
+
+
+
+ {course.stats.modules}
+
+
+ Modules
+
+
+
+
+ {course.stats.videos}
+
+
+ Videos
+
+
+
+
+ {course.stats.practices}
+
+
+ Practices
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/pages/content-management/components/AddModuleModal.tsx b/src/pages/content-management/components/AddModuleModal.tsx
new file mode 100644
index 0000000..7cc989f
--- /dev/null
+++ b/src/pages/content-management/components/AddModuleModal.tsx
@@ -0,0 +1,131 @@
+import { X } from "lucide-react";
+import { Button } from "../../../components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogClose,
+} from "../../../components/ui/dialog";
+import { Input } from "../../../components/ui/input";
+import { Select } from "../../../components/ui/select";
+import uploadIcon from "../../../assets/icons/upload.png";
+
+interface AddModuleModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+}
+
+export function AddModuleModal({ isOpen, onClose }: AddModuleModalProps) {
+ return (
+
+ );
+}
diff --git a/src/pages/content-management/components/VideoCard.tsx b/src/pages/content-management/components/VideoCard.tsx
new file mode 100644
index 0000000..63946f3
--- /dev/null
+++ b/src/pages/content-management/components/VideoCard.tsx
@@ -0,0 +1,100 @@
+import { MoreVertical, Edit2, Play } from "lucide-react";
+import { Button } from "../../../components/ui/button";
+import { cn } from "../../../lib/utils";
+
+interface VideoCardProps {
+ id: string;
+ title: string;
+ duration: string;
+ status: "Draft" | "Published";
+ thumbnailGradient: string;
+ onEdit?: () => void;
+ onPublish?: () => void;
+}
+
+export function VideoCard({
+ title,
+ duration,
+ status,
+ thumbnailGradient,
+ onEdit,
+ onPublish,
+}: VideoCardProps) {
+ return (
+
+ {/* Thumbnail */}
+
+ {/* Duration Badge */}
+
+ {duration}
+
+ {/* Play Overlay */}
+
+
+
+ {/* Content */}
+
+
+ {/* Status Badge */}
+
+ {/* Menu */}
+
+
+
+
+ {title}
+
+
+ {/* Actions */}
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/content-management/components/practice-steps/ContextStep.tsx b/src/pages/content-management/components/practice-steps/ContextStep.tsx
new file mode 100644
index 0000000..165bfff
--- /dev/null
+++ b/src/pages/content-management/components/practice-steps/ContextStep.tsx
@@ -0,0 +1,146 @@
+import { GraduationCap, ArrowRight, LayoutGrid, Monitor } from "lucide-react";
+import { Button } from "../../../../components/ui/button";
+import { Card } from "../../../../components/ui/card";
+import { Select } from "../../../../components/ui/select";
+
+interface ContextStepProps {
+ formData: any;
+ setFormData: (data: any) => void;
+ nextStep: () => void;
+ navigate: (path: string) => void;
+ level: string;
+ isModuleContext?: boolean;
+}
+
+export function ContextStep({
+ formData,
+ setFormData,
+ nextStep,
+ navigate,
+ level,
+ isModuleContext,
+}: ContextStepProps) {
+ return (
+
+
+
+ Step 1: Context Definition
+
+
+ Define the educational level and curriculum module for this practice.
+
+
+
+
+ {/* Program Field */}
+
+
+
+
+
+
+
+
+
+
+ {/* Course Field */}
+
+
+
+
+
+
+
+
+
+
+ {/* Select Module Field */}
+
+
+
+
+
+
+
+
+
+ Select the specific learning module this practice will reinforce.
+
+
+
+ {/* Select Video Field (Conditional) */}
+ {isModuleContext && (
+
+
+
+
+
+
+
+
+
+ Select the specific learning module this practice will reinforce.
+
+
+ )}
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/content-management/components/practice-steps/PersonaStep.tsx b/src/pages/content-management/components/practice-steps/PersonaStep.tsx
new file mode 100644
index 0000000..133b6e4
--- /dev/null
+++ b/src/pages/content-management/components/practice-steps/PersonaStep.tsx
@@ -0,0 +1,91 @@
+import { Check, ArrowRight } from "lucide-react";
+import { Button } from "../../../../components/ui/button";
+import {
+ Avatar,
+ AvatarFallback,
+ AvatarImage,
+} from "../../../../components/ui/avatar";
+import { cn } from "../../../../lib/utils";
+import { PERSONAS } from "./constants";
+
+interface PersonaStepProps {
+ selectedPersona: string | null;
+ setSelectedPersona: (id: string) => void;
+ nextStep: () => void;
+ prevStep: () => void;
+}
+
+export function PersonaStep({
+ selectedPersona,
+ setSelectedPersona,
+ nextStep,
+ prevStep,
+}: PersonaStepProps) {
+ return (
+
+
+
+ Select Personas
+
+
+ Choose the characters that will participate in this practice scenario.
+
+
+
+ {PERSONAS.map((persona) => (
+
setSelectedPersona(persona.id)}
+ className={cn(
+ "group relative cursor-pointer rounded-2xl border-2 bg-white p-6 transition-all duration-300",
+ selectedPersona === persona.id
+ ? "border-brand-500 shadow-xl scale-105"
+ : "border-grayScale-50 hover:border-brand-200",
+ )}
+ >
+
+
+
+
+
+ {persona.name.substring(0, 2)}
+
+
+ {selectedPersona === persona.id && (
+
+
+
+ )}
+
+
+ {persona.name}
+
+
+
+ ))}
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/content-management/components/practice-steps/QuestionsStep.tsx b/src/pages/content-management/components/practice-steps/QuestionsStep.tsx
new file mode 100644
index 0000000..184f95c
--- /dev/null
+++ b/src/pages/content-management/components/practice-steps/QuestionsStep.tsx
@@ -0,0 +1,140 @@
+import { GripVertical, Trash2, Plus, ArrowRight } from "lucide-react";
+import { Button } from "../../../../components/ui/button";
+import { Card } from "../../../../components/ui/card";
+import { Input } from "../../../../components/ui/input";
+import { VoicePrompt } from "./VoicePrompt";
+
+interface QuestionsStepProps {
+ formData: any;
+ setFormData: (data: any) => void;
+ nextStep: () => void;
+ prevStep: () => void;
+}
+
+export function QuestionsStep({
+ formData,
+ setFormData,
+ nextStep,
+ prevStep,
+}: QuestionsStepProps) {
+ const addQuestion = () => {
+ const newQuestion = {
+ id: `q${formData.questions.length + 1}`,
+ text: "",
+ type: "Speaking",
+ voicePrompt: "upload_audio.mp3",
+ sampleAnswer: "upload_audio.mp3",
+ };
+ setFormData({
+ ...formData,
+ questions: [...formData.questions, newQuestion],
+ });
+ };
+
+ return (
+
+
+
+ Create Practice Questions
+
+
+ Define the dialogue flow and interactions for this scenario.
+
+
+
+ {formData.questions.map((q: any, i: number) => (
+
+
+
+
+
+
+
+ Question {i + 1}
+
+
+
+
+
+
+
+ {
+ const newQuestions = [...formData.questions];
+ newQuestions[i].text = e.target.value;
+ setFormData({ ...formData, questions: newQuestions });
+ }}
+ className="h-16 rounded-xl border-grayScale-200 focus:border-brand-500 font-medium px-6 text-lg placeholder:text-grayScale-300 bg-white"
+ placeholder="e.g. How long have you been studying English?"
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/content-management/components/practice-steps/ReviewStep.tsx b/src/pages/content-management/components/practice-steps/ReviewStep.tsx
new file mode 100644
index 0000000..649dc7f
--- /dev/null
+++ b/src/pages/content-management/components/practice-steps/ReviewStep.tsx
@@ -0,0 +1,290 @@
+import { Edit2, GripVertical, Trash2, Rocket, Info } from "lucide-react";
+import { Button } from "../../../../components/ui/button";
+import { Card } from "../../../../components/ui/card";
+import {
+ Avatar,
+ AvatarFallback,
+ AvatarImage,
+} from "../../../../components/ui/avatar";
+import { PERSONAS } from "./constants";
+import { VoicePrompt } from "./VoicePrompt";
+import { cn } from "../../../../lib/utils";
+
+interface ReviewStepProps {
+ formData: any;
+ selectedPersona: string | null;
+ prevStep: () => void;
+ setIsPublished: (val: boolean) => void;
+ isModuleContext?: boolean;
+}
+
+export function ReviewStep({
+ formData,
+ selectedPersona,
+ prevStep,
+ setIsPublished,
+ isModuleContext,
+}: ReviewStepProps) {
+ const persona = PERSONAS.find((p) => p.id === selectedPersona);
+
+ return (
+
+
+
+ Review Practice Questions
+
+
+
+ {/* 1. Basic Info Card (Image 1436.1) */}
+
+
+
+ Basic Information
+
+
+
+
+
+
+

+
+
+
+ {formData.title || "Business English 101: Communication"}
+
+
+
+ Program:{" "}
+
+ {formData.program}
+
+
+
+ Course:{" "}
+
+ {formData.course}
+
+
+
+ Module:{" "}
+
+ Module 101
+
+
+
+
+
+
+
+ Persona
+
+
+
+
+ P
+
+
+ {persona?.name || "Alex Johnson"}
+
+
+
+
+
+
+ {/* 2. Tips Section (Image 1436.1) */}
+
+
+
+
+
+
+
+ {formData.tips ||
+ "Focus on using the present perfect continuous tense to describe an action that started in the past and continues now."}
+
+
+
+
+ {isModuleContext ? (
+ /* 3. Split Questions & Answers Layout (Image 1413.1) */
+
+ {/* Left Column: Questions */}
+
+
+
+ Questions
+
+
+ {formData.questions.length}
+
+
+
+ {formData.questions.map((q: any, i: number) => (
+
+
+ {(i + 1).toString().padStart(2, "0")}
+
+
+
+
+ TEXT PROMPT
+
+
+ {q.text}
+
+
+
+
+ VOICE PROMPT
+
+
+
+
+
+ ))}
+
+
+
+ {/* Right Column: Answers */}
+
+
+
+
Answers
+
+ {formData.questions.length}
+
+
+
+
+
+ {formData.questions.map((q: any, i: number) => (
+
+
+ {(i + 1).toString().padStart(2, "0")}
+
+
+
+ VOICE PROMPT
+
+
+
+
+ ))}
+
+
+
+ ) : (
+ /* Original Non-Module View */
+
+ {formData.questions.map((q: any, i: number) => (
+
+ ))}
+
+ )}
+
+ {/* Action Footer */}
+
+
+
+
+
+
+
+
+ );
+}
+
+function ReviewItem({ q, index }: { q: any; index: number }) {
+ return (
+
+
+
+
+
+
+
+ Question {index + 1}
+
+
+
+
+
+
+
+
+
+
+
+ TEXT PROMPT
+
+
+ {q.text}
+
+
+
+
+ VOICE PROMPT
+
+
+
+
+
+
+
+ SAMPLE ANSWER
+
+
+
+
+
+ );
+}
diff --git a/src/pages/content-management/components/practice-steps/ScenarioStep.tsx b/src/pages/content-management/components/practice-steps/ScenarioStep.tsx
new file mode 100644
index 0000000..c3ace37
--- /dev/null
+++ b/src/pages/content-management/components/practice-steps/ScenarioStep.tsx
@@ -0,0 +1,115 @@
+import { Upload, ArrowRight } from "lucide-react";
+import { Button } from "../../../../components/ui/button";
+import { Card } from "../../../../components/ui/card";
+import { Input } from "../../../../components/ui/input";
+import { Textarea } from "../../../../components/ui/textarea";
+
+interface ScenarioStepProps {
+ formData: any;
+ setFormData: (data: any) => void;
+ nextStep: () => void;
+ prevStep: () => void;
+}
+
+export function ScenarioStep({
+ formData,
+ setFormData,
+ nextStep,
+ prevStep,
+}: ScenarioStepProps) {
+ return (
+
+
+
+ Define Scenario Details
+
+
+ Set the scene and context for this English practice session.
+
+
+
+
+
+
+ This image will appear as the background for the scenario.
+
+
+
+
+
+
+
+ Click to upload
+ {" "}
+ or drag and drop
+
+
+ SVG, PNG, JPG (MAX 5MB)
+
+
+
+
+
+
+
+
+
+ setFormData({ ...formData, title: e.target.value })
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/content-management/components/practice-steps/VoicePrompt.tsx b/src/pages/content-management/components/practice-steps/VoicePrompt.tsx
new file mode 100644
index 0000000..1be5b6c
--- /dev/null
+++ b/src/pages/content-management/components/practice-steps/VoicePrompt.tsx
@@ -0,0 +1,44 @@
+import { Play, X } from "lucide-react";
+import { Button } from "../../../../components/ui/button";
+import { cn } from "../../../../lib/utils";
+
+interface VoicePromptProps {
+ filename: string;
+ className?: string;
+}
+
+export function VoicePrompt({ filename, className }: VoicePromptProps) {
+ return (
+
+
+
+
+ {[...Array(20)].map((_, idx) => (
+
+ ))}
+
+
+ {filename}
+
+
+
+
+ );
+}
diff --git a/src/pages/content-management/components/practice-steps/constants.ts b/src/pages/content-management/components/practice-steps/constants.ts
new file mode 100644
index 0000000..fdbc99b
--- /dev/null
+++ b/src/pages/content-management/components/practice-steps/constants.ts
@@ -0,0 +1,44 @@
+export const PERSONAS = [
+ {
+ id: "dawit",
+ name: "Dawit",
+ avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Dawit",
+ },
+ {
+ id: "mahlet",
+ name: "Mahlet",
+ avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Mahlet",
+ },
+ {
+ id: "amanuel",
+ name: "Amanuel",
+ avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Amanuel",
+ },
+ {
+ id: "bethel",
+ name: "Bethel",
+ avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Bethel",
+ },
+ {
+ id: "liya",
+ name: "Liya",
+ avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Liya",
+ },
+ {
+ id: "aseffa",
+ name: "Aseffa",
+ avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Aseffa",
+ },
+ {
+ id: "hana",
+ name: "Hana",
+ avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Hana",
+ },
+ {
+ id: "nahom",
+ name: "Nahom",
+ avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Nahom",
+ },
+];
+
+export const STEPS = ["Context", "Scenario", "Persona", "Questions", "Review"];
diff --git a/src/pages/content-management/components/video-steps/ReviewPublishStep.tsx b/src/pages/content-management/components/video-steps/ReviewPublishStep.tsx
new file mode 100644
index 0000000..e68be15
--- /dev/null
+++ b/src/pages/content-management/components/video-steps/ReviewPublishStep.tsx
@@ -0,0 +1,162 @@
+import { Rocket, Edit2, Layout } from "lucide-react";
+import { Button } from "../../../../components/ui/button";
+
+interface ReviewPublishStepProps {
+ formData: any;
+ prevStep: () => void;
+ setIsPublished: (val: boolean) => void;
+}
+
+export function ReviewPublishStep({
+ formData,
+ prevStep,
+ setIsPublished,
+}: ReviewPublishStepProps) {
+ return (
+
+ {/* 1. Video Preview Card */}
+
+
+
+ Video Preview
+
+
+ PROCESSED
+
+
+
+
+ {/* Mock Player Control Overlays */}
+
+
+ {/* Bottom Controls Placeholder */}
+
+
+
+
+
+ {/* 2. Content Details Card */}
+
+
+
+ Content Details
+
+
+
+
+
+ {/* Metadata Grid */}
+
+
+
+ TITLE
+
+
+ {formData.title || "Introduction to Past Tense"}
+
+
+
+
+
+ ASSIGNED MODULE
+
+
+
+
+ Grammar Basics - Level 1
+
+
+
+
+
+
+ TEACHER NAME
+
+
+ Abebe Kebede
+
+
+
+
+
+ FILE SIZE
+
+
+
+ 245 MB
+
+
+ (1080p MP4)
+
+
+
+
+
+ {/* Description Section */}
+
+
+
+ {/* 3. Normal Footer (Inside Card) */}
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/content-management/components/video-steps/VideoDetailStep.tsx b/src/pages/content-management/components/video-steps/VideoDetailStep.tsx
new file mode 100644
index 0000000..c66b31d
--- /dev/null
+++ b/src/pages/content-management/components/video-steps/VideoDetailStep.tsx
@@ -0,0 +1,257 @@
+import { useRef, useEffect } from "react";
+import {
+ Video,
+ List,
+ Link as LinkIcon,
+ Lightbulb,
+ ChevronRight,
+ ImageIcon,
+} from "lucide-react";
+import { Button } from "../../../../components/ui/button";
+import { Input } from "../../../../components/ui/input";
+import { Select } from "../../../../components/ui/select";
+
+interface VideoDetailStepProps {
+ formData: any;
+ setFormData: (data: any) => void;
+ nextStep: () => void;
+}
+
+export function VideoDetailStep({
+ formData,
+ setFormData,
+ nextStep,
+}: VideoDetailStepProps) {
+ const editorRef = useRef(null);
+ const isInternalChange = useRef(false);
+
+ // Initialize editor content only once or when needed from outside
+ useEffect(() => {
+ if (editorRef.current && !isInternalChange.current) {
+ editorRef.current.innerHTML = formData.description || "";
+ }
+ }, []);
+
+ const handleCommand = (command: string, value?: string) => {
+ document.execCommand(command, false, value);
+ syncState();
+ };
+
+ const syncState = () => {
+ if (editorRef.current) {
+ isInternalChange.current = true;
+ setFormData({ ...formData, description: editorRef.current.innerHTML });
+ // Reset after a short delay to allow exterior updates if any (e.g., from step change)
+ setTimeout(() => {
+ isInternalChange.current = false;
+ }, 0);
+ }
+ };
+
+ const handleInput = () => {
+ syncState();
+ };
+
+ return (
+
+ {/* Single Unified Card for Everything */}
+
+ {/* 1. Upload Video Section */}
+
+
+ Upload Video
+
+
+
+
+
+ Drag and drop video files here
+
+
+ MP4, MOV, WebM. Max size 2GB.
+
+
+
+
+
+
+
+
+
+ {/* 2. Form & Side Panel Grid */}
+
+ {/* Left Column: Title, Order, Description */}
+
+
+
+
+ setFormData({ ...formData, title: e.target.value })
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+ {/* Toolbar */}
+
+
+
+
+
+
+
+
+
+
+ {(!formData.description ||
+ formData.description === "
" ||
+ formData.description === "" ||
+ formData.description === "
") && (
+
+ Provide a brief summary of what the student will learn...
+
+ )}
+
+
+
+
+
+
+ {/* Right Column: Thumbnail, Pro Tip */}
+
+ {/* Thumbnail Section */}
+
+
+
+ Thumbnail
+
+
+ Upload your video thumbnail. 1280×720px recommended.
+
+
+
+
+
+
+
+
+ Click to upload
+
+
+
+
+
+ {/* Pro Tip Section */}
+
+
+
+ Short, descriptive titles work best. Include keywords like
+ "Grammar" or "Vocabulary" to help students find your content.
+
+
+
+
+
+ {/* Footer (Inside Card Container) */}
+
+
+
+
+ Last saved: Just now
+
+
+
+
+
+
+ );
+}