Initial commit

This commit is contained in:
2025-12-04 22:24:47 +01:00
commit 453ce10494
106 changed files with 17145 additions and 0 deletions

View File

@@ -0,0 +1,217 @@
import { useState, useEffect, useRef } from 'react';
import { useAuth } from '../../contexts/AuthContext';
import { useTranslation } from '../../contexts/LanguageContext';
import { useSidebar } from '../../contexts/SidebarContext';
import { useModules } from '../../contexts/ModulesContext';
import type { ModuleId } from '../../contexts/ModulesContext';
import Feature1Tab from '../../components/admin/Feature1Tab';
import '../../styles/AdminPanel.css';
type TabId = 'feature1' | 'feature2' | 'feature3';
export default function Features() {
const { user: currentUser } = useAuth();
const { t } = useTranslation();
const { toggleMobileMenu } = useSidebar();
const { moduleStates, setModuleEnabled, saveModulesToBackend, hasInitialized } = useModules();
const [activeTab, setActiveTab] = useState<TabId>('feature1');
const hasUserMadeChanges = useRef(false);
const saveRef = useRef(saveModulesToBackend);
const [tooltip, setTooltip] = useState<{ text: string; left: number; visible: boolean }>({
text: '',
left: 0,
visible: false,
});
const handleTabMouseEnter = (text: string, e: React.MouseEvent) => {
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
setTooltip({
text,
left: rect.left + rect.width / 2,
visible: true,
});
};
const handleTabMouseLeave = () => {
setTooltip((prev) => ({ ...prev, visible: false }));
};
// Keep saveRef updated with latest function
useEffect(() => {
saveRef.current = saveModulesToBackend;
}, [saveModulesToBackend]);
if (!currentUser?.is_superuser) {
return null;
}
const handleModuleToggle = async (moduleId: ModuleId, type: 'admin' | 'user', enabled: boolean) => {
hasUserMadeChanges.current = true;
setModuleEnabled(moduleId, type, enabled);
};
// Save changes when moduleStates change, but debounce to avoid too many requests
// Only save if: 1) Backend data has been loaded, and 2) User has made changes
useEffect(() => {
if (!hasInitialized || !hasUserMadeChanges.current) {
return;
}
const timeoutId = setTimeout(() => {
saveRef.current().catch(console.error);
}, 300);
return () => clearTimeout(timeoutId);
}, [moduleStates, hasInitialized]);
// Save on unmount if there are pending changes (empty deps = only on unmount)
useEffect(() => {
return () => {
if (hasUserMadeChanges.current) {
saveRef.current().catch(console.error);
}
};
}, []);
const getModuleDescription = (moduleId: string): string => {
const key = `${moduleId}Desc` as keyof typeof t.admin;
return t.admin[key] || t.admin.moduleDefaultDesc;
};
const renderModuleToggle = (moduleId: ModuleId) => {
const state = moduleStates[moduleId];
const adminEnabled = state?.admin ?? true;
const userEnabled = state?.user ?? true;
return (
<div className="feature-header">
<div className="feature-header-info">
<p>{getModuleDescription(moduleId)}</p>
</div>
<div className="feature-header-actions">
<div className={`feature-status-badge ${adminEnabled ? 'active' : ''}`}>
{adminEnabled ? t.admin.active : t.admin.inactive}
</div>
<div className="toggle-group">
<span className="toggle-label">{t.admin.adminRole}</span>
<label className="toggle-modern">
<input
type="checkbox"
checked={adminEnabled}
onChange={(e) => handleModuleToggle(moduleId, 'admin', e.target.checked)}
/>
<span className="toggle-slider-modern"></span>
</label>
</div>
<div className="toggle-group">
<span className="toggle-label">{t.admin.userRole}</span>
<label className={`toggle-modern ${!adminEnabled ? 'disabled' : ''}`}>
<input
type="checkbox"
checked={userEnabled}
disabled={!adminEnabled}
onChange={(e) => handleModuleToggle(moduleId, 'user', e.target.checked)}
/>
<span className="toggle-slider-modern"></span>
</label>
</div>
</div>
</div>
);
};
const renderTabContent = () => {
switch (activeTab) {
case 'feature1':
return (
<>
{renderModuleToggle('feature1')}
<Feature1Tab />
</>
);
case 'feature2':
return (
<>
{renderModuleToggle('feature2')}
<div className="tab-content-placeholder">
<div className="placeholder-icon">
<span className="material-symbols-outlined">download</span>
</div>
<h3>{t.features.feature2}</h3>
<p>{t.features.comingSoon}</p>
</div>
</>
);
case 'feature3':
return (
<>
{renderModuleToggle('feature3')}
<div className="tab-content-placeholder">
<div className="placeholder-icon">
<span className="material-symbols-outlined">cast</span>
</div>
<h3>{t.features.feature3}</h3>
<p>{t.features.comingSoon}</p>
</div>
</>
);
default:
return null;
}
};
return (
<main className="main-content admin-panel-root">
{/* Tab Tooltip */}
{tooltip.visible && (
<div
className="admin-tab-tooltip"
style={{ left: tooltip.left }}
>
{tooltip.text}
</div>
)}
<div className="admin-tabs-container">
<div className="admin-tabs-slider">
<button className="mobile-menu-btn" onClick={toggleMobileMenu} aria-label={t.theme.toggleMenu}>
<span className="material-symbols-outlined">menu</span>
</button>
<div className="admin-title-section">
<span className="material-symbols-outlined">extension</span>
<span className="admin-title-text">{t.featuresPage.title}</span>
</div>
<div className="admin-tabs-divider"></div>
<button
className={`admin-tab-btn ${activeTab === 'feature1' ? 'active' : ''}`}
onClick={() => setActiveTab('feature1')}
onMouseEnter={(e) => handleTabMouseEnter(t.sidebar.feature1, e)}
onMouseLeave={handleTabMouseLeave}
>
<span className="material-symbols-outlined">playlist_play</span>
<span>{t.sidebar.feature1}</span>
</button>
<button
className={`admin-tab-btn ${activeTab === 'feature2' ? 'active' : ''}`}
onClick={() => setActiveTab('feature2')}
onMouseEnter={(e) => handleTabMouseEnter(t.sidebar.feature2, e)}
onMouseLeave={handleTabMouseLeave}
>
<span className="material-symbols-outlined">download</span>
<span>{t.sidebar.feature2}</span>
</button>
<button
className={`admin-tab-btn ${activeTab === 'feature3' ? 'active' : ''}`}
onClick={() => setActiveTab('feature3')}
onMouseEnter={(e) => handleTabMouseEnter(t.sidebar.feature3, e)}
onMouseLeave={handleTabMouseLeave}
>
<span className="material-symbols-outlined">cast</span>
<span>{t.sidebar.feature3}</span>
</button>
</div>
</div>
<div className="admin-tab-content">
{renderTabContent()}
</div>
</main>
);
}