From aa998e55990ba5612621a3b53b02c19dccae465f Mon Sep 17 00:00:00 2001 From: elnatansamuel25 Date: Thu, 7 May 2026 09:58:52 +0300 Subject: [PATCH] setting --- src/components/sidebar/Sidebar.tsx | 2 + src/pages/SettingsPage.tsx | 521 ++++++++++------- .../ContentManagementLayout.tsx | 9 +- .../components/ContentHierarchyList.tsx | 539 ++++++++++++++++++ 4 files changed, 871 insertions(+), 200 deletions(-) create mode 100644 src/pages/content-management/components/ContentHierarchyList.tsx diff --git a/src/components/sidebar/Sidebar.tsx b/src/components/sidebar/Sidebar.tsx index ce361c2..fdabf56 100644 --- a/src/components/sidebar/Sidebar.tsx +++ b/src/components/sidebar/Sidebar.tsx @@ -12,6 +12,7 @@ import { UserCircle2, Users, Users2, + Settings, X, } from "lucide-react"; import { type ComponentType, useEffect, useState } from "react"; @@ -39,6 +40,7 @@ const navItems: NavItem[] = [ { label: "Analytics", to: "/analytics", icon: BarChart3 }, { label: "Team Management", to: "/team", icon: Users2 }, { label: "Profile", to: "/profile", icon: UserCircle2 }, + { label: "Settings", to: "/settings", icon: Settings }, ]; type SidebarProps = { diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index f129d98..1f5f501 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import { Bell, Eye, @@ -13,21 +13,43 @@ import { Shield, Sun, User, + CreditCard, + AlertTriangle, + X, } from "lucide-react"; -import { Card, CardContent, CardHeader, CardTitle } from "../components/ui/card"; +import { + Card, + CardContent, + CardHeader, + CardTitle, +} from "../components/ui/card"; import { Input } from "../components/ui/input"; import { Button } from "../components/ui/button"; import { Select } from "../components/ui/select"; import { Separator } from "../components/ui/separator"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "../components/ui/dialog"; import { cn } from "../lib/utils"; import { SpinnerIcon } from "../components/ui/spinner-icon"; import { getMyProfile, updateProfile } from "../api/users.api"; import type { UserProfileData } from "../types/user.types"; import { toast } from "sonner"; -type SettingsTab = "profile" | "security" | "notifications" | "appearance"; +type SettingsTab = + | "subscription" + | "profile" + | "security" + | "notifications" + | "appearance"; const tabs: { id: SettingsTab; label: string; icon: typeof User }[] = [ + { id: "subscription", label: "Subscription", icon: CreditCard }, { id: "profile", label: "Profile", icon: User }, { id: "security", label: "Security", icon: Shield }, { id: "notifications", label: "Notifications", icon: Bell }, @@ -48,14 +70,14 @@ function Toggle({ aria-checked={enabled} onClick={onToggle} className={cn( - "relative inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:ring-offset-2", - enabled ? "bg-brand-500" : "bg-grayScale-200" + "relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full transition-colors focus-visible:outline-none", + enabled ? "bg-brand-500" : "bg-grayScale-200", )} > @@ -68,20 +90,20 @@ function SettingRow({ description, children, }: { - icon: typeof User; + icon: any; title: string; description: string; children: React.ReactNode; }) { return ( -
+
-
+
-

{title}

-

{description}

+

{title}

+

{description}

{children}
@@ -89,34 +111,143 @@ function SettingRow({ ); } -function LoadingSkeleton() { +// --- Subscription Tab --- + +function SubscriptionTab() { + const [subs, setSubs] = useState([ + { + id: "auto_renew", + name: "Auto-renewal", + desc: "Automatically renew your subscription when it expires", + enabled: true, + }, + { + id: "marketing_emails", + name: "Marketing Emails", + desc: "Receive updates about new features and promotions", + enabled: true, + }, + { + id: "priority_support", + name: "Priority Support", + desc: "Access 24/7 priority customer support", + enabled: true, + }, + ]); + + const [pendingToggle, setPendingToggle] = useState(null); + const [showWarning, setShowWarning] = useState(false); + + const handleToggle = (id: string) => { + const item = subs.find((s) => s.id === id); + if (item?.enabled) { + setPendingToggle(id); + setShowWarning(true); + } else { + setSubs((prev) => + prev.map((s) => (s.id === id ? { ...s, enabled: true } : s)), + ); + } + }; + + const confirmToggleOff = () => { + if (pendingToggle) { + setSubs((prev) => + prev.map((s) => + s.id === pendingToggle ? { ...s, enabled: false } : s, + ), + ); + setShowWarning(false); + setPendingToggle(null); + } + }; + return ( -
-
-
-
- {[1, 2, 3, 4].map((i) => ( -
- ))} -
-
-
- {[1, 2, 3, 4].map((j) => ( -
-
-
-
-
-
+
+ + + + Subscription Features + +

+ Customize your subscription experience and management preferences +

+
+ + {subs.map((sub, idx) => ( + +
+ + handleToggle(sub.id)} + /> +
- ))} +
+ ))} +
+
+ + + +
+
+
+ +
+
+

+ Are you absolutely sure? +

+

+ Disabling this feature might limit your experience. +

+
+
+ +
+

+ By turning this off, you will no longer receive the benefits + associated with this feature. Some changes might take up to 24 + hours to reflect. +

+
+ +
+ + +
-
-
+ +
); } +// --- Other Tabs (Existing, but with sidebar layout updates) --- + function ProfileTab({ profile }: { profile: UserProfileData }) { const [firstName, setFirstName] = useState(profile.first_name); const [lastName, setLastName] = useState(profile.last_name); @@ -142,79 +273,88 @@ function ProfileTab({ profile }: { profile: UserProfileData }) { }; return ( -
- -
- -
-
- -
- - Personal Information - -
+
+ +
+ + + Personal Information +
- - setFirstName(e.target.value)} /> + + setFirstName(e.target.value)} + className="rounded-[6px]" + />
- - setLastName(e.target.value)} /> + + setLastName(e.target.value)} + className="rounded-[6px]" + />
- - setNickName(e.target.value)} /> + + setNickName(e.target.value)} + className="rounded-[6px]" + />
- -
- -
-
- -
- - Preferences - -
+ +
+ + + Preferences +
- - setLanguage(e.target.value)} + className="rounded-[6px]" + >
-
- - -
- @@ -240,96 +380,102 @@ function SecurityTab() { }; return ( -
- -
- -
-
- -
- - Change Password - -
+
+ +
+ + + Change Password +
- +
- +
- +
- +
- +
- +
-
- - -
- -
-
- -
- - Two-Factor Authentication - -
-
- - - toast.info("2FA coming soon")} /> - - -
); } @@ -338,20 +484,14 @@ function NotificationsTab() { const [emailNotifs, setEmailNotifs] = useState(true); const [pushNotifs, setPushNotifs] = useState(true); const [loginAlerts, setLoginAlerts] = useState(true); - const [weeklyDigest, setWeeklyDigest] = useState(false); return ( - -
- -
-
- -
- - Notification Preferences - -
+ +
+ + + Notification Preferences + - setEmailNotifs(!emailNotifs)} /> + setEmailNotifs(!emailNotifs)} + /> - + - setPushNotifs(!pushNotifs)} /> + setPushNotifs(!pushNotifs)} + /> - + - setLoginAlerts(!loginAlerts)} /> - - - - setWeeklyDigest(!weeklyDigest)} /> + setLoginAlerts(!loginAlerts)} + /> @@ -394,17 +535,12 @@ function AppearanceTab() { const [theme, setTheme] = useState<"light" | "dark" | "system">("light"); return ( - -
- -
-
- -
- - Theme - -
+ +
+ + + Theme +
@@ -420,16 +556,18 @@ function AppearanceTab() { type="button" onClick={() => setTheme(id)} className={cn( - "flex flex-col items-center gap-2.5 rounded-xl border-2 px-4 py-5 transition-all", + "flex flex-col items-center gap-2.5 rounded-[6px] border-2 px-4 py-5 transition-all", theme === id - ? "border-brand-500 bg-brand-100/30 text-brand-600 shadow-sm" - : "border-grayScale-100 bg-white text-grayScale-400 hover:border-grayScale-200 hover:bg-grayScale-100/40" + ? "border-brand-500 bg-brand-50 text-brand-600 shadow-sm" + : "border-grayScale-100 bg-white text-grayScale-400 hover:border-grayScale-200 hover:bg-grayScale-50", )} >
@@ -444,7 +582,7 @@ function AppearanceTab() { } export function SettingsPage() { - const [activeTab, setActiveTab] = useState("profile"); + const [activeTab, setActiveTab] = useState("subscription"); const [profile, setProfile] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -464,21 +602,27 @@ export function SettingsPage() { fetchProfile(); }, []); - if (loading) return ; + if (loading) { + return ( +
+ +
+ ); + } if (error || !profile) { return (
- +
-

+

{error || "Settings not available"}

-

+

Please check your connection and try again.

@@ -489,40 +633,23 @@ export function SettingsPage() { } return ( -
- {/* Page header */} -
-

Settings

-

- Manage your account preferences and configuration +

+
+

+ Settings +

+

+ Manage your account preferences, subscriptions, and system + configurations with ease

- {/* Tab navigation */} -
- {tabs.map(({ id, label, icon: Icon }) => ( - - ))} +
+ {/* Content Area */} +
+ {activeTab === "subscription" && } +
- - {/* Tab content */} - {activeTab === "profile" && } - {activeTab === "security" && } - {activeTab === "notifications" && } - {activeTab === "appearance" && }
); } diff --git a/src/pages/content-management/ContentManagementLayout.tsx b/src/pages/content-management/ContentManagementLayout.tsx index 014ad8a..29419a6 100644 --- a/src/pages/content-management/ContentManagementLayout.tsx +++ b/src/pages/content-management/ContentManagementLayout.tsx @@ -1,10 +1,11 @@ -import { Outlet } from "react-router-dom" +import { Outlet } from "react-router-dom"; +import { ContentHierarchyList } from "./components/ContentHierarchyList"; export function ContentManagementLayout() { return (
-
+

@@ -15,9 +16,11 @@ export function ContentManagementLayout() {

+ +
- ) + ); } diff --git a/src/pages/content-management/components/ContentHierarchyList.tsx b/src/pages/content-management/components/ContentHierarchyList.tsx new file mode 100644 index 0000000..93c98af --- /dev/null +++ b/src/pages/content-management/components/ContentHierarchyList.tsx @@ -0,0 +1,539 @@ +import React, { useState } from "react"; +import { + DndContext, + closestCenter, + KeyboardSensor, + PointerSensor, + useSensor, + useSensors, + DragOverlay, +} from "@dnd-kit/core"; +import type { + DragEndEvent, + DragStartEvent, + UniqueIdentifier, +} from "@dnd-kit/core"; +import { + arrayMove, + SortableContext, + sortableKeyboardCoordinates, + verticalListSortingStrategy, + useSortable, +} from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; +import { + GripVertical, + ChevronDown, + ChevronRight, + LayoutGrid, + BookOpen, + Layers, + PlayCircle, + RotateCcw, + Edit2, + Trash2, + Image as ImageIcon, +} from "lucide-react"; +import { cn } from "../../../lib/utils"; + +// --- Types --- +export type ItemType = "program" | "course" | "module" | "lesson"; + +export interface BaseItem { + id: string; + name: string; + thumbnail?: string; +} + +export interface Program extends BaseItem {} +export interface Course extends BaseItem { + programId: string; +} +export interface Module extends BaseItem { + courseId: string; +} +export interface Lesson extends BaseItem { + moduleId: string; +} + +// --- Mock Data --- +const initialPrograms: Program[] = [ + { + id: "p1", + name: "Web Development Masterclass", + thumbnail: + "https://images.unsplash.com/photo-1498050108023-c5249f4df085?w=100&h=100&fit=crop", + }, + { + id: "p2", + name: "Mobile App Development", + thumbnail: + "https://images.unsplash.com/photo-1512941937669-90a1b58e7e9c?w=100&h=100&fit=crop", + }, + { + id: "p3", + name: "UI/UX Design Fundamentals", + thumbnail: + "https://images.unsplash.com/photo-1586717791821-3f44a563eb4c?w=100&h=100&fit=crop", + }, +]; + +const initialCourses: Course[] = [ + { id: "c1", name: "React for Beginners", programId: "p1" }, + { id: "c2", name: "Advanced Node.js", programId: "p1" }, + { id: "c3", name: "Swift UI Intro", programId: "p2" }, +]; + +const initialModules: Module[] = [ + { id: "m1", name: "Introduction to Hooks", courseId: "c1" }, + { id: "m2", name: "State Management", courseId: "c1" }, + { id: "m3", name: "Backend Architecture", courseId: "c2" }, +]; + +const initialLessons: Lesson[] = [ + { id: "l1", name: "What is useState?", moduleId: "m1" }, + { id: "l2", name: "useEffect deep dive", moduleId: "m1" }, + { id: "l3", name: "Redux Setup", moduleId: "m2" }, +]; + +// --- Components --- + +interface SortableItemProps { + id: string; + name: string; + icon: React.ReactNode; + thumbnail?: string; + onEdit?: (id: string) => void; + onDelete?: (id: string) => void; +} + +function SortableItem({ + id, + name, + icon, + thumbnail, + onEdit, + onDelete, +}: SortableItemProps) { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + }; + + return ( +
+
+ + +
+ {/* Thumbnail/Icon Container */} +
+ {thumbnail ? ( + {name} + ) : ( +
+ {icon} +
+ )} +
+ +
+ + {name} + +
+
+
+ +
+ + +
+
+ ); +} + +interface DraggableListProps { + items: BaseItem[]; + onReorder: (activeId: string, overId: string) => void; + icon: React.ReactNode; + onEdit?: (id: string) => void; + onDelete?: (id: string) => void; +} + +function DraggableList({ + items, + onReorder, + icon, + onEdit, + onDelete, +}: DraggableListProps) { + const [activeId, setActiveId] = useState(null); + + const sensors = useSensors( + useSensor(PointerSensor, { activationConstraint: { distance: 5 } }), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }), + ); + + const handleDragStart = (event: DragStartEvent) => + setActiveId(event.active.id); + + const handleDragEnd = (event: DragEndEvent) => { + const { active, over } = event; + if (over && active.id !== over.id) { + onReorder(active.id as string, over.id as string); + } + setActiveId(null); + }; + + const activeItem = items.find((i) => i.id === activeId); + + return ( + + +
+ {items.map((item) => ( + + ))} +
+
+ + {activeItem ? ( +
+
+
+ +
+
+
+ {activeItem.thumbnail ? ( + {activeItem.name} + ) : ( +
{icon}
+ )} +
+ + {activeItem.name} + +
+
+
+ ) : null} +
+
+ ); +} + +interface SectionProps { + title: string; + icon: React.ReactNode; + isOpen: boolean; + onToggle: () => void; + children: React.ReactNode; +} + +function HierarchySection({ + title, + icon, + isOpen, + onToggle, + children, +}: SectionProps) { + return ( +
+ +
+
{children}
+
+
+ ); +} + +export function ContentHierarchyList() { + const [programs, setPrograms] = useState(initialPrograms); + const [courses, setCourses] = useState(initialCourses); + const [modules, setModules] = useState(initialModules); + const [lessons, setLessons] = useState(initialLessons); + const [openSections, setOpenSections] = useState>({ + program: true, + }); + + const toggleSection = (id: string) => { + setOpenSections((prev) => ({ ...prev, [id]: !prev[id] })); + }; + + const reorder = ( + list: T[], + setList: React.Dispatch>, + activeId: string, + overId: string, + ) => { + const oldIndex = list.findIndex((i) => i.id === activeId); + const newIndex = list.findIndex((i) => i.id === overId); + if (oldIndex !== -1 && newIndex !== -1) { + setList(arrayMove(list, oldIndex, newIndex)); + } + }; + + const handleEdit = (type: ItemType, id: string) => { + console.log(`Edit ${type}: ${id}`); + // Logic for opening edit modal would go here + }; + + const handleDelete = (type: ItemType, id: string) => { + if (!window.confirm(`Are you sure you want to delete this ${type}?`)) + return; + + switch (type) { + case "program": + setPrograms((prev) => prev.filter((p) => p.id !== id)); + break; + case "course": + setCourses((prev) => prev.filter((c) => c.id !== id)); + break; + case "module": + setModules((prev) => prev.filter((m) => m.id !== id)); + break; + case "lesson": + setLessons((prev) => prev.filter((l) => l.id !== id)); + break; + } + }; + + const handleReset = () => { + setPrograms(initialPrograms); + setCourses(initialCourses); + setModules(initialModules); + setLessons(initialLessons); + }; + + return ( +
+
+
+

+ Content Hierarchy +

+

+ Manage the ordering and structure of your educational content +

+
+ +
+ +
+ {/* Program Section */} + } + isOpen={openSections.program} + onToggle={() => toggleSection("program")} + > + + reorder(programs, setPrograms, active, over) + } + icon={} + onEdit={(id) => handleEdit("program", id)} + onDelete={(id) => handleDelete("program", id)} + /> + + + {/* Course Section */} + } + isOpen={openSections.course} + onToggle={() => toggleSection("course")} + > + {programs.map((program) => { + const programCourses = courses.filter( + (c) => c.programId === program.id, + ); + if (programCourses.length === 0) return null; + return ( +
+

+ {program.name} +

+ + reorder(courses, setCourses, active, over) + } + icon={} + onEdit={(id) => handleEdit("course", id)} + onDelete={(id) => handleDelete("course", id)} + /> +
+ ); + })} +
+ + {/* Module Section */} + } + isOpen={openSections.module} + onToggle={() => toggleSection("module")} + > + {courses.map((course) => { + const courseModules = modules.filter( + (m) => m.courseId === course.id, + ); + if (courseModules.length === 0) return null; + return ( +
+

+ {course.name} +

+ + reorder(modules, setModules, active, over) + } + icon={} + onEdit={(id) => handleEdit("module", id)} + onDelete={(id) => handleDelete("module", id)} + /> +
+ ); + })} +
+ + {/* Lesson Section */} + } + isOpen={openSections.lesson} + onToggle={() => toggleSection("lesson")} + > + {modules.map((module) => { + const moduleLessons = lessons.filter( + (l) => l.moduleId === module.id, + ); + if (moduleLessons.length === 0) return null; + return ( +
+

+ {module.name} +

+ + reorder(lessons, setLessons, active, over) + } + icon={} + onEdit={(id) => handleEdit("lesson", id)} + onDelete={(id) => handleDelete("lesson", id)} + /> +
+ ); + })} +
+
+
+ ); +}