Portal Development Standards
Tech Stack
- Frontend: React 19 + TypeScript 5.9 + Vite 7 (strict mode ON)
- Styling: Custom CSS design tokens (portal-tokens.css), NOT Tailwind in portal
- Auth: Clerk (JWT RS256, phone/email login)
- Backend: PHP (plain, no framework) on cPanel with MySQL via PDO
- State: React Context + hooks (PortalAuthContext, NotificationContext)
TypeScript Rules
- No
any — use unknown and narrow with type guards
- No
@ts-ignore — fix the type instead
- Prefer
interface over type for object shapes
- All API responses typed in
src/portal/types.ts
- Use discriminated unions for state (loading | error | success)
React Rules
- Functional components only, custom hooks for reusable logic (useX naming)
- No prop drilling > 2 levels — use context or composition
- useEffect only for sync with external systems (API, DOM, subscriptions)
- Never store derived state — compute inline or useMemo
- Handle loading, error, empty states in every async component
- Dialog pattern: parent owns
showDialog state, dialog has open/onConfirm/onCancel props
CSS Rules (Portal)
- Use portal design tokens:
--p-red, --p-bg-card, --p-border, --p-cream, --p-amber, --p-text-muted
- Class naming:
portal-{component}-{modifier} (e.g. portal-btn-primary, portal-badge-revoked)
- Add reusable styles to
portal-components.css, inline styles OK for one-off layout
- Responsive: test at 320px, 768px, 1024px, 1440px
PHP Backend Rules
- PDO prepared statements for ALL queries — never concatenate user input into SQL
- JSON responses:
{ success: true, data: ... } or { success: false, error: "..." }
- Function naming:
portalVerbNoun (e.g. portalGetUsers, portalCreateUnit, portalRevokeUser)
- Always check JWT + role before data access
- Return appropriate HTTP status codes (200, 201, 400, 401, 403, 404, 500)
API Call Pattern (Frontend)
// In api.ts — all API calls go through apiFetch with auth header
// Errors thrown as Error objects, caught in component try/catch
// Always show error to user via setSaveError or setError state
Date Handling
- Storage: DDMMYYYY string (e.g. "25031999")
- Display: DD/MM/YYYY or "25 Mar 1999"
- Input:
<DateInput> component (calendar picker, always DD/MM/YYYY)
- Never use raw
<input type="date"> — always use DateInput component
File Naming
components/ PascalCase.tsx (DateInput.tsx, StatusBadge.tsx)
pages/ PascalCase.tsx (DashboardPage.tsx, ViewMemberPage.tsx)
hooks/ camelCase.ts (useNotifications.ts)
context/ PascalCase.tsx (PortalAuthContext.tsx)
__tests__/ camelCase.test.ts (api.test.ts)
*.ts camelCase.ts (api.ts, types.ts, constants.ts)
*.css kebab-case.css (portal-components.css)
Build Verification
Always run after changes:
npx tsc --noEmit # Type check
npx vite build # Production build
Key Files
- Types:
src/portal/types.ts
- API client:
src/portal/api.ts
- Constants & field definitions:
src/portal/constants.ts
- Routes:
src/portal/PortalRoutes.tsx
- Auth context:
src/portal/context/PortalAuthContext.tsx
- Backend routes:
api/routes/portal.php
- API router:
api/index.php
- Design tokens:
src/portal/portal-tokens.css
- Component styles:
src/portal/portal-components.css
Role Hierarchy
admin > zonal_secretary > regional_president > unit_president/campus_president > member
Member Statuses
active | inactive | migrated | revoked
- inactive: reversible, stores reasons + who set it
- revoked: reversible, hidden from unit/regional, stores reason + who revoked
- Delete: permanent removal (admin/zonal only)
1---2name: portal-dev3description: Coding standards and patterns for the SIO Delhi portal. Use when writing new portal features, components, API endpoints, or fixing bugs in the portal codebase. Automatically loaded for portal development tasks.4---56# Portal Development Standards78## Tech Stack9- Frontend: React 19 + TypeScript 5.9 + Vite 7 (strict mode ON)10- Styling: Custom CSS design tokens (portal-tokens.css), NOT Tailwind in portal11- Auth: Clerk (JWT RS256, phone/email login)12- Backend: PHP (plain, no framework) on cPanel with MySQL via PDO13- State: React Context + hooks (PortalAuthContext, NotificationContext)1415## TypeScript Rules16- No `any` — use `unknown` and narrow with type guards17- No `@ts-ignore` — fix the type instead18- Prefer `interface` over `type` for object shapes19- All API responses typed in `src/portal/types.ts`20- Use discriminated unions for state (loading | error | success)2122## React Rules23- Functional components only, custom hooks for reusable logic (useX naming)24- No prop drilling > 2 levels — use context or composition25- useEffect only for sync with external systems (API, DOM, subscriptions)26- Never store derived state — compute inline or useMemo27- Handle loading, error, empty states in every async component28- Dialog pattern: parent owns `showDialog` state, dialog has `open/onConfirm/onCancel` props2930## CSS Rules (Portal)31- Use portal design tokens: `--p-red`, `--p-bg-card`, `--p-border`, `--p-cream`, `--p-amber`, `--p-text-muted`32- Class naming: `portal-{component}-{modifier}` (e.g. `portal-btn-primary`, `portal-badge-revoked`)33- Add reusable styles to `portal-components.css`, inline styles OK for one-off layout34- Responsive: test at 320px, 768px, 1024px, 1440px3536## PHP Backend Rules37- PDO prepared statements for ALL queries — never concatenate user input into SQL38- JSON responses: `{ success: true, data: ... }` or `{ success: false, error: "..." }`39- Function naming: `portalVerbNoun` (e.g. `portalGetUsers`, `portalCreateUnit`, `portalRevokeUser`)40- Always check JWT + role before data access41- Return appropriate HTTP status codes (200, 201, 400, 401, 403, 404, 500)4243## API Call Pattern (Frontend)44```typescript45// In api.ts — all API calls go through apiFetch with auth header46// Errors thrown as Error objects, caught in component try/catch47// Always show error to user via setSaveError or setError state48```4950## Date Handling51- Storage: DDMMYYYY string (e.g. "25031999")52- Display: DD/MM/YYYY or "25 Mar 1999"53- Input: `<DateInput>` component (calendar picker, always DD/MM/YYYY)54- Never use raw `<input type="date">` — always use DateInput component5556## File Naming57```58components/ PascalCase.tsx (DateInput.tsx, StatusBadge.tsx)59pages/ PascalCase.tsx (DashboardPage.tsx, ViewMemberPage.tsx)60hooks/ camelCase.ts (useNotifications.ts)61context/ PascalCase.tsx (PortalAuthContext.tsx)62__tests__/ camelCase.test.ts (api.test.ts)63*.ts camelCase.ts (api.ts, types.ts, constants.ts)64*.css kebab-case.css (portal-components.css)65```6667## Build Verification68Always run after changes:69```bash70npx tsc --noEmit # Type check71npx vite build # Production build72```7374## Key Files75- Types: `src/portal/types.ts`76- API client: `src/portal/api.ts`77- Constants & field definitions: `src/portal/constants.ts`78- Routes: `src/portal/PortalRoutes.tsx`79- Auth context: `src/portal/context/PortalAuthContext.tsx`80- Backend routes: `api/routes/portal.php`81- API router: `api/index.php`82- Design tokens: `src/portal/portal-tokens.css`83- Component styles: `src/portal/portal-components.css`8485## Role Hierarchy86```87admin > zonal_secretary > regional_president > unit_president/campus_president > member88```8990## Member Statuses91`active | inactive | migrated | revoked`92- inactive: reversible, stores reasons + who set it93- revoked: reversible, hidden from unit/regional, stores reason + who revoked94- Delete: permanent removal (admin/zonal only)