Skip to content

Commit b963fd5

Browse files
authored
Enhance Friends, Groups, and Profile Management Features (#206)
* feat: enhance Friends page with group breakdown and loading states - Refactored Friends component to include group breakdown for each friend. - Added loading skeletons while fetching friends data. - Implemented expandable friend rows to show detailed group balances. - Improved error handling and user feedback during data fetching. feat: improve GroupDetails with member management and settings tabs - Added functionality to leave groups and kick members with confirmation prompts. - Introduced settings tabs for group information, members, and danger actions. - Enhanced UI for inviting members and managing group settings. feat: create Profile page for user account management - Implemented profile editing functionality with image upload and name change. - Added modal for editing profile details and handling image selection. - Integrated logout functionality and menu items for account settings. feat: update API service with new endpoints for profile and group management - Added API calls for updating user profiles and managing group memberships. - Included Google login functionality in the authentication service. chore: add Firebase service for Google authentication - Set up Firebase configuration and authentication methods for Google sign-in. * feat: Implement core application pages and reusable UI components. * feat: Enhance UI accessibility and improve error handling in profile management * feat: Improve authentication error handling and add file upload validation in profile settings * feat: Enhance keyboard accessibility and ARIA attributes for Friends and GroupDetails components * feat: Add error handling and retry functionality in Friends component
1 parent 13ac19d commit b963fd5

File tree

15 files changed

+2863
-932
lines changed

15 files changed

+2863
-932
lines changed

web/App.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import React from 'react';
2-
import { HashRouter, Routes, Route, Navigate } from 'react-router-dom';
2+
import { HashRouter, Navigate, Route, Routes } from 'react-router-dom';
3+
import { Layout } from './components/layout/Layout';
4+
import { ThemeWrapper } from './components/layout/ThemeWrapper';
35
import { AuthProvider, useAuth } from './contexts/AuthContext';
46
import { ThemeProvider } from './contexts/ThemeContext';
57
import { Auth } from './pages/Auth';
68
import { Dashboard } from './pages/Dashboard';
7-
import { Groups } from './pages/Groups';
8-
import { GroupDetails } from './pages/GroupDetails';
99
import { Friends } from './pages/Friends';
10-
import { Layout } from './components/layout/Layout';
11-
import { ThemeWrapper } from './components/layout/ThemeWrapper';
10+
import { GroupDetails } from './pages/GroupDetails';
11+
import { Groups } from './pages/Groups';
12+
import { Profile } from './pages/Profile';
1213

1314
// Protected Route Wrapper
1415
const ProtectedRoute = ({ children }: { children: React.ReactElement }) => {
@@ -35,7 +36,7 @@ const AppRoutes = () => {
3536
<Route path="/groups" element={<ProtectedRoute><Groups /></ProtectedRoute>} />
3637
<Route path="/groups/:id" element={<ProtectedRoute><GroupDetails /></ProtectedRoute>} />
3738
<Route path="/friends" element={<ProtectedRoute><Friends /></ProtectedRoute>} />
38-
<Route path="/profile" element={<ProtectedRoute><div className="p-8 text-center text-xl">Profile Management Coming Soon</div></ProtectedRoute>} />
39+
<Route path="/profile" element={<ProtectedRoute><Profile /></ProtectedRoute>} />
3940

4041
<Route path="*" element={<Navigate to={isAuthenticated ? "/dashboard" : "/login"} />} />
4142
</Routes>

web/components/layout/Sidebar.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import React from 'react';
1+
import { CreditCard, Layers, LayoutDashboard, LogOut, Moon, Sun, UserCircle, Users } from 'lucide-react';
22
import { Link, useLocation } from 'react-router-dom';
3-
import { useTheme } from '../../contexts/ThemeContext';
4-
import { useAuth } from '../../contexts/AuthContext';
53
import { THEMES } from '../../constants';
6-
import { LayoutDashboard, Users, UserCircle, LogOut, Sun, Moon, Layers, CreditCard, UserPlus } from 'lucide-react';
4+
import { useAuth } from '../../contexts/AuthContext';
5+
import { useTheme } from '../../contexts/ThemeContext';
76
import { Button } from '../ui/Button';
87

98
export const Sidebar = () => {
@@ -56,9 +55,13 @@ export const Sidebar = () => {
5655
<div className="mt-auto flex flex-col gap-4">
5756
{user && (
5857
<div className={`p-4 flex items-center gap-3 ${style === THEMES.NEOBRUTALISM ? 'border-2 border-black bg-white text-black' : 'rounded-xl bg-black/20 text-white'}`}>
59-
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-yellow-400 to-orange-500 flex items-center justify-center font-bold text-white">
60-
{user.name.charAt(0)}
61-
</div>
58+
{user.imageUrl && /^(https?:|data:image)/.test(user.imageUrl) ? (
59+
<img src={user.imageUrl} alt={user.name} className="w-8 h-8 rounded-full object-cover" />
60+
) : (
61+
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-yellow-400 to-orange-500 flex items-center justify-center font-bold text-white">
62+
{user.name.charAt(0)}
63+
</div>
64+
)}
6265
<div className="flex-1 overflow-hidden">
6366
<p className="font-bold truncate">{user.name}</p>
6467
</div>

web/components/ui/Button.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
import React from 'react';
2-
import { useTheme } from '../../contexts/ThemeContext';
32
import { THEMES } from '../../constants';
3+
import { useTheme } from '../../contexts/ThemeContext';
44

55
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
66
variant?: 'primary' | 'secondary' | 'danger' | 'ghost';
77
size?: 'sm' | 'md' | 'lg';
88
}
99

10-
export const Button: React.FC<ButtonProps> = ({
11-
children,
12-
variant = 'primary',
13-
size = 'md',
14-
className = '',
15-
...props
10+
export const Button: React.FC<ButtonProps> = ({
11+
children,
12+
variant = 'primary',
13+
size = 'md',
14+
className = '',
15+
...props
1616
}) => {
1717
const { style } = useTheme();
1818

1919
const baseStyles = "transition-all duration-200 font-bold flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed";
20-
20+
2121
const sizeStyles = {
2222
sm: "px-3 py-1.5 text-sm",
2323
md: "px-5 py-2.5 text-base",
@@ -27,8 +27,8 @@ export const Button: React.FC<ButtonProps> = ({
2727
let themeStyles = "";
2828

2929
if (style === THEMES.NEOBRUTALISM) {
30-
themeStyles = "border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:translate-x-[4px] active:translate-y-[4px] active:shadow-none rounded-none uppercase tracking-wider";
31-
30+
themeStyles = "border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:translate-x-[4px] active:translate-y-[4px] active:shadow-none rounded-none uppercase tracking-wider font-mono";
31+
3232
if (variant === 'primary') themeStyles += " bg-neo-main text-white";
3333
if (variant === 'secondary') themeStyles += " bg-neo-second text-black";
3434
if (variant === 'danger') themeStyles += " bg-red-500 text-white";
@@ -37,15 +37,15 @@ export const Button: React.FC<ButtonProps> = ({
3737
} else {
3838
// Glassmorphism
3939
themeStyles = "rounded-xl backdrop-blur-md border border-white/20 shadow-lg hover:shadow-xl active:scale-95";
40-
40+
4141
if (variant === 'primary') themeStyles += " bg-gradient-to-r from-blue-500 to-purple-600 text-white shadow-blue-500/30";
4242
if (variant === 'secondary') themeStyles += " bg-white/10 text-white hover:bg-white/20";
4343
if (variant === 'danger') themeStyles += " bg-gradient-to-r from-red-500 to-pink-600 text-white shadow-red-500/30";
4444
if (variant === 'ghost') themeStyles += " bg-transparent border-none shadow-none hover:bg-white/10 text-current";
4545
}
4646

4747
return (
48-
<button
48+
<button
4949
className={`${baseStyles} ${sizeStyles[size]} ${themeStyles} ${className}`}
5050
{...props}
5151
>

web/components/ui/Modal.tsx

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
import { AnimatePresence, motion, Variants } from 'framer-motion';
2+
import { X } from 'lucide-react';
13
import React from 'react';
2-
import { motion, AnimatePresence, Variants } from 'framer-motion';
3-
import { useTheme } from '../../contexts/ThemeContext';
44
import { THEMES } from '../../constants';
5-
import { X } from 'lucide-react';
5+
import { useTheme } from '../../contexts/ThemeContext';
66

77
interface ModalProps {
88
isOpen: boolean;
@@ -22,20 +22,20 @@ export const Modal: React.FC<ModalProps> = ({ isOpen, onClose, title, children,
2222

2323
const modalVariants: Variants = style === THEMES.NEOBRUTALISM ? {
2424
hidden: { y: '100%', rotate: -5, opacity: 0 },
25-
visible: {
26-
y: 0,
27-
rotate: 0,
25+
visible: {
26+
y: 0,
27+
rotate: 0,
2828
opacity: 1,
29-
transition: { type: 'spring', damping: 15, stiffness: 200 }
29+
transition: { type: 'spring', damping: 15, stiffness: 200 }
3030
},
3131
exit: { y: '100%', rotate: 5, opacity: 0 }
3232
} : {
3333
hidden: { scale: 0.8, opacity: 0, backdropFilter: 'blur(0px)' },
34-
visible: {
35-
scale: 1,
36-
opacity: 1,
34+
visible: {
35+
scale: 1,
36+
opacity: 1,
3737
backdropFilter: 'blur(10px)',
38-
transition: { type: 'spring', damping: 20, stiffness: 300 }
38+
transition: { type: 'spring', damping: 20, stiffness: 300 }
3939
},
4040
exit: { scale: 0.8, opacity: 0 }
4141
};
@@ -58,8 +58,8 @@ export const Modal: React.FC<ModalProps> = ({ isOpen, onClose, title, children,
5858
animate="visible"
5959
exit="exit"
6060
className={`relative w-full max-w-lg overflow-hidden flex flex-col max-h-[90vh]
61-
${style === THEMES.NEOBRUTALISM
62-
? 'bg-white border-2 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)]'
61+
${style === THEMES.NEOBRUTALISM
62+
? 'bg-white border-2 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] rounded-none'
6363
: 'bg-gray-900/80 border border-white/20 rounded-3xl shadow-2xl text-white'}`}
6464
>
6565
{/* Header */}

web/contexts/AuthContext.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
2-
import { User } from '../types';
1+
import { createContext, ReactNode, useContext, useEffect, useState } from 'react';
32
import { getProfile } from '../services/api';
3+
import { User } from '../types';
44

55
interface AuthContextType {
66
user: User | null;
77
isAuthenticated: boolean;
88
isLoading: boolean;
99
login: (token: string, user: User) => void;
1010
logout: () => void;
11+
updateUserInContext: (userData: User) => void;
1112
}
1213

1314
const AuthContext = createContext<AuthContextType | undefined>(undefined);
@@ -47,8 +48,12 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
4748
setUser(null);
4849
};
4950

51+
const updateUserInContext = (userData: User) => {
52+
setUser(userData);
53+
};
54+
5055
return (
51-
<AuthContext.Provider value={{ user, isAuthenticated: !!user, isLoading, login, logout }}>
56+
<AuthContext.Provider value={{ user, isAuthenticated: !!user, isLoading, login, logout, updateUserInContext }}>
5257
{children}
5358
</AuthContext.Provider>
5459
);

0 commit comments

Comments
 (0)