Frontend Architecture - StockEase
Overview
The StockEase frontend is a modern React 18 application with TypeScript, built with Vite and styled with Tailwind CSS. It provides a responsive, feature-rich user interface for inventory management with multi-language support and dark mode capabilities.
Tech Stack: React 18 β’ TypeScript
5.x β’ Vite 5 β’ Tailwind CSS 3 β’ React Router 6
Deployment: Render
(https://stockease-frontend.onrender.com)
Environment: Node.js 18+
Component Hierarchy
App (Root Component)
β
βββ Layout Components
β βββ Header/Navigation Bar
β β βββ Logo
β βββ Dark Mode Toggle
β βββ Language Switcher
β
βββ Routes (React Router)
β βββ PublicRoutes
β β βββ LoginPage (/login)
β β βββ RegisterPage (/register)
β β βββ NotFoundPage (404)
β β
β βββ ProtectedRoutes (JWT-required)
β βββ ProductDashboard (/dashboard)
β β βββ ProductList
β β β βββ ProductCard (reusable)
β β β βββ ProductFilter
β β β βββ ProductPagination
β β βββ ProductForm (Create/Edit modal)
β β βββ ProductSearch
β β βββ StockValueDisplay
β β
β βββ AdminPanel (/admin) - Admin only
β β βββ UserManagement
β β βββ AuditLog
β β βββ SystemSettings
β β
β βββ ProfilePage (/profile)
β βββ UserInfo
β βββ PreferencesPanel
β
βββ Global Providers
βββ QueryClientProvider (React Query)
βββ i18nextProvider (i18next)
βββ ThemeProvider (Dark Mode)
βββ AuthContext (JWT Management)
Layered Architecture
βββββββββββββββββββββββββββββββββββββββ
β React Components & Pages β β UI Layer
β (Stateless presentational) β
βββββββββββββββββββββββββββββββββββββββ€
β Custom Hooks & Services Layer β β Logic Layer
β (useQuery, useMutation, context) β
βββββββββββββββββββββββββββββββββββββββ€
β API Integration (Axios) β β API Adapter Layer
β (Configured with interceptors) β
βββββββββββββββββββββββββββββββββββββββ€
β Backend REST API (Spring Boot) β β External
β Running at :8081 β
βββββββββββββββββββββββββββββββββββββββ
Project Structure
frontend/
βββ src/
β βββ pages/ # Route-level components
β β βββ LoginPage.tsx
β β βββ RegisterPage.tsx
β β βββ ProductDashboard.tsx
β β βββ AdminPanel.tsx
β β βββ NotFoundPage.tsx
β β
β βββ components/ # Reusable UI components
β β βββ Header/
β β βββ Navigation/
β β βββ ProductCard/
β β βββ ProductForm/
β β βββ ProductFilter/
β β βββ ProductPagination/
β β βββ Button/
β β βββ Modal/
β β βββ FormField/
β β
β βββ api/ # API layer
β β βββ auth.ts # Auth endpoints
β β βββ products.ts # Product endpoints
β β βββ index.ts # Axios config & interceptors
β β βββ types.ts # API response types
β β
β βββ hooks/ # Custom React hooks
β β βββ useAuth.ts # Auth context hook
β β βββ useProducts.ts # Product queries
β β βββ useLocalStorage.ts # Persist preferences
β β
β βββ services/ # Business logic
β β βββ authService.ts
β β βββ productService.ts
β β βββ storageService.ts
β β
β βββ types/ # TypeScript interfaces
β β βββ auth.types.ts
β β βββ product.types.ts
β β βββ api.types.ts
β β
β βββ styles/ # Global styles
β β βββ globals.css
β β βββ variables.css
β β
β βββ locales/ # i18n translations
β β βββ en.json # English
β β βββ de.json # German
β β βββ index.ts
β β
β βββ assets/ # Static assets
β β βββ images/
β β βββ icons/
β β βββ fonts/
β β
β βββ App.tsx # Root component
β βββ main.tsx # Entry point
β βββ i18n.ts # i18n config
β βββ vite-env.d.ts
β
βββ public/ # Static files (favicons, etc)
βββ package.json
βββ vite.config.ts
βββ tailwind.config.js
βββ tsconfig.json
βββ tsconfig.app.json
βββ tsconfig.node.json
βββ eslint.config.js
βββ postcss.config.cjs
βββ Dockerfile
βββ index.html
βββ README.md
State Management Strategy
1. Server State (React Query)
Manages data fetched from the backend:
// useProducts hook
const { data: products, isLoading, error } = useQuery({
queryKey: ['products', page, size],
queryFn: () => api.getProducts(page, size)
});Cached data: Products list, user
info, pagination state
Refetch triggers: Manual refetch,
window focus, interval
2. Client State (React Hooks)
Manages local component state:
const [filterText, setFilterText] = useState('');
const [sortBy, setSortBy] = useState('name');
const [showModal, setShowModal] = useState(false);3. Global State (Context API)
Manages authentication and theme:
// AuthContext
const { user, token, login, logout, isAuthenticated } = useAuth();
// ThemeContext
const { isDark, toggleDarkMode } = useTheme();
// LanguageContext
const { language, setLanguage } = useI18n();4. Persistent State (Local Storage)
Stores user preferences: - JWT token - Dark mode preference - Selected language - Recent filters
Authentication Flow
User Input (Username + Password)
β
[Login Form Component]
β
[API Call: POST /api/auth/login]
β
[Backend validates credentials]
β
[JWT Token + User Info returned]
β
[Store token in Local Storage]
β
[Update AuthContext]
β
[Configure Axios Authorization Header]
β
[Redirect to Dashboard]
β
[Protected Routes Check JWT validity]
Token Management
// Store token after login
localStorage.setItem('token', response.token);
localStorage.setItem('user', JSON.stringify(response.user));
// Include in all API requests
api.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Clear on logout
localStorage.removeItem('token');
localStorage.removeItem('user');API Integration
Axios Configuration
// api/index.ts
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL
|| 'http://localhost:8081/api';
const apiClient = axios.create({
baseURL: API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
});
// Request interceptor - add JWT token
apiClient.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Response interceptor - handle errors globally
apiClient.interceptors.response.use(
response => response,
error => {
if (error.response?.status === 401) {
// Token expired - redirect to login
window.location.href = '/login';
}
return Promise.reject(error);
}
);API Service Modules
auth.ts
export const authAPI = {
register: (username: string, password: string) =>
apiClient.post('/auth/register', { username, password }),
login: (username: string, password: string) =>
apiClient.post('/auth/login', { username, password }),
logout: () => {
localStorage.removeItem('token');
localStorage.removeItem('user');
}
};products.ts
export const productsAPI = {
getAll: (page?: number, size?: number) =>
apiClient.get('/products', { params: { page, size } }),
getById: (id: number) =>
apiClient.get(`/products/${id}`),
create: (product: CreateProductDTO) =>
apiClient.post('/products', product),
update: (id: number, product: UpdateProductDTO) =>
apiClient.put(`/products/${id}`, product),
delete: (id: number) =>
apiClient.delete(`/products/${id}`)
};Multi-Language Support (i18next)
Configuration
// i18n.ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import enMessages from './locales/en.json';
import deMessages from './locales/de.json';
i18n.use(initReactI18next).init({
resources: {
en: { translation: enMessages },
de: { translation: deMessages }
},
lng: localStorage.getItem('language') || 'en',
fallbackLng: 'en',
interpolation: { escapeValue: false }
});Usage in Components
import { useTranslation } from 'react-i18next';
export const ProductCard = ({ product }) => {
const { t, i18n } = useTranslation();
return (
<div>
<h3>{product.name}</h3>
<p>{t('product.quantity')}: {product.quantity}</p>
<p>{t('product.price')}: ${product.price}</p>
<button onClick={() => i18n.changeLanguage('de')}>
{t('switch-language')}
</button>
</div>
);
};Supported Languages
- English (en) - Default
- German (de) - Full translation
Adding New Translations
- Add key-value pairs to
locales/en.jsonandlocales/de.json - Use
i18n.t('key.path')in components - Test both languages
Dark Mode Support
Implementation
// hooks/useTheme.ts
export const useTheme = () => {
const [isDark, setIsDark] = useState(
localStorage.getItem('theme') === 'dark'
);
useEffect(() => {
if (isDark) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
localStorage.setItem('theme', isDark ? 'dark' : 'light');
}, [isDark]);
return {
isDark,
toggleDarkMode: () => setIsDark(!isDark)
};
};Usage in Components
// Components automatically respond to dark: prefix in Tailwind
<div className="bg-white dark:bg-slate-900 text-black dark:text-white">
{/* Content adapts to theme */}
</div>CSS Variables
/* styles/variables.css */
:root {
--color-primary: #3b82f6;
--color-secondary: #ef4444;
--background: #ffffff;
--foreground: #000000;
}
.dark {
--background: #1e293b;
--foreground: #f1f5f9;
}Responsive Design
Tailwind Breakpoints
sm: 640pxmd: 768pxlg: 1024pxxl: 1280px2xl: 1536px
Mobile-First Approach
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
Component Responsive Patterns
- Navigation: Hamburger menu on mobile, full navbar on desktop
- Tables: Cards on mobile, table on desktop
- Forms: Single column on mobile, multi-column on desktop
- Images: Scaled proportionally with max-width constraints
Form Management
React Hook Form + TypeScript
import { useForm } from 'react-hook-form';
interface ProductFormData {
name: string;
quantity: number;
price: number;
}
export const ProductForm = () => {
const { register, handleSubmit, formState: { errors } } = useForm<ProductFormData>();
const onSubmit = async (data: ProductFormData) => {
await productsAPI.create(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('name', { required: true })} />
{errors.name && <span>Name is required</span>}
<input {...register('quantity', { min: 1 })} type="number" />
{errors.quantity && <span>Must be positive</span>}
<button type="submit">Create Product</button>
</form>
);
};Error Handling
Global Error Handler
// services/errorService.ts
export const handleApiError = (error: AxiosError) => {
if (error.response?.status === 401) {
return 'Session expired. Please login again.';
}
if (error.response?.status === 403) {
return 'You do not have permission for this action.';
}
if (error.response?.status === 404) {
return 'Resource not found.';
}
return error.message || 'An error occurred.';
};Component Error Boundary
interface ErrorBoundaryState {
hasError: boolean;
}
export class ErrorBoundary extends React.Component<any, ErrorBoundaryState> {
componentDidCatch(error: Error) {
this.setState({ hasError: true });
console.error('Error caught:', error);
}
render() {
if (this.state.hasError) {
return <div>Something went wrong. Please refresh.</div>;
}
return this.props.children;
}
}Performance Optimization
1. Code Splitting
Vite automatically splits code by route:
const ProductDashboard = lazy(() => import('./pages/ProductDashboard'));
const AdminPanel = lazy(() => import('./pages/AdminPanel'));
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/dashboard" element={<ProductDashboard />} />
<Route path="/admin" element={<AdminPanel />} />
</Routes>
</Suspense>2. React Query Caching
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
}
}
});3. Memoization
export const ProductCard = memo(({ product }: Props) => {
return <div>{product.name}</div>;
}, (prevProps, nextProps) =>
prevProps.product.id === nextProps.product.id
);4. Image Optimization
- Use optimized formats (WebP with fallbacks)
- Lazy load images
- Responsive images with srcset
5. Bundle Analysis
npm run build -- --analyze # If analyzer plugin installedDeployment
Environment Configuration
# .env.production
VITE_API_BASE_URL=https://stockease-backend-production.koyeb.app/api
VITE_APP_VERSION=1.0.0Build Process
npm install # Install dependencies
npm run build # Build production bundle β dist/
npm run preview # Preview production build locallyDeployment to Render
- Connect GitHub repository to Render
- Set build command:
npm run build - Set publish directory:
dist - Add environment variables:
VITE_API_BASE_URL - Deploy on push to main branch
Performance Metrics
- Bundle Size: ~150-200 KB (gzipped)
- Build Time: 30-60 seconds
- Lighthouse Score: 90+
- Time to Interactive: <2 seconds
Testing
Unit Tests (Vitest)
// ProductCard.test.tsx
import { render, screen } from '@testing-library/react';
import { ProductCard } from './ProductCard';
describe('ProductCard', () => {
it('renders product name', () => {
const product = { id: 1, name: 'Test', price: 100, quantity: 5 };
render(<ProductCard product={product} />);
expect(screen.getByText('Test')).toBeInTheDocument();
});
});Integration Tests
- Test API calls with mock server
- Test authentication flow
- Test form submission
E2E Tests
- Consider Playwright or Cypress for future
- Test complete user workflows
Browser Support
- Chrome/Edge: Latest 2 versions
- Firefox: Latest 2 versions
- Safari: Latest 2 versions
- Mobile browsers: Latest versions
Accessibility (a11y)
- Semantic HTML structure
- ARIA labels on form inputs
- Keyboard navigation support
- Color contrast ratios meet WCAG AA
- Focus indicators on interactive elements
Related Documentation
Main Architecture Topics
- Architecture Overview - Backend system context and decisions
- Backend Architecture - Backend APIs that frontend consumes
- Service Layers - Backend layer architecture
- Security Architecture - JWT token handling and authentication flow
- Deployment Architecture - Frontend deployment to Render and backend integration
Architecture Decisions (ADRs)
- Database Choice - Backend database (PostgreSQL)
- Validation Strategy - Server-side and client-side validation
Design Patterns & Practices
- Security Patterns - JWT token management and secure APIs
- Repository Pattern - Backend data access patterns
Infrastructure & Deployment
- CI/CD Pipeline - Frontend build and deployment automation
- Staging Configuration - Frontend staging environment
Frontend Documentation Version:
1.0
Last Updated: October 31, 2025
Status: Production Ready