tsk-6: Add feature to update password in profile (#13)

Closes #6

Reviewed-on: phoenix/textsender#13
This commit was merged in pull request #13.
This commit is contained in:
2026-07-02 23:24:31 -04:00
parent be1238e24d
commit cde21b09c8
5 changed files with 268 additions and 89 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "textsender", "name": "textsender",
"version": "0.0.6", "version": "0.0.7",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "textsender", "name": "textsender",
"version": "0.0.6", "version": "0.0.7",
"dependencies": { "dependencies": {
"react": "^19.2.7", "react": "^19.2.7",
"react-dom": "^19.2.7", "react-dom": "^19.2.7",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "textsender", "name": "textsender",
"private": true, "private": true,
"version": "0.0.6", "version": "0.0.7",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+29
View File
@@ -37,4 +37,33 @@ export const userApi = {
throw new Error('Error logging in'); throw new Error('Error logging in');
} }
}, },
update_password: async (
userId,
currentPassword,
updatedPassword,
confirmedPassword
) => {
const request = {
user_id: userId,
current_password: currentPassword,
updated_password: updatedPassword,
confirmed_password: confirmedPassword,
};
const reponse = await fetch(
`${AUTH_API_BASE_URL}/api/v1/user/password/update`,
{
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request),
}
);
if (reponse.ok) {
return reponse.json();
} else {
throw new Error('Error logging in');
}
},
}; };
+71 -17
View File
@@ -1,41 +1,95 @@
.profile-form { .profile-form {
max-width: 400px; background: white;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
padding: 40px;
width: 100%;
max-width: 420px;
margin: 0 auto; margin: 0 auto;
padding: 20px;
border: 1px solid #ccc;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
background-color: #ffffff;
} }
.profile-form h2 { .profile-form h2 {
color: #333;
font-size: 28px;
font-weight: 600;
text-align: center; text-align: center;
margin-bottom: 20px; margin-bottom: 30px;
} }
.profile-form form { .profile-form form {
display: flex; margin-bottom: 25px;
flex-direction: column;
} }
.profile-form div { .profile-form div {
margin-bottom: 15px; margin-bottom: 20px;
} }
.profile-form label { .profile-form label {
font-weight: bold;
margin-bottom: 5px;
display: block; display: block;
color: #555;
font-size: 14px;
font-weight: 500;
margin-bottom: 6px;
} }
.profile-form input[type='text'], .profile-form input[type='text'],
.profile-form input[type='tel'] { .profile-form input[type='tel'],
.profile-form input[type='password'] {
width: 100%; width: 100%;
padding: 8px; padding: 12px 16px;
border: 1px solid #ccc; border: 2px solid #e1e5e9;
border-radius: 4px; border-radius: 8px;
font-size: 16px;
transition: all 0.3s ease;
box-sizing: border-box;
}
.profile-form input[type='text']:focus,
.profile-form input[type='tel']:focus,
.profile-form input[type='password']:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
} }
.profile-form input[readonly] { .profile-form input[readonly] {
background-color: #f9f9f9; background-color: #f9fafb;
cursor: not-allowed;
}
.profile-form button[type='submit'] {
width: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 8px;
padding: 14px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 10px;
}
.profile-form button[type='submit']:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 10px 25px rgba(102, 126, 234, 0.4);
}
.profile-form button[type='button'] {
width: 100%;
background: #f1f5f9;
color: #334155;
border: 1px solid #cbd5e1;
border-radius: 8px;
padding: 14px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 10px;
}
.profile-form button[type='button']:hover {
background: #e2e8f0;
} }
+165 -69
View File
@@ -9,6 +9,7 @@ import './ProfileForm.css';
const ProfileForm = () => { const ProfileForm = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [activeView, setActiveView] = useState('profile'); const [activeView, setActiveView] = useState('profile');
const [subView, setSubView] = useState('details');
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
firstname: '', firstname: '',
lastname: '', lastname: '',
@@ -18,6 +19,12 @@ const ProfileForm = () => {
lastLoginDate: new Date().toISOString().split('T')[0], lastLoginDate: new Date().toISOString().split('T')[0],
}); });
const [passwordData, setPasswordData] = useState({
currentPassword: '',
newPassword: '',
confirmNewPassword: '',
});
useEffect(() => { useEffect(() => {
const fetchData = async () => { const fetchData = async () => {
const userId = tokenService.getUserId(); const userId = tokenService.getUserId();
@@ -47,6 +54,14 @@ const ProfileForm = () => {
setFormData({ ...formData, [name]: value }); setFormData({ ...formData, [name]: value });
}; };
const handlePasswordChange = (e) => {
const { name, value } = e.target;
setPasswordData((prev) => ({
...prev,
[name]: value,
}));
};
const handleNavClick = (view) => { const handleNavClick = (view) => {
if (view === 'profile') { if (view === 'profile') {
navigate('/user/profile'); navigate('/user/profile');
@@ -55,6 +70,155 @@ const ProfileForm = () => {
} }
}; };
const handlePasswordSubmit = async (e) => {
e.preventDefault();
try {
const userId = tokenService.getUserId();
const currentPassword = passwordData.currentPassword;
const newPassword = passwordData.newPassword;
const confirmNewPassword = passwordData.confirmNewPassword;
const response = await userApi.update_password(
userId,
currentPassword,
newPassword,
confirmNewPassword
);
if (response) {
console.log('Successfully updated password');
} else {
console.error('Error updating password');
}
} catch (error) {
console.error('Login error:', error);
} finally {
}
};
const renderPasswordForm = () => (
<div className="profile-form">
<h2>Change Password</h2>
<form onSubmit={handlePasswordSubmit}>
<div>
<label htmlFor="currentPassword">Current Password</label>
<input
type="password"
id="currentPassword"
name="currentPassword"
value={passwordData.currentPassword}
onChange={handlePasswordChange}
required
/>
</div>
<div>
<label htmlFor="newPassword">New Password</label>
<input
type="password"
id="newPassword"
name="newPassword"
value={passwordData.newPassword}
onChange={handlePasswordChange}
required
/>
</div>
<div>
<label htmlFor="confirmNewPassword">Confirm New Password</label>
<input
type="password"
id="confirmNewPassword"
name="confirmNewPassword"
value={passwordData.confirmNewPassword}
onChange={handlePasswordChange}
required
/>
</div>
<button type="submit">Update Password</button>
<button type="button" onClick={() => setSubView('details')}>
Back to Profile
</button>
</form>
</div>
);
const renderProfileForm = () => (
<div className="profile-form">
<h2>User Profile</h2>
<form>
<div>
<label htmlFor="firstname">First Name</label>
<input
type="text"
id="firstname"
name="firstname"
value={formData.firstname}
onChange={handleChange}
readOnly
/>
</div>
<div>
<label htmlFor="lastname">Last Name</label>
<input
type="text"
id="lastname"
name="lastname"
value={formData.lastname}
onChange={handleChange}
readOnly
/>
</div>
<div>
<label htmlFor="phone">Phone</label>
<input
type="tel"
id="phone"
name="phone"
value={formData.phone}
onChange={handleChange}
readOnly
/>
</div>
<div>
<label htmlFor="username">Username</label>
<input
type="text"
id="username"
name="username"
value={formData.username}
onChange={handleChange}
readOnly
/>
</div>
<div>
<label htmlFor="createdDate">Created Date</label>
<input
type="text"
id="createdDate"
name="createdDate"
value={formData.createdDate}
readOnly
/>
</div>
<div>
<label htmlFor="lastLoginDate">Last Login Date</label>
<input
type="text"
id="lastLoginDate"
name="lastLoginDate"
value={formData.lastLoginDate}
readOnly
/>
</div>
</form>
<div>
<button type="button" onClick={() => setSubView('password')}>
Change Password
</button>
</div>
</div>
);
return ( return (
<div className="dashboard-container"> <div className="dashboard-container">
{/* Navbar */} {/* Navbar */}
@@ -85,75 +249,7 @@ const ProfileForm = () => {
</nav> </nav>
{/* Main Content */} {/* Main Content */}
<div className="profile-form"> {subView === 'details' ? renderProfileForm() : renderPasswordForm()}
<h2>User Profile</h2>
<form>
<div>
<label htmlFor="firstname">First Name:</label>
<input
type="text"
id="firstname"
name="firstname"
value={formData.firstname}
onChange={handleChange}
readOnly
/>
</div>
<div>
<label htmlFor="lastname">Last Name:</label>
<input
type="text"
id="lastname"
name="lastname"
value={formData.lastname}
onChange={handleChange}
readOnly
/>
</div>
<div>
<label htmlFor="phone">Phone:</label>
<input
type="tel"
id="phone"
name="phone"
value={formData.phone}
onChange={handleChange}
readOnly
/>
</div>
<div>
<label htmlFor="username">Username:</label>
<input
type="text"
id="username"
name="username"
value={formData.username}
onChange={handleChange}
readOnly
/>
</div>
<div>
<label htmlFor="createdDate">Created Date:</label>
<input
type="text"
id="createdDate"
name="createdDate"
value={formData.createdDate}
readOnly
/>
</div>
<div>
<label htmlFor="lastLoginDate">Last Login Date:</label>
<input
type="text"
id="lastLoginDate"
name="lastLoginDate"
value={formData.lastLoginDate}
readOnly
/>
</div>
</form>
</div>
</div> </div>
); );
}; };