Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Web/React/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
"preview": "vite preview",
"start": "vite"
},
"dependencies": {
"axios": "^1.13.1",
Expand Down
4 changes: 2 additions & 2 deletions Web/React/src/components/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import GroupDetail from './GroupDetail';
import Receipts from './Receipts';
import ReceiptsView from './ReceiptsView'; // <-- adăugat
import ReceiptsManual from './ReceiptsManual';
import ChatBot from './ChatBot';
import Data from './Data';
import Categories from './Categories';

Expand All @@ -36,6 +35,7 @@ const Dashboard: React.FC = () =>
| 'profile'
| 'groupDetail'
| 'data'
| 'support'
>('home');
const [selectedGroupId, setSelectedGroupId] = useState<number | null>(null);
const [recentTx, setRecentTx] = useState<Tx[]>([]);
Expand All @@ -50,7 +50,7 @@ const Dashboard: React.FC = () =>
{
try
{
const res = await apiClient.get('/expenses', {
const res = await apiClient.get('/expenses/', {
params: {
offset: 0,
limit: 5,
Expand Down
2 changes: 1 addition & 1 deletion Web/React/src/components/Data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ const Data: React.FC = () => {
// or you can pass `category` (name) to backend if your API supports it.
// For now, we fetch all for the date range and filter locally.

const res = await apiClient.get('/expenses', { params });
const res = await apiClient.get('/expenses/', { params });
const items: Expense[] = Array.isArray(res.data) ? res.data : (res.data.data || []);

if (cancelled) return;
Expand Down
4 changes: 2 additions & 2 deletions Web/React/src/components/GroupDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ const GroupDetail: React.FC<Props> = ({ groupId, onBack }) => {

// Fetch categories - fetch all categories since backend returns all anyway (known bug)
// This ensures we can map categories from all users in the group
const categories = await categoryService.getCategories(user?.id);
const categories: Category[] = await categoryService.getCategories(user?.id);
const categoryMap = new Map(categories.map(c => [c.id, c.title]));

// Get unique user IDs from both expenses and logs
Expand Down Expand Up @@ -634,7 +634,7 @@ const GroupDetail: React.FC<Props> = ({ groupId, onBack }) => {
group_id: groupId,
};

await apiClient.post('/expenses', body);
await apiClient.post('/expenses/', body);

// Refresh the expense list and statistics
await fetchGroupExpenses();
Expand Down
4 changes: 2 additions & 2 deletions Web/React/src/components/Groups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const Groups: React.FC<{

setLoading(true);
try {
const res = await apiClient.get('/groups', {
const res = await apiClient.get('/groups/', {
params: { offset: 0, limit: 1000 }
});

Expand Down Expand Up @@ -133,7 +133,7 @@ const Groups: React.FC<{
body.description = newGroup.description.trim();
}

const createRes = await apiClient.post('/groups', body);
const createRes = await apiClient.post('/groups/', body);

// Backend returns APIResponse { success: true, data: { id: groupId } }
const responseData = createRes.data;
Expand Down
6 changes: 3 additions & 3 deletions Web/React/src/components/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ const Profile: React.FC<ProfileProps> = ({ onRequestNavigate }) => {
setErrorMessage('Please enter a valid positive number');
return;
}
await apiClient.put(`/users/${user.id}/budget?new_budget=${budgetValue}`);
await apiClient.put(`/users/${user.id}/budget/?new_budget=${budgetValue}`);
setBudget(budgetValue);
setSavedMessage('Budget updated successfully');
setTimeout(() => setSavedMessage(null), 2500);
Expand All @@ -123,7 +123,7 @@ const Profile: React.FC<ProfileProps> = ({ onRequestNavigate }) => {
setErrorMessage('Please enter both current and new password');
return;
}
await apiClient.put('/users/password/change', { old_password: oldPassword, new_password: newPassword });
await apiClient.put('/users/password/change/', { old_password: oldPassword, new_password: newPassword });
setSavedMessage('Password changed successfully');
setTimeout(() => setSavedMessage(null), 2500);
setEditingField(null);
Expand All @@ -134,7 +134,7 @@ const Profile: React.FC<ProfileProps> = ({ onRequestNavigate }) => {

if (!user) throw new Error('Not authenticated');
const payload: any = { [editingField]: tempValue };
await apiClient.put(`/users/${user.id}`, payload);
await apiClient.put(`/users/${user.id}/`, payload);

// Optimistically update local and global user so UI reflects changes immediately
setProfile(prev => ({ ...prev, [editingField as string]: tempValue }));
Expand Down
8 changes: 4 additions & 4 deletions Web/React/src/components/Receipts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const Receipts: React.FC<{ navigate?: (to: string) => void }> = ({ navigate }) =
const updatedItems = analysisData.items.filter((_, idx) => idx !== indexToDelete);

// Recalculate Total
const newTotal = updatedItems.reduce((sum, item) => sum + item.price, 0);
const newTotal = updatedItems.reduce((sum, item) => sum + (item.price || 0), 0);

setAnalysisData({
items: updatedItems,
Expand Down Expand Up @@ -96,7 +96,7 @@ const Receipts: React.FC<{ navigate?: (to: string) => void }> = ({ navigate }) =
setStatusMessage(`Saving item ${i + 1} of ${analysisData.items.length}...`);

// Normalize category name from AI (e.g., "Food " -> "food")
const aiCategoryName = item.category.trim();
const aiCategoryName = (item.category || 'Other').trim();
const aiCategoryKey = aiCategoryName.toLowerCase();

let targetCategory = categoryMap.get(aiCategoryKey);
Expand Down Expand Up @@ -254,13 +254,13 @@ const Receipts: React.FC<{ navigate?: (to: string) => void }> = ({ navigate }) =
<span style={{background:'#e6f7ff', color:'#1890ff', padding:'2px 6px', borderRadius:4, marginRight:5}}>
{item.category}
</span>
{item.quantity > 1 && `x${item.quantity}`}
{item.quantity && item.quantity > 1 && `x${item.quantity}`}
</div>
</div>

{/* Price */}
<div style={{ fontWeight: 600 }}>
{item.price.toFixed(2)}
{(item.price ?? 0).toFixed(2)}
</div>

{/* DELETE BUTTON */}
Expand Down
24 changes: 16 additions & 8 deletions Web/React/src/components/ReceiptsManual.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// src/components/ReceiptsManual.tsx
import React, { useState, useEffect } from 'react';
import '../App.css';
import type { ReceiptItem } from './Receipts';
import { useAuth } from '../hooks/useAuth';
import apiClient from '../services/api-client';
import categoryService, { Category } from '../services/category-service';
import groupService, { Group } from '../services/group-service';
import { ReceiptItem } from '../services/receipt-service';

const ReceiptsManual: React.FC<{ onCreated: (it: ReceiptItem) => void; groupId?: number | null }> = ({ onCreated, groupId = null }) => {
const [title, setTitle] = useState('');
Expand All @@ -13,9 +14,10 @@ const ReceiptsManual: React.FC<{ onCreated: (it: ReceiptItem) => void; groupId?:
const [amount, setAmount] = useState<string>('');
const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategoryId, setSelectedCategoryId] = useState<number | null>(null);
const [groups, setGroups] = useState<Group[]>([]);

useEffect(() => {
const fetchCategories = async () => {
const fetchData = async () => {
try {
const cats = await categoryService.getCategories(user?.id);

Expand All @@ -28,7 +30,7 @@ const ReceiptsManual: React.FC<{ onCreated: (it: ReceiptItem) => void; groupId?:
try {
const createdData = await categoryService.createCategory(title);
// Fetch the created category details
const allCats = await categoryService.getCategories(user?.id);
const allCats : Category[] = await categoryService.getCategories(user?.id);
const newCat = allCats.find(c => c.id === createdData.id);
if (newCat) createdCats.push(newCat);
} catch (err) {
Expand All @@ -46,12 +48,18 @@ const ReceiptsManual: React.FC<{ onCreated: (it: ReceiptItem) => void; groupId?:
setSelectedCategoryId(cats[0].id);
}
}

// Fetch groups
if (user?.id) {
const groupsData = await groupService.getUserGroups(user.id);
setGroups(groupsData);
}
} catch (err) {
console.error('Failed to fetch categories', err);
console.error('Failed to fetch data', err);
}
};
fetchCategories();
}, []);
fetchData();
}, [user?.id]);

const onSubmit = async (e: React.FormEvent) => {
e.preventDefault();
Expand Down Expand Up @@ -79,7 +87,7 @@ const ReceiptsManual: React.FC<{ onCreated: (it: ReceiptItem) => void; groupId?:
body.group_id = groupId;
}

const res = await apiClient.post('/expenses', body);
const res = await apiClient.post('/expenses/', body);

// Backend returns APIResponse { success: true, data: { id: ... } }
const responseData = res.data;
Expand Down Expand Up @@ -150,7 +158,7 @@ const ReceiptsManual: React.FC<{ onCreated: (it: ReceiptItem) => void; groupId?:
<input type="number" value={amount} onChange={e=>setAmount(e.target.value)} placeholder="50" required step="any" />

{groupId && (
<div style={{ color:'var(--muted-dark)', fontSize:13 }}>Linked to group: {mockGroups.find(g=>g.id===groupId)?.name}</div>
<div style={{ color:'var(--muted-dark)', fontSize:13 }}>Linked to group: {groups.find(g=>g.id===groupId)?.name}</div>
)}

<div style={{ display:'flex', gap:8 }}>
Expand Down
12 changes: 6 additions & 6 deletions Web/React/src/components/ReceiptsView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import '../App.css';
import type { ReceiptItem } from './Receipts';
import type { ReceiptItem } from '../services/receipt-service';
import { useAuth } from '../hooks/useAuth';
import { useCurrency } from '../contexts/CurrencyContext';
import apiClient from '../services/api-client';
Expand Down Expand Up @@ -64,7 +64,7 @@ async function fetchPage(pageIndex: number, pageSize: number, filters: any) {
// For group expenses, we could use group_ids param, but we don't know all group IDs
// So we fetch all and filter client-side based on whether expense has group_id

const res = await apiClient.get('/expenses', { params });
const res = await apiClient.get('/expenses/', { params });

// Backend returns APIResponse { success: true, data: [expenses] }
const responseData = res.data;
Expand Down Expand Up @@ -110,7 +110,7 @@ async function fetchPage(pageIndex: number, pageSize: number, filters: any) {

// Title filtering (backend doesn't support it)
if (filters.qTitle) {
filtered = filtered.filter(item => item.title.toLowerCase().includes(filters.qTitle.toLowerCase()));
filtered = filtered.filter(item => item.title?.toLowerCase().includes(filters.qTitle.toLowerCase()));
}

// Expense type filtering
Expand Down Expand Up @@ -145,7 +145,7 @@ async function deleteExpense(id: number) {
//update API call
async function updateExpense(id: number, body: any) {
try {
const res = await apiClient.put(`/expenses/${id}`, body);
const res = await apiClient.put(`/expenses/${id}/`, body);
return res.data;
} catch (err) {
console.error('Failed to update expense', err);
Expand Down Expand Up @@ -196,7 +196,7 @@ const ReceiptsView: React.FC<{ onNeedRefresh?: () => void; refreshKey?: number }

// Extract unique categories from items - accumulate, don't replace
useEffect(() => {
const newCategories = items.map(item => item.subtitle).filter(Boolean);
const newCategories = items.map(item => item.subtitle).filter((c): c is string => Boolean(c));
setCategories(prev => {
const combined = new Set([...prev, ...newCategories]);
return Array.from(combined);
Expand Down Expand Up @@ -522,7 +522,7 @@ const saveEdit = async () => {

<div style={{ textAlign:'right' }}>
<div style={{ fontWeight:800, color: '#ff6b6b', fontSize:16 }}>{useCurrency().formatAmount(-Math.abs(it.amount))}</div>
<div style={{ fontSize:12, color:'var(--muted-dark)' }}>{it.dateTransaction}</div>
<div style={{ fontSize:12, color:'var(--muted-dark)' }}>{it.dateTransaction || it.dateAdded}</div>
</div>
</div>

Expand Down
22 changes: 18 additions & 4 deletions Web/React/src/contexts/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
const checkAuthStatus = async () => {
try {
const response = await authService.getMe(); // GET /users/auth/me
// Backend returns APIResponse { success: true, data: UserResponse }
// Backend returns UserResponse - check if wrapped
const userData = response.data?.data || response.data;
setUser(userData);
} catch {
Expand All @@ -61,7 +61,9 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
// call login endpoint
const response = await authService.login({ email, password });

// Backend returns APIResponse { success: true, data: { access_token, user } }
console.log('Login response:', response.data);

// Backend returns AuthResponse - check if wrapped in data object
const responseData = response.data?.data || response.data;

// If backend indicates 2FA is required, it should return e.g.:
Expand All @@ -74,14 +76,19 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
return;
}

// Store access token in sessionStorage
if (responseData?.access_token) {
sessionStorage.setItem('access_token', responseData.access_token);
}

// Extract user from response
if (responseData?.user) {
setUser(responseData.user);
} else {
// fallback: attempt to fetch /me
try {
const me = await authService.getMe();
const userData = me.data?.data || me.data;
const userData = me.data;
setUser(userData);
} catch (err) {
console.error('Login: failed to fetch /me after login', err);
Expand Down Expand Up @@ -124,10 +131,15 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
}, []);

const register = useCallback(async (accountData: any, verificationMethod?: 'email'|'number') => {
// Register endpoint returns { success: true, data: { access_token, id, user } }
// Register endpoint returns AuthResponse { access_token, user }
const response = await authService.register(accountData);
const responseData = response.data?.data || response.data;

// Store access token in sessionStorage
if (responseData?.access_token) {
sessionStorage.setItem('access_token', responseData.access_token);
}

// Extract user from register response
if (responseData?.user) {
setUser(responseData.user);
Expand All @@ -146,9 +158,11 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
const logout = useCallback(async () => {
try {
await authService.logout();
sessionStorage.removeItem('access_token');
setUser(null);
} catch (error) {
console.error('Logout error:', error);
sessionStorage.removeItem('access_token');
setUser(null);
}
}, []);
Expand Down
18 changes: 9 additions & 9 deletions Web/React/src/services/api-client.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import axios, { CanceledError } from 'axios';

const apiClient = axios.create({
baseURL: 'http://localhost:8000',
withCredentials: true,
baseURL: 'https://backend.gitpushforce-ubb.com',
// withCredentials: true, // Temporarily disabled to test CORS
})

// apiClient.interceptors.request.use((config) => {
// const token = sessionStorage.getItem('access_token');
// if(token) {
// config.headers.Authorization = `Bearer ${token}`;
// }
// return config;
// })
apiClient.interceptors.request.use((config) => {
const token = sessionStorage.getItem('access_token');
if(token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
})

export default apiClient;
export {CanceledError};
Loading
Loading