Skip to content

Commit

Permalink
feat: add change password functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
AryanVBW committed Jan 6, 2025
1 parent 1b22557 commit a4a91ab
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 12 deletions.
7 changes: 4 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

145 changes: 145 additions & 0 deletions src/components/ChangePasswordModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import React, { useState } from 'react';
import { X, Lock } from 'lucide-react';
import { toast } from 'react-toastify';
import { changePassword } from '../services/auth';
import { User } from '../types/auth';

interface ChangePasswordModalProps {
user: User;
isOpen: boolean;
onClose: () => void;
}

export default function ChangePasswordModal({ user, isOpen, onClose }: ChangePasswordModalProps) {
const [formData, setFormData] = useState({
currentPassword: '',
newPassword: '',
confirmPassword: '',
});
const [loading, setLoading] = useState(false);

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
};

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();

if (formData.newPassword !== formData.confirmPassword) {
toast.error('New passwords do not match');
return;
}

setLoading(true);
try {
await changePassword(user.id, formData.currentPassword, formData.newPassword);
toast.success('Password changed successfully');
onClose();
// Clear form data
setFormData({
currentPassword: '',
newPassword: '',
confirmPassword: '',
});
} catch (error: any) {
toast.error(error.message);
} finally {
setLoading(false);
}
};

if (!isOpen) return null;

return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg w-full max-w-md p-6 relative">
<button
onClick={onClose}
className="absolute top-4 right-4 text-gray-500 hover:text-gray-700"
>
<X className="h-5 w-5" />
</button>

<div className="flex items-center space-x-2 mb-6">
<Lock className="h-6 w-6 text-indigo-600" />
<h2 className="text-xl font-semibold">Change Password</h2>
</div>

<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="currentPassword" className="block text-sm font-medium text-gray-700 mb-1">
Current Password
</label>
<input
type="password"
id="currentPassword"
name="currentPassword"
value={formData.currentPassword}
onChange={handleChange}
required
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500"
/>
</div>

<div>
<label htmlFor="newPassword" className="block text-sm font-medium text-gray-700 mb-1">
New Password
</label>
<input
type="password"
id="newPassword"
name="newPassword"
value={formData.newPassword}
onChange={handleChange}
required
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500"
/>
</div>

<div>
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-1">
Confirm New Password
</label>
<input
type="password"
id="confirmPassword"
name="confirmPassword"
value={formData.confirmPassword}
onChange={handleChange}
required
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500"
/>
</div>

<div className="text-sm text-gray-600">
Password must:
<ul className="list-disc list-inside mt-1">
<li>Be at least 8 characters long</li>
<li>Contain at least one uppercase letter</li>
<li>Contain at least one number</li>
<li>Contain at least one special character (!@#$%^&*)</li>
</ul>
</div>

<div className="flex justify-end space-x-3 mt-6">
<button
type="button"
onClick={onClose}
className="px-4 py-2 text-sm font-medium text-gray-700 hover:text-gray-900"
>
Cancel
</button>
<button
type="submit"
disabled={loading}
className="px-4 py-2 bg-indigo-600 text-white rounded-md text-sm font-medium hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading ? 'Changing Password...' : 'Change Password'}
</button>
</div>
</form>
</div>
</div>
);
}
35 changes: 26 additions & 9 deletions src/pages/ProfilePage.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import React, { useState } from 'react';
import { useAuth } from '../contexts/AuthContext';
import { Github, Linkedin, Mail, Award, Calendar, Edit2 } from 'lucide-react';
import { Github, Linkedin, Mail, Award, Calendar, Edit2, Lock } from 'lucide-react';
import EditProfileModal from '../components/EditProfileModal';
import ChangePasswordModal from '../components/ChangePasswordModal';
import { updateUserProfile } from '../services/localStore';
import type { User } from '../types/auth';

export default function ProfilePage() {
const { user } = useAuth();
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
const [isChangePasswordModalOpen, setIsChangePasswordModalOpen] = useState(false);

if (!user) {
return (
Expand All @@ -26,14 +28,23 @@ export default function ProfilePage() {
{/* Profile Header */}
<div className="bg-white rounded-xl shadow-md overflow-hidden mb-8">
<div className="relative h-32 bg-gradient-to-r from-indigo-500 to-purple-600">
{/* Edit Profile Button */}
<button
onClick={() => setIsEditModalOpen(true)}
className="absolute top-4 right-4 flex items-center space-x-2 px-4 py-2 bg-white bg-opacity-90 rounded-md text-sm font-medium text-gray-700 hover:bg-opacity-100 transition-all shadow-md"
>
<Edit2 className="h-4 w-4" />
<span>Edit Profile</span>
</button>
{/* Profile Actions */}
<div className="absolute top-4 right-4 flex items-center space-x-2">
<button
onClick={() => setIsChangePasswordModalOpen(true)}
className="flex items-center space-x-2 px-4 py-2 bg-white bg-opacity-90 rounded-md text-sm font-medium text-gray-700 hover:bg-opacity-100 transition-all shadow-md"
>
<Lock className="h-4 w-4" />
<span>Change Password</span>
</button>
<button
onClick={() => setIsEditModalOpen(true)}
className="flex items-center space-x-2 px-4 py-2 bg-white bg-opacity-90 rounded-md text-sm font-medium text-gray-700 hover:bg-opacity-100 transition-all shadow-md"
>
<Edit2 className="h-4 w-4" />
<span>Edit Profile</span>
</button>
</div>
{/* Profile picture overlapping the banner */}
<img
src={user.avatar}
Expand Down Expand Up @@ -125,6 +136,12 @@ export default function ProfilePage() {
onClose={() => setIsEditModalOpen(false)}
onUpdate={handleUpdateProfile}
/>

<ChangePasswordModal
user={user}
isOpen={isChangePasswordModalOpen}
onClose={() => setIsChangePasswordModalOpen(false)}
/>
</div>
);
}
48 changes: 48 additions & 0 deletions src/services/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,52 @@ export const signOut = async (): Promise<void> => {

export const getCurrentSession = (): User | null => {
return getCurrentUser();
};

export const changePassword = async (userId: string, currentPassword: string, newPassword: string): Promise<void> => {
try {
const users = getUsers();
const user = users.find(u => u.id === userId);

if (!user) {
throw new Error('User not found');
}

// For demo purposes, we're only checking the super admin's password
if (user.email === '[email protected]' && currentPassword !== 'Vivek@2024') {
throw new Error('Current password is incorrect');
}

// In a real app, you would:
// 1. Verify the current password hash matches
// 2. Hash the new password
// 3. Update the password in the database

// For this demo, we'll just validate the password format
if (newPassword.length < 8) {
throw new Error('Password must be at least 8 characters long');
}

if (!/[A-Z]/.test(newPassword)) {
throw new Error('Password must contain at least one uppercase letter');
}

if (!/[0-9]/.test(newPassword)) {
throw new Error('Password must contain at least one number');
}

if (!/[!@#$%^&*]/.test(newPassword)) {
throw new Error('Password must contain at least one special character (!@#$%^&*)');
}

// Update the password (in a real app, this would be hashed)
if (user.email === '[email protected]') {
// For demo purposes, we're only actually changing the super admin's password
user.password = newPassword;
const updatedUsers = users.map(u => u.id === userId ? user : u);
localStorage.setItem('users', JSON.stringify(updatedUsers));
}
} catch (error: any) {
throw new Error(error.message);
}
};

0 comments on commit a4a91ab

Please sign in to comment.