content tab convert as submenu
This commit is contained in:
49
src/App.tsx
49
src/App.tsx
@@ -1,4 +1,3 @@
|
||||
// In App.tsx - FIXED VERSION
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Login } from "./components/auth/Login";
|
||||
import { ForgotPassword } from "./components/auth/ForgotPassword";
|
||||
@@ -50,6 +49,7 @@ import { EditPodcast } from "./components/pages/EditPodcast";
|
||||
import { EditCaseStudy } from "./components/pages/EditCaseStudy";
|
||||
import { EditKlcArchiveContent } from "./components/pages/EditKlcArchiveContent";
|
||||
|
||||
|
||||
export default function App() {
|
||||
const [currentRoute, setCurrentRoute] = useState<Route>("/login");
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
@@ -269,7 +269,11 @@ export default function App() {
|
||||
return <Organizations {...commonProps} />;
|
||||
case "/users/organizations/new":
|
||||
return <NewOrganization {...commonProps} />;
|
||||
|
||||
// Updated content routes section
|
||||
// In App.tsx - Update the content routes section
|
||||
case "/content":
|
||||
case "/content/profiler":
|
||||
case "/content/blogs":
|
||||
case "/content/faqs":
|
||||
case "/content/webcasts":
|
||||
@@ -277,13 +281,28 @@ export default function App() {
|
||||
case "/content/reading-materials":
|
||||
case "/content/podcasts":
|
||||
case "/content/case-studies":
|
||||
case "/content/klc-content-archive":
|
||||
case "/content/klc-archives":
|
||||
console.log('🏠 App.tsx - Rendering ContentManager with route:', currentRoute);
|
||||
return <ContentManager {...commonProps} />;
|
||||
return <ContentManager {...commonProps} currentRoute={currentRoute} />;
|
||||
|
||||
// Add these new routes for content creation
|
||||
case "/content/blogs/new":
|
||||
return <NewBlog {...commonProps} />;
|
||||
case "/content/faqs/new":
|
||||
return <NewFAQ {...commonProps} />;
|
||||
// case "/content/webcasts/new":
|
||||
// return <NewWebcast {...commonProps} />;
|
||||
// case "/content/training-materials/new":
|
||||
// return <NewTrainingMaterial {...commonProps} />;
|
||||
// case "/content/reading-materials/new":
|
||||
// return <NewReadingMaterial {...commonProps} />;
|
||||
// case "/content/podcasts/new":
|
||||
// return <NewPodcast {...commonProps} />;
|
||||
// case "/content/case-studies/new":
|
||||
// return <NewCaseStudy {...commonProps} />;
|
||||
// case "/content/klc-archives/new":
|
||||
// return <NewKlcArchive {...commonProps} />;
|
||||
|
||||
case "/courses":
|
||||
return <Courses {...commonProps} />;
|
||||
case "/courses/course-builder":
|
||||
@@ -356,22 +375,10 @@ export default function App() {
|
||||
const blogId = currentRoute.split("/").pop();
|
||||
return <EditBlog {...commonProps} blogId={blogId} />;
|
||||
}
|
||||
if (currentRoute.startsWith("/content/case-studies/edit/")) {
|
||||
const caseStudyId = currentRoute.split("/").pop();
|
||||
return <EditCaseStudy {...commonProps} caseStudyId={caseStudyId} />;
|
||||
}
|
||||
if (currentRoute.startsWith("/content/klc-archive/edit/")) {
|
||||
const archiveId = currentRoute.split("/").pop();
|
||||
return <EditKlcArchiveContent {...commonProps} archiveId={archiveId} />;
|
||||
}
|
||||
if (currentRoute.startsWith("/content/webcasts/edit/")) {
|
||||
const webcastId = currentRoute.split("/").pop();
|
||||
return <EditWebcast {...commonProps} webcastId={webcastId} />;
|
||||
}
|
||||
if (currentRoute.startsWith("/content/podcasts/edit/")) {
|
||||
const podcastId = currentRoute.split("/").pop();
|
||||
return <EditPodcast {...commonProps} podcastId={podcastId} />;
|
||||
}
|
||||
if (currentRoute.startsWith("/content/training-materials/edit/")) {
|
||||
const trainingMaterialId = currentRoute.split("/").pop();
|
||||
return <EditTrainingMaterial {...commonProps} trainingMaterialId={trainingMaterialId} />;
|
||||
@@ -380,6 +387,18 @@ export default function App() {
|
||||
const readingMaterialId = currentRoute.split("/").pop();
|
||||
return <EditReadingMaterial {...commonProps} readingMaterialId={readingMaterialId} />;
|
||||
}
|
||||
if (currentRoute.startsWith("/content/podcasts/edit/")) {
|
||||
const podcastId = currentRoute.split("/").pop();
|
||||
return <EditPodcast {...commonProps} podcastId={podcastId} />;
|
||||
}
|
||||
if (currentRoute.startsWith("/content/case-studies/edit/")) {
|
||||
const caseStudyId = currentRoute.split("/").pop();
|
||||
return <EditCaseStudy {...commonProps} caseStudyId={caseStudyId} />;
|
||||
}
|
||||
if (currentRoute.startsWith("/content/klc-archive/edit/")) {
|
||||
const archiveId = currentRoute.split("/").pop();
|
||||
return <EditKlcArchiveContent {...commonProps} archiveId={archiveId} />;
|
||||
}
|
||||
if (currentRoute.startsWith("/programmes/") && currentRoute.endsWith("/assign")) {
|
||||
const programmeId = currentRoute.split("/")[2];
|
||||
return <ProgrammeAssignment programmeId={programmeId} {...commonProps} />;
|
||||
|
||||
@@ -20,7 +20,13 @@ import {
|
||||
Webcam,
|
||||
ClipboardList,
|
||||
ContactRound,
|
||||
Cog
|
||||
Cog,
|
||||
Video,
|
||||
BookOpen,
|
||||
Mic,
|
||||
Archive,
|
||||
ChevronDown,
|
||||
Target
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
Breadcrumb,
|
||||
@@ -103,7 +109,63 @@ const navigationItems: NavigationItem[] = [
|
||||
id: 'content',
|
||||
label: 'Content',
|
||||
icon: FileText,
|
||||
route: '/content'
|
||||
route: '/content',
|
||||
children: [
|
||||
{
|
||||
id: 'profiler',
|
||||
label: 'Profiler',
|
||||
icon: Target,
|
||||
route: '/content'
|
||||
},
|
||||
{
|
||||
id: 'blogs',
|
||||
label: 'Blogs',
|
||||
icon: FileText,
|
||||
route: '/content/blogs'
|
||||
},
|
||||
{
|
||||
id: 'faqs',
|
||||
label: 'FAQs',
|
||||
icon: FileText,
|
||||
route: '/content/faqs'
|
||||
},
|
||||
{
|
||||
id: 'webcasts',
|
||||
label: 'Webcasts',
|
||||
icon: Video,
|
||||
route: '/content/webcasts'
|
||||
},
|
||||
{
|
||||
id: 'training-materials',
|
||||
label: 'Training Materials',
|
||||
icon: BookOpen,
|
||||
route: '/content/training-materials'
|
||||
},
|
||||
{
|
||||
id: 'reading-materials',
|
||||
label: 'Reading Materials',
|
||||
icon: BookOpen,
|
||||
route: '/content/reading-materials'
|
||||
},
|
||||
{
|
||||
id: 'podcasts',
|
||||
label: 'Podcasts',
|
||||
icon: Mic,
|
||||
route: '/content/podcasts'
|
||||
},
|
||||
{
|
||||
id: 'case-studies',
|
||||
label: 'Case Studies',
|
||||
icon: ClipboardList,
|
||||
route: '/content/case-studies'
|
||||
},
|
||||
{
|
||||
id: 'klc-archives',
|
||||
label: 'KLC Archives',
|
||||
icon: Archive,
|
||||
route: '/content/klc-archives'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'courses',
|
||||
@@ -159,7 +221,6 @@ const navigationItems: NavigationItem[] = [
|
||||
icon: Cog,
|
||||
route: '/admin/profiler-master'
|
||||
},
|
||||
|
||||
{
|
||||
id: 'roles',
|
||||
label: 'Roles',
|
||||
@@ -275,6 +336,7 @@ export function AuthenticatedLayout({
|
||||
}: AuthenticatedLayoutProps) {
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const [localNotifications, setLocalNotifications] = useState(notifications);
|
||||
const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());
|
||||
|
||||
// Initialize auto-save and session timeout
|
||||
useAutoSave(formData, onAutoSave, currentRoute, user.email);
|
||||
@@ -285,6 +347,24 @@ export function AuthenticatedLayout({
|
||||
setLocalNotifications(notifications);
|
||||
}, [notifications]);
|
||||
|
||||
// Auto-expand parent items when on child routes
|
||||
useEffect(() => {
|
||||
const newExpandedItems = new Set<string>();
|
||||
|
||||
navigationItems.forEach(item => {
|
||||
if (item.children) {
|
||||
const isChildActive = item.children.some(child =>
|
||||
isActiveRoute(child.route)
|
||||
);
|
||||
if (isChildActive) {
|
||||
newExpandedItems.add(item.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setExpandedItems(newExpandedItems);
|
||||
}, [currentRoute]);
|
||||
|
||||
// Notification handlers
|
||||
const handleMarkAsRead = (notificationId: string) => {
|
||||
setLocalNotifications(prev =>
|
||||
@@ -328,8 +408,17 @@ export function AuthenticatedLayout({
|
||||
|
||||
const isActiveRoute = (route: string) => {
|
||||
if (!currentRoute || !route) return false;
|
||||
return currentRoute === route ||
|
||||
(route === '/users/individual' && currentRoute.startsWith('/users/'));
|
||||
|
||||
// Exact match
|
||||
if (currentRoute === route) return true;
|
||||
|
||||
// User routes
|
||||
if (route === '/users/individual' && currentRoute.startsWith('/users/')) return true;
|
||||
|
||||
// Content routes - match both parent and children
|
||||
if (route === '/content' && currentRoute.startsWith('/content/')) return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const getActiveParent = (items: NavigationItem[]): string | null => {
|
||||
@@ -346,11 +435,38 @@ export function AuthenticatedLayout({
|
||||
|
||||
const activeParent = getActiveParent(navigationItems);
|
||||
|
||||
const toggleExpanded = (itemId: string) => {
|
||||
const newExpandedItems = new Set(expandedItems);
|
||||
if (newExpandedItems.has(itemId)) {
|
||||
newExpandedItems.delete(itemId);
|
||||
} else {
|
||||
newExpandedItems.add(itemId);
|
||||
}
|
||||
setExpandedItems(newExpandedItems);
|
||||
};
|
||||
|
||||
const isExpanded = (itemId: string) => expandedItems.has(itemId);
|
||||
|
||||
const renderNavigationItem = (item: NavigationItem, isChild = false) => {
|
||||
const isActive = isActiveRoute(item.route);
|
||||
const isParentActive = activeParent === item.id;
|
||||
const hasChildren = item.children && item.children.length > 0;
|
||||
const expanded = isExpanded(item.id);
|
||||
const Icon = item.icon;
|
||||
|
||||
const handleItemClick = () => {
|
||||
if (hasChildren && !isChild) {
|
||||
// Toggle dropdown for parent items
|
||||
toggleExpanded(item.id);
|
||||
// If not active and has children, navigate to first child
|
||||
if (!isActive && item.children && item.children.length > 0) {
|
||||
onNavigate(item.children[0].route);
|
||||
}
|
||||
} else {
|
||||
// Navigate directly for child items or items without children
|
||||
onNavigate(item.route);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={item.id}>
|
||||
<Button
|
||||
@@ -358,20 +474,27 @@ export function AuthenticatedLayout({
|
||||
className={`w-full justify-start min-h-[44px] ${
|
||||
isActive
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: isParentActive && !isChild
|
||||
: expanded && !isChild
|
||||
? 'bg-accent text-accent-foreground'
|
||||
: 'hover:bg-accent hover:text-accent-foreground'
|
||||
} ${isChild ? 'ml-4 text-sm' : ''}`}
|
||||
onClick={() => onNavigate(item.route)}
|
||||
} ${isChild ? 'ml-4 text-sm' : ''} ${hasChildren ? 'pr-2' : ''}`}
|
||||
onClick={handleItemClick}
|
||||
>
|
||||
<Icon className={`${sidebarCollapsed ? 'mx-auto' : 'mr-2'} h-4 w-4 flex-shrink-0`} />
|
||||
{!sidebarCollapsed && (
|
||||
<span className="truncate">{item.label}</span>
|
||||
<>
|
||||
<span className="truncate flex-1 text-left">{item.label}</span>
|
||||
{hasChildren && (
|
||||
<ChevronDown className={`h-4 w-4 ml-auto transition-transform ${
|
||||
expanded ? 'rotate-180' : ''
|
||||
}`} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{/* Render children if parent is active and not collapsed */}
|
||||
{item.children && isParentActive && !sidebarCollapsed && (
|
||||
{/* Render children if dropdown is expanded and not collapsed */}
|
||||
{item.children && expanded && !sidebarCollapsed && (
|
||||
<div className="mt-1 space-y-1">
|
||||
{item.children.map(child => renderNavigationItem(child, true))}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { AuthenticatedLayout } from "../layout/AuthenticatedLayout";
|
||||
import { Card, CardContent } from "../ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
||||
import {
|
||||
Target,
|
||||
FileText,
|
||||
@@ -30,34 +29,38 @@ interface ContentManagerProps {
|
||||
currentRoute?: Route;
|
||||
}
|
||||
|
||||
const contentTabs = [
|
||||
const contentTypes = [
|
||||
{
|
||||
id: "profiler",
|
||||
label: "Profiler",
|
||||
icon: Target,
|
||||
canCreate: true,
|
||||
primaryAction: "New Profiler"
|
||||
primaryAction: "New Profiler",
|
||||
route: "/content"
|
||||
},
|
||||
{
|
||||
id: "blogs",
|
||||
label: "Blogs",
|
||||
icon: FileText,
|
||||
canCreate: true,
|
||||
primaryAction: "New Blog"
|
||||
primaryAction: "New Blog",
|
||||
route: "/content/blogs"
|
||||
},
|
||||
{
|
||||
id: "faq", // This is the tab ID - note it's "faq" without 's'
|
||||
label: "FAQ",
|
||||
id: "faqs",
|
||||
label: "FAQs",
|
||||
icon: HelpCircle,
|
||||
canCreate: true,
|
||||
primaryAction: "New FAQ"
|
||||
primaryAction: "New FAQ",
|
||||
route: "/content/faqs"
|
||||
},
|
||||
{
|
||||
id: "webcasts",
|
||||
label: "Webcasts",
|
||||
icon: Video,
|
||||
canCreate: false,
|
||||
primaryAction: "Upload Webcast"
|
||||
primaryAction: "Upload Webcast",
|
||||
route: "/content/webcasts"
|
||||
},
|
||||
{
|
||||
id: "training-materials",
|
||||
@@ -65,6 +68,7 @@ const contentTabs = [
|
||||
icon: BookOpen,
|
||||
canCreate: false,
|
||||
primaryAction: "Upload File",
|
||||
route: "/content/training-materials",
|
||||
hasInnerTabs: true,
|
||||
innerTabs: ["Facilitator Manual", "Participant handouts", "To be printed (for KLC team)"]
|
||||
},
|
||||
@@ -73,28 +77,32 @@ const contentTabs = [
|
||||
label: "Reading Materials",
|
||||
icon: BookOpen,
|
||||
canCreate: false,
|
||||
primaryAction: "Upload Reading Material"
|
||||
primaryAction: "Upload Reading Material",
|
||||
route: "/content/reading-materials"
|
||||
},
|
||||
{
|
||||
id: "podcasts",
|
||||
label: "Podcasts",
|
||||
icon: Mic,
|
||||
canCreate: false,
|
||||
primaryAction: "Upload Podcast"
|
||||
primaryAction: "Upload Podcast",
|
||||
route: "/content/podcasts"
|
||||
},
|
||||
{
|
||||
id: "case-studies",
|
||||
label: "Case Studies",
|
||||
icon: FileText,
|
||||
canCreate: false,
|
||||
primaryAction: "Upload Case Study"
|
||||
primaryAction: "Upload Case Study",
|
||||
route: "/content/case-studies"
|
||||
},
|
||||
{
|
||||
id: "klc-content-archive",
|
||||
label: "KLC Content Archive",
|
||||
id: "klc-archives",
|
||||
label: "KLC Archives",
|
||||
icon: FolderOpen,
|
||||
canCreate: false,
|
||||
primaryAction: "Upload File",
|
||||
route: "/content/klc-archives",
|
||||
hasInnerTabs: true,
|
||||
innerTabs: [
|
||||
"Leadership Lego Blocks",
|
||||
@@ -117,165 +125,131 @@ export function ContentManager({
|
||||
}: ContentManagerProps) {
|
||||
console.log('📦 ContentManager received currentRoute:', currentRoute);
|
||||
|
||||
const getInitialTab = () => {
|
||||
console.log('🔄 getInitialTab called with currentRoute:', currentRoute);
|
||||
|
||||
// Map routes to tab IDs correctly
|
||||
if (currentRoute.includes('/content/blogs')) return 'blogs';
|
||||
if (currentRoute.includes('/content/faqs')) return 'faq'; // This should be 'faq' to match the tab ID
|
||||
if (currentRoute.includes('/content/webcasts')) return 'webcasts';
|
||||
if (currentRoute.includes('/content/training-materials')) return 'training-materials';
|
||||
if (currentRoute.includes('/content/reading-materials')) return 'reading-materials';
|
||||
if (currentRoute.includes('/content/podcasts')) return 'podcasts';
|
||||
if (currentRoute.includes('/content/case-studies')) return 'case-studies';
|
||||
if (currentRoute.includes('/content/klc-content-archive')) return 'klc-content-archive';
|
||||
|
||||
console.log('📌 Defaulting to profiler tab');
|
||||
return 'profiler';
|
||||
};
|
||||
|
||||
const [activeTab, setActiveTab] = useState(getInitialTab());
|
||||
const [activeInnerTab, setActiveInnerTab] = useState<{ [key: string]: string }>({
|
||||
"training-materials": "Facilitator Manual",
|
||||
"klc-content-archive": "Leadership Lego Blocks"
|
||||
"klc-archives": "Leadership Lego Blocks"
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
console.log('🔄 Route changed, updating active tab. Current route:', currentRoute);
|
||||
const newTab = getInitialTab();
|
||||
console.log('📌 Setting active tab to:', newTab);
|
||||
setActiveTab(newTab);
|
||||
}, [currentRoute]);
|
||||
|
||||
const handleTabChange = (tabId: string) => {
|
||||
console.log('🔄 Tab changed to:', tabId);
|
||||
setActiveTab(tabId);
|
||||
const getCurrentContentType = () => {
|
||||
console.log('🔄 getCurrentContentType called with currentRoute:', currentRoute);
|
||||
|
||||
// Navigate to specific routes when tabs are clicked
|
||||
switch (tabId) {
|
||||
case 'blogs':
|
||||
onNavigate('/content/blogs');
|
||||
break;
|
||||
case 'faq': // This should be 'faq' to match the tab ID
|
||||
onNavigate('/content/faqs');
|
||||
break;
|
||||
case 'webcasts':
|
||||
onNavigate('/content/webcasts');
|
||||
break;
|
||||
case 'training-materials':
|
||||
onNavigate('/content/training-materials');
|
||||
break;
|
||||
case 'reading-materials':
|
||||
onNavigate('/content/reading-materials');
|
||||
break;
|
||||
case 'podcasts':
|
||||
onNavigate('/content/podcasts');
|
||||
break;
|
||||
case 'case-studies':
|
||||
onNavigate('/content/case-studies');
|
||||
break;
|
||||
case 'klc-content-archive':
|
||||
onNavigate('/content/klc-content-archive');
|
||||
break;
|
||||
// Find exact route match first
|
||||
const exactMatch = contentTypes.find(type => currentRoute === type.route);
|
||||
if (exactMatch) {
|
||||
console.log('✅ Exact route match found:', exactMatch.id);
|
||||
return exactMatch;
|
||||
}
|
||||
|
||||
// Find partial match for nested routes
|
||||
const partialMatch = contentTypes.find(type =>
|
||||
currentRoute.startsWith(type.route + '/') ||
|
||||
currentRoute.includes(type.id)
|
||||
);
|
||||
|
||||
if (partialMatch) {
|
||||
console.log('✅ Partial route match found:', partialMatch.id);
|
||||
return partialMatch;
|
||||
}
|
||||
|
||||
console.log('⚠️ No match found, defaulting to profiler');
|
||||
return contentTypes[0]; // Default to profiler
|
||||
};
|
||||
|
||||
const currentContentType = getCurrentContentType();
|
||||
console.log('📌 Current content type:', currentContentType.label);
|
||||
|
||||
const getBreadcrumbs = () => {
|
||||
const breadcrumbs = [
|
||||
{ label: "Content", href: "/content" }
|
||||
];
|
||||
|
||||
// Add specific content type to breadcrumbs if not on the main content page
|
||||
if (currentRoute !== "/content") {
|
||||
breadcrumbs.push({ label: currentContentType.label });
|
||||
}
|
||||
|
||||
return breadcrumbs;
|
||||
};
|
||||
|
||||
const renderContent = () => {
|
||||
console.log('🎨 Rendering content for:', currentContentType.id);
|
||||
|
||||
switch (currentContentType.id) {
|
||||
case "profiler":
|
||||
return <ProfilerTab onNavigate={onNavigate} user={user} />;
|
||||
|
||||
case "blogs":
|
||||
return <BlogsTab onNavigate={onNavigate} user={user} />;
|
||||
|
||||
case "faqs":
|
||||
return <FAQTab onNavigate={onNavigate} user={user} />;
|
||||
|
||||
case "webcasts":
|
||||
return <WebcastsTab onNavigate={onNavigate} user={user} />;
|
||||
|
||||
case "training-materials":
|
||||
return (
|
||||
<TrainingMaterialsTab
|
||||
onNavigate={onNavigate}
|
||||
user={user}
|
||||
activeInnerTab={activeInnerTab}
|
||||
setActiveInnerTab={setActiveInnerTab}
|
||||
/>
|
||||
);
|
||||
|
||||
case "reading-materials":
|
||||
return <ReadingMaterialsTab onNavigate={onNavigate} user={user} />;
|
||||
|
||||
case "podcasts":
|
||||
return <PodcastsTab onNavigate={onNavigate} user={user} />;
|
||||
|
||||
case "case-studies":
|
||||
return <CaseStudiesTab onNavigate={onNavigate} user={user} />;
|
||||
|
||||
case "klc-archives":
|
||||
return (
|
||||
<KLCContentArchiveTab
|
||||
onNavigate={onNavigate}
|
||||
user={user}
|
||||
activeInnerTab={activeInnerTab}
|
||||
setActiveInnerTab={setActiveInnerTab}
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
onNavigate('/content');
|
||||
console.log('⚠️ Defaulting to profiler tab');
|
||||
return <ProfilerTab onNavigate={onNavigate} user={user} />;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log('📌 Current active tab:', activeTab);
|
||||
}, [activeTab]);
|
||||
|
||||
const breadcrumbs = [
|
||||
{ label: "Content" }
|
||||
];
|
||||
|
||||
const currentTab = contentTabs.find(tab => tab.id === activeTab);
|
||||
|
||||
return (
|
||||
<AuthenticatedLayout
|
||||
currentRoute="/content"
|
||||
currentRoute={currentRoute}
|
||||
onNavigate={onNavigate}
|
||||
onLogout={onLogout}
|
||||
user={user}
|
||||
breadcrumbs={breadcrumbs}
|
||||
breadcrumbs={getBreadcrumbs()}
|
||||
>
|
||||
<div className="p-6 space-y-6 max-w-[1440px] mx-auto">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1>Content Manager</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Manage all content types and profiler submissions with approval workflow
|
||||
<h1 className="text-3xl font-bold tracking-tight">
|
||||
{currentContentType.label}
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
{currentContentType.id === "profiler"
|
||||
? "Manage all content types and profiler submissions with approval workflow"
|
||||
: `Manage and organize ${currentContentType.label.toLowerCase()}`
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tabs value={activeTab} onValueChange={handleTabChange}>
|
||||
<TabsList className="grid w-full grid-cols-9 h-auto p-1">
|
||||
{contentTabs.map((tab) => {
|
||||
const Icon = tab.icon;
|
||||
return (
|
||||
<TabsTrigger
|
||||
key={tab.id}
|
||||
value={tab.id}
|
||||
className="flex flex-col gap-1 py-3 px-2 text-xs data-[state=active]:bg-[var(--color-brand-primary)] data-[state=active]:text-white focus-visible:ring-2 focus-visible:ring-[var(--color-brand-primary)] focus-visible:ring-opacity-50"
|
||||
>
|
||||
<Icon className="h-4 w-4" />
|
||||
<span className="hidden sm:block">{tab.label}</span>
|
||||
</TabsTrigger>
|
||||
);
|
||||
})}
|
||||
</TabsList>
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<TabsContent value="profiler">
|
||||
<ProfilerTab onNavigate={onNavigate} user={user} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="blogs">
|
||||
<BlogsTab onNavigate={onNavigate} user={user} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="faq"> {/* This matches the tab ID */}
|
||||
<FAQTab onNavigate={onNavigate} user={user} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="webcasts">
|
||||
<WebcastsTab onNavigate={onNavigate} user={user} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="training-materials">
|
||||
<TrainingMaterialsTab
|
||||
onNavigate={onNavigate}
|
||||
user={user}
|
||||
activeInnerTab={activeInnerTab}
|
||||
setActiveInnerTab={setActiveInnerTab}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="reading-materials">
|
||||
<ReadingMaterialsTab onNavigate={onNavigate} user={user} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="podcasts">
|
||||
<PodcastsTab onNavigate={onNavigate} user={user} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="case-studies">
|
||||
<CaseStudiesTab onNavigate={onNavigate} user={user} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="klc-content-archive">
|
||||
<KLCContentArchiveTab
|
||||
onNavigate={onNavigate}
|
||||
user={user}
|
||||
activeInnerTab={activeInnerTab}
|
||||
setActiveInnerTab={setActiveInnerTab}
|
||||
/>
|
||||
</TabsContent>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Tabs>
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
{renderContent()}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</AuthenticatedLayout>
|
||||
);
|
||||
|
||||
@@ -100,6 +100,8 @@ export function UploadDrawer({
|
||||
const config = contentTypeConfig[contentType];
|
||||
const Icon = config.icon;
|
||||
|
||||
// Commenting out file upload functionality since we're only using URL input
|
||||
/*
|
||||
const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const selectedFiles = event.target.files;
|
||||
if (!selectedFiles) return;
|
||||
@@ -123,6 +125,14 @@ export function UploadDrawer({
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveFile = (index: number) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
files: prev.files.filter((_, i) => i !== index)
|
||||
}));
|
||||
};
|
||||
*/
|
||||
|
||||
const handleFileUrlChange = (url: string) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
@@ -138,13 +148,6 @@ export function UploadDrawer({
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveFile = (index: number) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
files: prev.files.filter((_, i) => i !== index)
|
||||
}));
|
||||
};
|
||||
|
||||
const handleAddTag = () => {
|
||||
if (tagInput.trim() && !formData.tags.includes(tagInput.trim())) {
|
||||
setFormData(prev => ({
|
||||
@@ -180,13 +183,13 @@ export function UploadDrawer({
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if either files or fileUrl are provided
|
||||
if (formData.files.length === 0 && !formData.fileUrl) {
|
||||
toast.error("Please select at least one file or add a file URL");
|
||||
// Check if fileUrl is provided (files upload is commented out)
|
||||
if (!formData.fileUrl) {
|
||||
toast.error("Please add a file URL");
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate fileUrl if provided
|
||||
// Validate fileUrl
|
||||
if (formData.fileUrl) {
|
||||
try {
|
||||
new URL(formData.fileUrl);
|
||||
@@ -210,10 +213,6 @@ export function UploadDrawer({
|
||||
if (formData.fileUrl) {
|
||||
// Use the provided URL directly
|
||||
uploadData.fileUrl = formData.fileUrl.trim();
|
||||
} else if (formData.files.length > 0) {
|
||||
// If files are uploaded, simulate upload and get URL
|
||||
const fileUrl = await uploadFileToServer(formData.files[0]);
|
||||
uploadData.fileUrl = fileUrl;
|
||||
}
|
||||
|
||||
// Validate that we have a fileUrl
|
||||
@@ -246,6 +245,8 @@ export function UploadDrawer({
|
||||
}
|
||||
};
|
||||
|
||||
// Commenting out file upload server function since we're only using URL input
|
||||
/*
|
||||
// Function to upload file to your server and get back the file URL
|
||||
const uploadFileToServer = async (file: File): Promise<string> => {
|
||||
// TODO: Implement your actual file upload logic here
|
||||
@@ -268,6 +269,7 @@ export function UploadDrawer({
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
};
|
||||
*/
|
||||
|
||||
// Reset form when drawer closes
|
||||
React.useEffect(() => {
|
||||
@@ -324,13 +326,13 @@ export function UploadDrawer({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* File Upload Section */}
|
||||
{/* File Upload Section - ONLY URL INPUT */}
|
||||
<div className="space-y-4">
|
||||
<Label>
|
||||
File URL <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
|
||||
{/* File URL Input */}
|
||||
{/* File URL Input - PRIMARY METHOD: User provides a direct URL to the file */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Link className="h-4 w-4 text-green-600" />
|
||||
@@ -341,12 +343,14 @@ export function UploadDrawer({
|
||||
className="flex-1"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{/* Commenting out the "Or upload a file below" text */}
|
||||
{/* <p className="text-xs text-muted-foreground">
|
||||
Or upload a file below
|
||||
</p>
|
||||
</p> */}
|
||||
</div>
|
||||
|
||||
{/* File Upload as alternative */}
|
||||
{/* COMMENTING OUT FILE UPLOAD SECTION - Only using URL input */}
|
||||
{/*
|
||||
<div className="border-2 border-dashed border-muted-foreground/25 rounded-lg p-6 text-center">
|
||||
{formData.files.length > 0 ? (
|
||||
<div className="space-y-3">
|
||||
@@ -410,6 +414,7 @@ export function UploadDrawer({
|
||||
className="hidden"
|
||||
/>
|
||||
</div>
|
||||
*/}
|
||||
</div>
|
||||
|
||||
{/* Tags */}
|
||||
@@ -465,7 +470,7 @@ export function UploadDrawer({
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleUpload}
|
||||
disabled={isUploading || !formData.title.trim() || (!formData.fileUrl && formData.files.length === 0)}
|
||||
disabled={isUploading || !formData.title.trim() || !formData.fileUrl}
|
||||
className="flex-1"
|
||||
style={{ backgroundColor: "var(--color-brand-primary)" }}
|
||||
>
|
||||
|
||||
@@ -10,6 +10,7 @@ export type Route =
|
||||
| "/users/organizations"
|
||||
| "/users/organizations/new"
|
||||
| "/content"
|
||||
| "/content/profiler"
|
||||
| "/content/blogs"
|
||||
| "/content/blogs/new"
|
||||
| `/content/blogs/edit/${string}`
|
||||
@@ -19,16 +20,22 @@ export type Route =
|
||||
| `/content/faqs/edit/${string}`
|
||||
| `/content/faqs/view/${string}`
|
||||
| "/content/webcasts"
|
||||
| "/content/training-materials"
|
||||
| "/content/reading-materials"
|
||||
| "/content/podcasts"
|
||||
| "/content/case-studies"
|
||||
| "/content/klc-content-archive"
|
||||
| "/content/webcasts/new" // Add this
|
||||
| `/content/webcasts/edit/${string}`
|
||||
| "/content/training-materials"
|
||||
| "/content/training-materials/new" // Add this
|
||||
| `/content/training-materials/edit/${string}`
|
||||
| "/content/reading-materials"
|
||||
| "/content/reading-materials/new" // Add this
|
||||
| `/content/reading-materials/edit/${string}`
|
||||
| "/content/podcasts"
|
||||
| "/content/podcasts/new" // Add this
|
||||
| `/content/podcasts/edit/${string}`
|
||||
| "/content/case-studies"
|
||||
| "/content/case-studies/new" // Add this
|
||||
| `/content/case-studies/edit/${string}`
|
||||
| "/content/klc-archives" // Changed from klc-content-archive
|
||||
| "/content/klc-archives/new" // Add this
|
||||
| `/content/klc-archive/edit/${string}`
|
||||
| "/courses"
|
||||
| "/courses/course-builder"
|
||||
|
||||
Reference in New Issue
Block a user