Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 41 additions & 11 deletions frontend/src/pages/SettingsPage/Settings.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,58 @@
import React from 'react';
import React, { useState } from 'react';

// Import modular components
import FolderManagementCard from './components/FolderManagementCard';
import UserPreferencesCard from './components/UserPreferencesCard';
import ApplicationControlsCard from './components/ApplicationControlsCard';
import AccountSettingsCard from './components/AccountSettingsCard';

/**
* Settings page component
* Acts as an orchestrator for the settings sections
*/
const Settings: React.FC = () => {
const [activeTab, setActiveTab] = useState('general');
const baseTabStyle = 'px-4 py-2 rounded-md font-medium transition-colors';
const activeTabStyle = 'bg-background text-foreground';
const inactiveTabStyle =
'text-muted-foreground hover:bg-gray-100 dark:hover:bg-gray-800';
return (
<div className="mx-auto flex-1 px-8 py-6">
<div className="mx-auto max-w-5xl space-y-8">
{/* Folder Management */}
<FolderManagementCard />
<>
<h1 className="mt-6 mb-6 text-2xl font-bold">Settings</h1>
<div className="mx-auto flex-1 pr-3">
<div className="mx-auto space-y-8">
<div className="bg-card w-50 rounded-lg border p-1">
<button
onClick={() => setActiveTab('general')}
className={`${baseTabStyle} ${activeTab === 'general' ? activeTabStyle : inactiveTabStyle}`}
>
General
</button>
<button
onClick={() => setActiveTab('account')}
className={`${baseTabStyle} ${activeTab === 'account' ? activeTabStyle : inactiveTabStyle}`}
>
Account
</button>
</div>
<div className="mt-6 space-y-8">
{activeTab === 'general' && (
<>
<FolderManagementCard />
<UserPreferencesCard />
<ApplicationControlsCard />
</>
)}

{/* User Preferences */}
<UserPreferencesCard />

{/* Application Controls */}
<ApplicationControlsCard />
{activeTab === 'account' && (
<>
<AccountSettingsCard />
</>
)}
</div>
</div>
</div>
</div>
</>
);
};

