This commit is contained in:
elnatansamuel25 2026-04-27 09:52:30 +03:00
parent d4d61bfed2
commit 7308d9bbcd
9 changed files with 1654 additions and 17 deletions

View File

@ -29,6 +29,8 @@ import { ProgramTypeSelectionPage } from "../pages/content-management/ProgramTyp
import { ProgramDetailPage } from "../pages/content-management/ProgramDetailPage"; import { ProgramDetailPage } from "../pages/content-management/ProgramDetailPage";
import { CourseManagementPage } from "../pages/content-management/CourseManagementPage"; import { CourseManagementPage } from "../pages/content-management/CourseManagementPage";
import { UnitManagementPage } from "../pages/content-management/UnitManagementPage"; import { UnitManagementPage } from "../pages/content-management/UnitManagementPage";
import { QuestionTypeLibraryPage } from "../pages/content-management/QuestionTypeLibraryPage";
import { CreateQuestionTypeFlow } from "../pages/content-management/CreateQuestionTypeFlow";
import { NotFoundPage } from "../pages/NotFoundPage"; import { NotFoundPage } from "../pages/NotFoundPage";
import { NotificationsPage } from "../pages/notifications/NotificationsPage"; import { NotificationsPage } from "../pages/notifications/NotificationsPage";
import { CreateNotificationPage } from "../pages/notifications/CreateNotificationPage"; import { CreateNotificationPage } from "../pages/notifications/CreateNotificationPage";
@ -165,6 +167,14 @@ export function AppRoutes() {
path="/new-content/courses" path="/new-content/courses"
element={<ProgramTypeSelectionPage />} element={<ProgramTypeSelectionPage />}
/> />
<Route
path="/new-content/question-types"
element={<QuestionTypeLibraryPage />}
/>
<Route
path="/new-content/question-types/create"
element={<CreateQuestionTypeFlow />}
/>
<Route <Route
path="/new-content/courses/:programType" path="/new-content/courses/:programType"
element={<ProgramDetailPage />} element={<ProgramDetailPage />}

View File

@ -22,7 +22,7 @@ export function Stepper({ steps, currentStep, className }: StepperProps) {
{index < steps.length - 1 && ( {index < steps.length - 1 && (
<div <div
className="absolute top-4 h-[1.5px] bg-grayScale-200 z-0" className="absolute top-4 h-[1.5px] bg-grayScale-200 z-0"
style={{ left: "calc(50% + 24px)", right: "calc(-50% + 24px)" }} style={{ left: "calc(50% + 50px)", right: "calc(-50% + 50px)" }}
/> />
)} )}

View File

@ -86,19 +86,7 @@ export function CourseDetailPage() {
</Button> </Button>
</div> </div>
</div> </div>
<div className="relative">
<div className="absolute inset-0 flex items-center" aria-hidden="true">
<div className="w-full border-t border-grayScale-200" />
</div>
<div className="relative flex justify-center">
<div
className="h-[0.5px] w-full opacity-20 rounded-full"
style={{
background: "gray",
}}
/>
</div>
</div>
<AddModuleModal <AddModuleModal
isOpen={isAddModuleOpen} isOpen={isAddModuleOpen}

View File

@ -0,0 +1,87 @@
import { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { ArrowLeft } from "lucide-react";
import { Button } from "../../components/ui/button";
import { Stepper } from "../../components/ui/stepper";
import { QuestionTypeBasicInfoStep } from "./components/question-type-steps/QuestionTypeBasicInfoStep";
import { QuestionTypeConfigStep } from "./components/question-type-steps/QuestionTypeConfigStep";
export function CreateQuestionTypeFlow() {
const navigate = useNavigate();
const [currentStep, setCurrentStep] = useState(1);
const steps = [
"Basic Info",
"Input & Answer Configuration",
"Versions",
"Review & Publish",
];
const handleNext = () =>
setCurrentStep((prev) => Math.min(prev + 1, steps.length));
const handleBack = () => setCurrentStep((prev) => Math.max(prev - 1, 1));
return (
<div className="min-h-screen pb-20 overflow-x-hidden">
{/* Header */}
<div className=" border-b border-grayScale-100 sticky top-0 z-50">
<div className="max-w-[1440px] mx-auto py-6">
<div className="flex items-center justify-between mb-8">
<Link
to="/new-content/question-types"
className="flex items-center gap-2 text-[15px] font-medium text-grayScale-600 transition-colors hover:text-brand-500 group"
>
<ArrowLeft className="h-5 w-5 transition-transform group-hover:-translate-x-1" />
Back to Question Type Library
</Link>
</div>
<div className="flex items-start justify-between">
<div className="space-y-1">
<h1 className="text-[28px] font-bold text-grayScale-900 tracking-tight">
Create Question Type
</h1>
<p className="text-grayScale-500 text-[14px] font-medium">
Create a new immersive practice session for students.
</p>
</div>
<div className="flex items-center gap-4">
<Button
variant="outline"
className="h-10 px-8 rounded-[6px] border-grayScale-200 text-grayScale-900 font-medium hover:bg-grayScale-50"
onClick={() => navigate("/new-content/question-types")}
>
Cancel
</Button>
<Button className="h-10 px-8 rounded-[6px] bg-[#9E2891] font-medium text-white shadow-lg shadow-brand-500/10 hover:bg-[#8A237E] transition-all">
Save as Draft
</Button>
</div>
</div>
<div className="mt-12 mx-auto">
<Stepper steps={steps} currentStep={currentStep} />
</div>
</div>
</div>
{/* Main Content */}
<div className="max-w-[1440px] mx-auto px-10 mt-12 animate-in fade-in slide-in-from-bottom-4 duration-500">
{currentStep === 1 && <QuestionTypeBasicInfoStep onNext={handleNext} />}
{currentStep === 2 && (
<QuestionTypeConfigStep onNext={handleNext} onBack={handleBack} />
)}
{currentStep > 2 && (
<div className="bg-white rounded-2xl p-12 text-center border border-grayScale-100 shadow-sm">
<p className="text-grayScale-400 font-medium">
Step {currentStep} implementation in progress...
</p>
<Button onClick={handleBack} variant="outline" className="mt-4">
Go Back
</Button>
</div>
)}
</div>
</div>
);
}

View File

@ -16,9 +16,11 @@ export function ProgramTypeSelectionPage() {
exams. Select a program type to manage curriculum and modules. exams. Select a program type to manage curriculum and modules.
</p> </p>
</div> </div>
<Button className="h-10 px-6 rounded-[6px] bg-brand-500 font-bold text-white shadow-sm hover:bg-brand-600 transition-all flex items-center gap-2 mt-4"> <Link to="/new-content/question-types">
Manage Question Types <Button className="h-10 px-6 rounded-[6px] bg-brand-500 font-bold text-white shadow-sm hover:bg-brand-600 transition-all flex items-center gap-2 mt-4">
</Button> Manage Question Types
</Button>
</Link>
</div> </div>
{/* Gradient Divider */} {/* Gradient Divider */}

View File

@ -0,0 +1,127 @@
import { useState } from "react";
import { Link } from "react-router-dom";
import { ArrowLeft, Plus, Search } from "lucide-react";
import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input";
import { Select } from "../../components/ui/select";
import { Card } from "../../components/ui/card";
import { cn } from "../../lib/utils";
import { QuestionTypeCard } from "./components/QuestionTypeCard";
export function QuestionTypeLibraryPage() {
const [activeTab, setActiveTab] = useState("All");
const questionTypes = [
{
title: "Describe a Photo",
exam: "DUOLINGO" as const,
skill: "Speaking" as const,
variations: 12,
status: "Published" as const,
},
{
title: "Write About the Topic",
exam: "DUOLINGO" as const,
skill: "Writing" as const,
variations: 12,
status: "Published" as const,
},
{
title: "Fill in the Blanks",
exam: "IELTS" as const,
skill: "Writing" as const,
variations: 12,
status: "Published" as const,
},
{
title: "Describe a Photo",
exam: "DUOLINGO" as const,
skill: "Speaking" as const,
variations: 12,
status: "Published" as const,
},
];
return (
<div className="space-y-8 animate-in fade-in duration-500 pb-20">
{/* Navigation & Header */}
<div className="space-y-6">
<Link
to="/new-content/courses"
className="flex items-center gap-2 text-[15px] font-bold text-grayScale-600 transition-colors hover:text-brand-500 group w-fit"
>
<ArrowLeft className="h-5 w-5 transition-transform group-hover:-translate-x-1" />
Back to Courses
</Link>
<div className="flex items-start justify-between">
<div className="space-y-1">
<h1 className="text-[32px] font-medium text-grayScale-900 tracking-tight">
Question Type Library
</h1>
<p className="text-grayScale-500 text-[16px] font-medium">
Create and manage reusable question structures for practices and
assessments.
</p>
</div>
<Link to="/new-content/question-types/create">
<Button className="h-12 px-8 rounded-[10px] bg-[#9E2891] font-bold text-white shadow-lg shadow-brand-500/10 hover:bg-[#8A237E] transition-all flex items-center gap-3">
<Plus className="h-5 w-5" />
Create Question Type
</Button>
</Link>
</div>
</div>
{/* Control Bar */}
<Card className="p-6 border-grayScale-200 rounded-2xl bg-white space-y-6">
<div className="flex items-center gap-4">
<div className="relative flex-1">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 h-5 w-5 text-grayScale-600" />
<Input
className="h-10 pl-12 rounded-[6px] border-grayScale-200 placeholder:text-grayScale-600 bg-[#F8FAFC] transition-all text-sm"
placeholder="Search by practice name, ID, or keywords..."
/>
</div>
<Select className="h-10 w-[180px] rounded-[6px] border-grayScale-200 placeholder:text-grayScale-600 text-grayScale-700 bg-[#F8FAFC] transition-all text-sm">
<option>All Exams</option>
<option>IELTS</option>
<option>Duolingo</option>
</Select>
<Select className="h-10 w-[180px] rounded-[6px] border-grayScale-200 placeholder:text-grayScale-600 text-grayScale-700 bg-[#F8FAFC] transition-all text-sm">
<option>All Skills</option>
<option>Speaking</option>
<option>Writing</option>
</Select>
</div>
<div className="flex items-center gap-3">
<span className="text-[12px] font-medium text-grayScale-400 uppercase tracking-widest mr-2">
STATUS:
</span>
{["All", "Published", "Drafts", "Archived"].map((tab) => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
className={cn(
"h-10 px-4 rounded-full text-[13px] font-medium transition-all",
activeTab === tab
? "bg-[#9E2891] text-white shadow-md shadow-brand-500/20"
: "bg-grayScale-100 text-grayScale-400 hover:bg-grayScale-100",
)}
>
{tab}
</button>
))}
</div>
</Card>
{/* Grid of Cards */}
<div className="grid grid-cols-4 gap-6">
{questionTypes.map((qt, index) => (
<QuestionTypeCard key={index} {...qt} />
))}
</div>
</div>
);
}

View File

@ -0,0 +1,83 @@
import { Edit2, Trash2, Mic2, Keyboard, Layers, MicIcon } from "lucide-react";
import { Badge } from "../../../components/ui/badge";
import { Card } from "../../../components/ui/card";
import { cn } from "../../../lib/utils";
interface QuestionTypeCardProps {
title: string;
exam: "DUOLINGO" | "IELTS" | "TOEFL";
skill: "Speaking" | "Writing" | "Listening" | "Reading";
variations: number;
status: "Published" | "Draft" | "Archived";
}
export function QuestionTypeCard({
title,
exam,
skill,
variations,
status,
}: QuestionTypeCardProps) {
const SkillIcon = skill === "Speaking" ? MicIcon : Keyboard;
const examColors = {
DUOLINGO: "bg-[#22C55EE5] text-[#fff] border-transparent",
IELTS: "bg-[#EF4444E5] text-[#fff] border-transparent",
TOEFL: "bg-[#DBEAFE] text-[#fff] border-transparent",
};
const statusColors = {
Published: "bg-[#F0FDF4] text-[#16A34A]",
Draft: "bg-grayScale-50 text-grayScale-500",
Archived: "bg-red-50 text-red-500",
};
return (
<Card className="group overflow-hidden border-grayScale-200 rounded-[12px] bg-white transition-all duration-300">
<div className="px-4 py-6 space-y-8">
<h3 className="text-[20px] font-bold text-grayScale-900 leading-[1.2]">
{title}
</h3>
<div className="flex items-center justify-between">
<Badge
className={cn(
"px-3 py-1 rounded-[4px] text-[11px] font-bold tracking-wider shadow-none border-none",
examColors[exam],
)}
>
{exam}
</Badge>
<div className="flex items-center gap-1 text-grayScale-900 font-bold text-[13px]">
<SkillIcon className="h-4 w-4" />
{skill}
</div>
</div>
<div className="flex items-center gap-2.5 text-[#9E2891] font-medium text-[15px]">
<Layers className="h-[16px] w-[16px]" />
{variations} Variations
</div>
<div className="pt-4 flex items-center justify-between border-t border-grayScale-200">
<Badge
className={cn(
"px-3 py-1 rounded-[4px] text-[12px] font-bold shadow-none border-none",
statusColors[status],
)}
>
{status}
</Badge>
<div className="flex items-center gap-5 transition-opacity">
<button className="text-grayScale-500/70 transition-all">
<Edit2 className="h-5 w-5" />
</button>
<button className="text-grayScale-500/70 transition-all">
<Trash2 className="h-5 w-5" />
</button>
</div>
</div>
</div>
</Card>
);
}

View File

@ -0,0 +1,150 @@
import { useState } from "react";
import { X, ArrowRight } from "lucide-react";
import { Button } from "../../../../components/ui/button";
import { Card } from "../../../../components/ui/card";
import { Select } from "../../../../components/ui/select";
import { Badge } from "../../../../components/ui/badge";
interface QuestionTypeBasicInfoStepProps {
onNext: () => void;
}
export function QuestionTypeBasicInfoStep({
onNext,
}: QuestionTypeBasicInfoStepProps) {
const [selectedChips, setSelectedChips] = useState([
"Multiple Choice",
"Sentence Completion",
]);
const suggestions = ["Matching Headings", "True/False/NG"];
const removeChip = (chip: string) => {
setSelectedChips(selectedChips.filter((c) => c !== chip));
};
const addChip = (chip: string) => {
if (!selectedChips.includes(chip)) {
setSelectedChips([...selectedChips, chip]);
}
};
return (
<div className="space-y-8 pb-32">
<Card className="max-w-4xl mx-auto overflow-hidden border-grayScale-100 shadow-sm rounded-2xl bg-white">
<div className="p-10 border-b border-grayScale-200">
<h2 className="text-[20px] font-medium text-grayScale-900">
STEP 1: Basic Info
</h2>
<p className="text-grayScale-500 font-medium mt-1">
Define what this question type is and where it applies.
</p>
</div>
<div className="p-10 space-y-10">
{/* Top Row: Course Type & Skill Category */}
<div className="grid grid-cols-2 gap-10">
<div className="space-y-3">
<label className="text-[14px] font-medium text-grayScale-700 flex items-center gap-1">
Course Type <span className="text-red-500">*</span>
</label>
<Select className="h-12 rounded-[12px] border-grayScale-300 bg-[#F8FAFC] font-medium text-grayScale-900 transition-all ">
<option>Select an exam type</option>
<option>IELTS</option>
<option>Duolingo</option>
<option>TOEFL</option>
</Select>
<p className="text-grayScale-400 text-[13px] font-medium leading-relaxed">
The core framework for the practice test.
</p>
</div>
<div className="space-y-3">
<label className="text-[14px] font-medium text-grayScale-700 flex items-center gap-1">
Skill Category <span className="text-red-500">*</span>
</label>
<Select className="h-12 rounded-[12px] border-grayScale-300 bg-[#F8FAFC] font-medium text-grayScale-900 transition-all ">
<option>Select a skill</option>
<option>Speaking</option>
<option>Writing</option>
<option>Listening</option>
<option>Reading</option>
</Select>
</div>
</div>
{/* Question Type */}
<div className="space-y-3">
<label className="text-[14px] font-bold text-grayScale-700 flex items-center gap-1">
Question Type <span className="text-red-500">*</span>
</label>
<Select className="h-12 rounded-[12px] border-grayScale-300 bg-[#F8FAFC] font-medium text-grayScale-900 transition-all ">
<option>Single Format</option>
<option>Mixed Format</option>
</Select>
</div>
{/* Question Types Chip Input */}
<div className="space-y-3">
<label className="text-[14px] font-bold text-grayScale-700 flex items-center gap-1">
Question Types <span className="text-red-500">*</span>
</label>
<div className="min-h-[56px] p-3 flex flex-wrap gap-2.5 rounded-[12px] border border-grayScale-300 bg-[#F8FAFC]">
{selectedChips.map((chip) => (
<Badge
key={chip}
className="bg-[#9E28911A] text-[#9E2891] border-[#9E289133] px-2 py-0 rounded-full text-[13px] font-medium flex items-center gap-2"
>
{chip}
<button
onClick={() => removeChip(chip)}
className="hover:text-red-500 transition-colors"
>
<X className="h-3.5 w-3.5" />
</button>
</Badge>
))}
<input
className="flex-1 min-w-[150px] bg-transparent border-none focus:ring-0 text-[14px] font-medium text-grayScale-900 px-3 placeholder:text-grayScale-400"
placeholder="Add question types..."
/>
</div>
<div className="flex items-center gap-3 pt-1">
<span className="text-[13px] font-medium text-grayScale-500">
Suggestions:
</span>
<div className="flex items-center gap-2">
{suggestions.map((s) => (
<button
key={s}
onClick={() => addChip(s)}
className="px-3 py-1.5 rounded-[6px] border border-grayScale-300 text-[13px] font-medium text-grayScale-600 hover:bg-grayScale-50 hover:text-[#9E2891] hover:border-[#9E2891]/20 transition-all"
>
{s}
</button>
))}
</div>
</div>
</div>
</div>
{/* Footer */}
<div className="px-4 py-4 border border-grayScale-200 flex items-center justify-between bg-[#F8FAFC]">
<Button
variant="outline"
className="h-10 px-6 rounded-[6px] border-none shadow-none text-grayScale-600 font-bold hover:bg-grayScale-100"
>
Cancel
</Button>
<Button
onClick={onNext}
className="h-10 px-10 rounded-[6px] bg-[#9E2891] font-medium text-white shadow-lg shadow-brand-500/10 hover:bg-[#8A237E] transition-all flex items-center gap-3"
>
Next: Structure
<ArrowRight className="h-5 w-5" />
</Button>
</div>
</Card>
</div>
);
}