mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-29 18:13:47 +00:00
feat: Linear-style UI redesign with improved mobile experience
Major changes: - Redesign cabinet with Linear-style components and top navigation - Replace detail modals with dedicated pages (users, broadcasts, email templates) - Add email channel support for broadcasts - Remove pull-to-refresh, improve drag-and-drop on touch devices - Fix Telegram Mini App: fullscreen, back button, scroll restoration - Unify admin pages color scheme to design system - Mobile-first improvements: horizontal tabs for settings, better touch targets
This commit is contained in:
@@ -4,13 +4,13 @@ import { useTranslation } from 'react-i18next';
|
||||
import { adminSettingsApi, SettingDefinition } from '../api/adminSettings';
|
||||
import { themeColorsApi } from '../api/themeColors';
|
||||
import { useFavoriteSettings } from '../hooks/useFavoriteSettings';
|
||||
import { MenuIcon, MENU_SECTIONS, MenuItem, formatSettingKey } from '../components/admin';
|
||||
import { MENU_SECTIONS, MenuItem, formatSettingKey, AdminBackButton } from '../components/admin';
|
||||
import { AnalyticsTab } from '../components/admin/AnalyticsTab';
|
||||
import { BrandingTab } from '../components/admin/BrandingTab';
|
||||
import { ThemeTab } from '../components/admin/ThemeTab';
|
||||
import { FavoritesTab } from '../components/admin/FavoritesTab';
|
||||
import { SettingsTab } from '../components/admin/SettingsTab';
|
||||
import { SettingsSidebar } from '../components/admin/SettingsSidebar';
|
||||
import { SettingsMobileTabs } from '../components/admin/SettingsMobileTabs';
|
||||
import {
|
||||
SettingsSearch,
|
||||
SettingsSearchMobile,
|
||||
@@ -23,19 +23,13 @@ export default function AdminSettings() {
|
||||
// State
|
||||
const [activeSection, setActiveSection] = useState('branding');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
|
||||
// Favorites hook
|
||||
const { favorites, toggleFavorite, isFavorite } = useFavoriteSettings();
|
||||
|
||||
// Scroll to top on mount and section change (fix for mobile webviews)
|
||||
// Scroll to top on section change
|
||||
useEffect(() => {
|
||||
// Use requestAnimationFrame for smoother scroll on mobile
|
||||
requestAnimationFrame(() => {
|
||||
window.scrollTo({ top: 0, behavior: 'instant' });
|
||||
document.documentElement.scrollTop = 0;
|
||||
document.body.scrollTop = 0;
|
||||
});
|
||||
window.scrollTo({ top: 0, behavior: 'instant' });
|
||||
}, [activeSection]);
|
||||
|
||||
// Queries
|
||||
@@ -173,57 +167,92 @@ export default function AdminSettings() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="pt-safe flex min-h-screen">
|
||||
{/* Mobile overlay */}
|
||||
{mobileMenuOpen && (
|
||||
<div
|
||||
className="fixed inset-0 z-40 bg-black/50 lg:hidden"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
<>
|
||||
{/* Mobile Layout */}
|
||||
<div className="space-y-4 lg:hidden">
|
||||
<SettingsMobileTabs
|
||||
activeSection={activeSection}
|
||||
setActiveSection={setActiveSection}
|
||||
favoritesCount={favorites.length}
|
||||
/>
|
||||
)}
|
||||
<SettingsSearchMobile searchQuery={searchQuery} setSearchQuery={setSearchQuery} />
|
||||
<SettingsSearchResults searchQuery={searchQuery} resultsCount={filteredSettings.length} />
|
||||
{renderContent()}
|
||||
</div>
|
||||
|
||||
{/* Sidebar */}
|
||||
<SettingsSidebar
|
||||
activeSection={activeSection}
|
||||
setActiveSection={setActiveSection}
|
||||
mobileMenuOpen={mobileMenuOpen}
|
||||
setMobileMenuOpen={setMobileMenuOpen}
|
||||
favoritesCount={favorites.length}
|
||||
/>
|
||||
{/* Desktop Layout - fixed sidebar, scrollable content */}
|
||||
<div className="hidden h-[calc(100vh-120px)] lg:flex">
|
||||
{/* Fixed Sidebar */}
|
||||
<div className="w-64 shrink-0 overflow-y-auto border-r border-dark-700/50">
|
||||
<div className="border-b border-dark-700/50 p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<AdminBackButton />
|
||||
<h1 className="text-lg font-bold text-dark-100">{t('admin.settings.title')}</h1>
|
||||
</div>
|
||||
</div>
|
||||
<nav className="space-y-1 p-2">
|
||||
{MENU_SECTIONS.map((section, sectionIdx) => (
|
||||
<div key={section.id}>
|
||||
{sectionIdx > 0 && <div className="my-3 border-t border-dark-700/50" />}
|
||||
{section.items.map((item) => {
|
||||
const isActive = activeSection === item.id;
|
||||
const hasIcon = item.iconType === 'star';
|
||||
return (
|
||||
<button
|
||||
key={item.id}
|
||||
onClick={() => setActiveSection(item.id)}
|
||||
className={`flex w-full items-center gap-3 rounded-xl px-3 py-2.5 transition-all ${
|
||||
isActive
|
||||
? 'bg-accent-500/10 text-accent-400'
|
||||
: 'text-dark-400 hover:bg-dark-800/50 hover:text-dark-200'
|
||||
}`}
|
||||
>
|
||||
{hasIcon && (
|
||||
<svg
|
||||
className={`h-4 w-4 ${isActive ? 'fill-current' : ''}`}
|
||||
fill={isActive ? 'currentColor' : 'none'}
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
<span className="font-medium">{t(`admin.settings.${item.id}`)}</span>
|
||||
{item.id === 'favorites' && favorites.length > 0 && (
|
||||
<span className="ml-auto rounded-full bg-warning-500/20 px-2 py-0.5 text-xs text-warning-400">
|
||||
{favorites.length}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Main content */}
|
||||
<main className="min-w-0 flex-1">
|
||||
{/* Header */}
|
||||
<div className="sticky top-0 z-30 border-b border-dark-700/50 bg-dark-900/95 p-3 backdrop-blur-xl sm:p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={() => setMobileMenuOpen(true)}
|
||||
className="rounded-xl bg-dark-800 p-2 transition-colors hover:bg-dark-700 lg:hidden"
|
||||
>
|
||||
<MenuIcon />
|
||||
</button>
|
||||
|
||||
<h2 className="truncate text-lg font-semibold text-dark-100 sm:text-xl">
|
||||
{/* Scrollable Content */}
|
||||
<div className="min-w-0 flex-1 overflow-y-auto p-6">
|
||||
<div className="mb-4 flex items-center gap-3">
|
||||
<h2 className="truncate text-xl font-semibold text-dark-100">
|
||||
{t(`admin.settings.${activeSection}`)}
|
||||
</h2>
|
||||
|
||||
<div className="flex-1" />
|
||||
|
||||
<SettingsSearch
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={setSearchQuery}
|
||||
resultsCount={filteredSettings.length}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SettingsSearchMobile searchQuery={searchQuery} setSearchQuery={setSearchQuery} />
|
||||
|
||||
<SettingsSearchResults searchQuery={searchQuery} resultsCount={filteredSettings.length} />
|
||||
{renderContent()}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-3 sm:p-4 lg:p-6">{renderContent()}</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user