- Remove deprecated CouchDB service files - Update database test configurations - Add test files for components and auth modules - Update user context and admin interface - Remove migration script for unified config - Fix User interface properties in tests (use status instead of isActive)
220 lines
5.6 KiB
TypeScript
220 lines
5.6 KiB
TypeScript
import React, {
|
|
createContext,
|
|
useContext,
|
|
useState,
|
|
useEffect,
|
|
ReactNode,
|
|
} from 'react';
|
|
import { User } from '../types';
|
|
import { databaseService } from '../services/database';
|
|
import { authService } from '../services/auth/auth.service';
|
|
|
|
const SESSION_KEY = 'medication_app_session';
|
|
|
|
interface UserContextType {
|
|
user: User | null;
|
|
isLoading: boolean;
|
|
login: (email: string, password: string) => Promise<boolean>;
|
|
register: (
|
|
email: string,
|
|
password: string,
|
|
username?: string
|
|
) => Promise<boolean>;
|
|
loginWithOAuth: (
|
|
provider: 'google' | 'github',
|
|
userData: { email: string; username: string; avatar?: string }
|
|
) => Promise<boolean>;
|
|
changePassword: (
|
|
currentPassword: string,
|
|
newPassword: string
|
|
) => Promise<boolean>;
|
|
logout: () => void;
|
|
updateUser: (
|
|
updatedUser: Omit<User, '_rev'> & { _rev: string }
|
|
) => Promise<void>;
|
|
}
|
|
|
|
const UserContext = createContext<UserContextType | undefined>(undefined);
|
|
|
|
export const UserProvider: React.FC<{ children: ReactNode }> = ({
|
|
children,
|
|
}) => {
|
|
const [user, setUser] = useState<User | null>(null);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
try {
|
|
const sessionUser = localStorage.getItem(SESSION_KEY);
|
|
if (sessionUser) {
|
|
setUser(JSON.parse(sessionUser));
|
|
}
|
|
} catch {
|
|
// silent fail
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (user) {
|
|
localStorage.setItem(SESSION_KEY, JSON.stringify(user));
|
|
} else {
|
|
localStorage.removeItem(SESSION_KEY);
|
|
}
|
|
}, [user]);
|
|
|
|
const login = async (email: string, password: string): Promise<boolean> => {
|
|
try {
|
|
// Use auth service for password-based login
|
|
const result = await authService.login({ email, password });
|
|
|
|
console.warn('Login result received:', result);
|
|
console.warn('User from login:', result.user);
|
|
console.warn('User _id:', result.user._id);
|
|
|
|
// Update last login time
|
|
const updatedUser = { ...result.user, lastLoginAt: new Date() };
|
|
await databaseService.updateUser(updatedUser);
|
|
|
|
console.warn('Updated user with last login:', updatedUser);
|
|
|
|
// Store access token for subsequent API calls.
|
|
localStorage.setItem('access_token', result.accessToken);
|
|
// Set the user from the login result
|
|
setUser(updatedUser);
|
|
|
|
console.warn('User set in context');
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Login error:', error);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const register = async (
|
|
email: string,
|
|
password: string,
|
|
username?: string
|
|
): Promise<boolean> => {
|
|
try {
|
|
await authService.register(email, password, username);
|
|
// Don't auto-login after registration, require email verification
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Registration error:', error);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const loginWithOAuth = async (
|
|
provider: 'google' | 'github',
|
|
userData: { email: string; username: string; avatar?: string }
|
|
): Promise<boolean> => {
|
|
try {
|
|
const result = await authService.loginWithOAuth(provider, userData);
|
|
|
|
console.warn('OAuth login result received:', result);
|
|
console.warn('OAuth user:', result.user);
|
|
console.warn('OAuth user _id:', result.user._id);
|
|
|
|
// Update last login time
|
|
const updatedUser = { ...result.user, lastLoginAt: new Date() };
|
|
await databaseService.updateUser(updatedUser);
|
|
|
|
console.warn('Updated OAuth user with last login:', updatedUser);
|
|
|
|
localStorage.setItem('access_token', result.accessToken);
|
|
setUser(updatedUser);
|
|
|
|
console.warn('OAuth user set in context');
|
|
return true;
|
|
} catch (error) {
|
|
console.error('OAuth login error:', error);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const changePassword = async (
|
|
currentPassword: string,
|
|
newPassword: string
|
|
): Promise<boolean> => {
|
|
try {
|
|
if (!user) {
|
|
throw new Error('No user logged in');
|
|
}
|
|
|
|
await authService.changePassword(user._id, currentPassword, newPassword);
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Password change error:', error);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const logout = () => {
|
|
setUser(null);
|
|
};
|
|
|
|
const updateUser = async (updatedUser: User) => {
|
|
try {
|
|
const savedUser = await databaseService.updateUser(updatedUser);
|
|
setUser(savedUser);
|
|
} catch (error) {
|
|
console.error('Failed to update user', error);
|
|
// Optionally revert state or show error
|
|
}
|
|
};
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className='min-h-screen flex items-center justify-center bg-slate-50 dark:bg-slate-900'>
|
|
<PillIcon className='w-12 h-12 text-indigo-500 animate-spin' />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<UserContext.Provider
|
|
value={{
|
|
user,
|
|
isLoading: false,
|
|
login,
|
|
register,
|
|
loginWithOAuth,
|
|
changePassword,
|
|
logout,
|
|
updateUser,
|
|
}}
|
|
>
|
|
{children}
|
|
</UserContext.Provider>
|
|
);
|
|
};
|
|
|
|
export const useUser = (): UserContextType => {
|
|
const context = useContext(UserContext);
|
|
if (context === undefined) {
|
|
throw new Error('useUser must be used within a UserProvider');
|
|
}
|
|
return context;
|
|
};
|
|
|
|
// Dummy icon for loading screen
|
|
const PillIcon: React.FC<React.SVGProps<SVGSVGElement>> = props => (
|
|
<svg
|
|
xmlns='http://www.w3.org/2000/svg'
|
|
width='24'
|
|
height='24'
|
|
viewBox='0 0 24 24'
|
|
fill='none'
|
|
stroke='currentColor'
|
|
strokeWidth='2'
|
|
strokeLinecap='round'
|
|
strokeLinejoin='round'
|
|
{...props}
|
|
>
|
|
<path d='m10.5 20.5 10-10a4.95 4.95 0 1 0-7-7l-10 10a4.95 4.95 0 1 0 7 7Z' />
|
|
<path d='m8.5 8.5 7 7' />
|
|
</svg>
|
|
);
|