refactor(web): split SettingsView into settings-view module

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
senke 2026-02-06 21:59:42 +01:00
parent 81a92b9b5a
commit 1a1b4972af
10 changed files with 272 additions and 199 deletions

View file

@ -1,5 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react';
import { SettingsView } from './SettingsView';
import { SettingsView, SettingsViewSkeleton } from './settings-view';
/**
* SettingsView - Vue principale des paramètres
@ -8,42 +8,55 @@ import { SettingsView } from './SettingsView';
* sections de paramètres (profil, compte, apparence, sécurité, etc.)
*/
const meta: Meta<typeof SettingsView> = {
title: 'Components/Features/Views/SettingsView',
component: SettingsView,
parameters: {
layout: 'fullscreen',
docs: {
description: {
component: 'Vue avec navigation par onglets vers toutes les sections de paramètres.',
},
},
title: 'Components/Features/Views/SettingsView',
component: SettingsView,
parameters: {
layout: 'fullscreen',
docs: {
description: {
component:
'Vue avec navigation par onglets vers toutes les sections de paramètres.',
},
},
tags: ['autodocs'],
argTypes: {
initialTab: {
control: 'select',
options: ['profile', 'account', 'appearance', 'accessibility', 'security', 'integrations', 'cloud', 'backups', 'data', 'audio', 'notifications'],
description: 'Onglet à afficher par défaut',
},
},
tags: ['autodocs'],
argTypes: {
initialTab: {
control: 'select',
options: [
'profile',
'account',
'appearance',
'accessibility',
'security',
'integrations',
'cloud',
'backups',
'data',
'audio',
'notifications',
],
description: 'Onglet à afficher par défaut',
},
decorators: [
(Story) => (
<div className="bg-kodo-background min-h-screen p-4">
<Story />
</div>
),
],
},
decorators: [
(Story) => (
<div className="bg-kodo-background min-h-layout-page p-4">
<Story />
</div>
),
],
};
export default meta;
type Story = StoryObj<typeof meta>;
/**
* État par défaut avec onglet Profile.
*/
export const Default: Story = {
name: 'Par défaut (Profile)',
args: {
initialTab: 'profile',
},
export const Loading: Story = {
name: 'Loading',
render: () => <SettingsViewSkeleton />,
};
export const Default: Story = {
name: 'Par défaut (Profile)',
args: { initialTab: 'profile' },
};

View file

@ -1,167 +1 @@
import React, { useState, useEffect } from 'react';
import { Card } from '../ui/card';
import { Tabs, TabsList, TabsTrigger } from '../ui/tabs';
import { useToast } from '../../components/feedback/ToastProvider';
import {
User,
Bell,
Palette,
Shield,
Volume2,
UserCog,
Cloud,
Database,
Accessibility,
Plug,
HardDrive,
} from 'lucide-react';
import { SecuritySettings } from '../settings/security/SecuritySettings';
import { EditProfile } from '../settings/profile/EditProfile';
import { AccountSettings } from '../settings/account/AccountSettings';
import { CloudIntegrationView } from '../settings/cloud/CloudIntegrationView';
import { BackupsView } from '../settings/backups/BackupsView';
import { AppearanceSettingsView } from '../settings/appearance/AppearanceSettingsView';
import { AccessibilitySettingsView } from '../settings/accessibility/AccessibilitySettingsView';
import { IntegrationsView } from '../settings/integrations/IntegrationsView';
import { DataExportView } from '../settings/data/DataExportView';
interface SettingsViewProps {
initialTab?: string;
}
export const SettingsView: React.FC<SettingsViewProps> = ({
initialTab = 'profile',
}) => {
const { addToast: _addToast } = useToast();
const [activeTab, setActiveTab] = useState(initialTab);
// Sync active tab if initialTab changes
useEffect(() => {
if (initialTab) setActiveTab(initialTab);
}, [initialTab]);
const settingsTabs = [
{ id: 'profile', label: 'Profile', icon: <User className="w-4 h-4" /> },
{ id: 'account', label: 'Account', icon: <UserCog className="w-4 h-4" /> },
{
id: 'appearance',
label: 'Appearance',
icon: <Palette className="w-4 h-4" />,
},
{
id: 'accessibility',
label: 'Accessibility',
icon: <Accessibility className="w-4 h-4" />,
},
{ id: 'security', label: 'Security', icon: <Shield className="w-4 h-4" /> },
{
id: 'integrations',
label: 'Integrations',
icon: <Plug className="w-4 h-4" />,
},
{ id: 'cloud', label: 'Cloud & Sync', icon: <Cloud className="w-4 h-4" /> },
{
id: 'backups',
label: 'Backups',
icon: <HardDrive className="w-4 h-4" />,
},
{
id: 'data',
label: 'Privacy & Data',
icon: <Database className="w-4 h-4" />,
},
{ id: 'audio', label: 'Audio', icon: <Volume2 className="w-4 h-4" /> },
{
id: 'notifications',
label: 'Notifications',
icon: <Bell className="w-4 h-4" />,
},
];
return (
<div className="flex flex-col gap-8 animate-fadeIn pb-20">
{/* Header */}
<div>
<h2 className="text-3xl font-display font-bold text-white mb-2">
SETTINGS
</h2>
<p className="text-kodo-content-dim font-mono text-sm">
Configure your studio, account, and preferences.
</p>
</div>
{/* Top Navigation using new Tabs component */}
<div className="border-b border-kodo-steel/50">
<Tabs value={activeTab} onValueChange={setActiveTab} className="pb-0">
<TabsList className="bg-transparent border-none p-0">
{settingsTabs.map((tab) => (
<TabsTrigger
key={tab.id}
value={tab.id}
className="rounded-none border-b-2 border-transparent data-[state=active]:border-kodo-steel data-[state=active]:bg-transparent data-[state=active]:shadow-none px-4 py-2"
>
<span className="flex items-center gap-2">
{tab.icon}
{tab.label}
</span>
</TabsTrigger>
))}
</TabsList>
</Tabs>
</div>
{/* Content Area */}
<div className="min-h-[500px]">
{/* PROFILE TAB (Phase 3) */}
{activeTab === 'profile' && <EditProfile />}
{/* ACCOUNT TAB (Phase 4) */}
{activeTab === 'account' && <AccountSettings />}
{/* APPEARANCE TAB (Phase 21) */}
{activeTab === 'appearance' && <AppearanceSettingsView />}
{/* ACCESSIBILITY TAB (Phase 21) */}
{activeTab === 'accessibility' && <AccessibilitySettingsView />}
{/* SECURITY TAB (Phase 2) */}
{activeTab === 'security' && <SecuritySettings />}
{/* INTEGRATIONS TAB (Phase 23) */}
{activeTab === 'integrations' && <IntegrationsView />}
{/* CLOUD TAB (Phase 18) */}
{activeTab === 'cloud' && <CloudIntegrationView />}
{/* BACKUPS TAB (Phase 18) */}
{activeTab === 'backups' && <BackupsView />}
{/* DATA EXPORT TAB (Phase 23) */}
{activeTab === 'data' && <DataExportView />}
{/* Placeholder for Audio & Notifications */}
{['audio', 'notifications'].includes(activeTab) && (
<Card variant="default" className="min-h-[400px]">
<div className="flex flex-col items-center justify-center h-full text-center animate-fadeIn py-24">
<div className="w-20 h-20 rounded-full bg-kodo-slate flex items-center justify-center mb-6">
{activeTab === 'audio' ? (
<Volume2 className="w-10 h-10 text-kodo-content-dim" />
) : (
<Bell className="w-10 h-10 text-kodo-content-dim" />
)}
</div>
<h3 className="text-xl font-bold text-white capitalize mb-2">
{activeTab} Settings
</h3>
<p className="text-kodo-content-dim max-w-md">
Advanced configurations for {activeTab} will be available in the
next system update (v2.1).
</p>
</div>
</Card>
)}
</div>
</div>
);
};
export { SettingsView } from './settings-view';

View file

@ -0,0 +1,29 @@
import React from 'react';
import { useSettingsView } from './useSettingsView';
import { SettingsViewHeader } from './SettingsViewHeader';
import { SettingsViewTabs } from './SettingsViewTabs';
import { SettingsViewContent } from './SettingsViewContent';
import type { SettingsViewProps } from './types';
import type { SettingsTabId } from './types';
export function SettingsView({ initialTab = 'profile' }: SettingsViewProps = {}) {
const { activeTab, setActiveTab, tabs } = useSettingsView(
(initialTab as SettingsTabId) ?? 'profile',
);
return (
<div className="flex flex-col gap-8 animate-fadeIn pb-20">
<SettingsViewHeader />
<SettingsViewTabs
activeTab={activeTab}
onTabChange={(v) => setActiveTab(v as SettingsTabId)}
tabs={tabs}
/>
<div className="min-h-layout-page">
<SettingsViewContent activeTab={activeTab} />
</div>
</div>
);
}

View file

@ -0,0 +1,54 @@
import React from 'react';
import { Card } from '@/components/ui/card';
import { Volume2, Bell } from 'lucide-react';
import { SecuritySettings } from '@/components/settings/security/SecuritySettings';
import { EditProfile } from '@/components/settings/profile/EditProfile';
import { AccountSettings } from '@/components/settings/account/AccountSettings';
import { CloudIntegrationView } from '@/components/settings/cloud/CloudIntegrationView';
import { BackupsView } from '@/components/settings/backups/BackupsView';
import { AppearanceSettingsView } from '@/components/settings/appearance/AppearanceSettingsView';
import { AccessibilitySettingsView } from '@/components/settings/accessibility/AccessibilitySettingsView';
import { IntegrationsView } from '@/components/settings/integrations/IntegrationsView';
import { DataExportView } from '@/components/settings/data/DataExportView';
import type { SettingsTabId } from './types';
interface SettingsViewContentProps {
activeTab: SettingsTabId;
}
export function SettingsViewContent({ activeTab }: SettingsViewContentProps) {
if (activeTab === 'profile') return <EditProfile />;
if (activeTab === 'account') return <AccountSettings />;
if (activeTab === 'appearance') return <AppearanceSettingsView />;
if (activeTab === 'accessibility') return <AccessibilitySettingsView />;
if (activeTab === 'security') return <SecuritySettings />;
if (activeTab === 'integrations') return <IntegrationsView />;
if (activeTab === 'cloud') return <CloudIntegrationView />;
if (activeTab === 'backups') return <BackupsView />;
if (activeTab === 'data') return <DataExportView />;
if (activeTab === 'audio' || activeTab === 'notifications') {
return (
<Card variant="default" className="min-h-layout-page-sm">
<div className="flex flex-col items-center justify-center h-full text-center animate-fadeIn py-24">
<div className="w-20 h-20 rounded-full bg-kodo-slate flex items-center justify-center mb-6">
{activeTab === 'audio' ? (
<Volume2 className="w-10 h-10 text-kodo-content-dim" />
) : (
<Bell className="w-10 h-10 text-kodo-content-dim" />
)}
</div>
<h3 className="text-xl font-bold text-white capitalize mb-2">
{activeTab} Settings
</h3>
<p className="text-kodo-content-dim max-w-md">
Advanced configurations for {activeTab} will be available in the next
system update (v2.1).
</p>
</div>
</Card>
);
}
return null;
}

View file

@ -0,0 +1,12 @@
import React from 'react';
export function SettingsViewHeader() {
return (
<div>
<h2 className="text-3xl font-display font-bold text-white mb-2">SETTINGS</h2>
<p className="text-kodo-content-dim font-mono text-sm">
Configure your studio, account, and preferences.
</p>
</div>
);
}

View file

@ -0,0 +1,25 @@
import React from 'react';
import { Skeleton } from '@/components/ui/skeleton';
export function SettingsViewSkeleton() {
return (
<div className="flex flex-col gap-8 animate-fadeIn pb-20">
<div>
<Skeleton className="h-9 w-48 mb-2" />
<Skeleton className="h-4 w-80" />
</div>
<div className="border-b border-kodo-steel/50 pb-2">
<div className="flex gap-2 flex-wrap">
{[1, 2, 3, 4, 5, 6].map((i) => (
<Skeleton key={i} className="h-10 w-24" />
))}
</div>
</div>
<div className="min-h-layout-page">
<Skeleton className="h-96 w-full rounded-xl" />
</div>
</div>
);
}

View file

@ -0,0 +1,36 @@
import React from 'react';
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
import type { SettingsTabId, SettingsTabConfig } from './types';
interface SettingsViewTabsProps {
activeTab: SettingsTabId;
onTabChange: (value: string) => void;
tabs: SettingsTabConfig[];
}
export function SettingsViewTabs({
activeTab,
onTabChange,
tabs,
}: SettingsViewTabsProps) {
return (
<div className="border-b border-kodo-steel/50">
<Tabs value={activeTab} onValueChange={onTabChange} className="pb-0">
<TabsList className="bg-transparent border-none p-0">
{tabs.map((tab) => (
<TabsTrigger
key={tab.id}
value={tab.id}
className="rounded-none border-b-2 border-transparent data-[state=active]:border-kodo-steel data-[state=active]:bg-transparent data-[state=active]:shadow-none px-4 py-2"
>
<span className="flex items-center gap-2">
{tab.icon}
{tab.label}
</span>
</TabsTrigger>
))}
</TabsList>
</Tabs>
</div>
);
}

View file

@ -0,0 +1,4 @@
export type { SettingsViewProps, SettingsTabId, SettingsTabConfig } from './types';
export { SettingsView } from './SettingsView';
export { SettingsViewSkeleton } from './SettingsViewSkeleton';
export { useSettingsView } from './useSettingsView';

View file

@ -0,0 +1,23 @@
export type SettingsTabId =
| 'profile'
| 'account'
| 'appearance'
| 'accessibility'
| 'security'
| 'integrations'
| 'cloud'
| 'backups'
| 'data'
| 'audio'
| 'notifications';
export interface SettingsViewProps {
initialTab?: SettingsTabId;
}
export interface SettingsTabConfig {
id: SettingsTabId;
label: string;
icon: React.ReactNode;
}

View file

@ -0,0 +1,43 @@
import { useState, useEffect } from 'react';
import {
User,
Bell,
Palette,
Shield,
Volume2,
UserCog,
Cloud,
Database,
Accessibility,
Plug,
HardDrive,
} from 'lucide-react';
import type { SettingsTabId, SettingsTabConfig } from './types';
const SETTINGS_TABS: SettingsTabConfig[] = [
{ id: 'profile', label: 'Profile', icon: <User className="w-4 h-4" /> },
{ id: 'account', label: 'Account', icon: <UserCog className="w-4 h-4" /> },
{ id: 'appearance', label: 'Appearance', icon: <Palette className="w-4 h-4" /> },
{ id: 'accessibility', label: 'Accessibility', icon: <Accessibility className="w-4 h-4" /> },
{ id: 'security', label: 'Security', icon: <Shield className="w-4 h-4" /> },
{ id: 'integrations', label: 'Integrations', icon: <Plug className="w-4 h-4" /> },
{ id: 'cloud', label: 'Cloud & Sync', icon: <Cloud className="w-4 h-4" /> },
{ id: 'backups', label: 'Backups', icon: <HardDrive className="w-4 h-4" /> },
{ id: 'data', label: 'Privacy & Data', icon: <Database className="w-4 h-4" /> },
{ id: 'audio', label: 'Audio', icon: <Volume2 className="w-4 h-4" /> },
{ id: 'notifications', label: 'Notifications', icon: <Bell className="w-4 h-4" /> },
];
export function useSettingsView(initialTab: SettingsTabId = 'profile') {
const [activeTab, setActiveTab] = useState<SettingsTabId>(initialTab);
useEffect(() => {
if (initialTab) setActiveTab(initialTab);
}, [initialTab]);
return {
activeTab,
setActiveTab,
tabs: SETTINGS_TABS,
};
}