Skip to content

Commit c500124

Browse files
Vallayahgikf
andauthored
feat: add admin-only user profile page (freeCodeCamp-2025-Summer-Hackathon#177)
Co-authored-by: Krzysztof G. <[email protected]>
1 parent af70ec4 commit c500124

File tree

1 file changed

+183
-7
lines changed

1 file changed

+183
-7
lines changed

frontend/src/pages/UserPage.jsx

Lines changed: 183 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,189 @@
1-
import { useParams } from 'react-router';
1+
import { useEffect, useState, useRef } from 'react';
2+
import { useParams, useNavigate, Link } from 'react-router';
3+
import { useUser } from '../hooks/useUser';
4+
import { useApi } from '../hooks/useApi';
5+
import Spinny from '../components/Spinny';
6+
27
const UserPage = () => {
3-
const { id } = useParams(); // Get the user ID from the URL
8+
const { id } = useParams();
9+
const navigate = useNavigate();
10+
const { isLogged, isAdmin } = useUser();
11+
const [userData, setUserData] = useState(null);
12+
const [showDeactivateModal, setShowDeactivateModal] = useState(false);
13+
const deactivateModalRef = useRef(null);
14+
15+
const {
16+
data: fetchUserData,
17+
error: fetchUserError,
18+
isLoading: isUserLoading,
19+
fetchFromApi: fetchUser,
20+
} = useApi();
21+
22+
const {
23+
data: updateUserData,
24+
error: updateError,
25+
isLoading: isUpdating,
26+
fetchFromApi: updateUserStatus,
27+
} = useApi({ method: 'PATCH' });
28+
29+
useEffect(() => {
30+
if (!isLogged) {
31+
navigate('/login');
32+
return;
33+
}
34+
if (!isAdmin) {
35+
navigate('/');
36+
return;
37+
}
38+
39+
if (id) {
40+
fetchUser(`/users/${id}`);
41+
}
42+
}, [id, isLogged, isAdmin, navigate, fetchUser]);
43+
44+
useEffect(() => {
45+
if (fetchUserData && !fetchUserError) {
46+
setUserData(fetchUserData);
47+
}
48+
if (fetchUserError) {
49+
console.error('Error fetching user data:', fetchUserError);
50+
}
51+
}, [fetchUserData, fetchUserError]);
52+
53+
useEffect(() => {
54+
if (updateUserData && !updateError) {
55+
console.log('User status updated:', updateUserData);
56+
closeDeactivateModal();
57+
setUserData(prevData => ({
58+
...prevData,
59+
is_active: updateUserData.is_active,
60+
}));
61+
}
62+
if (updateError) {
63+
console.error('Error updating user status:', updateError);
64+
}
65+
}, [updateUserData, updateError, navigate]);
66+
67+
const handleToggleStatusClick = () => {
68+
setShowDeactivateModal(true);
69+
if (deactivateModalRef.current) {
70+
deactivateModalRef.current.showModal();
71+
}
72+
};
73+
74+
const confirmToggleStatus = () => {
75+
if (id && !isUpdating && userData) {
76+
const newStatus = !userData.is_active;
77+
updateUserStatus(`/users/${id}`, {
78+
headers: { 'content-type': 'application/json' },
79+
body: JSON.stringify({ is_active: newStatus }),
80+
});
81+
}
82+
};
83+
84+
const closeDeactivateModal = () => {
85+
setShowDeactivateModal(false);
86+
if (deactivateModalRef.current) {
87+
deactivateModalRef.current.close();
88+
}
89+
};
90+
91+
if (isUserLoading) {
92+
return <Spinny />;
93+
}
94+
95+
if (fetchUserError || !userData) {
96+
return (
97+
<div className='section-card flex flex-col items-center justify-center min-h-[60vh]'>
98+
<h1 className='section-heading text-error'>Error</h1>
99+
<p className='text-lg text-gray-600 mb-8'>
100+
{fetchUserError?.message ||
101+
'Could not load user data or user not found.'}
102+
</p>
103+
<Link to='/users' className='animated-button'>
104+
Back to All Users
105+
</Link>
106+
</div>
107+
);
108+
}
109+
110+
const isUserActive = userData.is_active;
111+
const modalAction = isUserActive ? 'Deactivate' : 'Activate';
112+
const modalTitle = `Confirm ${isUserActive ? 'Deactivation' : 'Activation'}`;
113+
const confirmButtonClass = isUserActive
114+
? 'animated-button !bg-gradient-to-r from-red-500 to-red-700 hover:!from-red-600 hover:!to-red-800'
115+
: 'animated-button !bg-gradient-to-r from-green-500 to-green-700 hover:!from-green-600 hover:!to-green-800';
4116

5117
return (
6-
<div className='section-card flex flex-col items-center justify-center min-h-[60vh]'>
7-
<h1 className='section-heading'>User Profile: {id}</h1>
8-
<p className='text-lg text-gray-600 mb-8'>
9-
This page will display the profile for user with ID: {id}.
10-
</p>
118+
<div className='section-card flex flex-col items-center min-h-[60vh]'>
119+
<h1 className='section-heading'>User Profile: {userData.name}</h1>
120+
<div className='w-full max-w-xl p-4 bg-base-200 rounded-lg shadow-md'>
121+
<p className='text-lg mb-2'>
122+
<strong>Username:</strong> {userData.username}
123+
</p>
124+
<p className='text-lg mb-2'>
125+
<strong>Name:</strong> {userData.name}
126+
</p>
127+
<p className='text-lg mb-4'>
128+
<strong>Admin:</strong> {userData.is_admin ? 'Yes' : 'No'}
129+
</p>
130+
<p className='text-lg mb-4'>
131+
<strong>Status:</strong> {isUserActive ? 'Active' : 'Inactive'}
132+
</p>
133+
134+
<div className='flex flex-col sm:flex-row justify-center gap-4 mt-6'>
135+
<Link to={`/users/${id}/edit`} className='animated-button'>
136+
Edit User
137+
</Link>
138+
<Link to={`/users/${id}/ideas`} className='animated-button'>
139+
View All Ideas
140+
</Link>
141+
<button
142+
onClick={handleToggleStatusClick}
143+
disabled={isUpdating}
144+
className={confirmButtonClass}
145+
>
146+
{isUpdating ? 'Updating Status...' : `${modalAction} Account`}
147+
</button>
148+
</div>
149+
</div>
150+
151+
<dialog
152+
ref={deactivateModalRef}
153+
className='modal'
154+
open={showDeactivateModal}
155+
>
156+
<div className='modal-box'>
157+
<h3 className='font-bold text-lg'>{modalTitle}</h3>
158+
<p className='py-4'>{`${modalAction} ${userData.name}'s account?`}</p>
159+
{updateError && (
160+
<p className='text-error mb-4'>Error: {updateError.message}</p>
161+
)}
162+
<div className='modal-action'>
163+
<button
164+
className='animated-button mr-2'
165+
onClick={closeDeactivateModal}
166+
disabled={isUpdating}
167+
>
168+
Cancel
169+
</button>
170+
<button
171+
className={confirmButtonClass}
172+
onClick={confirmToggleStatus}
173+
disabled={isUpdating}
174+
>
175+
{isUpdating ? 'Confirming...' : `Confirm ${modalAction}`}
176+
</button>
177+
</div>
178+
</div>
179+
<form
180+
method='dialog'
181+
className='modal-backdrop'
182+
onClick={closeDeactivateModal}
183+
>
184+
<button>close</button>
185+
</form>
186+
</dialog>
11187
</div>
12188
);
13189
};

0 commit comments

Comments
 (0)