1 Commits

Author SHA1 Message Date
5bcabb2736 revert 30b1c97043
revert Merge pull request 'try to do better' (#18) from refactor/frontend-code-quality into master

Reviewed-on: http://192.168.31.100:3847/Arrelin/family_budget/pulls/18
2026-01-29 12:38:21 +00:00
34 changed files with 61 additions and 43 deletions

View File

@@ -1,5 +1,4 @@
import React, { Component } from 'react'; import React, { Component, ReactNode } from 'react';
import type { ReactNode } from 'react';
import { AlertTriangle } from 'lucide-react'; import { AlertTriangle } from 'lucide-react';
import { Button } from './ui'; import { Button } from './ui';
@@ -23,7 +22,7 @@ export class ErrorBoundary extends Component<Props, State> {
}; };
} }
static getDerivedStateFromError(_error: Error): Partial<State> { static getDerivedStateFromError(error: Error): Partial<State> {
return { hasError: true }; return { hasError: true };
} }
@@ -58,7 +57,7 @@ export class ErrorBoundary extends Component<Props, State> {
<p className="text-gray-600 dark:text-gray-400 mb-6"> <p className="text-gray-600 dark:text-gray-400 mb-6">
We're sorry, but something unexpected happened. Please try refreshing the page or going back to the home page. We're sorry, but something unexpected happened. Please try refreshing the page or going back to the home page.
</p> </p>
{import.meta.env.DEV && this.state.error && ( {process.env.NODE_ENV === 'development' && this.state.error && (
<div className="mb-6 p-4 bg-red-50 dark:bg-red-900/10 rounded-lg text-left"> <div className="mb-6 p-4 bg-red-50 dark:bg-red-900/10 rounded-lg text-left">
<p className="text-sm font-mono text-red-800 dark:text-red-300 break-all"> <p className="text-sm font-mono text-red-800 dark:text-red-300 break-all">
{this.state.error.toString()} {this.state.error.toString()}

View File

@@ -1,3 +1,4 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ShoppingCart, CheckCheck, Trash2 } from 'lucide-react'; import { ShoppingCart, CheckCheck, Trash2 } from 'lucide-react';
import { useShoppingList, useConfirm } from '../hooks'; import { useShoppingList, useConfirm } from '../hooks';
@@ -15,6 +16,7 @@ export default function ShoppingListModal({ familyId, onClose }: ShoppingListMod
const { t } = useTranslation(); const { t } = useTranslation();
const { items, loading, createItem, deleteItem, togglePurchased, markAllAsPurchased, clearAll } = useShoppingList(familyId); const { items, loading, createItem, deleteItem, togglePurchased, markAllAsPurchased, clearAll } = useShoppingList(familyId);
const { confirmState, confirm, cancel } = useConfirm(); const { confirmState, confirm, cancel } = useConfirm();
const [pendingAction, setPendingAction] = useState<{ type: 'delete' | 'markAll' | 'clearAll'; itemId?: number } | null>(null);
const stats = shoppingService.getStats(items); const stats = shoppingService.getStats(items);
@@ -23,18 +25,24 @@ export default function ShoppingListModal({ familyId, onClose }: ShoppingListMod
}; };
const handleDelete = async (itemId: number) => { const handleDelete = async (itemId: number) => {
setPendingAction({ type: 'delete', itemId });
await confirm(t('shopping.deleteConfirm'), t('shopping.deleteMessage')); await confirm(t('shopping.deleteConfirm'), t('shopping.deleteMessage'));
await deleteItem(itemId); await deleteItem(itemId);
setPendingAction(null);
}; };
const handleMarkAll = async () => { const handleMarkAll = async () => {
setPendingAction({ type: 'markAll' });
await confirm(t('shopping.markAllConfirm'), t('shopping.markAllMessage')); await confirm(t('shopping.markAllConfirm'), t('shopping.markAllMessage'));
await markAllAsPurchased(); await markAllAsPurchased();
setPendingAction(null);
}; };
const handleClearAll = async () => { const handleClearAll = async () => {
setPendingAction({ type: 'clearAll' });
await confirm(t('shopping.clearAllConfirm'), t('shopping.clearAllMessage')); await confirm(t('shopping.clearAllConfirm'), t('shopping.clearAllMessage'));
await clearAll(); await clearAll();
setPendingAction(null);
}; };
const handleUpdate = async (itemId: number, name: string) => { const handleUpdate = async (itemId: number, name: string) => {

View File

@@ -2,7 +2,7 @@ import { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Plus, X } from 'lucide-react'; import { Plus, X } from 'lucide-react';
import { Button, Input } from '../ui'; import { Button, Input } from '../ui';
import type { CreateCategoryRequest } from '../../types'; import { CreateCategoryRequest } from '../../types';
interface AddCategorySectionProps { interface AddCategorySectionProps {
showForm: boolean; showForm: boolean;

View File

@@ -1,11 +1,11 @@
import { useState } from 'react'; import { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Tag, TrendingDown, Plus, Trash2, RotateCcw, History, X, DollarSign, MessageSquare, Calendar } from 'lucide-react'; import { Tag, TrendingDown, Plus, Trash2, RotateCcw, History, X, DollarSign, MessageSquare, Calendar } from 'lucide-react';
import type { CategoryWithRemaining } from '../../services'; import { CategoryWithRemaining } from '../../services';
import { categoryService, expenseService } from '../../services'; import { categoryService, expenseService } from '../../services';
import { useExpenses } from '../../hooks'; import { useExpenses } from '../../hooks';
import { format } from '../../utils/format'; import { format } from '../../utils/format';
import { Button, Input } from '../ui'; import { Button, Input, Badge } from '../ui';
interface CategoryCardProps { interface CategoryCardProps {
category: CategoryWithRemaining; category: CategoryWithRemaining;

View File

@@ -1,4 +1,4 @@
import type { CategoryWithRemaining } from '../../services'; import { CategoryWithRemaining } from '../../services';
import { CategoryCard } from './CategoryCard'; import { CategoryCard } from './CategoryCard';
interface CategoryListProps { interface CategoryListProps {

View File

@@ -1,6 +1,6 @@
import { Wallet, ShoppingCart } from 'lucide-react'; import { Wallet, ShoppingCart } from 'lucide-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { CategoryWithRemaining } from '../../services'; import { CategoryWithRemaining } from '../../services';
import { format } from '../../utils/format'; import { format } from '../../utils/format';
interface FamilySummaryProps { interface FamilySummaryProps {

View File

@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
import { Copy, Check, Loader2 } from 'lucide-react'; import { Copy, Check, Loader2 } from 'lucide-react';
import { Modal, Button } from '../ui'; import { Modal, Button } from '../ui';
import { useInviteLink } from '../../hooks'; import { useInviteLink } from '../../hooks';
import type { InviteLinkResponse } from '../../types'; import { InviteLinkResponse } from '../../types';
interface InviteModalProps { interface InviteModalProps {
onClose: () => void; onClose: () => void;

View File

@@ -1,7 +1,7 @@
import { useState } from 'react'; import { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Users, Edit3, Save, X, AlertTriangle, Loader2 } from 'lucide-react'; import { Users, Edit3, Save, X, AlertTriangle, Loader2 } from 'lucide-react';
import type { Family } from '../../types'; import { Family } from '../../types';
import { Card, Button, Input } from '../ui'; import { Card, Button, Input } from '../ui';
import { familyService } from '../../services'; import { familyService } from '../../services';
import { showToast } from '../../utils/toast'; import { showToast } from '../../utils/toast';

View File

@@ -1,5 +1,6 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { FamilyMember, User } from '../../types'; import { Loader2 } from 'lucide-react';
import { FamilyMember, User } from '../../types';
import { Badge, LoadingSpinner } from '../ui'; import { Badge, LoadingSpinner } from '../ui';
interface MembersSectionProps { interface MembersSectionProps {

View File

@@ -1,6 +1,6 @@
import { Settings, Palette, Languages } from 'lucide-react'; import { Settings, Palette, Languages } from 'lucide-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { Theme } from '../../types'; import { Theme } from '../../types';
import { Card } from '../ui'; import { Card } from '../ui';
import { ThemeSelector } from './ThemeSelector'; import { ThemeSelector } from './ThemeSelector';
import { LanguageSelector } from './LanguageSelector'; import { LanguageSelector } from './LanguageSelector';

View File

@@ -1,6 +1,6 @@
import { Check } from 'lucide-react'; import { Check } from 'lucide-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { Theme } from '../../types'; import { Theme } from '../../types';
import { THEMES } from '../../constants'; import { THEMES } from '../../constants';
interface ThemeSelectorProps { interface ThemeSelectorProps {

View File

@@ -1,6 +1,6 @@
import { User as UserIcon, LogOut } from 'lucide-react'; import { User as UserIcon, LogOut } from 'lucide-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { User } from '../../types'; import { User } from '../../types';
import { Button, Card } from '../ui'; import { Button, Card } from '../ui';
interface UserInfoProps { interface UserInfoProps {

View File

@@ -1,6 +1,7 @@
import { useState } from 'react'; import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Trash2, Check, Pencil, X } from 'lucide-react'; import { Trash2, Check, Pencil, X } from 'lucide-react';
import type { ShoppingItem } from '../../types'; import { ShoppingItem } from '../../types';
import { Button, Input } from '../ui'; import { Button, Input } from '../ui';
interface ShoppingItemCardProps { interface ShoppingItemCardProps {
@@ -11,6 +12,7 @@ interface ShoppingItemCardProps {
} }
export function ShoppingItemCard({ item, onToggle, onDelete, onUpdate }: ShoppingItemCardProps) { export function ShoppingItemCard({ item, onToggle, onDelete, onUpdate }: ShoppingItemCardProps) {
const { t } = useTranslation();
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const [editName, setEditName] = useState(item.name); const [editName, setEditName] = useState(item.name);

View File

@@ -1,5 +1,4 @@
import { useState } from 'react'; import { useState, KeyboardEvent } from 'react';
import type { KeyboardEvent } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Plus } from 'lucide-react'; import { Plus } from 'lucide-react';
import { Button, Input } from '../ui'; import { Button, Input } from '../ui';

View File

@@ -1,5 +1,5 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { ShoppingItem } from '../../types'; import { ShoppingItem } from '../../types';
import { ShoppingItemCard } from './ShoppingItemCard'; import { ShoppingItemCard } from './ShoppingItemCard';
import { shoppingService } from '../../services'; import { shoppingService } from '../../services';

View File

@@ -1,4 +1,4 @@
import type { ReactNode } from 'react'; import { ReactNode } from 'react';
interface BadgeProps { interface BadgeProps {
children: ReactNode; children: ReactNode;

View File

@@ -1,4 +1,4 @@
import type { ButtonHTMLAttributes, ReactNode } from 'react'; import { ButtonHTMLAttributes, ReactNode } from 'react';
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'success' | 'danger' | 'secondary' | 'ghost'; variant?: 'primary' | 'success' | 'danger' | 'secondary' | 'ghost';

View File

@@ -1,4 +1,4 @@
import type { ReactNode } from 'react'; import { ReactNode } from 'react';
interface CardProps { interface CardProps {
children: ReactNode; children: ReactNode;

View File

@@ -1,5 +1,4 @@
import { forwardRef } from 'react'; import { InputHTMLAttributes, forwardRef } from 'react';
import type { InputHTMLAttributes } from 'react';
interface InputProps extends InputHTMLAttributes<HTMLInputElement> { interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
label?: string; label?: string;

View File

@@ -1,5 +1,4 @@
import { useEffect } from 'react'; import { ReactNode, useEffect } from 'react';
import type { ReactNode } from 'react';
interface ModalProps { interface ModalProps {
isOpen: boolean; isOpen: boolean;

View File

@@ -1,4 +1,4 @@
import type { Theme } from '../types'; import { Theme } from '../types';
export const THEMES: { id: Theme; gradient: string; name: string }[] = [ export const THEMES: { id: Theme; gradient: string; name: string }[] = [
{ id: 'light', gradient: 'bg-gradient-to-r from-gray-100 to-gray-200', name: 'Light' }, { id: 'light', gradient: 'bg-gradient-to-r from-gray-100 to-gray-200', name: 'Light' },

View File

@@ -1,7 +1,6 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { categoryService } from '../services'; import { categoryService, CategoryWithRemaining } from '../services';
import type { CategoryWithRemaining } from '../services'; import { CreateCategoryRequest } from '../types';
import type { CreateCategoryRequest } from '../types';
import { showToast } from '../utils/toast'; import { showToast } from '../utils/toast';
import { showErrorToast } from '../utils/errorHandler'; import { showErrorToast } from '../utils/errorHandler';

View File

@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { expenseService } from '../services'; import { expenseService } from '../services';
import type { Expense, CreateExpenseRequest } from '../types'; import { Expense, CreateExpenseRequest } from '../types';
import { showToast } from '../utils/toast'; import { showToast } from '../utils/toast';
import { showErrorToast } from '../utils/errorHandler'; import { showErrorToast } from '../utils/errorHandler';

View File

@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { familyService } from '../services'; import { familyService } from '../services';
import type { FamilyMember } from '../types'; import { FamilyMember } from '../types';
import { showErrorToast } from '../utils/errorHandler'; import { showErrorToast } from '../utils/errorHandler';
export function useFamilyMembers(familyId: number | null) { export function useFamilyMembers(familyId: number | null) {

View File

@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { inviteService } from '../services'; import { inviteService } from '../services';
import type { InviteLinkResponse, CreateInviteLinkRequest } from '../types'; import { InviteLinkResponse, CreateInviteLinkRequest } from '../types';
import { showToast } from '../utils/toast'; import { showToast } from '../utils/toast';
import { showErrorToast } from '../utils/errorHandler'; import { showErrorToast } from '../utils/errorHandler';

View File

@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { shoppingService } from '../services'; import { shoppingService } from '../services';
import type { ShoppingItem, CreateShoppingItemRequest } from '../types'; import { ShoppingItem, CreateShoppingItemRequest } from '../types';
import { showToast } from '../utils/toast'; import { showToast } from '../utils/toast';
import { showErrorToast } from '../utils/errorHandler'; import { showErrorToast } from '../utils/errorHandler';

View File

@@ -5,8 +5,9 @@ import { User as UserIcon } from 'lucide-react';
import { familyApi, authApi } from '../api/client'; import { familyApi, authApi } from '../api/client';
import { useStore } from '../store/useStore'; import { useStore } from '../store/useStore';
import { useFamilyMembers, useConfirm } from '../hooks'; import { useFamilyMembers, useConfirm } from '../hooks';
import type { Theme } from '../types'; import { Theme } from '../types';
import { ProfileHeader } from '../components/profile/ProfileHeader'; import { ProfileHeader } from '../components/profile/ProfileHeader';
import { UserInfo } from '../components/profile/UserInfo';
import { FamilySection } from '../components/profile/FamilySection'; import { FamilySection } from '../components/profile/FamilySection';
import { MembersSection } from '../components/profile/MembersSection'; import { MembersSection } from '../components/profile/MembersSection';
import { SettingsSection } from '../components/profile/SettingsSection'; import { SettingsSection } from '../components/profile/SettingsSection';
@@ -18,7 +19,7 @@ export default function Profile() {
const { t, i18n } = useTranslation(); const { t, i18n } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
const { user, selectedFamily, setSelectedFamily, setUser, preferences, setPreferences } = useStore(); const { user, selectedFamily, setSelectedFamily, setUser, preferences, setPreferences } = useStore();
const { members, loading: membersLoading } = useFamilyMembers(user?.family_id || null); const { members, loading: membersLoading, loadMembers } = useFamilyMembers(user?.family_id || null);
const { confirmState, confirm, cancel } = useConfirm(); const { confirmState, confirm, cancel } = useConfirm();
const [leavingFamily, setLeavingFamily] = useState(false); const [leavingFamily, setLeavingFamily] = useState(false);
@@ -60,6 +61,17 @@ export default function Profile() {
} }
}; };
const handleLogout = async () => {
try {
await authApi.logout();
setUser(null);
setSelectedFamily(null);
navigate('/login');
} catch (error) {
showErrorToast(error);
}
};
const handleThemeChange = (theme: Theme) => { const handleThemeChange = (theme: Theme) => {
setPreferences({ ...preferences, theme }); setPreferences({ ...preferences, theme });
showToast.success(t('profile.themeChanged')); showToast.success(t('profile.themeChanged'));

View File

@@ -1,5 +1,5 @@
import { categoryApi, expenseApi } from '../api/client'; import { categoryApi, expenseApi } from '../api/client';
import type { Category, CreateCategoryRequest } from '../types'; import { Category, CreateCategoryRequest, RemainingLimit } from '../types';
import { handleApiError } from '../utils/errorHandler'; import { handleApiError } from '../utils/errorHandler';
export interface CategoryWithRemaining extends Category { export interface CategoryWithRemaining extends Category {

View File

@@ -1,5 +1,5 @@
import { expenseApi } from '../api/client'; import { expenseApi } from '../api/client';
import type { Expense, CreateExpenseRequest } from '../types'; import { Expense, CreateExpenseRequest } from '../types';
import { handleApiError } from '../utils/errorHandler'; import { handleApiError } from '../utils/errorHandler';
export const expenseService = { export const expenseService = {

View File

@@ -1,5 +1,5 @@
import { familyApi } from '../api/client'; import { familyApi } from '../api/client';
import type { Family, CreateFamilyRequest, CreateMyFamilyRequest, CreateMyFamilyResponse, VerifyFamilyPasswordRequest, FamilyMember } from '../types'; import { Family, CreateFamilyRequest, CreateMyFamilyRequest, CreateMyFamilyResponse, VerifyFamilyPasswordRequest, FamilyMember } from '../types';
import { handleApiError } from '../utils/errorHandler'; import { handleApiError } from '../utils/errorHandler';
export const familyService = { export const familyService = {

View File

@@ -1,5 +1,5 @@
import { inviteLinkApi } from '../api/client'; import { inviteLinkApi } from '../api/client';
import type { CreateInviteLinkRequest, InviteLinkResponse, ValidateInviteResponse, JoinFamilyResponse } from '../types'; import { CreateInviteLinkRequest, InviteLinkResponse, ValidateInviteResponse, JoinFamilyResponse } from '../types';
import { handleApiError } from '../utils/errorHandler'; import { handleApiError } from '../utils/errorHandler';
export const inviteService = { export const inviteService = {

View File

@@ -1,5 +1,5 @@
import { shoppingItemApi } from '../api/client'; import { shoppingItemApi } from '../api/client';
import type { ShoppingItem, CreateShoppingItemRequest, UpdateShoppingItemRequest, MarkAsPurchasedRequest } from '../types'; import { ShoppingItem, CreateShoppingItemRequest, UpdateShoppingItemRequest, MarkAsPurchasedRequest } from '../types';
import { handleApiError } from '../utils/errorHandler'; import { handleApiError } from '../utils/errorHandler';
export const shoppingService = { export const shoppingService = {

View File

@@ -124,7 +124,7 @@ export const useStore = create<AppState>((set, get) => ({
}, },
clearCache: () => { clearCache: () => {
set(() => ({ set((state) => ({
cache: { cache: {
categories: new Map(), categories: new Map(),
members: new Map(), members: new Map(),

View File

@@ -1,5 +1,5 @@
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { AppError } from '../types/errors'; import { ApiError, AppError } from '../types/errors';
import { showToast } from './toast'; import { showToast } from './toast';
export function handleApiError(error: unknown): never { export function handleApiError(error: unknown): never {