feat: add comprehensive codebase rules and design system reference in CLAUDE.md
This commit is contained in:
643
frontend/CLAUDE.md
Normal file
643
frontend/CLAUDE.md
Normal file
@@ -0,0 +1,643 @@
|
|||||||
|
# CLAUDE.md — SaaS-PDF Codebase Rules & Design System Reference
|
||||||
|
|
||||||
|
> Comprehensive rules document for AI-assisted development and Figma-to-code integration via Model Context Protocol (MCP).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Project Overview
|
||||||
|
|
||||||
|
SaaS-PDF is a full-stack web application offering 16+ free online file-processing tools (PDF, image, video, text). The architecture is a **Python/Flask backend** with **Celery workers** for async processing, and a **React + TypeScript frontend** styled with **Tailwind CSS**.
|
||||||
|
|
||||||
|
### Tech Stack Summary
|
||||||
|
|
||||||
|
| Layer | Technology |
|
||||||
|
|-------------|------------------------------------------------------|
|
||||||
|
| Frontend | React 18, TypeScript 5.5, Vite 5.4 |
|
||||||
|
| Styling | Tailwind CSS 3.4, PostCSS, Autoprefixer |
|
||||||
|
| State | Zustand 4.5 (available but lightweight usage) |
|
||||||
|
| Routing | React Router DOM 6.23 |
|
||||||
|
| i18n | i18next 23 + react-i18next 14 (EN, AR, FR) |
|
||||||
|
| Icons | Lucide React 0.400 |
|
||||||
|
| HTTP | Axios 1.7 |
|
||||||
|
| SEO | react-helmet-async 2.0, JSON-LD structured data |
|
||||||
|
| Analytics | react-ga4 2.1 |
|
||||||
|
| File Upload | react-dropzone 14.2 |
|
||||||
|
| Toasts | sonner 1.5 |
|
||||||
|
| Backend | Flask, Celery, Redis |
|
||||||
|
| Build | Vite (dev + prod), Docker Compose |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Token Definitions (Design Tokens)
|
||||||
|
|
||||||
|
### 2.1 Color Tokens
|
||||||
|
|
||||||
|
Colors are defined in **two places** that must stay in sync:
|
||||||
|
|
||||||
|
#### Tailwind Extended Colors (`frontend/tailwind.config.js`)
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Primary palette (Blue)
|
||||||
|
primary: {
|
||||||
|
50: '#eff6ff', 100: '#dbeafe', 200: '#bfdbfe',
|
||||||
|
300: '#93c5fd', 400: '#60a5fa', 500: '#3b82f6',
|
||||||
|
600: '#2563eb', 700: '#1d4ed8', 800: '#1e40af',
|
||||||
|
900: '#1e3a8a', 950: '#172554',
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accent palette (Purple/Fuchsia)
|
||||||
|
accent: {
|
||||||
|
50: '#fdf4ff', 100: '#fae8ff', 200: '#f5d0fe',
|
||||||
|
300: '#f0abfc', 400: '#e879f9', 500: '#d946ef',
|
||||||
|
600: '#c026d3', 700: '#a21caf', 800: '#86198f',
|
||||||
|
900: '#701a75',
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Built-in Tailwind colors also in heavy use:** `slate`, `red`, `emerald`, `orange`, `violet`, `pink`, `teal`, `amber`, `lime`, `cyan`, `green`, `sky`, `blue`, `purple`, `indigo`.
|
||||||
|
|
||||||
|
#### CSS Custom Properties (`frontend/src/styles/global.css`)
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--color-bg: #ffffff;
|
||||||
|
--color-surface: #f8fafc;
|
||||||
|
--color-text: #0f172a;
|
||||||
|
--color-text-secondary: #64748b;
|
||||||
|
--color-border: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--color-bg: #0f172a;
|
||||||
|
--color-surface: #1e293b;
|
||||||
|
--color-text: #f1f5f9;
|
||||||
|
--color-text-secondary: #94a3b8;
|
||||||
|
--color-border: #334155;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Typography Tokens
|
||||||
|
|
||||||
|
Fonts are loaded from Google Fonts in `frontend/index.html`:
|
||||||
|
|
||||||
|
| Font | Weights | Usage |
|
||||||
|
|-----------|------------------|-----------------------------|
|
||||||
|
| Inter | 300,400,500,600,700 | Default sans-serif (LTR) |
|
||||||
|
| Tajawal | 300,400,500,700 | Arabic/RTL text |
|
||||||
|
|
||||||
|
```js
|
||||||
|
// tailwind.config.js
|
||||||
|
fontFamily: {
|
||||||
|
sans: ['Inter', 'Tajawal', 'system-ui', 'sans-serif'],
|
||||||
|
arabic: ['Tajawal', 'Inter', 'sans-serif'],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**RTL switching** happens automatically in `global.css`:
|
||||||
|
```css
|
||||||
|
body { font-family: 'Inter', 'Tajawal', system-ui, sans-serif; }
|
||||||
|
[dir="rtl"] body { font-family: 'Tajawal', 'Inter', system-ui, sans-serif; }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Spacing & Layout Tokens
|
||||||
|
|
||||||
|
No custom spacing tokens — uses **Tailwind's default spacing scale** (4px base unit: `p-1` = 4px, `p-2` = 8px, etc.).
|
||||||
|
|
||||||
|
**Layout constants used throughout:**
|
||||||
|
- Max content width: `max-w-7xl` (header/footer), `max-w-2xl` (tool pages)
|
||||||
|
- Container padding: `px-4 sm:px-6 lg:px-8`
|
||||||
|
- Page vertical padding: `py-8`
|
||||||
|
- Section gap: `space-y-4`
|
||||||
|
- Card padding: `p-6`
|
||||||
|
- Border radius: `rounded-xl` (buttons, inputs), `rounded-2xl` (cards, zones)
|
||||||
|
|
||||||
|
### 2.4 Token Transformation
|
||||||
|
|
||||||
|
**No token transformation pipeline** (e.g., Style Dictionary) is in place. Tokens are hand-coded in Tailwind config and CSS. When integrating Figma variables:
|
||||||
|
- Map Figma color variables → `tailwind.config.js` `theme.extend.colors`
|
||||||
|
- Map Figma text styles → Tailwind `fontFamily`, `fontSize` classes
|
||||||
|
- Map Figma spacing → Tailwind spacing scale (already standard 4px grid)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Component Library
|
||||||
|
|
||||||
|
### 3.1 Component Organization
|
||||||
|
|
||||||
|
```
|
||||||
|
frontend/src/components/
|
||||||
|
├── layout/ # App-wide structural components
|
||||||
|
│ ├── Header.tsx # Sticky header with nav, dark mode, language switcher
|
||||||
|
│ ├── Footer.tsx # Footer with links
|
||||||
|
│ └── AdSlot.tsx # Google AdSense container
|
||||||
|
├── shared/ # Reusable UI building blocks
|
||||||
|
│ ├── FileUploader.tsx # Drag & drop file upload (react-dropzone)
|
||||||
|
│ ├── DownloadButton.tsx # Result card with download link
|
||||||
|
│ ├── ProgressBar.tsx # Task processing indicator
|
||||||
|
│ └── ToolCard.tsx # Homepage tool grid card (link)
|
||||||
|
└── tools/ # One component per tool (16 total)
|
||||||
|
├── PdfCompressor.tsx
|
||||||
|
├── MergePdf.tsx
|
||||||
|
├── SplitPdf.tsx
|
||||||
|
├── PdfToWord.tsx
|
||||||
|
├── WordToPdf.tsx
|
||||||
|
├── RotatePdf.tsx
|
||||||
|
├── PdfToImages.tsx
|
||||||
|
├── ImagesToPdf.tsx
|
||||||
|
├── WatermarkPdf.tsx
|
||||||
|
├── ProtectPdf.tsx
|
||||||
|
├── UnlockPdf.tsx
|
||||||
|
├── AddPageNumbers.tsx
|
||||||
|
├── ImageConverter.tsx
|
||||||
|
├── VideoToGif.tsx
|
||||||
|
├── WordCounter.tsx
|
||||||
|
└── TextCleaner.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Component Architecture Pattern
|
||||||
|
|
||||||
|
**All components are React functional components with default exports.**
|
||||||
|
|
||||||
|
#### Tool Component Standard Template
|
||||||
|
|
||||||
|
Every tool page follows this exact pattern:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Helmet } from 'react-helmet-async';
|
||||||
|
import { SomeIcon } from 'lucide-react';
|
||||||
|
import FileUploader from '@/components/shared/FileUploader';
|
||||||
|
import ProgressBar from '@/components/shared/ProgressBar';
|
||||||
|
import DownloadButton from '@/components/shared/DownloadButton';
|
||||||
|
import AdSlot from '@/components/layout/AdSlot';
|
||||||
|
import { useFileUpload } from '@/hooks/useFileUpload';
|
||||||
|
import { useTaskPolling } from '@/hooks/useTaskPolling';
|
||||||
|
import { generateToolSchema } from '@/utils/seo';
|
||||||
|
|
||||||
|
export default function ToolName() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [phase, setPhase] = useState<'upload' | 'processing' | 'done'>('upload');
|
||||||
|
|
||||||
|
// 1. useFileUpload hook for file selection + upload
|
||||||
|
// 2. useTaskPolling hook for progress tracking
|
||||||
|
// 3. Three-phase UI: upload → processing → done
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet>
|
||||||
|
<title>{t('tools.toolKey.title')} — {t('common.appName')}</title>
|
||||||
|
<meta name="description" content={t('tools.toolKey.description')} />
|
||||||
|
<link rel="canonical" href={`${window.location.origin}/tools/tool-slug`} />
|
||||||
|
<script type="application/ld+json">{JSON.stringify(schema)}</script>
|
||||||
|
</Helmet>
|
||||||
|
|
||||||
|
<div className="mx-auto max-w-2xl">
|
||||||
|
{/* Icon + Title + Description header */}
|
||||||
|
<div className="mb-8 text-center">
|
||||||
|
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-2xl bg-{color}-100">
|
||||||
|
<SomeIcon className="h-8 w-8 text-{color}-600" />
|
||||||
|
</div>
|
||||||
|
<h1 className="section-heading">{t('tools.toolKey.title')}</h1>
|
||||||
|
<p className="mt-2 text-slate-500">{t('tools.toolKey.description')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AdSlot slot="top-banner" format="horizontal" className="mb-6" />
|
||||||
|
|
||||||
|
{/* Phase-based rendering */}
|
||||||
|
{phase === 'upload' && ( /* FileUploader + options + submit button */ )}
|
||||||
|
{phase === 'processing' && ( <ProgressBar ... /> )}
|
||||||
|
{phase === 'done' && result && ( <DownloadButton ... /> )}
|
||||||
|
{phase === 'done' && error && ( /* Error message + retry */ )}
|
||||||
|
|
||||||
|
<AdSlot slot="bottom-banner" className="mt-8" />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Key Patterns
|
||||||
|
|
||||||
|
| Pattern | Implementation |
|
||||||
|
|---------|---------------|
|
||||||
|
| State management | Local `useState` per tool; 3-phase state machine (`upload`/`processing`/`done`) |
|
||||||
|
| File upload | `useFileUpload` hook → wraps `uploadFile()` API call |
|
||||||
|
| Async processing | `useTaskPolling` hook → polls `/api/tasks/{id}/status` every 1.5s |
|
||||||
|
| SEO | `<Helmet>` with title, description, canonical URL, JSON-LD |
|
||||||
|
| i18n | All user-facing strings via `t('key')`, never hardcoded |
|
||||||
|
| Lazy loading | All tool + page components use `React.lazy()` + `Suspense` in `App.tsx` |
|
||||||
|
|
||||||
|
### 3.3 Component Documentation
|
||||||
|
|
||||||
|
**No Storybook or formal component documentation exists.** Components are self-documented through TypeScript interfaces on their props.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Frameworks & Build System
|
||||||
|
|
||||||
|
### 4.1 Build & Dev
|
||||||
|
|
||||||
|
| Tool | Config File | Purpose |
|
||||||
|
|---------|------------|---------|
|
||||||
|
| Vite | `frontend/vite.config.ts` | Dev server (port 5173) + production build |
|
||||||
|
| TypeScript | `frontend/tsconfig.json` | Strict mode, ES2020 target, `@/` path alias |
|
||||||
|
| PostCSS | `frontend/postcss.config.js` | Tailwind CSS + Autoprefixer |
|
||||||
|
|
||||||
|
### 4.2 Path Aliases
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// vite.config.ts + tsconfig.json
|
||||||
|
'@/*' → './src/*'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Always use `@/` imports**, never relative `../` paths:
|
||||||
|
```tsx
|
||||||
|
// ✅ Correct
|
||||||
|
import FileUploader from '@/components/shared/FileUploader';
|
||||||
|
|
||||||
|
// ❌ Wrong
|
||||||
|
import FileUploader from '../../components/shared/FileUploader';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Code Splitting
|
||||||
|
|
||||||
|
Vite is configured with manual chunks:
|
||||||
|
```ts
|
||||||
|
manualChunks: {
|
||||||
|
vendor: ['react', 'react-dom', 'react-router-dom'],
|
||||||
|
i18n: ['i18next', 'react-i18next'],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
All route-level components use `React.lazy()` for automatic code splitting.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Asset Management
|
||||||
|
|
||||||
|
### 5.1 Static Assets
|
||||||
|
|
||||||
|
```
|
||||||
|
frontend/public/ # Served at root URL, not processed by Vite
|
||||||
|
├── favicon.svg # SVG favicon (indigo rounded-rect with doc icon)
|
||||||
|
├── ads.txt # Google AdSense ads.txt
|
||||||
|
└── robots.txt # SEO robots directive
|
||||||
|
```
|
||||||
|
|
||||||
|
- **No image assets** are stored in the frontend — the app is icon-driven
|
||||||
|
- Uploaded files are stored server-side in Docker volumes (`/tmp/uploads`, `/tmp/outputs`)
|
||||||
|
- Download URLs are served from the backend API
|
||||||
|
|
||||||
|
### 5.2 Asset Optimization
|
||||||
|
|
||||||
|
- **Nginx caching** (production): 1-year cache for `js|css|png|jpg|jpeg|gif|ico|svg|woff2` with `Cache-Control: public, immutable`
|
||||||
|
- **Vite build**: Sourcemaps disabled in production (`sourcemap: false`)
|
||||||
|
- **No CDN** is currently configured — assets are served directly from the Nginx container
|
||||||
|
|
||||||
|
### 5.3 Font Loading
|
||||||
|
|
||||||
|
Fonts are loaded via Google Fonts CDN with `preconnect` hints in `index.html`:
|
||||||
|
```html
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Tajawal:wght@300;400;500;700&display=swap" rel="stylesheet" />
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Icon System
|
||||||
|
|
||||||
|
### 6.1 Library
|
||||||
|
|
||||||
|
All icons come from **[Lucide React](https://lucide.dev/)** (`lucide-react` v0.400).
|
||||||
|
|
||||||
|
### 6.2 Import Pattern
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { FileText, Upload, Download, X, Loader2 } from 'lucide-react';
|
||||||
|
```
|
||||||
|
|
||||||
|
Each icon is imported **individually by name** — Lucide is tree-shakeable.
|
||||||
|
|
||||||
|
### 6.3 Sizing Convention
|
||||||
|
|
||||||
|
| Context | Size Class | Example |
|
||||||
|
|-------------------|-----------------|---------|
|
||||||
|
| Tool page hero | `h-8 w-8` | `<Minimize2 className="h-8 w-8 text-orange-600" />` |
|
||||||
|
| Homepage card | `h-6 w-6` | `<FileText className="h-6 w-6 text-red-600" />` |
|
||||||
|
| Header nav | `h-7 w-7` | Logo icon |
|
||||||
|
| Buttons / inline | `h-5 w-5` | `<Download className="h-5 w-5" />` |
|
||||||
|
| Small indicators | `h-4 w-4` | Chevrons, checkmarks |
|
||||||
|
| Upload zone | `h-12 w-12` | `<Upload className="h-12 w-12" />` |
|
||||||
|
|
||||||
|
### 6.4 Icon Color Convention
|
||||||
|
|
||||||
|
Icons use **Tailwind text-color classes**, typically matching a semantic color:
|
||||||
|
```tsx
|
||||||
|
// Tool hero icons get a colored background container
|
||||||
|
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-2xl bg-orange-100">
|
||||||
|
<Minimize2 className="h-8 w-8 text-orange-600" />
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.5 Icon-to-Tool Color Mapping
|
||||||
|
|
||||||
|
| Tool | Icon | Background | Text Color |
|
||||||
|
|------|------|-----------|------------|
|
||||||
|
| PDF to Word | `FileText` | `bg-red-50` | `text-red-600` |
|
||||||
|
| Word to PDF | `FileOutput` | `bg-blue-50` | `text-blue-600` |
|
||||||
|
| Compress PDF | `Minimize2` | `bg-orange-50` | `text-orange-600` |
|
||||||
|
| Merge PDF | `Layers` | `bg-violet-50` | `text-violet-600` |
|
||||||
|
| Split PDF | `Scissors` | `bg-pink-50` | `text-pink-600` |
|
||||||
|
| Rotate PDF | `RotateCw` | `bg-teal-50` | `text-teal-600` |
|
||||||
|
| PDF to Images | `Image` | `bg-amber-50` | `text-amber-600` |
|
||||||
|
| Images to PDF | `FileImage` | `bg-lime-50` | `text-lime-600` |
|
||||||
|
| Watermark PDF | `Droplets` | `bg-cyan-50` | `text-cyan-600` |
|
||||||
|
| Protect PDF | `Lock` | `bg-red-50` | `text-red-600` |
|
||||||
|
| Unlock PDF | `Unlock` | `bg-green-50` | `text-green-600` |
|
||||||
|
| Page Numbers | `ListOrdered` | `bg-sky-50` | `text-sky-600` |
|
||||||
|
| Image Converter | `ImageIcon` | `bg-purple-50` | `text-purple-600` |
|
||||||
|
| Video to GIF | `Film` | `bg-emerald-50` | `text-emerald-600` |
|
||||||
|
| Word Counter | `Hash` | `bg-blue-50` | `text-blue-600` |
|
||||||
|
| Text Cleaner | `Eraser` | `bg-indigo-50` | `text-indigo-600` |
|
||||||
|
|
||||||
|
### 6.6 No Custom SVG Icons
|
||||||
|
|
||||||
|
The project uses **zero custom SVG icons** — everything is Lucide. If a Figma design introduces custom icons, they should be converted to React components following Lucide's pattern (24x24 viewBox, `currentColor` stroke, configurable `className`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Styling Approach
|
||||||
|
|
||||||
|
### 7.1 Methodology: Utility-First Tailwind CSS
|
||||||
|
|
||||||
|
- **Primary approach**: Tailwind utility classes applied directly in JSX
|
||||||
|
- **No CSS Modules**, no Styled Components, no CSS-in-JS
|
||||||
|
- **No BEM** or other class naming convention
|
||||||
|
|
||||||
|
### 7.2 Reusable Component Classes (`@layer components`)
|
||||||
|
|
||||||
|
Defined in `frontend/src/styles/global.css`:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Buttons */
|
||||||
|
.btn-primary /* Blue filled button, rounded-xl, primary-600 bg */
|
||||||
|
.btn-secondary /* White/outlined button, slate ring */
|
||||||
|
.btn-success /* Green filled button, emerald-600 bg */
|
||||||
|
|
||||||
|
/* Cards */
|
||||||
|
.card /* White card, rounded-2xl, ring-1 slate, shadow-sm */
|
||||||
|
.tool-card /* Card + cursor-pointer + hover effects */
|
||||||
|
|
||||||
|
/* Form */
|
||||||
|
.input-field /* Full-width input, rounded-xl, ring-1 slate */
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
.section-heading /* text-2xl/3xl bold tracking-tight */
|
||||||
|
|
||||||
|
/* Upload */
|
||||||
|
.upload-zone /* Dashed border, centered content, hover state */
|
||||||
|
|
||||||
|
/* Ads */
|
||||||
|
.ad-slot /* Centered container for AdSense units */
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 Dark Mode
|
||||||
|
|
||||||
|
- Strategy: **Tailwind `class` mode** (`darkMode: 'class'` in config)
|
||||||
|
- Toggled via `dark` class on `<html>` element
|
||||||
|
- Persisted in `localStorage` under key `theme`
|
||||||
|
- Respects `prefers-color-scheme` on first visit
|
||||||
|
- Implementation: `useDarkMode()` hook in `Header.tsx`
|
||||||
|
|
||||||
|
**Dark mode pattern — every visual element needs both variants:**
|
||||||
|
```tsx
|
||||||
|
// ✅ Always pair light + dark
|
||||||
|
className="bg-white text-slate-900 dark:bg-slate-800 dark:text-slate-100"
|
||||||
|
|
||||||
|
// ❌ Never omit dark variant for visible elements
|
||||||
|
className="bg-white text-slate-900"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.4 Responsive Design
|
||||||
|
|
||||||
|
- **Mobile-first** approach using Tailwind breakpoints
|
||||||
|
- Breakpoints: `sm:` (640px), `md:` (768px), `lg:` (1024px), `xl:` (1280px)
|
||||||
|
- Common patterns:
|
||||||
|
```tsx
|
||||||
|
// Responsive grid
|
||||||
|
className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"
|
||||||
|
|
||||||
|
// Show/hide
|
||||||
|
className="hidden md:flex" // Desktop nav
|
||||||
|
className="md:hidden" // Mobile menu button
|
||||||
|
|
||||||
|
// Responsive text
|
||||||
|
className="text-4xl sm:text-5xl"
|
||||||
|
|
||||||
|
// Responsive padding
|
||||||
|
className="px-4 sm:px-6 lg:px-8"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.5 RTL Support
|
||||||
|
|
||||||
|
- Automatic direction switching via `useDirection()` hook
|
||||||
|
- Arabic (`ar`) triggers `dir="rtl"` on `<html>`
|
||||||
|
- Uses Tailwind RTL-aware logical properties:
|
||||||
|
- `ms-auto` (margin-inline-start) instead of `ml-auto`
|
||||||
|
- `end-0` instead of `right-0`
|
||||||
|
- `[dir="rtl"] .ltr-only` utility for forcing LTR on specific elements
|
||||||
|
- Font family switches to Tajawal-first for Arabic
|
||||||
|
|
||||||
|
### 7.6 Animations
|
||||||
|
|
||||||
|
Defined in `global.css`:
|
||||||
|
```css
|
||||||
|
@keyframes progress-pulse { /* Pulsing opacity for progress bars */ }
|
||||||
|
@keyframes fadeSlideIn { /* Fade + slide for dropdowns */ }
|
||||||
|
```
|
||||||
|
|
||||||
|
Used via classes: `.progress-bar-animated`, `.animate-in`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Internationalization (i18n)
|
||||||
|
|
||||||
|
### 8.1 Setup
|
||||||
|
|
||||||
|
- **Library**: i18next + react-i18next + i18next-browser-languagedetector
|
||||||
|
- **Config**: `frontend/src/i18n/index.ts`
|
||||||
|
- **Translation files**:
|
||||||
|
- `frontend/src/i18n/en.json` (English — source of truth)
|
||||||
|
- `frontend/src/i18n/ar.json` (Arabic)
|
||||||
|
- `frontend/src/i18n/fr.json` (French)
|
||||||
|
|
||||||
|
### 8.2 Key Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"common": { "appName", "upload", "download", "processing", ... },
|
||||||
|
"home": { "hero", "heroSub", "popularTools", ... },
|
||||||
|
"tools": {
|
||||||
|
"toolKey": { "title", "description", "shortDesc", ...toolSpecificKeys }
|
||||||
|
},
|
||||||
|
"result": { "conversionComplete", "downloadReady", "linkExpiry", ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 Usage Rules
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// ✅ Always use translation keys
|
||||||
|
<h1>{t('tools.compressPdf.title')}</h1>
|
||||||
|
|
||||||
|
// ✅ Interpolation
|
||||||
|
{t('common.maxSize', { size: maxSizeMB })}
|
||||||
|
|
||||||
|
// ❌ Never hardcode user-facing strings
|
||||||
|
<h1>Compress PDF</h1>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Project Structure Rules
|
||||||
|
|
||||||
|
### 9.1 Directory Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
SaaS-PDF/
|
||||||
|
├── frontend/ # React SPA
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── components/ # UI Components (layout/, shared/, tools/)
|
||||||
|
│ │ ├── hooks/ # Custom React hooks
|
||||||
|
│ │ ├── i18n/ # Translation files
|
||||||
|
│ │ ├── pages/ # Route-level page components
|
||||||
|
│ │ ├── services/ # API client (axios)
|
||||||
|
│ │ ├── styles/ # Global CSS (Tailwind layers)
|
||||||
|
│ │ └── utils/ # Pure utility functions
|
||||||
|
│ └── public/ # Static assets
|
||||||
|
├── backend/ # Flask + Celery
|
||||||
|
│ ├── app/
|
||||||
|
│ │ ├── routes/ # API endpoints (Flask blueprints)
|
||||||
|
│ │ ├── services/ # Business logic
|
||||||
|
│ │ ├── tasks/ # Celery async tasks
|
||||||
|
│ │ ├── utils/ # Backend utilities
|
||||||
|
│ │ └── middleware/ # Rate limiting, etc.
|
||||||
|
│ ├── config/ # Flask configuration
|
||||||
|
│ └── tests/ # pytest tests
|
||||||
|
├── nginx/ # Reverse proxy configuration
|
||||||
|
├── scripts/ # Deployment & maintenance scripts
|
||||||
|
├── docs/ # Specifications & planning docs
|
||||||
|
├── docker-compose.yml # Development environment
|
||||||
|
└── docker-compose.prod.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 File Naming Conventions
|
||||||
|
|
||||||
|
| Type | Convention | Example |
|
||||||
|
|------|-----------|---------|
|
||||||
|
| React components | PascalCase | `PdfCompressor.tsx`, `FileUploader.tsx` |
|
||||||
|
| Hooks | camelCase with `use` prefix | `useFileUpload.ts`, `useDirection.ts` |
|
||||||
|
| Utils | camelCase | `textTools.ts`, `seo.ts` |
|
||||||
|
| Services | camelCase | `api.ts` |
|
||||||
|
| Translation files | lowercase ISO code | `en.json`, `ar.json` |
|
||||||
|
| CSS files | lowercase | `global.css` |
|
||||||
|
| Pages | PascalCase + `Page` suffix | `HomePage.tsx`, `AboutPage.tsx` |
|
||||||
|
|
||||||
|
### 9.3 Export Conventions
|
||||||
|
|
||||||
|
- **Components**: `export default function ComponentName()` (default export)
|
||||||
|
- **Hooks**: `export function useHookName()` (named export)
|
||||||
|
- **Utils**: `export function utilName()` (named export)
|
||||||
|
- **Services**: named exports + `export default api` for the axios instance
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Figma Integration Guidelines (MCP)
|
||||||
|
|
||||||
|
### 10.1 Mapping Figma to Code
|
||||||
|
|
||||||
|
| Figma Element | Maps To |
|
||||||
|
|---------------|---------|
|
||||||
|
| Color Variables | `tailwind.config.js` → `theme.extend.colors` |
|
||||||
|
| Text Styles | Tailwind `text-*`, `font-*` classes + `fontFamily` config |
|
||||||
|
| Spacing | Tailwind spacing scale (4px grid: `p-1`=4px, `p-4`=16px) |
|
||||||
|
| Border Radius | `rounded-xl` (12px) for buttons/inputs, `rounded-2xl` (16px) for cards |
|
||||||
|
| Shadow | `shadow-sm`, `shadow-md`, `shadow-lg` (Tailwind defaults) |
|
||||||
|
| Icons | Map to Lucide React icon names |
|
||||||
|
| Components | React functional components with Tailwind utility classes |
|
||||||
|
|
||||||
|
### 10.2 When Creating New Components from Figma
|
||||||
|
|
||||||
|
1. **Place the file** in the correct directory (`layout/`, `shared/`, or `tools/`)
|
||||||
|
2. **Use `@/` path alias** for all imports
|
||||||
|
3. **Apply dark mode** classes to every visual element
|
||||||
|
4. **Add i18n keys** to all three translation files (`en.json`, `ar.json`, `fr.json`)
|
||||||
|
5. **Use Lucide icons** — do not add custom SVG files unless absolutely necessary
|
||||||
|
6. **Follow the tool template** pattern for any new tool page
|
||||||
|
7. **Add the route** to `App.tsx` with `React.lazy()` import
|
||||||
|
8. **Add SEO** via `<Helmet>` + `generateToolSchema()`
|
||||||
|
9. **Support RTL** — use logical properties (`ms-`, `me-`, `ps-`, `pe-`, `start-`, `end-`)
|
||||||
|
|
||||||
|
### 10.3 Color Translation Cheat Sheet
|
||||||
|
|
||||||
|
| Figma Token | Tailwind Class |
|
||||||
|
|-------------|---------------|
|
||||||
|
| Primary / Brand Blue | `primary-600` (buttons), `primary-500` (dark mode) |
|
||||||
|
| Background | `white` / `dark:slate-950` (page), `slate-50` / `dark:slate-800` (surface) |
|
||||||
|
| Text Primary | `slate-900` / `dark:slate-100` |
|
||||||
|
| Text Secondary | `slate-500` / `dark:slate-400` |
|
||||||
|
| Border | `slate-200` / `dark:slate-700` |
|
||||||
|
| Success | `emerald-600` |
|
||||||
|
| Error | `red-600` / `red-700` |
|
||||||
|
| Warning | `orange-600` |
|
||||||
|
|
||||||
|
### 10.4 Spacing Translation
|
||||||
|
|
||||||
|
| Figma px | Tailwind class |
|
||||||
|
|----------|---------------|
|
||||||
|
| 4px | `1` |
|
||||||
|
| 8px | `2` |
|
||||||
|
| 12px | `3` |
|
||||||
|
| 16px | `4` |
|
||||||
|
| 20px | `5` |
|
||||||
|
| 24px | `6` |
|
||||||
|
| 32px | `8` |
|
||||||
|
| 40px | `10` |
|
||||||
|
| 48px | `12` |
|
||||||
|
| 64px | `16` |
|
||||||
|
| 80px | `20` |
|
||||||
|
| 96px | `24` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Code Quality Rules
|
||||||
|
|
||||||
|
### 11.1 TypeScript
|
||||||
|
|
||||||
|
- **Strict mode** enabled
|
||||||
|
- All component props must have an `interface` (not inline types)
|
||||||
|
- Use `type` imports when importing only types: `import type { TaskResult } from '@/services/api'`
|
||||||
|
|
||||||
|
### 11.2 Component Rules
|
||||||
|
|
||||||
|
- Every component must be a **default export** function
|
||||||
|
- No class components
|
||||||
|
- Use `useTranslation()` for all user-facing text
|
||||||
|
- Use `<Helmet>` for page-level SEO on every route-level component
|
||||||
|
|
||||||
|
### 11.3 Styling Rules
|
||||||
|
|
||||||
|
- **No inline styles** except for dynamic values (e.g., `style={{ width: '${percent}%' }}`)
|
||||||
|
- **No external CSS files** per component — everything in Tailwind utilities or `global.css` `@layer`
|
||||||
|
- Always include both light and dark mode variants
|
||||||
|
- Always include responsive breakpoints for layout-affecting properties
|
||||||
|
|
||||||
|
### 11.4 Accessibility
|
||||||
|
|
||||||
|
- Use semantic HTML (`<header>`, `<main>`, `<footer>`, `<nav>`, `<section>`)
|
||||||
|
- Include `aria-label` on icon-only buttons
|
||||||
|
- Use `aria-expanded`, `aria-haspopup`, `role`, `aria-selected` for interactive widgets
|
||||||
|
- Support keyboard navigation
|
||||||
|
- Use `dir="auto"` on user text input fields for mixed-direction content
|
||||||
Reference in New Issue
Block a user