All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 16m0s
276 lines
8.6 KiB
Markdown
276 lines
8.6 KiB
Markdown
# ADR-044: Frontend Feature Organization Pattern
|
|
|
|
**Date**: 2026-01-09
|
|
|
|
**Status**: Accepted
|
|
|
|
**Implemented**: 2026-01-09
|
|
|
|
## Context
|
|
|
|
The React frontend has grown to include multiple distinct features:
|
|
|
|
- Flyer viewing and management
|
|
- Shopping list creation
|
|
- Budget tracking and charts
|
|
- Voice assistant
|
|
- User personalization
|
|
- Admin dashboard
|
|
|
|
Without clear organization, code becomes scattered across generic folders (`/components`, `/hooks`, `/utils`), making it hard to:
|
|
|
|
1. Understand feature boundaries
|
|
2. Find related code
|
|
3. Refactor or remove features
|
|
4. Onboard new developers
|
|
|
|
## Decision
|
|
|
|
We will adopt a **feature-based folder structure** where each major feature is self-contained in its own directory under `/features`. Shared code lives in dedicated top-level folders.
|
|
|
|
### Design Principles
|
|
|
|
- **Colocation**: Keep related code together (components, hooks, types, utils).
|
|
- **Feature Independence**: Features should minimize cross-dependencies.
|
|
- **Shared Extraction**: Only extract to shared folders when truly reused.
|
|
- **Flat Within Features**: Avoid deep nesting within feature folders.
|
|
|
|
## Implementation Details
|
|
|
|
### Directory Structure
|
|
|
|
```
|
|
src/
|
|
├── features/ # Feature modules
|
|
│ ├── flyer/ # Flyer viewing/management
|
|
│ │ ├── components/
|
|
│ │ ├── hooks/
|
|
│ │ ├── types.ts
|
|
│ │ └── index.ts
|
|
│ ├── shopping/ # Shopping lists
|
|
│ │ ├── components/
|
|
│ │ ├── hooks/
|
|
│ │ └── index.ts
|
|
│ ├── charts/ # Budget/analytics charts
|
|
│ │ ├── components/
|
|
│ │ └── index.ts
|
|
│ ├── voice-assistant/ # Voice commands
|
|
│ │ ├── components/
|
|
│ │ └── index.ts
|
|
│ └── admin/ # Admin dashboard
|
|
│ ├── components/
|
|
│ └── index.ts
|
|
├── components/ # Shared UI components
|
|
│ ├── ui/ # Primitive components (Button, Input, etc.)
|
|
│ ├── layout/ # Layout components (Header, Footer, etc.)
|
|
│ └── common/ # Shared composite components
|
|
├── hooks/ # Shared hooks
|
|
│ ├── queries/ # TanStack Query hooks
|
|
│ ├── mutations/ # TanStack Mutation hooks
|
|
│ └── utils/ # Utility hooks (useDebounce, etc.)
|
|
├── providers/ # React context providers
|
|
│ ├── AppProviders.tsx
|
|
│ ├── UserDataProvider.tsx
|
|
│ └── FlyersProvider.tsx
|
|
├── pages/ # Route page components
|
|
├── services/ # API clients, external services
|
|
├── types/ # Shared TypeScript types
|
|
├── utils/ # Shared utility functions
|
|
└── lib/ # Third-party library wrappers
|
|
```
|
|
|
|
### Feature Module Structure
|
|
|
|
Each feature follows a consistent internal structure:
|
|
|
|
```
|
|
features/flyer/
|
|
├── components/
|
|
│ ├── FlyerCard.tsx
|
|
│ ├── FlyerGrid.tsx
|
|
│ ├── FlyerUploader.tsx
|
|
│ ├── FlyerItemList.tsx
|
|
│ └── index.ts # Re-exports all components
|
|
├── hooks/
|
|
│ ├── useFlyerDetails.ts
|
|
│ ├── useFlyerUpload.ts
|
|
│ └── index.ts # Re-exports all hooks
|
|
├── types.ts # Feature-specific types
|
|
├── utils.ts # Feature-specific utilities
|
|
└── index.ts # Public API of the feature
|
|
```
|
|
|
|
### Feature Index File
|
|
|
|
Each feature has an `index.ts` that defines its public API:
|
|
|
|
```typescript
|
|
// features/flyer/index.ts
|
|
export { FlyerCard, FlyerGrid, FlyerUploader } from './components';
|
|
export { useFlyerDetails, useFlyerUpload } from './hooks';
|
|
export type { FlyerViewProps, FlyerUploadState } from './types';
|
|
```
|
|
|
|
### Import Patterns
|
|
|
|
```typescript
|
|
// Importing from a feature (preferred)
|
|
import { FlyerCard, useFlyerDetails } from '@/features/flyer';
|
|
|
|
// Importing shared components
|
|
import { Button, Card } from '@/components/ui';
|
|
import { useDebounce } from '@/hooks/utils';
|
|
|
|
// Avoid: reaching into feature internals
|
|
// import { FlyerCard } from '@/features/flyer/components/FlyerCard';
|
|
```
|
|
|
|
### Provider Organization
|
|
|
|
Located in `src/providers/`:
|
|
|
|
```typescript
|
|
// AppProviders.tsx - Composes all providers
|
|
export function AppProviders({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<QueryClientProvider client={queryClient}>
|
|
<AuthProvider>
|
|
<UserDataProvider>
|
|
<FlyersProvider>
|
|
<ThemeProvider>
|
|
{children}
|
|
</ThemeProvider>
|
|
</FlyersProvider>
|
|
</UserDataProvider>
|
|
</AuthProvider>
|
|
</QueryClientProvider>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Query/Mutation Hook Organization
|
|
|
|
Located in `src/hooks/`:
|
|
|
|
```typescript
|
|
// hooks/queries/useFlyersQuery.ts
|
|
export function useFlyersQuery(options?: { storeId?: number }) {
|
|
return useQuery({
|
|
queryKey: ['flyers', options],
|
|
queryFn: () => flyerService.getFlyers(options),
|
|
staleTime: 5 * 60 * 1000,
|
|
});
|
|
}
|
|
|
|
// hooks/mutations/useFlyerUploadMutation.ts
|
|
export function useFlyerUploadMutation() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: flyerService.uploadFlyer,
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['flyers'] });
|
|
},
|
|
});
|
|
}
|
|
```
|
|
|
|
### Page Components
|
|
|
|
Pages are thin wrappers that compose feature components:
|
|
|
|
```typescript
|
|
// pages/Flyers.tsx
|
|
import { FlyerGrid, FlyerUploader } from '@/features/flyer';
|
|
import { PageLayout } from '@/components/layout';
|
|
|
|
export function FliversPage() {
|
|
return (
|
|
<PageLayout title="My Flyers">
|
|
<FlyerUploader />
|
|
<FlyerGrid />
|
|
</PageLayout>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Cross-Feature Communication
|
|
|
|
When features need to communicate, use:
|
|
|
|
1. **Shared State Providers**: For global state (user, theme).
|
|
2. **Query Invalidation**: For data synchronization.
|
|
3. **Event Bus**: For loose coupling (see ADR-036).
|
|
|
|
```typescript
|
|
// Feature A triggers update
|
|
const uploadMutation = useFlyerUploadMutation();
|
|
await uploadMutation.mutateAsync(file);
|
|
// Query invalidation automatically updates Feature B's flyer list
|
|
```
|
|
|
|
## Naming Conventions
|
|
|
|
| Item | Convention | Example |
|
|
| -------------- | -------------------- | -------------------- |
|
|
| Feature folder | kebab-case | `voice-assistant/` |
|
|
| Component file | PascalCase | `FlyerCard.tsx` |
|
|
| Hook file | camelCase with `use` | `useFlyerDetails.ts` |
|
|
| Type file | lowercase | `types.ts` |
|
|
| Utility file | lowercase | `utils.ts` |
|
|
| Index file | lowercase | `index.ts` |
|
|
|
|
## When to Create a New Feature
|
|
|
|
Create a new feature folder when:
|
|
|
|
1. The functionality is distinct and self-contained.
|
|
2. It has its own set of components, hooks, and potentially types.
|
|
3. It could theoretically be extracted into a separate package.
|
|
4. It has minimal dependencies on other features.
|
|
|
|
Do NOT create a feature folder for:
|
|
|
|
- A single reusable component (use `components/`).
|
|
- A single utility function (use `utils/`).
|
|
- A single hook (use `hooks/`).
|
|
|
|
## Consequences
|
|
|
|
### Positive
|
|
|
|
- **Discoverability**: Easy to find all code related to a feature.
|
|
- **Encapsulation**: Features have clear boundaries and public APIs.
|
|
- **Refactoring**: Can modify or remove features with confidence.
|
|
- **Scalability**: Supports team growth with feature ownership.
|
|
- **Testing**: Can test features in isolation.
|
|
|
|
### Negative
|
|
|
|
- **Duplication Risk**: Similar utilities might be duplicated across features.
|
|
- **Decision Overhead**: Must decide when to extract to shared folders.
|
|
- **Import Verbosity**: Feature imports can be longer.
|
|
|
|
### Mitigation
|
|
|
|
- Regular refactoring sessions to extract shared code.
|
|
- Lint rules to prevent importing from feature internals.
|
|
- Code review focus on proper feature boundaries.
|
|
|
|
## Key Directories
|
|
|
|
- `src/features/flyer/` - Flyer viewing and management
|
|
- `src/features/shopping/` - Shopping list functionality
|
|
- `src/features/charts/` - Budget and analytics charts
|
|
- `src/features/voice-assistant/` - Voice command interface
|
|
- `src/features/admin/` - Admin dashboard
|
|
- `src/components/ui/` - Shared primitive components
|
|
- `src/hooks/queries/` - TanStack Query hooks
|
|
- `src/providers/` - React context providers
|
|
|
|
## Related ADRs
|
|
|
|
- [ADR-005](./0005-frontend-state-management-and-server-cache-strategy.md) - State Management
|
|
- [ADR-012](./0012-frontend-component-library-and-design-system.md) - Component Library
|
|
- [ADR-026](./0026-standardized-client-side-structured-logging.md) - Client Logging
|