Expand Down
122 changes: 122 additions & 0 deletions frontend/src/pages/SettingsPage/components/AccountSettingsCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import React, { useState } from 'react'; // No need for useEffect
import { useDispatch } from 'react-redux';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { setAvatar, setName } from '@/features/onboardingSlice';
import { User } from 'lucide-react';
import SettingsCard from './SettingsCard';
import { avatars } from '@/constants/avatars';
import { CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';

const AccountSettingsCard: React.FC = () => {
const dispatch = useDispatch();
const [name, setLocalName] = useState(
() => localStorage.getItem('name') || '',
);
const [selectedAvatar, setLocalAvatar] = useState(
() => localStorage.getItem('avatar') || '',
);
const [nameError, setNameError] = useState(false);

// The redundant useEffect has been removed.

const handleAvatarSelect = (avatar: string) => {
setLocalAvatar(avatar);
};

const handleNameChange = (value: string) => {
setLocalName(value);
if (nameError) {
setNameError(false);
}
};

const handleSave = () => {
if (!name.trim()) {
setNameError(true);
return;
}

setNameError(false);
if (!selectedAvatar) return;

dispatch(setName(name));
dispatch(setAvatar(selectedAvatar));
localStorage.setItem('name', name);
localStorage.setItem('avatar', selectedAvatar);
};

return (
<SettingsCard
icon={User}
title="Account Information"
description="Manage your account details and profile information."
>
<CardContent className="flex-1 space-y-6 overflow-y-hidden p-1 px-2">
<div className="w-fit space-y-6">
{/* Name Change */}
<div className="w-full">
<Label htmlFor="name" className="mb-2 block text-base font-medium">
Name
</Label>
<Input
id="name"
placeholder={
nameError ? "Name can't be empty" : 'Enter your name'
}
value={name}
onChange={(e) => handleNameChange(e.target.value)}
className={`h-10 w-full text-sm placeholder:text-sm ${
nameError
? 'border-red-500 placeholder:text-red-500/80 focus-visible:ring-red-500'
: ''
}`}
/>
</div>

{/* Avatar Section */}
<div className="w-full">
<Label className="mb-3 block text-base font-medium">Avatar</Label>
<div className="grid grid-cols-4 gap-8">
{avatars.map((avatar) => {
const isSelected = selectedAvatar === avatar;
return (
<button
type="button"
key={avatar}
onClick={() => handleAvatarSelect(avatar)}
className={`bg-background relative inline-flex h-24 w-24 items-center justify-center rounded-full transition-all duration-300 ${
isSelected
? 'ring-offset-background scale-90 ring-2 ring-blue-500 ring-offset-4'
: 'hover:ring-4 hover:ring-blue-500 hover:ring-offset-4'
}`}
>
<img
src={avatar}
alt="Avatar"
className={`h-24 w-24 rounded-full object-cover transition-all duration-300 ${
isSelected ? 'brightness-110' : ''
}`}
/>
</button>
);
})}
</div>
</div>
</div>

{/* Save Changes Button */}
<Button
className="mt-4 w-auto bg-blue-500 px-6 py-2 text-sm font-medium text-white hover:bg-blue-600"
onClick={handleSave}
disabled={!selectedAvatar}
>
Save Changes
</Button>
</CardContent>
</SettingsCard>
);
};

export default AccountSettingsCard;
158 changes: 88 additions & 70 deletions frontend/src/pages/SettingsPage/components/FolderManagementCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import { Folder, Trash2, Check } from 'lucide-react';

import { Switch } from '@/components/ui/switch';
Expand Down Expand Up @@ -29,6 +29,12 @@ const FolderManagementCard: React.FC = () => {
(state: RootState) => state.folders.taggingStatus,
);

const [visibleFoldersCount, setVisibleFoldersCount] = useState(6);

const handleViewMore = () => {
setVisibleFoldersCount((prevCount) => prevCount + 5);
};

return (
<SettingsCard
icon={Folder}
Expand All @@ -37,84 +43,86 @@ const FolderManagementCard: React.FC = () => {
>
{folders.length > 0 ? (
<div className="space-y-3">
{folders.map((folder: FolderDetails, index: number) => (
<div
key={index}
className="group border-border bg-background/50 relative rounded-lg border p-4 transition-all hover:border-gray-300 hover:shadow-sm dark:hover:border-gray-600"
>
<div className="flex items-center justify-between">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-3">
<Folder className="h-4 w-4 flex-shrink-0 text-gray-500 dark:text-gray-400" />
<span className="text-foreground truncate">
{folder.folder_path}
</span>
{folders
.slice(0, visibleFoldersCount)
.map((folder: FolderDetails, index: number) => (
<div
key={folder.folder_id}
className="group border-border bg-background/50 relative rounded-lg border p-4 transition-all hover:border-gray-300 hover:shadow-sm dark:hover:border-gray-600"
>
<div className="flex items-center justify-between">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-3">
<Folder className="h-4 w-4 flex-shrink-0 text-gray-500 dark:text-gray-400" />
<span className="text-foreground truncate">
{folder.folder_path}
</span>
</div>
</div>
</div>

<div className="ml-4 flex items-center gap-4">
<div className="flex items-center gap-3">
<span className="text-muted-foreground text-sm">
AI Tagging
</span>
<Switch
className="cursor-pointer"
checked={folder.AI_Tagging}
onCheckedChange={() => toggleAITagging(folder)}
disabled={
enableAITaggingPending || disableAITaggingPending
}
/>
</div>
<div className="ml-4 flex items-center gap-4">
<div className="flex items-center gap-3">
<span className="text-muted-foreground text-sm">
AI Tagging
</span>
<Switch
className="cursor-pointer"
checked={folder.AI_Tagging}
onCheckedChange={() => toggleAITagging(folder)}
disabled={
enableAITaggingPending || disableAITaggingPending
}
/>
</div>

<Button
onClick={() => deleteFolder(folder.folder_id)}
variant="outline"
size="sm"
className="h-8 w-8 cursor-pointer text-gray-500 hover:border-red-300 hover:text-red-600 dark:text-gray-400 dark:hover:text-red-400"
disabled={deleteFolderPending}
>
<Trash2 className="h-4 w-4" />
</Button>
<Button
onClick={() => deleteFolder(folder.folder_id)}
variant="outline"
size="sm"
className="h-8 w-8 cursor-pointer text-gray-500 hover:border-red-300 hover:text-red-600 dark:text-gray-400 dark:hover:text-red-400"
disabled={deleteFolderPending}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</div>
</div>

{folder.AI_Tagging && (
<div className="mt-3">
<div className="text-muted-foreground mb-1 flex items-center justify-between text-xs">
<span>AI Tagging Progress</span>
<span
className={
{folder.AI_Tagging && (
<div className="mt-3">
<div className="text-muted-foreground mb-1 flex items-center justify-between text-xs">
<span>AI Tagging Progress</span>
<span
className={
(taggingStatus[folder.folder_id]
?.tagging_percentage ?? 0) >= 100
? 'flex items-center gap-1 text-green-500'
: 'text-muted-foreground'
}
>
{(taggingStatus[folder.folder_id]?.tagging_percentage ??
0) >= 100 && <Check className="h-3 w-3" />}
{Math.round(
taggingStatus[folder.folder_id]?.tagging_percentage ??
0,
)}
%
</span>
</div>
<Progress
value={
taggingStatus[folder.folder_id]?.tagging_percentage ?? 0
}
indicatorClassName={
(taggingStatus[folder.folder_id]?.tagging_percentage ??
0) >= 100
? 'flex items-center gap-1 text-green-500'
: 'text-muted-foreground'
? 'bg-green-500'
: 'bg-blue-500'
}
>
{(taggingStatus[folder.folder_id]?.tagging_percentage ??
0) >= 100 && <Check className="h-3 w-3" />}
{Math.round(
taggingStatus[folder.folder_id]?.tagging_percentage ??
0,
)}
%
</span>
/>
</div>
<Progress
value={
taggingStatus[folder.folder_id]?.tagging_percentage ?? 0
}
indicatorClassName={
(taggingStatus[folder.folder_id]?.tagging_percentage ??
0) >= 100
? 'bg-green-500'
: 'bg-blue-500'
}
/>
</div>
)}
</div>
))}
)}
</div>
))}
</div>
) : (
<div className="py-8 text-center">
Expand All @@ -128,6 +136,16 @@ const FolderManagementCard: React.FC = () => {
</div>
)}

{folders.length > visibleFoldersCount && (
<Button
onClick={handleViewMore}
variant="outline"
className="mt-4 w-full"
>
View More
</Button>
)}

<div className="border-border mt-6 border-t pt-6">
<FolderPicker />
</div>
Expand Down
Loading