Yassine Mhirsi
commited on
Commit
·
cbf89ee
1
Parent(s):
3d7ddc5
init
Browse files- .cursor/rules/rules.mdc +124 -0
- .env.example +15 -0
- .gitignore +68 -2
- LLM_GUIDE.md +172 -0
- README.md +22 -72
- package-lock.json +0 -0
- package.json +6 -1
- postcss.config.js +7 -0
- src/App.css +0 -38
- src/App.js +0 -25
- src/App.test.js +3 -5
- src/app/App.tsx +13 -0
- src/app/components/common/ErrorBoundary.tsx +60 -0
- src/app/components/common/Loading.tsx +31 -0
- src/app/constants/index.ts +21 -0
- src/app/hooks/index.ts +7 -0
- src/app/hooks/useApi.ts +64 -0
- src/app/layouts/MainLayout.tsx +14 -0
- src/app/services/api-wrapper.ts +73 -0
- src/app/types/api.types.ts +20 -0
- src/app/types/index.ts +7 -0
- src/app/utils/index.ts +51 -0
- src/index.css +15 -9
- src/index.js +5 -2
- tailwind.config.js +9 -0
.cursor/rules/rules.mdc
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
alwaysApply: true
|
| 3 |
+
---
|
| 4 |
+
|
| 5 |
+
# NLP IBM Debater - Cursor Rules
|
| 6 |
+
|
| 7 |
+
This is a React + TypeScript project using Create React App, Tailwind CSS, and a modular feature-first architecture.
|
| 8 |
+
|
| 9 |
+
## File Extensions & TypeScript
|
| 10 |
+
|
| 11 |
+
- **`.tsx`** - Use for React components (components, pages, layouts)
|
| 12 |
+
- **`.ts`** - Use for non-JSX files (hooks, utils, types, services)
|
| 13 |
+
- **`.js`** - Only for config files (index.js, config files)
|
| 14 |
+
- **Always type everything**: props, functions, API responses, hook returns
|
| 15 |
+
|
| 16 |
+
## Project Structure
|
| 17 |
+
|
| 18 |
+
```
|
| 19 |
+
src/app/
|
| 20 |
+
├── App.tsx # Entry composition
|
| 21 |
+
├── layouts/ # Layout components (.tsx)
|
| 22 |
+
├── pages/ # Page components (.tsx)
|
| 23 |
+
├── components/ # Reusable UI (.tsx)
|
| 24 |
+
│ └── common/ # Common components (Loading, ErrorBoundary)
|
| 25 |
+
├── hooks/ # Custom hooks (.ts)
|
| 26 |
+
├── services/ # API services (.ts)
|
| 27 |
+
├── types/ # TypeScript types (.ts)
|
| 28 |
+
├── utils/ # Utility functions (.ts)
|
| 29 |
+
└── constants/ # App constants (.ts)
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
## Code Patterns
|
| 33 |
+
|
| 34 |
+
### Components
|
| 35 |
+
- Use functional components with `React.FC<PropsType>`
|
| 36 |
+
- Always type props with interfaces or types
|
| 37 |
+
- Use Tailwind CSS for styling (avoid custom CSS files)
|
| 38 |
+
- Keep components presentational - pass data via props
|
| 39 |
+
|
| 40 |
+
```typescript
|
| 41 |
+
type MyComponentProps = {
|
| 42 |
+
title: string;
|
| 43 |
+
count?: number;
|
| 44 |
+
};
|
| 45 |
+
|
| 46 |
+
const MyComponent: React.FC<MyComponentProps> = ({ title, count = 0 }) => {
|
| 47 |
+
return <div>{title}: {count}</div>;
|
| 48 |
+
};
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
### API Calls
|
| 52 |
+
- Use `api-wrapper.ts` for direct API calls
|
| 53 |
+
- Use `useApi` hook for components needing loading/error states
|
| 54 |
+
- Always type API responses
|
| 55 |
+
|
| 56 |
+
```typescript
|
| 57 |
+
// Direct API call
|
| 58 |
+
import api from '../services/api-wrapper';
|
| 59 |
+
const data = await api.get<User[]>('/api/users');
|
| 60 |
+
|
| 61 |
+
// With hook
|
| 62 |
+
import { useApi } from '../hooks/useApi';
|
| 63 |
+
const { data, loading, error } = useApi<User[]>('/api/users');
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
### Types
|
| 67 |
+
- Add types to `src/app/types/` and export from `index.ts`
|
| 68 |
+
- Use descriptive type names
|
| 69 |
+
- Export types for reuse
|
| 70 |
+
|
| 71 |
+
```typescript
|
| 72 |
+
// src/app/types/debater.types.ts
|
| 73 |
+
export type Debate = {
|
| 74 |
+
id: number;
|
| 75 |
+
topic: string;
|
| 76 |
+
arguments: Argument[];
|
| 77 |
+
};
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
### Constants
|
| 81 |
+
- Add constants to `src/app/constants/index.ts`
|
| 82 |
+
- Use `API_ENDPOINTS` for API paths
|
| 83 |
+
- Use `APP_CONFIG` for app configuration
|
| 84 |
+
- Use `UI` for UI-related constants
|
| 85 |
+
|
| 86 |
+
### Utils
|
| 87 |
+
- Add utility functions to `src/app/utils/index.ts`
|
| 88 |
+
- Keep functions pure and typed
|
| 89 |
+
- Export for reuse
|
| 90 |
+
|
| 91 |
+
## Conventions
|
| 92 |
+
|
| 93 |
+
1. **Imports**: Use relative paths from `src/`
|
| 94 |
+
2. **Styling**: Tailwind-first, avoid custom CSS
|
| 95 |
+
3. **Error Handling**: Use ErrorBoundary for React errors, try/catch for API errors
|
| 96 |
+
4. **Loading States**: Use `Loading` component from `components/common/Loading`
|
| 97 |
+
5. **Environment Variables**: All must start with `REACT_APP_` prefix
|
| 98 |
+
6. **Testing**: Use React Testing Library, co-locate tests
|
| 99 |
+
|
| 100 |
+
## What NOT to Do
|
| 101 |
+
|
| 102 |
+
- ❌ Don't use class components (except ErrorBoundary)
|
| 103 |
+
- ❌ Don't use `.jsx` - use `.tsx` for components
|
| 104 |
+
- ❌ Don't use raw `fetch` - use `api-wrapper.ts`
|
| 105 |
+
- ❌ Don't create custom CSS files - use Tailwind
|
| 106 |
+
- ❌ Don't commit `.env*` files (except `.env.example`)
|
| 107 |
+
- ❌ Don't skip TypeScript types
|
| 108 |
+
|
| 109 |
+
## When Adding New Features
|
| 110 |
+
|
| 111 |
+
1. **New Page**: Create in `src/app/pages/` (`.tsx`), wire via `App.tsx`
|
| 112 |
+
2. **New Component**: Create in `src/app/components/` (`.tsx`), type props
|
| 113 |
+
3. **New API Endpoint**: Add to `API_ENDPOINTS` in constants, use `api-wrapper.ts`
|
| 114 |
+
4. **New Type**: Add to `src/app/types/`, export from `index.ts`
|
| 115 |
+
5. **New Hook**: Create in `src/app/hooks/` (`.ts`), type return values
|
| 116 |
+
6. **New Utility**: Add to `src/app/utils/index.ts`, keep pure and typed
|
| 117 |
+
|
| 118 |
+
## Quality Checks
|
| 119 |
+
|
| 120 |
+
- Run `npm run build` to verify TypeScript compilation
|
| 121 |
+
- Run `npm test` before committing
|
| 122 |
+
- Ensure all props are typed
|
| 123 |
+
- Ensure all API calls use typed responses
|
| 124 |
+
- Follow existing code patterns and structure
|
.env.example
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Environment Variables Template
|
| 2 |
+
#
|
| 3 |
+
# Copy this file to create your local environment files:
|
| 4 |
+
# - For development: cp .env.example .env.development.local
|
| 5 |
+
# - For production: cp .env.example .env.production.local
|
| 6 |
+
#
|
| 7 |
+
# All .env files are gitignored for security.
|
| 8 |
+
# Update this template when adding new required environment variables.
|
| 9 |
+
|
| 10 |
+
# API Configuration
|
| 11 |
+
REACT_APP_API_BASE_URL=http://localhost:4000
|
| 12 |
+
|
| 13 |
+
# Add other environment variables below (must start with REACT_APP_)
|
| 14 |
+
# REACT_APP_ANOTHER_VAR=value
|
| 15 |
+
|
.gitignore
CHANGED
|
@@ -11,13 +11,79 @@
|
|
| 11 |
# production
|
| 12 |
/build
|
| 13 |
|
| 14 |
-
#
|
| 15 |
-
.
|
| 16 |
.env.local
|
|
|
|
| 17 |
.env.development.local
|
|
|
|
| 18 |
.env.test.local
|
|
|
|
| 19 |
.env.production.local
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
npm-debug.log*
|
| 22 |
yarn-debug.log*
|
| 23 |
yarn-error.log*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
# production
|
| 12 |
/build
|
| 13 |
|
| 14 |
+
# environment variables (all .env files ignored - use .env.example as template)
|
| 15 |
+
.env
|
| 16 |
.env.local
|
| 17 |
+
.env.development
|
| 18 |
.env.development.local
|
| 19 |
+
.env.test
|
| 20 |
.env.test.local
|
| 21 |
+
.env.production
|
| 22 |
.env.production.local
|
| 23 |
|
| 24 |
+
# misc
|
| 25 |
+
.DS_Store
|
| 26 |
+
.DS_Store?
|
| 27 |
+
._*
|
| 28 |
+
.Spotlight-V100
|
| 29 |
+
.Trashes
|
| 30 |
+
ehthumbs.db
|
| 31 |
+
Thumbs.db
|
| 32 |
+
|
| 33 |
+
# IDE / Editor
|
| 34 |
+
.vscode/
|
| 35 |
+
.idea/
|
| 36 |
+
*.swp
|
| 37 |
+
*.swo
|
| 38 |
+
*~
|
| 39 |
+
.project
|
| 40 |
+
.classpath
|
| 41 |
+
.settings/
|
| 42 |
+
*.sublime-project
|
| 43 |
+
*.sublime-workspace
|
| 44 |
+
|
| 45 |
+
# Logs
|
| 46 |
+
logs
|
| 47 |
+
*.log
|
| 48 |
npm-debug.log*
|
| 49 |
yarn-debug.log*
|
| 50 |
yarn-error.log*
|
| 51 |
+
pnpm-debug.log*
|
| 52 |
+
lerna-debug.log*
|
| 53 |
+
|
| 54 |
+
# Runtime data
|
| 55 |
+
pids
|
| 56 |
+
*.pid
|
| 57 |
+
*.seed
|
| 58 |
+
*.pid.lock
|
| 59 |
+
|
| 60 |
+
# Optional npm cache directory
|
| 61 |
+
.npm
|
| 62 |
+
|
| 63 |
+
# Optional eslint cache
|
| 64 |
+
.eslintcache
|
| 65 |
+
|
| 66 |
+
# Optional REPL history
|
| 67 |
+
.node_repl_history
|
| 68 |
+
|
| 69 |
+
# Output of 'npm pack'
|
| 70 |
+
*.tgz
|
| 71 |
+
|
| 72 |
+
# Yarn Integrity file
|
| 73 |
+
.yarn-integrity
|
| 74 |
+
|
| 75 |
+
# parcel-bundler cache
|
| 76 |
+
.cache
|
| 77 |
+
.parcel-cache
|
| 78 |
+
|
| 79 |
+
# Next.js (if ever migrated)
|
| 80 |
+
.next
|
| 81 |
+
out
|
| 82 |
+
|
| 83 |
+
# Nuxt.js (if ever migrated)
|
| 84 |
+
.nuxt
|
| 85 |
+
dist
|
| 86 |
+
|
| 87 |
+
# Temporary folders
|
| 88 |
+
tmp/
|
| 89 |
+
temp/
|
LLM_GUIDE.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# LLM Guide: NLP-IBM-Debater Workspace
|
| 2 |
+
|
| 3 |
+
This repository is a React + TypeScript (Create React App) workspace prepared with Tailwind CSS and a modular app layout. Use this guide as the single source of truth when making automated or AI-assisted changes.
|
| 4 |
+
|
| 5 |
+
## Project map
|
| 6 |
+
|
| 7 |
+
- `src/app/App.tsx` — entry composition (TypeScript).
|
| 8 |
+
- `src/app/layouts/MainLayout.tsx` — basic layout wrapper (TypeScript).
|
| 9 |
+
- `src/app/pages/` — page components (use `.tsx` extension).
|
| 10 |
+
- `src/app/components/` — reusable UI pieces (common/navigation, use `.tsx` extension).
|
| 11 |
+
- `common/ErrorBoundary.tsx` — error boundary component (class component, required by React).
|
| 12 |
+
- `common/Loading.tsx` — loading spinner component.
|
| 13 |
+
- `src/app/services/api-wrapper.ts` — centralized API client with fetch wrapper (GET/POST/PUT/PATCH/DELETE).
|
| 14 |
+
- `src/app/hooks/` — custom React hooks (use `.ts` extension).
|
| 15 |
+
- `useApi.ts` — hook for API calls with loading/error states.
|
| 16 |
+
- `src/app/types/` — TypeScript type definitions (use `.ts` extension).
|
| 17 |
+
- `api.types.ts` — API-related types.
|
| 18 |
+
- `index.ts` — central export point for all types.
|
| 19 |
+
- `src/app/utils/` — utility functions (use `.ts` extension, debounce, formatError, etc.).
|
| 20 |
+
- `src/app/constants/` — app-wide constants (use `.ts` extension, API endpoints, config).
|
| 21 |
+
- `src/index.js` — CRA entry point mounting `App` with ErrorBoundary.
|
| 22 |
+
- `tailwind.config.js` & `postcss.config.js` — Tailwind setup.
|
| 23 |
+
- `.env.example` — environment variables template (all `.env*` files are gitignored).
|
| 24 |
+
|
| 25 |
+
## Conventions
|
| 26 |
+
|
| 27 |
+
- **File Extensions**:
|
| 28 |
+
- Use `.tsx` for React components (components, pages, layouts)
|
| 29 |
+
- Use `.ts` for non-JSX TypeScript files (hooks, utils, types, services)
|
| 30 |
+
- Use `.js` only for config files (index.js, config files)
|
| 31 |
+
- **TypeScript**: This project uses TypeScript for type safety. Always type your props, functions, and API responses.
|
| 32 |
+
- Prefer feature-first folders under `src/app` (`components`, `pages`, `layouts`, `data`, `hooks`, `services` as needed).
|
| 33 |
+
- Styling is Tailwind-first; avoid ad-hoc CSS files unless adding global tokens.
|
| 34 |
+
- Keep components presentational and pass data via props; co-locate tests next to features or under `__tests__`.
|
| 35 |
+
- Use functional components and hooks; avoid class components (except ErrorBoundary which requires a class).
|
| 36 |
+
- Keep imports relative to `src/` (CRA default) and prefer absolute paths only if `NODE_PATH`/aliases are introduced.
|
| 37 |
+
|
| 38 |
+
## Commands
|
| 39 |
+
|
| 40 |
+
- Install deps: `npm install`
|
| 41 |
+
- Start dev server: `npm start` (loads `.env.development`)
|
| 42 |
+
- Build for prod: `npm run build` (loads `.env.production`)
|
| 43 |
+
- Run tests: `npm test`
|
| 44 |
+
|
| 45 |
+
## Environment Variables
|
| 46 |
+
|
| 47 |
+
- **`.env.example`** — Template showing required variables (committed to git)
|
| 48 |
+
- **All `.env*` files** — Gitignored for security (create from `.env.example`)
|
| 49 |
+
|
| 50 |
+
**Important**: All environment variables exposed to the browser must start with `REACT_APP_` prefix.
|
| 51 |
+
|
| 52 |
+
**Setup**:
|
| 53 |
+
1. Copy `.env.example` to create your local env files:
|
| 54 |
+
- Development: `cp .env.example .env.development.local`
|
| 55 |
+
- Production: `cp .env.example .env.production.local`
|
| 56 |
+
2. Update the values in your local `.env.*.local` files
|
| 57 |
+
|
| 58 |
+
Current variables:
|
| 59 |
+
- `REACT_APP_API_BASE_URL` — API base URL (defaults to `http://localhost:4000` if not set)
|
| 60 |
+
|
| 61 |
+
To add new variables:
|
| 62 |
+
1. Add to `.env.example` as documentation
|
| 63 |
+
2. Update your local `.env.*.local` files
|
| 64 |
+
3. Access via `process.env.REACT_APP_YOUR_VAR`
|
| 65 |
+
|
| 66 |
+
## Patterns to follow
|
| 67 |
+
|
| 68 |
+
- Add new UI to `src/app/pages` (use `.tsx` extension) and wire via `App.tsx`.
|
| 69 |
+
- Reuse `MainLayout` for consistent shell elements.
|
| 70 |
+
- Keep static strings in `src/app/data` for reuse/testing.
|
| 71 |
+
- **API calls**:
|
| 72 |
+
- Use `src/app/services/api-wrapper.ts` for direct API calls: `api.get()`, `api.post()`, etc.
|
| 73 |
+
- Use `useApi` hook from `src/app/hooks/useApi.ts` for components that need loading/error states.
|
| 74 |
+
- **Types**: Add new TypeScript types to `src/app/types/` (use `.ts` extension) and export from `index.ts`.
|
| 75 |
+
- **Constants**: Add app-wide constants to `src/app/constants/index.ts`.
|
| 76 |
+
- **Utils**: Add reusable utility functions to `src/app/utils/index.ts`.
|
| 77 |
+
- **Component Props**: Always type your component props using TypeScript interfaces or types.
|
| 78 |
+
- When adding new services, create `src/app/services/*` (use `.ts` extension) and keep fetchers pure; inject into components via hooks or props.
|
| 79 |
+
|
| 80 |
+
## Quality and safety
|
| 81 |
+
|
| 82 |
+
- Use React Testing Library for behavior-first tests.
|
| 83 |
+
- Avoid editing generated configs unless necessary; prefer small, reviewable diffs.
|
| 84 |
+
- Document non-obvious decisions inline with concise comments.
|
| 85 |
+
|
| 86 |
+
## API Service Usage
|
| 87 |
+
|
| 88 |
+
### Direct API calls
|
| 89 |
+
|
| 90 |
+
The `api-wrapper.ts` service provides a typed, centralized way to make HTTP requests:
|
| 91 |
+
|
| 92 |
+
```typescript
|
| 93 |
+
import api from '../services/api-wrapper';
|
| 94 |
+
|
| 95 |
+
// GET request
|
| 96 |
+
const data = await api.get<User[]>('/users');
|
| 97 |
+
|
| 98 |
+
// POST request
|
| 99 |
+
const newUser = await api.post<User, CreateUserDto>('/users', { name: 'John' });
|
| 100 |
+
|
| 101 |
+
// With error handling
|
| 102 |
+
try {
|
| 103 |
+
const result = await api.get('/endpoint');
|
| 104 |
+
} catch (error) {
|
| 105 |
+
// error is typed as ApiError with status, message, details
|
| 106 |
+
console.error(error.status, error.message);
|
| 107 |
+
}
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
### Using the useApi hook
|
| 111 |
+
|
| 112 |
+
For components that need loading and error states:
|
| 113 |
+
|
| 114 |
+
```typescript
|
| 115 |
+
import { useApi } from '../hooks/useApi';
|
| 116 |
+
import Loading from '../components/common/Loading';
|
| 117 |
+
import type { User } from '../types';
|
| 118 |
+
|
| 119 |
+
const MyComponent: React.FC = () => {
|
| 120 |
+
const { data, loading, error, refetch } = useApi<User[]>('/api/users');
|
| 121 |
+
|
| 122 |
+
if (loading) return <Loading />;
|
| 123 |
+
if (error) return <div>Error: {error.message}</div>;
|
| 124 |
+
|
| 125 |
+
return <div>{/* render data */}</div>;
|
| 126 |
+
};
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
The service automatically:
|
| 130 |
+
- Uses `REACT_APP_API_BASE_URL` from environment variables
|
| 131 |
+
- Sets JSON content-type headers
|
| 132 |
+
- Handles JSON parsing
|
| 133 |
+
- Throws typed errors for non-OK responses
|
| 134 |
+
- Supports AbortSignal for request cancellation
|
| 135 |
+
|
| 136 |
+
## TypeScript Guidelines
|
| 137 |
+
|
| 138 |
+
- **Components**: Always type props using interfaces or types
|
| 139 |
+
```typescript
|
| 140 |
+
type MyComponentProps = {
|
| 141 |
+
title: string;
|
| 142 |
+
count?: number;
|
| 143 |
+
};
|
| 144 |
+
|
| 145 |
+
const MyComponent: React.FC<MyComponentProps> = ({ title, count = 0 }) => {
|
| 146 |
+
// ...
|
| 147 |
+
};
|
| 148 |
+
```
|
| 149 |
+
|
| 150 |
+
- **API Responses**: Type your API responses
|
| 151 |
+
```typescript
|
| 152 |
+
type User = {
|
| 153 |
+
id: number;
|
| 154 |
+
name: string;
|
| 155 |
+
};
|
| 156 |
+
|
| 157 |
+
const users = await api.get<User[]>('/api/users');
|
| 158 |
+
```
|
| 159 |
+
|
| 160 |
+
- **Hooks**: Type hook return values and parameters
|
| 161 |
+
```typescript
|
| 162 |
+
const { data, loading } = useApi<Debate[]>('/api/debates');
|
| 163 |
+
```
|
| 164 |
+
|
| 165 |
+
## How to help
|
| 166 |
+
|
| 167 |
+
- Before large refactors, note intent and file targets in the PR description.
|
| 168 |
+
- After code edits, run tests or `npm run build` to verify TypeScript compilation.
|
| 169 |
+
- When adding API endpoints, use the `api-wrapper.ts` service instead of raw `fetch`.
|
| 170 |
+
- Always use TypeScript types for props, API responses, and function parameters.
|
| 171 |
+
- Keep this guide updated when changing structure, commands, or adding new services.
|
| 172 |
+
|
README.md
CHANGED
|
@@ -1,82 +1,32 @@
|
|
| 1 |
-
|
| 2 |
-
title: NLP IBM Debater
|
| 3 |
-
emoji: 🐠
|
| 4 |
-
colorFrom: indigo
|
| 5 |
-
colorTo: red
|
| 6 |
-
sdk: static
|
| 7 |
-
pinned: false
|
| 8 |
-
app_build_command: npm run build
|
| 9 |
-
app_file: build/index.html
|
| 10 |
-
license: mit
|
| 11 |
-
---
|
| 12 |
|
| 13 |
-
|
| 14 |
|
| 15 |
-
|
| 16 |
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
-
|
| 20 |
|
| 21 |
-
|
|
|
|
|
|
|
| 22 |
|
| 23 |
-
|
| 24 |
-
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
|
| 25 |
|
| 26 |
-
|
| 27 |
-
You may also see any lint errors in the console.
|
| 28 |
|
| 29 |
-
|
| 30 |
|
| 31 |
-
|
| 32 |
-
|
|
|
|
|
|
|
| 33 |
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
Builds the app for production to the `build` folder.\
|
| 37 |
-
It correctly bundles React in production mode and optimizes the build for the best performance.
|
| 38 |
-
|
| 39 |
-
The build is minified and the filenames include the hashes.\
|
| 40 |
-
Your app is ready to be deployed!
|
| 41 |
-
|
| 42 |
-
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
| 43 |
-
|
| 44 |
-
### `npm run eject`
|
| 45 |
-
|
| 46 |
-
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
|
| 47 |
-
|
| 48 |
-
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
| 49 |
-
|
| 50 |
-
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
|
| 51 |
-
|
| 52 |
-
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
|
| 53 |
-
|
| 54 |
-
## Learn More
|
| 55 |
-
|
| 56 |
-
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
| 57 |
-
|
| 58 |
-
To learn React, check out the [React documentation](https://reactjs.org/).
|
| 59 |
-
|
| 60 |
-
### Code Splitting
|
| 61 |
-
|
| 62 |
-
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
|
| 63 |
-
|
| 64 |
-
### Analyzing the Bundle Size
|
| 65 |
-
|
| 66 |
-
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
|
| 67 |
-
|
| 68 |
-
### Making a Progressive Web App
|
| 69 |
-
|
| 70 |
-
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
|
| 71 |
-
|
| 72 |
-
### Advanced Configuration
|
| 73 |
-
|
| 74 |
-
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
|
| 75 |
-
|
| 76 |
-
### Deployment
|
| 77 |
-
|
| 78 |
-
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
|
| 79 |
-
|
| 80 |
-
### `npm run build` fails to minify
|
| 81 |
-
|
| 82 |
-
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
|
|
|
|
| 1 |
+
# NLP IBM Debater — React + Tailwind Workspace
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
+
An opinionated Create React App setup designed for fast iteration on NLP experiences. Tailwind CSS is wired in, and the app structure is organized for feature-first development.
|
| 4 |
|
| 5 |
+
## Project structure
|
| 6 |
|
| 7 |
+
- `src/app/App.jsx` — root composition.
|
| 8 |
+
- `src/app/layouts/MainLayout.jsx` — shared shell with navigation and footer.
|
| 9 |
+
- `src/app/pages/HomePage.jsx` — starter page showing layout and cards.
|
| 10 |
+
- `src/app/components/` — reusable UI (navigation/common).
|
| 11 |
+
- `src/app/data/` — UI copy and data stubs.
|
| 12 |
+
- `LLM_GUIDE.md` — instructions for AI collaborators.
|
| 13 |
+
- `tailwind.config.js` and `postcss.config.js` — Tailwind setup.
|
| 14 |
|
| 15 |
+
## Scripts
|
| 16 |
|
| 17 |
+
- `npm start` — run the dev server.
|
| 18 |
+
- `npm test` — run tests in watch mode.
|
| 19 |
+
- `npm run build` — production build.
|
| 20 |
|
| 21 |
+
## Tailwind usage
|
|
|
|
| 22 |
|
| 23 |
+
Tailwind is configured via `tailwind.config.js`. Global styles live in `src/index.css` with Tailwind directives. Use utility classes for new UI and extend the config when adding design tokens.
|
|
|
|
| 24 |
|
| 25 |
+
## Adding features
|
| 26 |
|
| 27 |
+
1. Create a page in `src/app/pages` and wire it through `App.jsx`.
|
| 28 |
+
2. Add shared UI to `src/app/components`.
|
| 29 |
+
3. Store static copy or mock data in `src/app/data`.
|
| 30 |
+
4. Write behavioral tests with React Testing Library.
|
| 31 |
|
| 32 |
+
For more collaboration guidance, see `LLM_GUIDE.md`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
package-lock.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
{
|
| 2 |
-
"name": "
|
| 3 |
"version": "0.1.0",
|
| 4 |
"private": true,
|
| 5 |
"dependencies": {
|
|
@@ -12,6 +12,11 @@
|
|
| 12 |
"react-scripts": "5.0.1",
|
| 13 |
"web-vitals": "^2.1.4"
|
| 14 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
"scripts": {
|
| 16 |
"start": "react-scripts start",
|
| 17 |
"build": "react-scripts build",
|
|
|
|
| 1 |
{
|
| 2 |
+
"name": "nlp-ibm-debater",
|
| 3 |
"version": "0.1.0",
|
| 4 |
"private": true,
|
| 5 |
"dependencies": {
|
|
|
|
| 12 |
"react-scripts": "5.0.1",
|
| 13 |
"web-vitals": "^2.1.4"
|
| 14 |
},
|
| 15 |
+
"devDependencies": {
|
| 16 |
+
"autoprefixer": "^10.4.22",
|
| 17 |
+
"postcss": "^8.5.6",
|
| 18 |
+
"tailwindcss": "^3.4.18"
|
| 19 |
+
},
|
| 20 |
"scripts": {
|
| 21 |
"start": "react-scripts start",
|
| 22 |
"build": "react-scripts build",
|
postcss.config.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
module.exports = {
|
| 2 |
+
plugins: {
|
| 3 |
+
tailwindcss: {},
|
| 4 |
+
autoprefixer: {},
|
| 5 |
+
},
|
| 6 |
+
};
|
| 7 |
+
|
src/App.css
DELETED
|
@@ -1,38 +0,0 @@
|
|
| 1 |
-
.App {
|
| 2 |
-
text-align: center;
|
| 3 |
-
}
|
| 4 |
-
|
| 5 |
-
.App-logo {
|
| 6 |
-
height: 40vmin;
|
| 7 |
-
pointer-events: none;
|
| 8 |
-
}
|
| 9 |
-
|
| 10 |
-
@media (prefers-reduced-motion: no-preference) {
|
| 11 |
-
.App-logo {
|
| 12 |
-
animation: App-logo-spin infinite 20s linear;
|
| 13 |
-
}
|
| 14 |
-
}
|
| 15 |
-
|
| 16 |
-
.App-header {
|
| 17 |
-
background-color: #282c34;
|
| 18 |
-
min-height: 100vh;
|
| 19 |
-
display: flex;
|
| 20 |
-
flex-direction: column;
|
| 21 |
-
align-items: center;
|
| 22 |
-
justify-content: center;
|
| 23 |
-
font-size: calc(10px + 2vmin);
|
| 24 |
-
color: white;
|
| 25 |
-
}
|
| 26 |
-
|
| 27 |
-
.App-link {
|
| 28 |
-
color: #61dafb;
|
| 29 |
-
}
|
| 30 |
-
|
| 31 |
-
@keyframes App-logo-spin {
|
| 32 |
-
from {
|
| 33 |
-
transform: rotate(0deg);
|
| 34 |
-
}
|
| 35 |
-
to {
|
| 36 |
-
transform: rotate(360deg);
|
| 37 |
-
}
|
| 38 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/App.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
| 1 |
-
import logo from './logo.svg';
|
| 2 |
-
import './App.css';
|
| 3 |
-
|
| 4 |
-
function App() {
|
| 5 |
-
return (
|
| 6 |
-
<div className="App">
|
| 7 |
-
<header className="App-header">
|
| 8 |
-
<img src={logo} className="App-logo" alt="logo" />
|
| 9 |
-
<p>
|
| 10 |
-
Edit <code>src/App.js</code> and save to reload.
|
| 11 |
-
</p>
|
| 12 |
-
<a
|
| 13 |
-
className="App-link"
|
| 14 |
-
href="https://reactjs.org"
|
| 15 |
-
target="_blank"
|
| 16 |
-
rel="noopener noreferrer"
|
| 17 |
-
>
|
| 18 |
-
Learn React
|
| 19 |
-
</a>
|
| 20 |
-
</header>
|
| 21 |
-
</div>
|
| 22 |
-
);
|
| 23 |
-
}
|
| 24 |
-
|
| 25 |
-
export default App;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/App.test.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
| 1 |
-
import { render
|
| 2 |
-
import App from './App';
|
| 3 |
|
| 4 |
-
test('renders
|
| 5 |
render(<App />);
|
| 6 |
-
const linkElement = screen.getByText(/learn react/i);
|
| 7 |
-
expect(linkElement).toBeInTheDocument();
|
| 8 |
});
|
|
|
|
| 1 |
+
import { render } from '@testing-library/react';
|
| 2 |
+
import App from './app/App';
|
| 3 |
|
| 4 |
+
test('renders app without crashing', () => {
|
| 5 |
render(<App />);
|
|
|
|
|
|
|
| 6 |
});
|
src/app/App.tsx
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import MainLayout from './layouts/MainLayout.tsx';
|
| 3 |
+
|
| 4 |
+
const App: React.FC = () => (
|
| 5 |
+
<MainLayout>
|
| 6 |
+
<div className="container mx-auto px-4 py-8">
|
| 7 |
+
{/* Your content here */}
|
| 8 |
+
</div>
|
| 9 |
+
</MainLayout>
|
| 10 |
+
);
|
| 11 |
+
|
| 12 |
+
export default App;
|
| 13 |
+
|
src/app/components/common/ErrorBoundary.tsx
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { Component, ErrorInfo, ReactNode } from 'react';
|
| 2 |
+
|
| 3 |
+
type Props = {
|
| 4 |
+
children: ReactNode;
|
| 5 |
+
};
|
| 6 |
+
|
| 7 |
+
type State = {
|
| 8 |
+
hasError: boolean;
|
| 9 |
+
error: Error | null;
|
| 10 |
+
};
|
| 11 |
+
|
| 12 |
+
/**
|
| 13 |
+
* Error Boundary component - catches React errors and displays fallback UI
|
| 14 |
+
* Note: Error boundaries must be class components (React limitation)
|
| 15 |
+
*/
|
| 16 |
+
class ErrorBoundary extends Component<Props, State> {
|
| 17 |
+
constructor(props: Props) {
|
| 18 |
+
super(props);
|
| 19 |
+
this.state = { hasError: false, error: null };
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
static getDerivedStateFromError(error: Error): State {
|
| 23 |
+
return { hasError: true, error };
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
| 27 |
+
console.error('ErrorBoundary caught an error:', error, errorInfo);
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
render() {
|
| 31 |
+
if (this.state.hasError) {
|
| 32 |
+
return (
|
| 33 |
+
<div className="flex min-h-screen items-center justify-center bg-gray-50 px-4">
|
| 34 |
+
<div className="text-center">
|
| 35 |
+
<h1 className="mb-4 text-2xl font-bold text-gray-900">
|
| 36 |
+
Something went wrong
|
| 37 |
+
</h1>
|
| 38 |
+
<p className="mb-4 text-gray-600">
|
| 39 |
+
{this.state.error?.message || 'An unexpected error occurred'}
|
| 40 |
+
</p>
|
| 41 |
+
<button
|
| 42 |
+
onClick={() => {
|
| 43 |
+
this.setState({ hasError: false, error: null });
|
| 44 |
+
window.location.reload();
|
| 45 |
+
}}
|
| 46 |
+
className="rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
|
| 47 |
+
>
|
| 48 |
+
Reload Page
|
| 49 |
+
</button>
|
| 50 |
+
</div>
|
| 51 |
+
</div>
|
| 52 |
+
);
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
return this.props.children;
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
export default ErrorBoundary;
|
| 60 |
+
|
src/app/components/common/Loading.tsx
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
|
| 3 |
+
type LoadingProps = {
|
| 4 |
+
size?: 'sm' | 'md' | 'lg';
|
| 5 |
+
text?: string;
|
| 6 |
+
};
|
| 7 |
+
|
| 8 |
+
/**
|
| 9 |
+
* Loading spinner component
|
| 10 |
+
*/
|
| 11 |
+
const Loading: React.FC<LoadingProps> = ({ size = 'md', text }) => {
|
| 12 |
+
const sizeClasses = {
|
| 13 |
+
sm: 'h-4 w-4',
|
| 14 |
+
md: 'h-8 w-8',
|
| 15 |
+
lg: 'h-12 w-12',
|
| 16 |
+
};
|
| 17 |
+
|
| 18 |
+
return (
|
| 19 |
+
<div className="flex flex-col items-center justify-center p-4">
|
| 20 |
+
<div
|
| 21 |
+
className={`${sizeClasses[size]} animate-spin rounded-full border-4 border-gray-200 border-t-blue-600`}
|
| 22 |
+
role="status"
|
| 23 |
+
aria-label="Loading"
|
| 24 |
+
/>
|
| 25 |
+
{text && <p className="mt-2 text-sm text-gray-600">{text}</p>}
|
| 26 |
+
</div>
|
| 27 |
+
);
|
| 28 |
+
};
|
| 29 |
+
|
| 30 |
+
export default Loading;
|
| 31 |
+
|
src/app/constants/index.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Application-wide constants
|
| 3 |
+
*/
|
| 4 |
+
|
| 5 |
+
// API Endpoints (add your endpoints here as you build)
|
| 6 |
+
export const API_ENDPOINTS = {
|
| 7 |
+
|
| 8 |
+
} as const;
|
| 9 |
+
|
| 10 |
+
// App configuration
|
| 11 |
+
export const APP_CONFIG = {
|
| 12 |
+
APP_NAME: 'NLP IBM Debater',
|
| 13 |
+
VERSION: '0.1.0',
|
| 14 |
+
} as const;
|
| 15 |
+
|
| 16 |
+
// UI Constants
|
| 17 |
+
export const UI = {
|
| 18 |
+
DEBOUNCE_DELAY: 300, // ms
|
| 19 |
+
ANIMATION_DURATION: 200, // ms
|
| 20 |
+
} as const;
|
| 21 |
+
|
src/app/hooks/index.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Central export point for all custom hooks
|
| 3 |
+
*/
|
| 4 |
+
|
| 5 |
+
export { default as useApi } from './useApi';
|
| 6 |
+
export { useApi as useApiHook } from './useApi';
|
| 7 |
+
|
src/app/hooks/useApi.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useState, useEffect } from 'react';
|
| 2 |
+
import api from '../services/api-wrapper';
|
| 3 |
+
import type { ApiError } from '../types/api.types';
|
| 4 |
+
|
| 5 |
+
type UseApiState<T> = {
|
| 6 |
+
data: T | null;
|
| 7 |
+
loading: boolean;
|
| 8 |
+
error: ApiError | null;
|
| 9 |
+
};
|
| 10 |
+
|
| 11 |
+
type UseApiOptions = {
|
| 12 |
+
immediate?: boolean; // Whether to fetch immediately on mount
|
| 13 |
+
};
|
| 14 |
+
|
| 15 |
+
/**
|
| 16 |
+
* Custom hook for making API calls with loading and error states
|
| 17 |
+
*
|
| 18 |
+
* @example
|
| 19 |
+
* const { data, loading, error, execute } = useApi<User[]>('/api/users');
|
| 20 |
+
*
|
| 21 |
+
* // Or with manual trigger
|
| 22 |
+
* const { data, loading, error, execute } = useApi<User[]>('/api/users', { immediate: false });
|
| 23 |
+
* execute(); // Call manually
|
| 24 |
+
*/
|
| 25 |
+
export function useApi<T>(
|
| 26 |
+
url: string,
|
| 27 |
+
options: UseApiOptions = { immediate: true }
|
| 28 |
+
): UseApiState<T> & { execute: () => Promise<void>; refetch: () => Promise<void> } {
|
| 29 |
+
const [state, setState] = useState<UseApiState<T>>({
|
| 30 |
+
data: null,
|
| 31 |
+
loading: options.immediate ?? true,
|
| 32 |
+
error: null,
|
| 33 |
+
});
|
| 34 |
+
|
| 35 |
+
const execute = async () => {
|
| 36 |
+
setState((prev) => ({ ...prev, loading: true, error: null }));
|
| 37 |
+
try {
|
| 38 |
+
const data = await api.get<T>(url);
|
| 39 |
+
setState({ data, loading: false, error: null });
|
| 40 |
+
} catch (err) {
|
| 41 |
+
setState({
|
| 42 |
+
data: null,
|
| 43 |
+
loading: false,
|
| 44 |
+
error: err as ApiError,
|
| 45 |
+
});
|
| 46 |
+
}
|
| 47 |
+
};
|
| 48 |
+
|
| 49 |
+
useEffect(() => {
|
| 50 |
+
if (options.immediate !== false) {
|
| 51 |
+
execute();
|
| 52 |
+
}
|
| 53 |
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
| 54 |
+
}, [url]);
|
| 55 |
+
|
| 56 |
+
return {
|
| 57 |
+
...state,
|
| 58 |
+
execute,
|
| 59 |
+
refetch: execute,
|
| 60 |
+
};
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
export default useApi;
|
| 64 |
+
|
src/app/layouts/MainLayout.tsx
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
|
| 3 |
+
type MainLayoutProps = {
|
| 4 |
+
children: React.ReactNode;
|
| 5 |
+
};
|
| 6 |
+
|
| 7 |
+
const MainLayout: React.FC<MainLayoutProps> = ({ children }) => (
|
| 8 |
+
<div className="min-h-screen">
|
| 9 |
+
{children}
|
| 10 |
+
</div>
|
| 11 |
+
);
|
| 12 |
+
|
| 13 |
+
export default MainLayout;
|
| 14 |
+
|
src/app/services/api-wrapper.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { ApiRequestOptions, ApiError } from '../types/api.types';
|
| 2 |
+
|
| 3 |
+
const DEFAULT_HEADERS: HeadersInit = {
|
| 4 |
+
'Content-Type': 'application/json',
|
| 5 |
+
};
|
| 6 |
+
|
| 7 |
+
const BASE_URL =
|
| 8 |
+
process.env.REACT_APP_API_BASE_URL?.replace(/\/$/, '');
|
| 9 |
+
|
| 10 |
+
export type { ApiRequestOptions, ApiError };
|
| 11 |
+
|
| 12 |
+
export async function apiRequest<TResponse = unknown, TBody = unknown>({
|
| 13 |
+
path,
|
| 14 |
+
method = 'GET',
|
| 15 |
+
body,
|
| 16 |
+
headers,
|
| 17 |
+
signal,
|
| 18 |
+
}: ApiRequestOptions<TBody>): Promise<TResponse> {
|
| 19 |
+
const url = `${BASE_URL}${path.startsWith('/') ? '' : '/'}${path}`;
|
| 20 |
+
|
| 21 |
+
const response = await fetch(url, {
|
| 22 |
+
method,
|
| 23 |
+
headers: {
|
| 24 |
+
...DEFAULT_HEADERS,
|
| 25 |
+
...(headers ?? {}),
|
| 26 |
+
},
|
| 27 |
+
body: body ? JSON.stringify(body) : undefined,
|
| 28 |
+
signal,
|
| 29 |
+
});
|
| 30 |
+
|
| 31 |
+
const contentType = response.headers.get('content-type');
|
| 32 |
+
const isJson = contentType?.includes('application/json');
|
| 33 |
+
const payload = isJson ? await response.json().catch(() => undefined) : undefined;
|
| 34 |
+
|
| 35 |
+
if (!response.ok) {
|
| 36 |
+
const error: ApiError = {
|
| 37 |
+
status: response.status,
|
| 38 |
+
message:
|
| 39 |
+
(payload as { message?: string })?.message ||
|
| 40 |
+
response.statusText ||
|
| 41 |
+
'Request failed',
|
| 42 |
+
details: payload,
|
| 43 |
+
};
|
| 44 |
+
throw error;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
return payload as TResponse;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
export const api = {
|
| 51 |
+
get: <TResponse>(path: string, init?: Omit<ApiRequestOptions, 'path' | 'method'>) =>
|
| 52 |
+
apiRequest<TResponse>({ path, method: 'GET', ...init }),
|
| 53 |
+
post: <TResponse, TBody = unknown>(
|
| 54 |
+
path: string,
|
| 55 |
+
body?: TBody,
|
| 56 |
+
init?: Omit<ApiRequestOptions<TBody>, 'path' | 'method' | 'body'>,
|
| 57 |
+
) => apiRequest<TResponse, TBody>({ path, method: 'POST', body, ...init }),
|
| 58 |
+
put: <TResponse, TBody = unknown>(
|
| 59 |
+
path: string,
|
| 60 |
+
body?: TBody,
|
| 61 |
+
init?: Omit<ApiRequestOptions<TBody>, 'path' | 'method' | 'body'>,
|
| 62 |
+
) => apiRequest<TResponse, TBody>({ path, method: 'PUT', body, ...init }),
|
| 63 |
+
patch: <TResponse, TBody = unknown>(
|
| 64 |
+
path: string,
|
| 65 |
+
body?: TBody,
|
| 66 |
+
init?: Omit<ApiRequestOptions<TBody>, 'path' | 'method' | 'body'>,
|
| 67 |
+
) => apiRequest<TResponse, TBody>({ path, method: 'PATCH', body, ...init }),
|
| 68 |
+
delete: <TResponse>(path: string, init?: Omit<ApiRequestOptions, 'path' | 'method'>) =>
|
| 69 |
+
apiRequest<TResponse>({ path, method: 'DELETE', ...init }),
|
| 70 |
+
};
|
| 71 |
+
|
| 72 |
+
export default api;
|
| 73 |
+
|
src/app/types/api.types.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* API-related TypeScript types and interfaces
|
| 3 |
+
*/
|
| 4 |
+
|
| 5 |
+
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
| 6 |
+
|
| 7 |
+
export type ApiRequestOptions<TBody = unknown> = {
|
| 8 |
+
path: string;
|
| 9 |
+
method?: HttpMethod;
|
| 10 |
+
body?: TBody;
|
| 11 |
+
headers?: HeadersInit;
|
| 12 |
+
signal?: AbortSignal;
|
| 13 |
+
};
|
| 14 |
+
|
| 15 |
+
export type ApiError = {
|
| 16 |
+
status: number;
|
| 17 |
+
message: string;
|
| 18 |
+
details?: unknown;
|
| 19 |
+
};
|
| 20 |
+
|
src/app/types/index.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Central export point for all types
|
| 3 |
+
* Import types from here: import { SomeType } from '../types';
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
export * from './api.types';
|
| 7 |
+
|
src/app/utils/index.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Utility functions and helpers
|
| 3 |
+
*/
|
| 4 |
+
|
| 5 |
+
/**
|
| 6 |
+
* Debounce function - delays execution until after wait time
|
| 7 |
+
*/
|
| 8 |
+
export function debounce<T extends (...args: any[]) => any>(
|
| 9 |
+
func: T,
|
| 10 |
+
wait: number
|
| 11 |
+
): (...args: Parameters<T>) => void {
|
| 12 |
+
let timeout: NodeJS.Timeout | null = null;
|
| 13 |
+
return function executedFunction(...args: Parameters<T>) {
|
| 14 |
+
const later = () => {
|
| 15 |
+
timeout = null;
|
| 16 |
+
func(...args);
|
| 17 |
+
};
|
| 18 |
+
if (timeout) clearTimeout(timeout);
|
| 19 |
+
timeout = setTimeout(later, wait);
|
| 20 |
+
};
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
/**
|
| 24 |
+
* Format error message from API error or string
|
| 25 |
+
*/
|
| 26 |
+
export function formatError(error: unknown): string {
|
| 27 |
+
if (typeof error === 'string') return error;
|
| 28 |
+
if (error && typeof error === 'object' && 'message' in error) {
|
| 29 |
+
return String(error.message);
|
| 30 |
+
}
|
| 31 |
+
return 'An unexpected error occurred';
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
/**
|
| 35 |
+
* Sleep/delay utility
|
| 36 |
+
*/
|
| 37 |
+
export function sleep(ms: number): Promise<void> {
|
| 38 |
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
/**
|
| 42 |
+
* Check if value is empty (null, undefined, empty string, empty array, empty object)
|
| 43 |
+
*/
|
| 44 |
+
export function isEmpty(value: unknown): boolean {
|
| 45 |
+
if (value == null) return true;
|
| 46 |
+
if (typeof value === 'string') return value.trim().length === 0;
|
| 47 |
+
if (Array.isArray(value)) return value.length === 0;
|
| 48 |
+
if (typeof value === 'object') return Object.keys(value).length === 0;
|
| 49 |
+
return false;
|
| 50 |
+
}
|
| 51 |
+
|
src/index.css
CHANGED
|
@@ -1,13 +1,19 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
|
|
|
|
|
|
| 5 |
sans-serif;
|
| 6 |
-
|
| 7 |
-
-
|
| 8 |
}
|
| 9 |
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
}
|
|
|
|
| 1 |
+
@tailwind base;
|
| 2 |
+
@tailwind components;
|
| 3 |
+
@tailwind utilities;
|
| 4 |
+
|
| 5 |
+
:root {
|
| 6 |
+
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
| 7 |
sans-serif;
|
| 8 |
+
color: #0f172a;
|
| 9 |
+
background-color: #f8fafc;
|
| 10 |
}
|
| 11 |
|
| 12 |
+
* {
|
| 13 |
+
box-sizing: border-box;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
body {
|
| 17 |
+
margin: 0;
|
| 18 |
+
min-height: 100vh;
|
| 19 |
}
|
src/index.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
| 1 |
import React from 'react';
|
| 2 |
import ReactDOM from 'react-dom/client';
|
| 3 |
import './index.css';
|
| 4 |
-
import App from './App';
|
|
|
|
| 5 |
import reportWebVitals from './reportWebVitals';
|
| 6 |
|
| 7 |
const root = ReactDOM.createRoot(document.getElementById('root'));
|
| 8 |
root.render(
|
| 9 |
<React.StrictMode>
|
| 10 |
-
<
|
|
|
|
|
|
|
| 11 |
</React.StrictMode>
|
| 12 |
);
|
| 13 |
|
|
|
|
| 1 |
import React from 'react';
|
| 2 |
import ReactDOM from 'react-dom/client';
|
| 3 |
import './index.css';
|
| 4 |
+
import App from './app/App.tsx';
|
| 5 |
+
import ErrorBoundary from './app/components/common/ErrorBoundary.tsx';
|
| 6 |
import reportWebVitals from './reportWebVitals';
|
| 7 |
|
| 8 |
const root = ReactDOM.createRoot(document.getElementById('root'));
|
| 9 |
root.render(
|
| 10 |
<React.StrictMode>
|
| 11 |
+
<ErrorBoundary>
|
| 12 |
+
<App />
|
| 13 |
+
</ErrorBoundary>
|
| 14 |
</React.StrictMode>
|
| 15 |
);
|
| 16 |
|
tailwind.config.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/** @type {import('tailwindcss').Config} */
|
| 2 |
+
module.exports = {
|
| 3 |
+
content: ['./src/**/*.{js,jsx,ts,tsx}'],
|
| 4 |
+
theme: {
|
| 5 |
+
extend: {},
|
| 6 |
+
},
|
| 7 |
+
plugins: [],
|
| 8 |
+
};
|
| 9 |
+
|