Skip to content

Commit 23ddb1f

Browse files
feat: Implement settings page functionality(#566) (#606)
* This commit introduces the initial implementation of the user settings page, providing core account management features and improving the UI. Key features include: - Users can now update their display name and select a profile avatar from the account section. - The folder list is now restricted to an initial view of 6 items, with a 'View More' button for more. * This commit introduces the initial implementation of the user settings page, providing core account management features and improving the UI. Key features include: - Users can now update their display name and select a profile avatar from the account section. - The folder list is now restricted to an initial view of 6 items, with a 'View More' button for more. * Updating some important changes and improving user experience * adding try and catch block * Update Settings.tsx * Connect Navbar Avatar with Setting#account + Nitpicks --------- Co-authored-by: ROHAN PANDEY <95585299+rohan-pandeyy@users.noreply.github.com>
1 parent f8ec976 commit 23ddb1f

4 files changed

Lines changed: 290 additions & 85 deletions

File tree

frontend/src/components/Navigation/Navbar/Navbar.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { selectAvatar, selectName } from '@/features/onboardingSelectors';
66
import { clearSearch } from '@/features/searchSlice';
77
import { convertFileSrc } from '@tauri-apps/api/core';
88
import { FaceSearchDialog } from '@/components/Dialog/FaceSearchDialog';
9+
import { Link } from 'react-router';
910

1011
export function Navbar() {
1112
const userName = useSelector(selectName);
@@ -20,10 +21,10 @@ export function Navbar() {
2021
<div className="sticky top-0 z-40 flex h-14 w-full items-center justify-between border-b pr-4 backdrop-blur">
2122
{/* Logo */}
2223
<div className="flex w-[256px] items-center justify-center">
23-
<a href="/" className="flex items-center space-x-2">
24+
<Link to="/" className="flex items-center space-x-2">
2425
<img src="/128x128.png" width={32} height={32} alt="PictoPy Logo" />
2526
<span className="text-xl font-bold">PictoPy</span>
26-
</a>
27+
</Link>
2728
</div>
2829

2930
{/* Search Bar */}
@@ -82,13 +83,13 @@ export function Navbar() {
8283
<span className="hidden text-sm sm:inline-block">
8384
Welcome <span className="text-muted-foreground">{userName}</span>
8485
</span>
85-
<a href="/settings" className="p-2">
86+
<Link to="/settings#account" className="p-2">
8687
<img
8788
src={userAvatar || '/photo1.png'}
8889
className="hover:ring-primary/50 h-8 w-8 cursor-pointer rounded-full transition-all hover:ring-2"
8990
alt="User avatar"
9091
/>
91-
</a>
92+
</Link>
9293
</div>
9394
</div>
9495
</div>

frontend/src/pages/SettingsPage/Settings.tsx

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,87 @@
1-
import React from 'react';
1+
import React, { useState, useEffect } from 'react';
2+
import { useLocation, useNavigate } from 'react-router';
23

34
// Import modular components
45
import FolderManagementCard from './components/FolderManagementCard';
56
import UserPreferencesCard from './components/UserPreferencesCard';
67
import ApplicationControlsCard from './components/ApplicationControlsCard';
8+
import AccountSettingsCard from './components/AccountSettingsCard';
79

810
/**
911
* Settings page component
1012
* Acts as an orchestrator for the settings sections
1113
*/
1214
const Settings: React.FC = () => {
13-
return (
14-
<div className="mx-auto flex-1 px-8 py-6">
15-
<div className="mx-auto max-w-5xl space-y-8">
16-
{/* Folder Management */}
17-
<FolderManagementCard />
15+
const location = useLocation();
16+
const navigate = useNavigate();
17+
const [activeTab, setActiveTab] = useState('general');
18+
19+
const handleTabChange = (tab: string) => {
20+
setActiveTab(tab);
21+
navigate(`#${tab}`, { replace: true });
22+
};
1823

19-
{/* User Preferences */}
20-
<UserPreferencesCard />
24+
useEffect(() => {
25+
const hash = location.hash;
26+
if (hash === '#account') {
27+
setActiveTab('account');
28+
} else if (hash === '#general') {
29+
setActiveTab('general');
30+
} else if (hash === '') {
31+
setActiveTab('general');
32+
navigate(`#general`, { replace: true });
33+
}
34+
}, [location.hash]);
35+
const baseTabStyle = 'px-4 py-2 rounded-md font-medium transition-colors';
36+
const activeTabStyle = 'bg-background text-foreground';
37+
const inactiveTabStyle =
38+
'text-muted-foreground hover:bg-gray-100 dark:hover:bg-gray-800';
39+
return (
40+
<>
41+
<h1 className="my-6 text-2xl font-bold">Settings</h1>
42+
<div className="flex-1 pr-3">
43+
<div className="flex flex-col space-y-8">
44+
<div
45+
className="bg-card flex w-fit items-center justify-center gap-1 rounded-lg border p-1"
46+
role="tablist"
47+
>
48+
<button
49+
role="tab"
50+
aria-selected={activeTab === 'general'}
51+
aria-controls="general-panel"
52+
onClick={() => handleTabChange('general')}
53+
className={`${baseTabStyle} ${activeTab === 'general' ? activeTabStyle : inactiveTabStyle}`}
54+
>
55+
General
56+
</button>
57+
<button
58+
role="tab"
59+
aria-selected={activeTab === 'account'}
60+
aria-controls="account-panel"
61+
onClick={() => handleTabChange('account')}
62+
className={`${baseTabStyle} ${activeTab === 'account' ? activeTabStyle : inactiveTabStyle}`}
63+
>
64+
Account
65+
</button>
66+
</div>
67+
<div className="mt-6 space-y-8">
68+
{activeTab === 'general' && (
69+
<>
70+
<FolderManagementCard />
71+
<UserPreferencesCard />
72+
<ApplicationControlsCard />
73+
</>
74+
)}
2175

22-
{/* Application Controls */}
23-
<ApplicationControlsCard />
76+
{activeTab === 'account' && (
77+
<>
78+
<AccountSettingsCard />
79+
</>
80+
)}
81+
</div>
82+
</div>
2483
</div>
25-
</div>
84+
</>
2685
);
2786
};
2887

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import React, { useState } from 'react'; // No need for useEffect
2+
import { useDispatch } from 'react-redux';
3+
import { Label } from '@/components/ui/label';
4+
import { Input } from '@/components/ui/input';
5+
import { setAvatar, setName } from '@/features/onboardingSlice';
6+
import { User } from 'lucide-react';
7+
import SettingsCard from './SettingsCard';
8+
import { avatars } from '@/constants/avatars';
9+
import { CardContent } from '@/components/ui/card';
10+
import { Button } from '@/components/ui/button';
11+
12+
const AccountSettingsCard: React.FC = () => {
13+
const dispatch = useDispatch();
14+
const [name, setLocalName] = useState(
15+
() => localStorage.getItem('name') || '',
16+
);
17+
const [selectedAvatar, setLocalAvatar] = useState(() => {
18+
const stored = localStorage.getItem('avatar') || '';
19+
return avatars.includes(stored) ? stored : '';
20+
});
21+
const [nameError, setNameError] = useState(false);
22+
23+
// The redundant useEffect has been removed.
24+
25+
const handleAvatarSelect = (avatar: string) => {
26+
setLocalAvatar(avatar);
27+
};
28+
29+
const handleNameChange = (value: string) => {
30+
setLocalName(value);
31+
if (nameError) {
32+
setNameError(false);
33+
}
34+
};
35+
36+
const handleSave = () => {
37+
if (!name.trim()) {
38+
setNameError(true);
39+
return;
40+
}
41+
42+
setNameError(false);
43+
if (!selectedAvatar) return;
44+
45+
try {
46+
dispatch(setName(name));
47+
dispatch(setAvatar(selectedAvatar));
48+
localStorage.setItem('name', name);
49+
localStorage.setItem('avatar', selectedAvatar);
50+
} catch (error) {
51+
console.error('Failed to save settings:');
52+
}
53+
};
54+
55+
return (
56+
<SettingsCard
57+
icon={User}
58+
title="Account Information"
59+
description="Manage your account details and profile information."
60+
>
61+
<CardContent className="flex-1 space-y-6 overflow-y-hidden p-1 px-2">
62+
<div className="w-fit space-y-6">
63+
{/* Name Change */}
64+
<div className="w-full">
65+
<Label htmlFor="name" className="mb-2 block text-base font-medium">
66+
Name
67+
</Label>
68+
<Input
69+
id="name"
70+
placeholder={
71+
nameError ? "Name can't be empty" : 'Enter your name'
72+
}
73+
value={name}
74+
onChange={(e) => handleNameChange(e.target.value)}
75+
className={`h-10 w-full text-sm placeholder:text-sm ${
76+
nameError
77+
? 'border-red-500 placeholder:text-red-500/80 focus-visible:ring-red-500'
78+
: ''
79+
}`}
80+
/>
81+
</div>
82+
83+
{/* Avatar Section */}
84+
<div className="w-full">
85+
<Label className="mb-3 block text-base font-medium">Avatar</Label>
86+
<div className="grid grid-cols-4 gap-8">
87+
{avatars.map((avatar) => {
88+
const isSelected = selectedAvatar === avatar;
89+
return (
90+
<button
91+
type="button"
92+
key={avatar}
93+
onClick={() => handleAvatarSelect(avatar)}
94+
className={`bg-background relative inline-flex h-24 w-24 items-center justify-center rounded-full transition-all duration-300 ${
95+
isSelected
96+
? 'ring-offset-background scale-90 ring-2 ring-blue-500 ring-offset-4'
97+
: 'hover:ring-4 hover:ring-blue-500 hover:ring-offset-4'
98+
}`}
99+
>
100+
<img
101+
src={avatar}
102+
alt="Avatar"
103+
className={`h-24 w-24 rounded-full object-cover transition-all duration-300 ${
104+
isSelected ? 'brightness-110' : ''
105+
}`}
106+
/>
107+
</button>
108+
);
109+
})}
110+
</div>
111+
</div>
112+
</div>
113+
114+
{/* Save Changes Button */}
115+
<Button
116+
className="mt-4 w-auto bg-blue-500 px-6 py-2 text-sm font-medium text-white hover:bg-blue-600"
117+
onClick={handleSave}
118+
disabled={!selectedAvatar}
119+
>
120+
Save Changes
121+
</Button>
122+
</CardContent>
123+
</SettingsCard>
124+
);
125+
};
126+
127+
export default AccountSettingsCard;

0 commit comments

Comments
 (0)