fix issues
This commit is contained in:
69
src/App.tsx
69
src/App.tsx
@@ -1,3 +1,4 @@
|
||||
// In App.tsx - FIXED VERSION
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Login } from "./components/auth/Login";
|
||||
import { ForgotPassword } from "./components/auth/ForgotPassword";
|
||||
@@ -18,7 +19,6 @@ import { ProfilerBuilder } from "./components/pages/ProfilerBuilder";
|
||||
import { ProfilerMaster } from "./components/pages/ProfilerMaster";
|
||||
import { ProfilerPreview } from "./components/pages/ProfilerPreview";
|
||||
import { ProfilerApproval } from "./components/pages/ProfilerApproval";
|
||||
|
||||
import { LandingPages } from "./components/pages/LandingPagesNew";
|
||||
import { HomeEditor } from "./components/pages/HomeEditor";
|
||||
import { ServicesEditor } from "./components/pages/ServicesEditor";
|
||||
@@ -38,13 +38,12 @@ import { Roles } from "./components/pages/Roles";
|
||||
import { Analytics } from "./components/pages/Analytics";
|
||||
import { Toaster } from "./components/ui/sonner";
|
||||
import { SESSION_CONFIG, AutoSaveData, mockNotifications, Notification, mockApprovalTasks, ApprovalTask } from "./data/mockData";
|
||||
|
||||
import { Route } from "./types/routes";
|
||||
import { ViewFAQ } from "./components/pages/ViewFAQ";
|
||||
import { ViewBlog } from "./components/pages/ViewBlog";
|
||||
import { EditFAQ } from "./components/pages/EditFAQ";
|
||||
import { EditBlog } from "./components/pages/EditBlog";
|
||||
import { EditWebcast, WebcastEditPage } from "./components/pages/EditWebcast";
|
||||
import { EditWebcast } from "./components/pages/EditWebcast";
|
||||
import { EditTrainingMaterial } from "./components/pages/EditTrainingMaterial";
|
||||
import { EditReadingMaterial } from "./components/pages/EditReadingMaterial";
|
||||
import { EditPodcast } from "./components/pages/EditPodcast";
|
||||
@@ -62,14 +61,10 @@ export default function App() {
|
||||
lastLogin: "2024-01-15 14:30",
|
||||
});
|
||||
|
||||
// Auto-save state management
|
||||
const [autoSaveData, setAutoSaveData] = useState<{ [key: string]: any }>({});
|
||||
|
||||
// Notification state management
|
||||
const [notifications, setNotifications] = useState<Notification[]>(mockNotifications);
|
||||
const [approvalTasks, setApprovalTasks] = useState<ApprovalTask[]>(mockApprovalTasks);
|
||||
|
||||
// Simulate authentication check
|
||||
useEffect(() => {
|
||||
const authToken = localStorage.getItem("klc_auth_token");
|
||||
if (authToken) {
|
||||
@@ -80,7 +75,6 @@ export default function App() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Auto-save functionality
|
||||
const handleAutoSave = (formData: any) => {
|
||||
const routeKey = `${currentRoute}_${user.email}`;
|
||||
setAutoSaveData(prev => ({
|
||||
@@ -88,7 +82,6 @@ export default function App() {
|
||||
[routeKey]: formData
|
||||
}));
|
||||
|
||||
// Persist to localStorage
|
||||
const autoSaveData: AutoSaveData = {
|
||||
userId: user.email,
|
||||
route: currentRoute,
|
||||
@@ -100,13 +93,11 @@ export default function App() {
|
||||
localStorage.setItem(`autosave_${routeKey}`, JSON.stringify(autoSaveData));
|
||||
};
|
||||
|
||||
// Get auto-saved data for current route
|
||||
const getAutoSavedData = () => {
|
||||
const routeKey = `${currentRoute}_${user.email}`;
|
||||
return autoSaveData[routeKey] || null;
|
||||
};
|
||||
|
||||
// Clear auto-saved data
|
||||
const clearAutoSavedData = (route?: string) => {
|
||||
const routeKey = route ? `${route}_${user.email}` : `${currentRoute}_${user.email}`;
|
||||
setAutoSaveData(prev => {
|
||||
@@ -117,7 +108,6 @@ export default function App() {
|
||||
localStorage.removeItem(`autosave_${routeKey}`);
|
||||
};
|
||||
|
||||
// Notification handlers
|
||||
const handleMarkNotificationAsRead = (notificationId: string) => {
|
||||
setNotifications(prev =>
|
||||
prev.map(n => n.id === notificationId ? { ...n, read: true } : n)
|
||||
@@ -132,14 +122,12 @@ export default function App() {
|
||||
setNotifications(prev => prev.filter(n => n.id !== notificationId));
|
||||
};
|
||||
|
||||
// Approval handlers
|
||||
const handleApproveTask = (taskId: string, comment: string) => {
|
||||
setApprovalTasks(prev =>
|
||||
prev.map(task =>
|
||||
task.id === taskId ? { ...task, status: 'approved' as const } : task
|
||||
)
|
||||
);
|
||||
// Add success notification
|
||||
const task = approvalTasks.find(t => t.id === taskId);
|
||||
if (task) {
|
||||
const newNotification: Notification = {
|
||||
@@ -166,7 +154,6 @@ export default function App() {
|
||||
task.id === taskId ? { ...task, status: 'rejected' as const } : task
|
||||
)
|
||||
);
|
||||
// Add notification
|
||||
const task = approvalTasks.find(t => t.id === taskId);
|
||||
if (task) {
|
||||
const newNotification: Notification = {
|
||||
@@ -193,7 +180,6 @@ export default function App() {
|
||||
task.id === taskId ? { ...task, status: 'changes_requested' as const } : task
|
||||
)
|
||||
);
|
||||
// Add notification
|
||||
const task = approvalTasks.find(t => t.id === taskId);
|
||||
if (task) {
|
||||
const newNotification: Notification = {
|
||||
@@ -215,12 +201,12 @@ export default function App() {
|
||||
};
|
||||
|
||||
const navigate = (route: Route) => {
|
||||
console.log('🧭 Navigating to:', route);
|
||||
setCurrentRoute(route);
|
||||
};
|
||||
|
||||
const login = () => {
|
||||
localStorage.setItem("klc_auth_token", "mock_token");
|
||||
// Set longer session timeout
|
||||
const expirationTime = Date.now() + SESSION_CONFIG.LOGOUT_TIMEOUT;
|
||||
localStorage.setItem("klc_auth_expiration", expirationTime.toString());
|
||||
|
||||
@@ -232,7 +218,6 @@ export default function App() {
|
||||
localStorage.removeItem("klc_auth_token");
|
||||
localStorage.removeItem("klc_auth_expiration");
|
||||
|
||||
// Clear all auto-saved data for this user
|
||||
Object.keys(localStorage).forEach(key => {
|
||||
if (key.startsWith(`autosave_`) && key.includes(user.email)) {
|
||||
localStorage.removeItem(key);
|
||||
@@ -244,9 +229,9 @@ export default function App() {
|
||||
navigate("/login");
|
||||
};
|
||||
|
||||
// In your App.tsx, update the renderPage function:
|
||||
|
||||
const renderPage = () => {
|
||||
console.log('🏠 App.tsx - Current route:', currentRoute);
|
||||
|
||||
if (!isAuthenticated && !currentRoute.startsWith("/login")) {
|
||||
return <Login onLogin={login} onNavigate={navigate} />;
|
||||
}
|
||||
@@ -261,7 +246,8 @@ export default function App() {
|
||||
notifications,
|
||||
onMarkNotificationAsRead: handleMarkNotificationAsRead,
|
||||
onMarkAllNotificationsAsRead: handleMarkAllNotificationsAsRead,
|
||||
onDeleteNotification: handleDeleteNotification
|
||||
onDeleteNotification: handleDeleteNotification,
|
||||
currentRoute
|
||||
};
|
||||
|
||||
switch (currentRoute) {
|
||||
@@ -284,27 +270,20 @@ export default function App() {
|
||||
case "/users/organizations/new":
|
||||
return <NewOrganization {...commonProps} />;
|
||||
case "/content":
|
||||
case "/content/blogs":
|
||||
case "/content/faqs":
|
||||
case "/content/webcasts":
|
||||
case "/content/training-materials":
|
||||
case "/content/reading-materials":
|
||||
case "/content/podcasts":
|
||||
case "/content/case-studies":
|
||||
case "/content/klc-content-archive":
|
||||
console.log('🏠 App.tsx - Rendering ContentManager with route:', currentRoute);
|
||||
return <ContentManager {...commonProps} />;
|
||||
case "/content/blogs/new":
|
||||
return <NewBlog {...commonProps} />;
|
||||
case "/content/faqs/new":
|
||||
return <NewFAQ {...commonProps} />;
|
||||
case "/content/faqs":
|
||||
return <ContentManager {...commonProps} />;
|
||||
case "/content/blogs":
|
||||
return <ContentManager {...commonProps} />;
|
||||
|
||||
// ADD THESE ROUTES FOR WEBCASTS AND TRAINING MATERIALS
|
||||
case "/content/webcasts":
|
||||
return <ContentManager {...commonProps} />;
|
||||
case "/content/training-materials":
|
||||
return <ContentManager {...commonProps} />;
|
||||
case "/content/reading-materials":
|
||||
return <ContentManager {...commonProps} />;
|
||||
case "/content/podcasts":
|
||||
return <ContentManager {...commonProps} />;
|
||||
|
||||
|
||||
case "/courses":
|
||||
return <Courses {...commonProps} />;
|
||||
case "/courses/course-builder":
|
||||
@@ -361,15 +340,12 @@ export default function App() {
|
||||
case "/admin/analytics":
|
||||
return <Analytics {...commonProps} />;
|
||||
default:
|
||||
// Handle dynamic routes - UPDATE THIS SECTION:
|
||||
if (currentRoute.startsWith("/content/faqs/view/")) {
|
||||
const faqId = currentRoute.split("/").pop();
|
||||
console.log('🔄 Rendering ViewFAQ with ID:', faqId);
|
||||
return <ViewFAQ {...commonProps} faqId={faqId} />;
|
||||
}
|
||||
if (currentRoute.startsWith("/content/blogs/view/")) {
|
||||
const blogId = currentRoute.split("/").pop();
|
||||
console.log('🔄 Rendering ViewBlog with ID:', blogId);
|
||||
return <ViewBlog {...commonProps} blogId={blogId} />;
|
||||
}
|
||||
if (currentRoute.startsWith("/content/faqs/edit/")) {
|
||||
@@ -380,17 +356,14 @@ 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} />;
|
||||
@@ -407,16 +380,6 @@ export default function App() {
|
||||
const readingMaterialId = currentRoute.split("/").pop();
|
||||
return <EditReadingMaterial {...commonProps} readingMaterialId={readingMaterialId} />;
|
||||
}
|
||||
|
||||
|
||||
if (currentRoute.startsWith("/content/faqs/edit/")) {
|
||||
const faqId = currentRoute.split("/").pop();
|
||||
return <NewFAQ {...commonProps} formData={{ editMode: true, faqId }} />;
|
||||
}
|
||||
if (currentRoute.startsWith("/content/blogs/edit/")) {
|
||||
const blogId = currentRoute.split("/").pop();
|
||||
return <NewBlog {...commonProps} formData={{ editMode: true, blogId }} />;
|
||||
}
|
||||
if (currentRoute.startsWith("/programmes/") && currentRoute.endsWith("/assign")) {
|
||||
const programmeId = currentRoute.split("/")[2];
|
||||
return <ProgrammeAssignment programmeId={programmeId} {...commonProps} />;
|
||||
|
||||
@@ -23,6 +23,11 @@ export function TwoFactorAuth({ onLogin, onNavigate }: TwoFactorAuthProps) {
|
||||
|
||||
const otpRefs = useRef<(HTMLInputElement | null)[]>([]);
|
||||
|
||||
// Initialize refs array
|
||||
useEffect(() => {
|
||||
otpRefs.current = otpRefs.current.slice(0, 6);
|
||||
}, []);
|
||||
|
||||
// Get masked email from login context
|
||||
useEffect(() => {
|
||||
const loginContext = sessionStorage.getItem('login_context');
|
||||
@@ -30,7 +35,6 @@ export function TwoFactorAuth({ onLogin, onNavigate }: TwoFactorAuthProps) {
|
||||
const { email } = JSON.parse(loginContext);
|
||||
setMaskedEmail(email);
|
||||
} else {
|
||||
// Fallback masked email if no context
|
||||
setMaskedEmail('ad***@klc.edu');
|
||||
}
|
||||
}, []);
|
||||
@@ -43,11 +47,14 @@ export function TwoFactorAuth({ onLogin, onNavigate }: TwoFactorAuthProps) {
|
||||
}
|
||||
}, [resendCooldown]);
|
||||
|
||||
// Focus management - focus first input on mount
|
||||
// Focus first input on mount
|
||||
useEffect(() => {
|
||||
if (otpRefs.current[0]) {
|
||||
otpRefs.current[0].focus();
|
||||
}
|
||||
const timer = setTimeout(() => {
|
||||
if (otpRefs.current[0]) {
|
||||
otpRefs.current[0].focus();
|
||||
}
|
||||
}, 100);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
const handleVerifyCode = async (e: React.FormEvent) => {
|
||||
@@ -62,21 +69,15 @@ export function TwoFactorAuth({ onLogin, onNavigate }: TwoFactorAuthProps) {
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
// Simulate API call with different scenarios
|
||||
setTimeout(() => {
|
||||
if (code === '000000') {
|
||||
// Simulate expired code
|
||||
setError('Code expired. Request a new one.');
|
||||
} else if (code === '999999') {
|
||||
// Simulate too many attempts
|
||||
setError('Too many attempts. Please wait before retrying.');
|
||||
} else if (code === '123456') {
|
||||
// Successful verification
|
||||
// Clear session storage and redirect to dashboard
|
||||
sessionStorage.removeItem('login_context');
|
||||
onLogin();
|
||||
} else {
|
||||
// Incorrect code
|
||||
const newAttempts = attempts + 1;
|
||||
setAttempts(newAttempts);
|
||||
|
||||
@@ -86,7 +87,6 @@ export function TwoFactorAuth({ onLogin, onNavigate }: TwoFactorAuthProps) {
|
||||
setError('Incorrect code.');
|
||||
}
|
||||
|
||||
// Clear the code inputs and focus first input
|
||||
setVerificationCode(['', '', '', '', '', '']);
|
||||
if (otpRefs.current[0]) {
|
||||
otpRefs.current[0].focus();
|
||||
@@ -96,53 +96,85 @@ export function TwoFactorAuth({ onLogin, onNavigate }: TwoFactorAuthProps) {
|
||||
}, 1200);
|
||||
};
|
||||
|
||||
const handleCodeInput = (index: number, value: string) => {
|
||||
const handleCodeChange = (index: number, value: string) => {
|
||||
// Clear errors when user starts typing
|
||||
if (error) {
|
||||
setError('');
|
||||
}
|
||||
|
||||
// Handle paste - distribute "123456" across all inputs
|
||||
if (value.length > 1) {
|
||||
const pastedCode = value.slice(0, 6).split('');
|
||||
const newCode = [...verificationCode];
|
||||
|
||||
pastedCode.forEach((char, i) => {
|
||||
if (index + i < 6 && /^\d$/.test(char)) {
|
||||
newCode[index + i] = char;
|
||||
}
|
||||
});
|
||||
|
||||
setVerificationCode(newCode);
|
||||
|
||||
// Focus the last filled input or the next empty one
|
||||
const lastIndex = Math.min(index + pastedCode.length - 1, 5);
|
||||
const nextEmptyIndex = newCode.findIndex((char, i) => i > lastIndex && char === '');
|
||||
const focusIndex = nextEmptyIndex !== -1 ? nextEmptyIndex : lastIndex;
|
||||
|
||||
if (otpRefs.current[focusIndex]) {
|
||||
otpRefs.current[focusIndex]?.focus();
|
||||
}
|
||||
// Only allow digits
|
||||
if (value && !/^\d+$/.test(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle single character input (digits only)
|
||||
if (/^\d$/.test(value) || value === '') {
|
||||
// Handle single digit input
|
||||
if (value.length <= 1) {
|
||||
const newCode = [...verificationCode];
|
||||
newCode[index] = value;
|
||||
setVerificationCode(newCode);
|
||||
|
||||
// Auto-advance to next input
|
||||
if (value && index < 5 && otpRefs.current[index + 1]) {
|
||||
otpRefs.current[index + 1]?.focus();
|
||||
// Auto-focus next input if a digit was entered
|
||||
if (value && index < 5) {
|
||||
const nextInput = otpRefs.current[index + 1];
|
||||
if (nextInput) {
|
||||
// Use setTimeout to ensure the state update has happened
|
||||
setTimeout(() => {
|
||||
nextInput.focus();
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleCodeKeyDown = (index: number, e: React.KeyboardEvent) => {
|
||||
// Backspace navigation
|
||||
if (e.key === 'Backspace' && !verificationCode[index] && index > 0) {
|
||||
otpRefs.current[index - 1]?.focus();
|
||||
const handleKeyDown = (index: number, e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
// Handle backspace
|
||||
if (e.key === 'Backspace') {
|
||||
if (!verificationCode[index] && index > 0) {
|
||||
// Move to previous input if current is empty
|
||||
const prevInput = otpRefs.current[index - 1];
|
||||
if (prevInput) {
|
||||
prevInput.focus();
|
||||
}
|
||||
} else if (verificationCode[index]) {
|
||||
// Clear current input but stay focused
|
||||
const newCode = [...verificationCode];
|
||||
newCode[index] = '';
|
||||
setVerificationCode(newCode);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle arrow keys
|
||||
else if (e.key === 'ArrowLeft' && index > 0) {
|
||||
const prevInput = otpRefs.current[index - 1];
|
||||
if (prevInput) {
|
||||
prevInput.focus();
|
||||
}
|
||||
} else if (e.key === 'ArrowRight' && index < 5) {
|
||||
const nextInput = otpRefs.current[index + 1];
|
||||
if (nextInput) {
|
||||
nextInput.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {
|
||||
e.preventDefault();
|
||||
const pastedData = e.clipboardData.getData('text');
|
||||
const digits = pastedData.replace(/\D/g, '').slice(0, 6).split('');
|
||||
|
||||
if (digits.length === 6) {
|
||||
const newCode = [...verificationCode];
|
||||
digits.forEach((digit, i) => {
|
||||
newCode[i] = digit;
|
||||
});
|
||||
setVerificationCode(newCode);
|
||||
|
||||
// Focus the last input after paste
|
||||
setTimeout(() => {
|
||||
if (otpRefs.current[5]) {
|
||||
otpRefs.current[5].focus();
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -154,25 +186,11 @@ export function TwoFactorAuth({ onLogin, onNavigate }: TwoFactorAuthProps) {
|
||||
setVerificationCode(['', '', '', '', '', '']);
|
||||
|
||||
// Focus first input after resend
|
||||
if (otpRefs.current[0]) {
|
||||
otpRefs.current[0].focus();
|
||||
}
|
||||
|
||||
// Simulate API call
|
||||
setTimeout(() => {
|
||||
// Success handling would go here
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFirstInputFocus = () => {
|
||||
// Announce "6 fields" when first input is focused
|
||||
const announcement = document.getElementById('otp-announcement');
|
||||
if (announcement) {
|
||||
announcement.textContent = '6 digit verification code input fields';
|
||||
setTimeout(() => {
|
||||
announcement.textContent = '';
|
||||
}, 2000);
|
||||
if (otpRefs.current[0]) {
|
||||
otpRefs.current[0].focus();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -220,19 +238,23 @@ export function TwoFactorAuth({ onLogin, onNavigate }: TwoFactorAuthProps) {
|
||||
{verificationCode.map((digit, index) => (
|
||||
<Input
|
||||
key={index}
|
||||
ref={(el) => (otpRefs.current[index] = el)}
|
||||
ref={(el) => {
|
||||
otpRefs.current[index] = el;
|
||||
}}
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
maxLength={6}
|
||||
pattern="[0-9]*"
|
||||
maxLength={1}
|
||||
value={digit}
|
||||
onChange={(e) => handleCodeInput(index, e.target.value)}
|
||||
onKeyDown={(e) => handleCodeKeyDown(index, e)}
|
||||
onFocus={index === 0 ? handleFirstInputFocus : undefined}
|
||||
onChange={(e) => handleCodeChange(index, e.target.value)}
|
||||
onKeyDown={(e) => handleKeyDown(index, e)}
|
||||
onPaste={index === 0 ? handlePaste : undefined}
|
||||
className="w-12 h-12 text-center font-mono text-lg focus-visible:ring-2 focus-visible:ring-[var(--color-brand-primary)] focus-visible:ring-opacity-50"
|
||||
aria-label={`Digit ${index + 1} of 6`}
|
||||
aria-describedby={error ? "code-error" : undefined}
|
||||
aria-invalid={!!error}
|
||||
disabled={tooManyAttempts}
|
||||
autoComplete="one-time-code"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -327,26 +349,6 @@ export function TwoFactorAuth({ onLogin, onNavigate }: TwoFactorAuthProps) {
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
{/* Screen reader announcements */}
|
||||
<div
|
||||
id="otp-announcement"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-label="OTP field information"
|
||||
className="sr-only"
|
||||
/>
|
||||
|
||||
<div
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-label="Form status"
|
||||
className="sr-only"
|
||||
>
|
||||
{isLoading && "Verifying code, please wait"}
|
||||
{error && `Error: ${error}`}
|
||||
{resendCooldown > 0 && `Resend available in ${resendCooldown} seconds`}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState } from "react";
|
||||
// In ContentManager.tsx - FIXED VERSION
|
||||
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";
|
||||
@@ -20,11 +21,13 @@ import { ReadingMaterialsTab } from "./tabs/ReadingMaterialsTab";
|
||||
import { KLCContentArchiveTab } from "./tabs/KLCContentArchiveTab";
|
||||
import { CaseStudiesTab } from "./tabs/CaseStudiesTab";
|
||||
import { PodcastsTab } from "./tabs/PodcastsTab";
|
||||
import { Route } from "../../types/routes";
|
||||
|
||||
interface ContentManagerProps {
|
||||
onNavigate: (route: string) => void;
|
||||
onNavigate: (route: Route) => void;
|
||||
onLogout: () => void;
|
||||
user: any;
|
||||
currentRoute?: Route;
|
||||
}
|
||||
|
||||
const contentTabs = [
|
||||
@@ -43,7 +46,7 @@ const contentTabs = [
|
||||
primaryAction: "New Blog"
|
||||
},
|
||||
{
|
||||
id: "faq",
|
||||
id: "faq", // This is the tab ID - note it's "faq" without 's'
|
||||
label: "FAQ",
|
||||
icon: HelpCircle,
|
||||
canCreate: true,
|
||||
@@ -110,13 +113,79 @@ export function ContentManager({
|
||||
onNavigate,
|
||||
onLogout,
|
||||
user,
|
||||
currentRoute = "/content",
|
||||
}: ContentManagerProps) {
|
||||
const [activeTab, setActiveTab] = useState("profiler");
|
||||
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"
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
// 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;
|
||||
default:
|
||||
onNavigate('/content');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log('📌 Current active tab:', activeTab);
|
||||
}, [activeTab]);
|
||||
|
||||
const breadcrumbs = [
|
||||
{ label: "Content" }
|
||||
];
|
||||
@@ -132,7 +201,6 @@ export function ContentManager({
|
||||
breadcrumbs={breadcrumbs}
|
||||
>
|
||||
<div className="p-6 space-y-6 max-w-[1440px] mx-auto">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1>Content Manager</h1>
|
||||
@@ -142,9 +210,7 @@ export function ContentManager({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
<Tabs value={activeTab} onValueChange={handleTabChange}>
|
||||
<TabsList className="grid w-full grid-cols-9 h-auto p-1">
|
||||
{contentTabs.map((tab) => {
|
||||
const Icon = tab.icon;
|
||||
@@ -162,27 +228,22 @@ export function ContentManager({
|
||||
</TabsList>
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
{/* Profiler Tab */}
|
||||
<TabsContent value="profiler">
|
||||
<ProfilerTab onNavigate={onNavigate} user={user} />
|
||||
</TabsContent>
|
||||
|
||||
{/* Blogs Tab */}
|
||||
<TabsContent value="blogs">
|
||||
<BlogsTab onNavigate={onNavigate} user={user} />
|
||||
</TabsContent>
|
||||
|
||||
{/* FAQ Tab */}
|
||||
<TabsContent value="faq">
|
||||
<TabsContent value="faq"> {/* This matches the tab ID */}
|
||||
<FAQTab onNavigate={onNavigate} user={user} />
|
||||
</TabsContent>
|
||||
|
||||
{/* Webcasts Tab */}
|
||||
<TabsContent value="webcasts">
|
||||
<WebcastsTab onNavigate={onNavigate} user={user} />
|
||||
</TabsContent>
|
||||
|
||||
{/* Training Materials Tab */}
|
||||
<TabsContent value="training-materials">
|
||||
<TrainingMaterialsTab
|
||||
onNavigate={onNavigate}
|
||||
@@ -192,22 +253,18 @@ export function ContentManager({
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
{/* Reading Materials Tab */}
|
||||
<TabsContent value="reading-materials">
|
||||
<ReadingMaterialsTab onNavigate={onNavigate} user={user} />
|
||||
</TabsContent>
|
||||
|
||||
{/* Podcasts Tab */}
|
||||
<TabsContent value="podcasts">
|
||||
<PodcastsTab onNavigate={onNavigate} user={user} />
|
||||
</TabsContent>
|
||||
|
||||
{/* Case Studies Tab */}
|
||||
<TabsContent value="case-studies">
|
||||
<CaseStudiesTab onNavigate={onNavigate} user={user} />
|
||||
</TabsContent>
|
||||
|
||||
{/* KLC Content Archive Tab */}
|
||||
<TabsContent value="klc-content-archive">
|
||||
<KLCContentArchiveTab
|
||||
onNavigate={onNavigate}
|
||||
@@ -216,7 +273,6 @@ export function ContentManager({
|
||||
setActiveInnerTab={setActiveInnerTab}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Tabs>
|
||||
|
||||
@@ -214,7 +214,7 @@ export function EditBlog({
|
||||
}
|
||||
|
||||
toast.success(`Blog ${status === 'draft' ? 'updated' : 'published'} successfully`);
|
||||
onNavigate('/content');
|
||||
onNavigate('/content/blogs');
|
||||
} catch (error: any) {
|
||||
console.error('Error updating blog:', error);
|
||||
toast.error(error.data?.message || 'Failed to update blog. Please try again.');
|
||||
|
||||
@@ -134,7 +134,7 @@ export function EditCaseStudy({
|
||||
}
|
||||
|
||||
toast.success('Case study updated successfully');
|
||||
onNavigate('/content');
|
||||
onNavigate('/content/case-studies');
|
||||
} catch (error: any) {
|
||||
console.error('Error updating case study:', error);
|
||||
toast.error(error.data?.message || 'Failed to update case study. Please try again.');
|
||||
|
||||
@@ -157,7 +157,7 @@ export function EditFAQ({
|
||||
}
|
||||
|
||||
toast.success('FAQ updated successfully');
|
||||
onNavigate('/content');
|
||||
onNavigate('/content/faqs');
|
||||
} catch (error: any) {
|
||||
console.error('Error updating FAQ:', error);
|
||||
toast.error(error.data?.message || 'Failed to update FAQ. Please try again.');
|
||||
|
||||
@@ -145,7 +145,7 @@ export function EditKlcArchiveContent({
|
||||
}
|
||||
|
||||
toast.success('Archive content updated successfully');
|
||||
onNavigate('/content');
|
||||
onNavigate('/content/klc-content-archive');
|
||||
} catch (error: any) {
|
||||
console.error('Error updating archive content:', error);
|
||||
toast.error(error.data?.message || 'Failed to update archive content. Please try again.');
|
||||
|
||||
@@ -134,7 +134,7 @@ export function EditPodcast({
|
||||
}
|
||||
|
||||
toast.success('Podcast updated successfully');
|
||||
onNavigate('/content');
|
||||
onNavigate('/content/podcasts');
|
||||
} catch (error: any) {
|
||||
console.error('Error updating podcast:', error);
|
||||
toast.error(error.data?.message || 'Failed to update podcast. Please try again.');
|
||||
|
||||
@@ -136,7 +136,7 @@ export function EditReadingMaterial({
|
||||
}
|
||||
|
||||
toast.success('Reading material updated successfully');
|
||||
onNavigate('/content');
|
||||
onNavigate('/content/reading-materials');
|
||||
} catch (error: any) {
|
||||
console.error('Error updating reading material:', error);
|
||||
toast.error(error.data?.message || 'Failed to update reading material. Please try again.');
|
||||
|
||||
@@ -142,7 +142,7 @@ export function EditTrainingMaterial({
|
||||
}
|
||||
|
||||
toast.success('Training Material updated successfully');
|
||||
onNavigate('/content');
|
||||
onNavigate('/content/training-materials');
|
||||
} catch (error: any) {
|
||||
console.error('Error updating training material:', error);
|
||||
toast.error(error.data?.message || 'Failed to update training material. Please try again.');
|
||||
|
||||
@@ -140,7 +140,7 @@ export function EditWebcast({
|
||||
}
|
||||
|
||||
toast.success('Webcast updated successfully');
|
||||
onNavigate('/content');
|
||||
onNavigate('/content/webcasts');
|
||||
} catch (error: any) {
|
||||
console.error('Error updating webcast:', error);
|
||||
toast.error(error.data?.message || 'Failed to update webcast. Please try again.');
|
||||
|
||||
@@ -150,7 +150,7 @@ export function NewBlog({
|
||||
}
|
||||
|
||||
toast.success(`Blog ${status === 'draft' ? 'saved as draft' : 'published'} successfully`);
|
||||
onNavigate('/content');
|
||||
onNavigate('/content/blogs');
|
||||
} catch (error: any) {
|
||||
console.error('Error creating blog:', error);
|
||||
|
||||
@@ -179,7 +179,7 @@ export function NewBlog({
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => onNavigate('/content')}
|
||||
onClick={() => onNavigate('/content/blogs')}
|
||||
className="min-h-[44px] focus-visible:ring-2 focus-visible:ring-[var(--color-brand-primary)] focus-visible:ring-opacity-50"
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
|
||||
@@ -188,7 +188,7 @@ export function NewFAQ({
|
||||
}
|
||||
|
||||
toast.success(`${faqs.length} FAQ(s) created successfully`);
|
||||
onNavigate('/content');
|
||||
onNavigate('/content/faqs');
|
||||
} catch (error: any) {
|
||||
console.error('Error creating FAQs:', error);
|
||||
|
||||
@@ -242,7 +242,7 @@ export function NewFAQ({
|
||||
}
|
||||
|
||||
toast.success(`${faqs.length} FAQ(s) created successfully`);
|
||||
onNavigate('/content');
|
||||
onNavigate('/content/faqs');
|
||||
} catch (error: any) {
|
||||
console.error('Error creating FAQs:', error);
|
||||
toast.error(error.data?.message || 'Failed to save FAQs. Please try again.');
|
||||
|
||||
@@ -136,7 +136,7 @@ export function CaseStudiesTab({ onNavigate, user }: CaseStudiesTabProps) {
|
||||
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<span>
|
||||
Showing {enhancedCaseStudies.length} of {paginationMeta?.total || 0} items
|
||||
{/* Showing {enhancedCaseStudies.length} of {paginationMeta?.total || 0} items */}
|
||||
{paginationMeta && paginationMeta.totalPages > 1 && (
|
||||
<span> (Page {currentPage} of {paginationMeta.totalPages})</span>
|
||||
)}
|
||||
|
||||
@@ -112,7 +112,6 @@ export function KLCContentArchiveTab({
|
||||
setSelectedItems([]);
|
||||
};
|
||||
|
||||
// REMOVED: Filter by category since API data doesn't have category field
|
||||
// Use all data instead of filtering by category
|
||||
const filteredArchive = archiveContent.filter((archive: any) =>
|
||||
archive.title?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
@@ -192,7 +191,7 @@ export function KLCContentArchiveTab({
|
||||
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<span>
|
||||
Showing {enhancedArchive.length} of {paginationMeta?.total || 0} items
|
||||
{/* Showing {enhancedArchive.length} of {paginationMeta?.total || 0} items */}
|
||||
{paginationMeta && paginationMeta.totalPages > 1 && (
|
||||
<span> (Page {currentPage} of {paginationMeta.totalPages})</span>
|
||||
)}
|
||||
|
||||
@@ -136,7 +136,7 @@ export function PodcastsTab({ onNavigate, user }: PodcastsTabProps) {
|
||||
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<span>
|
||||
Showing {enhancedPodcasts.length} of {paginationMeta?.total || 0} items
|
||||
{/* Showing {enhancedPodcasts.length} of {paginationMeta?.total || 0} items */}
|
||||
{paginationMeta && paginationMeta.totalPages > 1 && (
|
||||
<span> (Page {currentPage} of {paginationMeta.totalPages})</span>
|
||||
)}
|
||||
|
||||
@@ -133,7 +133,7 @@ export function ReadingMaterialsTab({ onNavigate, user }: ReadingMaterialsTabPro
|
||||
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<span>
|
||||
Showing {enhancedReadingMaterials.length} of {paginationMeta?.total || 0} items
|
||||
{/* Showing {enhancedReadingMaterials.length} of {paginationMeta?.total || 0} items */}
|
||||
{paginationMeta && paginationMeta.totalPages > 1 && (
|
||||
<span> (Page {currentPage} of {paginationMeta.totalPages})</span>
|
||||
)}
|
||||
|
||||
@@ -211,7 +211,7 @@ export function TrainingMaterialsTab({
|
||||
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<span>
|
||||
Showing {enhancedMaterials.length} of {paginationMeta?.total || 0} items
|
||||
{/* Showing {enhancedMaterials.length} of {paginationMeta?.total || 0} items */}
|
||||
{paginationMeta && paginationMeta.totalPages > 1 && (
|
||||
<span> (Page {currentPage} of {paginationMeta.totalPages})</span>
|
||||
)}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// In types/routes.ts
|
||||
export type Route =
|
||||
| "/login"
|
||||
| "/login/forget-password"
|
||||
@@ -12,11 +13,23 @@ export type Route =
|
||||
| "/content/blogs"
|
||||
| "/content/blogs/new"
|
||||
| `/content/blogs/edit/${string}`
|
||||
| `/content/blogs/view/${string}` // Add blog view route
|
||||
| `/content/blogs/view/${string}`
|
||||
| "/content/faqs"
|
||||
| "/content/faqs/new"
|
||||
| `/content/faqs/edit/${string}`
|
||||
| `/content/faqs/view/${string}` // Add FAQ view route
|
||||
| `/content/faqs/view/${string}`
|
||||
| "/content/webcasts"
|
||||
| "/content/training-materials"
|
||||
| "/content/reading-materials"
|
||||
| "/content/podcasts"
|
||||
| "/content/case-studies"
|
||||
| "/content/klc-content-archive"
|
||||
| `/content/webcasts/edit/${string}`
|
||||
| `/content/training-materials/edit/${string}`
|
||||
| `/content/reading-materials/edit/${string}`
|
||||
| `/content/podcasts/edit/${string}`
|
||||
| `/content/case-studies/edit/${string}`
|
||||
| `/content/klc-archive/edit/${string}`
|
||||
| "/courses"
|
||||
| "/courses/course-builder"
|
||||
| "/courses/new"
|
||||
|
||||
Reference in New Issue
Block a user