Initial commit: Complete NodeJS-native setup
- Migrated from Python pre-commit to NodeJS-native solution - Reorganized documentation structure - Set up Husky + lint-staged for efficient pre-commit hooks - Fixed Dockerfile healthcheck issue - Added comprehensive documentation index
This commit is contained in:
219
contexts/UserContext.tsx
Normal file
219
contexts/UserContext.tsx
Normal file
@@ -0,0 +1,219 @@
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useState,
|
||||
useEffect,
|
||||
ReactNode,
|
||||
} from 'react';
|
||||
import { User } from '../types';
|
||||
import { dbService } from '../services/couchdb.factory';
|
||||
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: any
|
||||
) => 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.log('Login result received:', result);
|
||||
console.log('User from login:', result.user);
|
||||
console.log('User _id:', result.user._id);
|
||||
|
||||
// Update last login time
|
||||
const updatedUser = { ...result.user, lastLoginAt: new Date() };
|
||||
await dbService.updateUser(updatedUser);
|
||||
|
||||
console.log('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.log('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 {
|
||||
const result = 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: any
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const result = await authService.loginWithOAuth(provider, userData);
|
||||
|
||||
console.log('OAuth login result received:', result);
|
||||
console.log('OAuth user:', result.user);
|
||||
console.log('OAuth user _id:', result.user._id);
|
||||
|
||||
// Update last login time
|
||||
const updatedUser = { ...result.user, lastLoginAt: new Date() };
|
||||
await dbService.updateUser(updatedUser);
|
||||
|
||||
console.log('Updated OAuth user with last login:', updatedUser);
|
||||
|
||||
localStorage.setItem('access_token', result.accessToken);
|
||||
setUser(updatedUser);
|
||||
|
||||
console.log('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 dbService.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>
|
||||
);
|
||||
Reference in New Issue
Block a user