22 KiB
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)
// 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)
: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 |
// tailwind.config.js
fontFamily: {
sans: ['Inter', 'Tajawal', 'system-ui', 'sans-serif'],
arabic: ['Tajawal', 'Inter', 'sans-serif'],
}
RTL switching happens automatically in global.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.jstheme.extend.colors - Map Figma text styles → Tailwind
fontFamily,fontSizeclasses - 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:
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
// vite.config.ts + tsconfig.json
'@/*' → './src/*'
Always use @/ imports, never relative ../ paths:
// ✅ Correct
import FileUploader from '@/components/shared/FileUploader';
// ❌ Wrong
import FileUploader from '../../components/shared/FileUploader';
4.3 Code Splitting
Vite is configured with manual chunks:
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|woff2withCache-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:
<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 (lucide-react v0.400).
6.2 Import Pattern
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:
// 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:
/* 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
classmode (darkMode: 'class'in config) - Toggled via
darkclass on<html>element - Persisted in
localStorageunder keytheme - Respects
prefers-color-schemeon first visit - Implementation:
useDarkMode()hook inHeader.tsx
Dark mode pattern — every visual element needs both variants:
// ✅ 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:
// 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) triggersdir="rtl"on<html> - Uses Tailwind RTL-aware logical properties:
ms-auto(margin-inline-start) instead ofml-autoend-0instead ofright-0[dir="rtl"] .ltr-onlyutility for forcing LTR on specific elements
- Font family switches to Tajawal-first for Arabic
7.6 Animations
Defined in global.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
{
"common": { "appName", "upload", "download", "processing", ... },
"home": { "hero", "heroSub", "popularTools", ... },
"tools": {
"toolKey": { "title", "description", "shortDesc", ...toolSpecificKeys }
},
"result": { "conversionComplete", "downloadReady", "linkExpiry", ... }
}
8.3 Usage Rules
// ✅ 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 apifor 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
- Place the file in the correct directory (
layout/,shared/, ortools/) - Use
@/path alias for all imports - Apply dark mode classes to every visual element
- Add i18n keys to all three translation files (
en.json,ar.json,fr.json) - Use Lucide icons — do not add custom SVG files unless absolutely necessary
- Follow the tool template pattern for any new tool page
- Add the route to
App.tsxwithReact.lazy()import - Add SEO via
<Helmet>+generateToolSchema() - 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
typeimports 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-labelon icon-only buttons - Use
aria-expanded,aria-haspopup,role,aria-selectedfor interactive widgets - Support keyboard navigation
- Use
dir="auto"on user text input fields for mixed-direction content