Fix frontend test setup and refresh docs
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
# SaaS-PDF Project Status Report
|
# SaaS-PDF Project Status Report
|
||||||
|
|
||||||
Generated on: 2026-03-10
|
Generated on: 2026-03-10
|
||||||
|
Updated on: 2026-03-25
|
||||||
Branch reviewed: feature/seo-content
|
Branch reviewed: feature/seo-content
|
||||||
|
|
||||||
## Executive Summary
|
## Executive Summary
|
||||||
@@ -27,7 +28,7 @@ The main remaining gaps are consistency and production hardening:
|
|||||||
|
|
||||||
### Backend
|
### Backend
|
||||||
|
|
||||||
- Flask application factory with 24 registered blueprints
|
- Flask application factory with 33 registered blueprints
|
||||||
- Celery async task processing
|
- Celery async task processing
|
||||||
- Redis-backed task flow
|
- Redis-backed task flow
|
||||||
- Service-oriented architecture under backend/app/services
|
- Service-oriented architecture under backend/app/services
|
||||||
@@ -133,7 +134,7 @@ Implemented pages:
|
|||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- Contact currently uses a mailto flow rather than a backend contact form endpoint.
|
- Contact now uses a backend submission endpoint plus direct email fallback.
|
||||||
- About, Privacy, and Terms are SEO-enabled pages with structured metadata.
|
- About, Privacy, and Terms are SEO-enabled pages with structured metadata.
|
||||||
|
|
||||||
## Phase 6 — Technical SEO Optimization
|
## Phase 6 — Technical SEO Optimization
|
||||||
@@ -215,11 +216,11 @@ The following improvements were started as part of this implementation step:
|
|||||||
1. Refresh docs/tool_inventory.md so it becomes the current source of truth again.
|
1. Refresh docs/tool_inventory.md so it becomes the current source of truth again.
|
||||||
2. Remove duplicate Helmet metadata from tool components that are already wrapped by ToolLandingPage.
|
2. Remove duplicate Helmet metadata from tool components that are already wrapped by ToolLandingPage.
|
||||||
3. Replace placeholder domain values in public SEO files with the production domain.
|
3. Replace placeholder domain values in public SEO files with the production domain.
|
||||||
4. Decide whether contact should remain mailto-based or move to a backend endpoint.
|
4. Surface or intentionally defer the tools that exist in routes but are not shown on the homepage.
|
||||||
5. Run full backend and frontend test/build validation in the target environment.
|
5. Run full backend and frontend test/build validation in the target environment.
|
||||||
|
|
||||||
## Final Assessment
|
## Final Assessment
|
||||||
|
|
||||||
SaaS-PDF is no longer just a basic MVP. It is already a broad multi-tool document-processing platform with strong progress across product scope, frontend SEO architecture, and backend task-based processing.
|
SaaS-PDF is no longer just a basic MVP. It is already a broad multi-tool document-processing platform with strong progress across product scope, frontend SEO architecture, and backend task-based processing.
|
||||||
|
|
||||||
The current priority is not missing core features. The current priority is tightening consistency, production configuration, and documentation so the implemented work is easier to maintain and safer to ship.
|
The current priority is not missing core features. The current priority is tightening consistency, production configuration, homepage/catalog alignment, and documentation so the implemented work is easier to maintain and safer to ship.
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
| **OpenGraph tags** | `og:title`, `og:description`, `og:url`, `og:type`, `og:site_name`, `og:locale` on all pages |
|
| **OpenGraph tags** | `og:title`, `og:description`, `og:url`, `og:type`, `og:site_name`, `og:locale` on all pages |
|
||||||
| **Twitter cards** | `twitter:card`, `twitter:title`, `twitter:description` on all pages |
|
| **Twitter cards** | `twitter:card`, `twitter:title`, `twitter:description` on all pages |
|
||||||
| **Structured data** | `WebSite`, `Organization`, `WebPage`, `WebApplication`, `BreadcrumbList`, `FAQPage` JSON-LD |
|
| **Structured data** | `WebSite`, `Organization`, `WebPage`, `WebApplication`, `BreadcrumbList`, `FAQPage` JSON-LD |
|
||||||
| **Sitemap** | Auto-generated via `scripts/generate_sitemap.py` — 37 URLs (5 pages + 32 tools) |
|
| **Sitemap** | Auto-generated via frontend SEO scripts — current committed snapshot contains 245 URLs across static pages, blog posts, tool routes, and programmatic SEO pages |
|
||||||
| **robots.txt** | Allows all crawlers; blocks `/api/`, `/account`, auth pages |
|
| **robots.txt** | Allows all crawlers; blocks `/api/`, `/account`, auth pages |
|
||||||
| **Internationalization** | Full i18n in EN, AR, FR — all tool pages, SEO content, and static pages |
|
| **Internationalization** | Full i18n in EN, AR, FR — all tool pages, SEO content, and static pages |
|
||||||
| **Font loading** | `dns-prefetch` + `preconnect` + `display=swap` for Google Fonts |
|
| **Font loading** | `dns-prefetch` + `preconnect` + `display=swap` for Google Fonts |
|
||||||
@@ -58,7 +58,7 @@ VITE_GOOGLE_SITE_VERIFICATION=your-verification-code
|
|||||||
|
|
||||||
## 3. SEO Content Architecture
|
## 3. SEO Content Architecture
|
||||||
|
|
||||||
### 3.1 Tool Landing Pages (32 pages)
|
### 3.1 Tool Landing Pages (44 direct tool routes + programmatic SEO pages)
|
||||||
|
|
||||||
Each tool page (`/tools/{slug}`) renders via `ToolLandingPage` wrapper:
|
Each tool page (`/tools/{slug}`) renders via `ToolLandingPage` wrapper:
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ All text is i18n-driven from `seo.{toolKey}.*` keys in EN/AR/FR.
|
|||||||
|------|--------|---------|
|
|------|--------|---------|
|
||||||
| `/` | `WebSite` + `Organization` | Homepage with hero + tool grid |
|
| `/` | `WebSite` + `Organization` | Homepage with hero + tool grid |
|
||||||
| `/about` | `WebPage` | Mission, technology, security |
|
| `/about` | `WebPage` | Mission, technology, security |
|
||||||
| `/contact` | `WebPage` | Contact form (mailto-based) |
|
| `/contact` | `WebPage` | Contact form backed by `/api/contact/submit` plus email fallback |
|
||||||
| `/privacy` | `WebPage` | Privacy policy |
|
| `/privacy` | `WebPage` | Privacy policy |
|
||||||
| `/terms` | `WebPage` | Terms of service |
|
| `/terms` | `WebPage` | Terms of service |
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ All text is i18n-driven from `seo.{toolKey}.*` keys in EN/AR/FR.
|
|||||||
|
|
||||||
| File | Purpose |
|
| File | Purpose |
|
||||||
|------|---------|
|
|------|---------|
|
||||||
| `sitemap.xml` | All 37 indexable URLs with priority and changefreq |
|
| `sitemap.xml` | Current public snapshot includes 245 indexable URLs with priority and changefreq values |
|
||||||
| `robots.txt` | Crawler directives + sitemap pointer |
|
| `robots.txt` | Crawler directives + sitemap pointer |
|
||||||
| `llms.txt` | AI/LLM discoverability file |
|
| `llms.txt` | AI/LLM discoverability file |
|
||||||
| `humans.txt` | Team credits |
|
| `humans.txt` | Team credits |
|
||||||
@@ -106,7 +106,7 @@ All text is i18n-driven from `seo.{toolKey}.*` keys in EN/AR/FR.
|
|||||||
- [ ] Verify Core Web Vitals pass (LCP < 2.5s, FID < 100ms, CLS < 0.1)
|
- [ ] Verify Core Web Vitals pass (LCP < 2.5s, FID < 100ms, CLS < 0.1)
|
||||||
|
|
||||||
**Content:**
|
**Content:**
|
||||||
- [ ] Publish all 32 tool pages with full SEO content
|
- [ ] Publish and maintain the 44 direct tool pages with full SEO content
|
||||||
- [ ] Ensure hreflang tags work across EN/AR/FR (add `hreflang` links if using subdomains or subdirectories)
|
- [ ] Ensure hreflang tags work across EN/AR/FR (add `hreflang` links if using subdomains or subdirectories)
|
||||||
- [ ] Add FAQ schema to all tool pages (already done)
|
- [ ] Add FAQ schema to all tool pages (already done)
|
||||||
|
|
||||||
@@ -209,7 +209,7 @@ All text is i18n-driven from `seo.{toolKey}.*` keys in EN/AR/FR.
|
|||||||
**Key differentiators for Dociva:**
|
**Key differentiators for Dociva:**
|
||||||
1. **Trilingual** — EN/AR/FR from day one (Arabic market is largely unserved)
|
1. **Trilingual** — EN/AR/FR from day one (Arabic market is largely unserved)
|
||||||
2. **No signup** — zero friction, instant file processing
|
2. **No signup** — zero friction, instant file processing
|
||||||
3. **32 tools** — broader coverage than most competitors
|
3. **44 direct tool routes** — broad product coverage with room to surface more of the catalog on the homepage
|
||||||
4. **AI-powered tools** — OCR, Chat PDF, Summarize, Translate (unique value)
|
4. **AI-powered tools** — OCR, Chat PDF, Summarize, Translate (unique value)
|
||||||
5. **Privacy-first** — files auto-deleted within 30 minutes
|
5. **Privacy-first** — files auto-deleted within 30 minutes
|
||||||
|
|
||||||
@@ -219,7 +219,7 @@ All text is i18n-driven from `seo.{toolKey}.*` keys in EN/AR/FR.
|
|||||||
|
|
||||||
```
|
```
|
||||||
□ Review Search Console for crawl errors and fix immediately
|
□ Review Search Console for crawl errors and fix immediately
|
||||||
□ Check index coverage — ensure all 37+ pages are indexed
|
□ Check index coverage — ensure the current sitemap inventory is being indexed as expected
|
||||||
□ Review top queries — identify rising keywords to create content for
|
□ Review top queries — identify rising keywords to create content for
|
||||||
□ Publish 8–16 blog posts (2–4/week × 3 languages)
|
□ Publish 8–16 blog posts (2–4/week × 3 languages)
|
||||||
□ Build 5–10 backlinks through outreach
|
□ Build 5–10 backlinks through outreach
|
||||||
|
|||||||
@@ -1,274 +1,328 @@
|
|||||||
# Dociva — Tool Inventory & Competitive Gap Analysis
|
# Dociva — Current Tool Inventory & Competitive Gap Analysis
|
||||||
|
|
||||||
> Generated: March 7, 2026
|
> Updated: March 25, 2026
|
||||||
> Branch: `feature/critical-maintenance-and-editor`
|
> Source of truth: current code in `frontend/src/App.tsx`, `frontend/src/pages/HomePage.tsx`, `backend/app/routes`, and `backend/app/routes/v1/tools.py`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Platform Infrastructure
|
## 1. Current Platform Snapshot
|
||||||
|
|
||||||
| Component | Technology | Status |
|
| Area | Current state |
|
||||||
|---|---|---|
|
|---|---|
|
||||||
| Backend | Flask + Gunicorn | ✅ Production-ready |
|
| Backend | Flask + Gunicorn + Celery + Redis |
|
||||||
| Frontend | React + Vite + TypeScript + Tailwind | ✅ Production-ready |
|
| Frontend | React + Vite + TypeScript + Tailwind |
|
||||||
| Task Queue | Celery + Redis | ✅ 3 queues (default, image, pdf_tools) |
|
| Storage | Local filesystem + optional S3 |
|
||||||
| Scheduler | Celery Beat | ✅ Expired-file cleanup every 30 min |
|
| Accounts | Session auth + usage history + API keys |
|
||||||
| Database | SQLite | ✅ Users, API keys, history, usage events |
|
| Monetization | Ad slots + pricing page + Stripe subscription plumbing |
|
||||||
| Storage | Local + S3 (optional) | ✅ Presigned URLs |
|
| API | `/api/v1/*` B2B surface for a significant subset of tools |
|
||||||
| Auth | Session-based + API Key (B2B) | ✅ Free & Pro plans |
|
| i18n | English, Arabic, French |
|
||||||
| Security | Talisman CSP, rate limiting, CORS, input sanitization | ✅ |
|
| SEO | Tool landing pages + structured data + generated public SEO files |
|
||||||
| i18n | react-i18next (en, ar, fr) | ✅ All tools translated |
|
|
||||||
| Monetization | Google AdSense slots | ✅ Integrated |
|
|
||||||
| Email | SMTP (password reset) | ✅ |
|
|
||||||
| Docker | docker-compose (dev + prod) | ✅ |
|
|
||||||
| Nginx | Reverse proxy + SSL | ✅ |
|
|
||||||
|
|
||||||
### Plans & Quotas
|
### Operational counts
|
||||||
|
|
||||||
| | Free | Pro |
|
|
||||||
|---|---|---|
|
|
||||||
| Web requests/month | 50 | 500 |
|
|
||||||
| API requests/month | — | 1,000 |
|
|
||||||
| Max file size | 50 MB | 100 MB |
|
|
||||||
| History retention | 25 | 250 |
|
|
||||||
| API key access | ❌ | ✅ |
|
|
||||||
|
|
||||||
### Registered Blueprints: 18
|
|
||||||
|
|
||||||
| Blueprint | Prefix | Purpose |
|
|
||||||
|---|---|---|
|
|
||||||
| `health_bp` | `/api` | Health check |
|
|
||||||
| `auth_bp` | `/api/auth` | Login, register, forgot/reset password |
|
|
||||||
| `account_bp` | `/api/account` | Profile, API keys, usage |
|
|
||||||
| `admin_bp` | `/api/internal/admin` | Plan management |
|
|
||||||
| `convert_bp` | `/api/convert` | PDF ↔ Word |
|
|
||||||
| `compress_bp` | `/api/compress` | PDF compression |
|
|
||||||
| `image_bp` | `/api/image` | Image convert & resize |
|
|
||||||
| `video_bp` | `/api/video` | Video to GIF |
|
|
||||||
| `history_bp` | `/api` | User history |
|
|
||||||
| `pdf_tools_bp` | `/api/pdf-tools` | Merge, split, rotate, watermark, etc. |
|
|
||||||
| `flowchart_bp` | `/api/flowchart` | AI flowchart extraction |
|
|
||||||
| `tasks_bp` | `/api/tasks` | Task status polling |
|
|
||||||
| `download_bp` | `/api/download` | Secure file download |
|
|
||||||
| `v1_bp` | `/api/v1` | B2B API (all tools) |
|
|
||||||
| `config_bp` | `/api/config` | Dynamic limits |
|
|
||||||
| `ocr_bp` | `/api/ocr` | OCR text extraction |
|
|
||||||
| `removebg_bp` | `/api/remove-bg` | Background removal |
|
|
||||||
| `pdf_editor_bp` | `/api/pdf-editor` | PDF text annotations |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Existing Tools — Complete Inventory (21 tools)
|
|
||||||
|
|
||||||
### 2.1 PDF Tools (14)
|
|
||||||
|
|
||||||
| # | Tool | Endpoint | Service | Task | Component | Route | i18n | B2B API |
|
|
||||||
|---|---|---|---|---|---|---|---|---|
|
|
||||||
| 1 | **Compress PDF** | `POST /api/compress/pdf` | `compress_service` | `compress_pdf_task` | `PdfCompressor.tsx` | `/tools/compress-pdf` | ✅ | ✅ |
|
|
||||||
| 2 | **PDF to Word** | `POST /api/convert/pdf-to-word` | `pdf_service` | `convert_pdf_to_word` | `PdfToWord.tsx` | `/tools/pdf-to-word` | ✅ | ✅ |
|
|
||||||
| 3 | **Word to PDF** | `POST /api/convert/word-to-pdf` | `pdf_service` | `convert_word_to_pdf` | `WordToPdf.tsx` | `/tools/word-to-pdf` | ✅ | ✅ |
|
|
||||||
| 4 | **Merge PDF** | `POST /api/pdf-tools/merge` | `pdf_tools_service` | `merge_pdfs_task` | `MergePdf.tsx` | `/tools/merge-pdf` | ✅ | ✅ |
|
|
||||||
| 5 | **Split PDF** | `POST /api/pdf-tools/split` | `pdf_tools_service` | `split_pdf_task` | `SplitPdf.tsx` | `/tools/split-pdf` | ✅ | ✅ |
|
|
||||||
| 6 | **Rotate PDF** | `POST /api/pdf-tools/rotate` | `pdf_tools_service` | `rotate_pdf_task` | `RotatePdf.tsx` | `/tools/rotate-pdf` | ✅ | ✅ |
|
|
||||||
| 7 | **PDF to Images** | `POST /api/pdf-tools/pdf-to-images` | `pdf_tools_service` | `pdf_to_images_task` | `PdfToImages.tsx` | `/tools/pdf-to-images` | ✅ | ✅ |
|
|
||||||
| 8 | **Images to PDF** | `POST /api/pdf-tools/images-to-pdf` | `pdf_tools_service` | `images_to_pdf_task` | `ImagesToPdf.tsx` | `/tools/images-to-pdf` | ✅ | ✅ |
|
|
||||||
| 9 | **Watermark PDF** | `POST /api/pdf-tools/watermark` | `pdf_tools_service` | `watermark_pdf_task` | `WatermarkPdf.tsx` | `/tools/watermark-pdf` | ✅ | ✅ |
|
|
||||||
| 10 | **Protect PDF** | `POST /api/pdf-tools/protect` | `pdf_tools_service` | `protect_pdf_task` | `ProtectPdf.tsx` | `/tools/protect-pdf` | ✅ | ✅ |
|
|
||||||
| 11 | **Unlock PDF** | `POST /api/pdf-tools/unlock` | `pdf_tools_service` | `unlock_pdf_task` | `UnlockPdf.tsx` | `/tools/unlock-pdf` | ✅ | ✅ |
|
|
||||||
| 12 | **Add Page Numbers** | `POST /api/pdf-tools/page-numbers` | `pdf_tools_service` | `add_page_numbers_task` | `AddPageNumbers.tsx` | `/tools/page-numbers` | ✅ | ✅ |
|
|
||||||
| 13 | **PDF Editor** | `POST /api/pdf-editor/edit` | `pdf_editor_service` | `edit_pdf_task` | `PdfEditor.tsx` | `/tools/pdf-editor` | ✅ | ❌ |
|
|
||||||
| 14 | **PDF Flowchart** | `POST /api/flowchart/extract` + 3 | `flowchart_service` | `extract_flowchart_task` | `PdfFlowchart.tsx` | `/tools/pdf-flowchart` | ✅ | ✅ |
|
|
||||||
|
|
||||||
### 2.2 Image Tools (4)
|
|
||||||
|
|
||||||
| # | Tool | Endpoint | Service | Task | Component | Route | i18n | B2B API |
|
|
||||||
|---|---|---|---|---|---|---|---|---|
|
|
||||||
| 15 | **Image Converter** | `POST /api/image/convert` | `image_service` | `convert_image_task` | `ImageConverter.tsx` | `/tools/image-converter` | ✅ | ✅ |
|
|
||||||
| 16 | **Image Resize** | `POST /api/image/resize` | `image_service` | `resize_image_task` | `ImageResize.tsx` | `/tools/image-resize` | ✅ | ✅ |
|
|
||||||
| 17 | **OCR** | `POST /api/ocr/image` + `/pdf` | `ocr_service` | `ocr_image_task` / `ocr_pdf_task` | `OcrTool.tsx` | `/tools/ocr` | ✅ | ❌ |
|
|
||||||
| 18 | **Remove Background** | `POST /api/remove-bg` | `removebg_service` | `remove_bg_task` | `RemoveBackground.tsx` | `/tools/remove-background` | ✅ | ❌ |
|
|
||||||
|
|
||||||
### 2.3 Video Tools (1)
|
|
||||||
|
|
||||||
| # | Tool | Endpoint | Service | Task | Component | Route | i18n | B2B API |
|
|
||||||
|---|---|---|---|---|---|---|---|---|
|
|
||||||
| 19 | **Video to GIF** | `POST /api/video/to-gif` | `video_service` | `create_gif_task` | `VideoToGif.tsx` | `/tools/video-to-gif` | ✅ | ✅ |
|
|
||||||
|
|
||||||
### 2.4 Text Tools — Client-Side Only (2)
|
|
||||||
|
|
||||||
| # | Tool | Backend | Component | Route | i18n |
|
|
||||||
|---|---|---|---|---|---|
|
|
||||||
| 20 | **Word Counter** | None (JS) | `WordCounter.tsx` | `/tools/word-counter` | ✅ |
|
|
||||||
| 21 | **Text Cleaner** | None (JS) | `TextCleaner.tsx` | `/tools/text-cleaner` | ✅ |
|
|
||||||
|
|
||||||
### Feature Flags
|
|
||||||
|
|
||||||
| Flag | Default | Controls |
|
|
||||||
|---|---|---|
|
|
||||||
| `FEATURE_EDITOR` | `false` | OCR, Remove Background, PDF Editor routes (403 when off) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Test Coverage
|
|
||||||
|
|
||||||
| Category | Test Files | Tests |
|
|
||||||
|---|---|---|
|
|
||||||
| Auth | `test_auth.py` | 5 |
|
|
||||||
| Config | `test_config.py` | 3 |
|
|
||||||
| Password reset | `test_password_reset.py` | 8 |
|
|
||||||
| Maintenance | `test_maintenance_tasks.py` | 8 |
|
|
||||||
| Compress | `test_compress.py`, `test_compress_service.py`, `test_compress_tasks.py` | 6 |
|
|
||||||
| Convert | `test_convert.py`, `test_convert_tasks.py` | 6 |
|
|
||||||
| Image | `test_image.py`, `test_image_service.py`, `test_image_tasks.py` | ~18 |
|
|
||||||
| Video | `test_video.py`, `test_video_service.py`, `test_video_tasks.py` | ~12 |
|
|
||||||
| PDF tools | `test_pdf_tools.py`, `test_pdf_tools_service.py`, `test_pdf_tools_tasks.py` | ~50 |
|
|
||||||
| Flowchart | `test_flowchart_tasks.py` | ~6 |
|
|
||||||
| OCR | `test_ocr.py`, `test_ocr_service.py` | 12 |
|
|
||||||
| Remove BG | `test_removebg.py` | 3 |
|
|
||||||
| PDF Editor | `test_pdf_editor.py` | 7 |
|
|
||||||
| Infra | `test_download.py`, `test_health.py`, `test_history.py`, `test_rate_limiter.py`, `test_sanitizer.py`, `test_storage_service.py`, `test_file_validator.py`, `test_utils.py`, `test_tasks_route.py` | ~36 |
|
|
||||||
| **TOTAL** | **30 files** | **180 ✅** |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Missing Tools — Competitive Gap Analysis
|
|
||||||
|
|
||||||
Comparison against: iLovePDF, SmallPDF, TinyWow, PDF24, Adobe Acrobat Online.
|
|
||||||
|
|
||||||
### 4.1 HIGH PRIORITY — Core tools competitors all have
|
|
||||||
|
|
||||||
| # | Tool | Category | Complexity | Dependencies | Notes |
|
|
||||||
|---|---|---|---|---|---|
|
|
||||||
| 1 | **Compress Image** | Image | Low | Pillow (exists) | JPEG/PNG/WebP quality reduction + resize. Pillow already installed. |
|
|
||||||
| 2 | **PDF to Excel** | PDF → Office | Medium | `camelot-py` or `tabula-py` | Table extraction from PDFs — high user demand. |
|
|
||||||
| 3 | **PDF to PowerPoint** | PDF → Office | Medium | `python-pptx` | Convert PDF pages to PPTX slides (images per slide or OCR). |
|
|
||||||
| 4 | **Excel to PDF** | Office → PDF | Medium | LibreOffice CLI | Same pattern as Word to PDF. |
|
|
||||||
| 5 | **PowerPoint to PDF** | Office → PDF | Medium | LibreOffice CLI | Same pattern as Word to PDF. |
|
|
||||||
| 6 | **HTML to PDF** | Web → PDF | Low | `weasyprint` or `playwright` | Input URL or HTML snippet → PDF. |
|
|
||||||
| 7 | **Reorder / Rearrange Pages** | PDF | Low | PyPDF2 (exists) | Drag-and-drop page reorder UI → backend rebuilds PDF. |
|
|
||||||
| 8 | **Extract Pages** | PDF | Low | PyPDF2 (exists) | Similar to Split but with visual page picker. Already partially covered by Split tool. |
|
|
||||||
| 9 | **Sign PDF** | PDF | Medium | ReportLab + canvas | Draw/upload signature → overlay onto PDF page. |
|
|
||||||
| 10 | **PDF Repair** | PDF | Low | PyPDF2 (exists) | Read → rewrite to fix broken xref tables. |
|
|
||||||
|
|
||||||
### 4.2 MEDIUM PRIORITY — Differentiators present on 2–3 competitors
|
|
||||||
|
|
||||||
| # | Tool | Category | Complexity | Dependencies | Notes |
|
|
||||||
|---|---|---|---|---|---|
|
|
||||||
| 11 | **PDF to PDF/A** | PDF | Medium | Ghostscript (exists) | Archival format conversion. |
|
|
||||||
| 12 | **Flatten PDF** | PDF | Low | PyPDF2 (exists) | Remove form fields / annotations → flat page. |
|
|
||||||
| 13 | **Crop PDF** | PDF | Medium | PyPDF2 (exists) | Crop margins / adjust page boundaries. |
|
|
||||||
| 14 | **Compare PDFs** | PDF | High | `diff-match-patch` + PyPDF2 | Side-by-side visual diff of two documents. |
|
|
||||||
| 15 | **QR Code Generator** | Utility | Low | `qrcode` + Pillow | Text/URL → QR image. Client-side possible but backend for API. |
|
|
||||||
| 16 | **Barcode Generator** | Utility | Low | `python-barcode` | Generate Code128, EAN, UPC barcodes. |
|
|
||||||
| 17 | **Image Crop** | Image | Low | Pillow (exists) | Visual cropping UI → backend Pillow crop. |
|
|
||||||
| 18 | **Image Rotate / Flip** | Image | Low | Pillow (exists) | 90°/180°/270° + horizontal/vertical flip. |
|
|
||||||
| 19 | **Image Filters** | Image | Low | Pillow (exists) | Grayscale, sepia, blur, sharpen, brightness, contrast. |
|
|
||||||
|
|
||||||
### 4.3 LOW PRIORITY — Advanced / niche (1–2 competitors, premium features)
|
|
||||||
|
|
||||||
| # | Tool | Category | Complexity | Dependencies | Notes |
|
|
||||||
|---|---|---|---|---|---|
|
|
||||||
| 20 | **AI Chat with PDF** | AI | High | OpenRouter (exists) | Upload PDF → ask questions. Flowchart service has partial foundation. |
|
|
||||||
| 21 | **AI PDF Summarizer** | AI | Medium | OpenRouter (exists) | Extract text → prompt LLM for summary. |
|
|
||||||
| 22 | **AI PDF Translator** | AI | Medium | OpenRouter (exists) | Extract text → translate via LLM → overlay or return translated doc. |
|
|
||||||
| 23 | **PDF Form Filler** | PDF | High | ReportLab + PyPDF2 | Detect form fields → UI to fill → save. |
|
|
||||||
| 24 | **Redact PDF** | PDF | Medium | ReportLab + PyPDF2 | Blackout sensitive text regions. |
|
|
||||||
| 25 | **PDF Metadata Editor** | PDF | Low | PyPDF2 (exists) | Edit title, author, subject, keywords. |
|
|
||||||
| 26 | **eSign / Digital Signature** | PDF | High | `cryptography` + PKCS#7 | Cryptographic digital signatures (different from visual sign). |
|
|
||||||
| 27 | **Batch Processing** | All | Medium | Existing tasks | Upload multiple files → apply same operation to all. |
|
|
||||||
| 28 | **GIF to Video** | Video | Medium | ffmpeg (exists) | Reverse of Video to GIF. |
|
|
||||||
| 29 | **Video Compress** | Video | Medium | ffmpeg (exists) | Reduce video file size. |
|
|
||||||
| 30 | **Audio Extract** | Video | Low | ffmpeg (exists) | Extract audio track from video → MP3/WAV. |
|
|
||||||
| 31 | **Screenshot to PDF** | Utility | Low | Pillow (exists) | Paste screenshot → generate PDF (similar to Images to PDF). |
|
|
||||||
| 32 | **Markdown to PDF** | Utility | Low | `markdown` + WeasyPrint | Render Markdown → PDF. |
|
|
||||||
| 33 | **JSON / CSV Viewer** | Utility | Low | Client-side | Pretty-print structured data. |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Implementation Readiness Matrix
|
|
||||||
|
|
||||||
Tools grouped by effort required (backend dependencies already present in the project):
|
|
||||||
|
|
||||||
### Ready to build (dependencies exist: PyPDF2, Pillow, Ghostscript, ffmpeg)
|
|
||||||
|
|
||||||
| Tool | Effort | Reuses |
|
|
||||||
|---|---|---|
|
|
||||||
| Compress Image | ~2h | `image_service.py` + Pillow |
|
|
||||||
| Reorder Pages | ~3h | `pdf_tools_service.py` + PyPDF2 |
|
|
||||||
| Extract Pages | ~2h | Split tool pattern |
|
|
||||||
| PDF Repair | ~2h | PyPDF2 read/write |
|
|
||||||
| Flatten PDF | ~2h | PyPDF2 |
|
|
||||||
| Crop PDF | ~3h | PyPDF2 MediaBox |
|
|
||||||
| Image Crop | ~2h | Pillow |
|
|
||||||
| Image Rotate/Flip | ~2h | Pillow |
|
|
||||||
| Image Filters | ~3h | Pillow ImageFilter |
|
|
||||||
| PDF Metadata Editor | ~2h | PyPDF2 |
|
|
||||||
| PDF to PDF/A | ~2h | Ghostscript (exists in Dockerfile) |
|
|
||||||
| QR Code Generator | ~2h | `qrcode` pip package |
|
|
||||||
| AI PDF Summarizer | ~3h | `ai_chat_service.py` + OpenRouter |
|
|
||||||
| GIF to Video | ~2h | ffmpeg |
|
|
||||||
| Audio Extract | ~2h | ffmpeg |
|
|
||||||
|
|
||||||
### Need new dependencies (1 pip package)
|
|
||||||
|
|
||||||
| Tool | New Dependency | Effort |
|
|
||||||
|---|---|---|
|
|
||||||
| PDF to Excel | `camelot-py[cv]` or `tabula-py` | ~4h |
|
|
||||||
| PDF to PowerPoint | `python-pptx` | ~4h |
|
|
||||||
| Excel to PDF | LibreOffice CLI (exists) | ~3h |
|
|
||||||
| PowerPoint to PDF | LibreOffice CLI (exists) | ~3h |
|
|
||||||
| HTML to PDF | `weasyprint` or `playwright` | ~4h |
|
|
||||||
| Sign PDF | ReportLab (exists) + canvas overlay | ~6h |
|
|
||||||
| Barcode Generator | `python-barcode` | ~2h |
|
|
||||||
| Markdown to PDF | `markdown` + `weasyprint` | ~3h |
|
|
||||||
|
|
||||||
### Requires significant new architecture
|
|
||||||
|
|
||||||
| Tool | Complexity | Effort |
|
|
||||||
|---|---|---|
|
|
||||||
| AI Chat with PDF | RAG pipeline or full-doc prompt | ~8h |
|
|
||||||
| AI PDF Translator | OCR + LLM + overlay | ~8h |
|
|
||||||
| PDF Form Filler | Field detection + fill engine | ~10h |
|
|
||||||
| Redact PDF | Region detection + blackout overlay | ~6h |
|
|
||||||
| Compare PDFs | Diff algorithm + visual rendering | ~10h |
|
|
||||||
| eSign / Digital Signature | PKCS#7 cryptographic signing | ~10h |
|
|
||||||
| Batch Processing | Queue orchestration for multi-file | ~6h |
|
|
||||||
| Video Compress | ffmpeg transcoding | ~4h |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Summary
|
|
||||||
|
|
||||||
| Metric | Count |
|
| Metric | Count |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **Existing tools** | 21 |
|
| Registered blueprints | 33 |
|
||||||
| **Missing HIGH priority** | 10 |
|
| Backend route modules | 33 |
|
||||||
| **Missing MEDIUM priority** | 9 |
|
| Backend service modules | 32 |
|
||||||
| **Missing LOW priority** | 14 |
|
| Backend task modules | 20 |
|
||||||
| **Total gap** | 33 |
|
| Frontend tool components | 44 |
|
||||||
| **Backend tests** | 180 ✅ |
|
| Direct frontend tool routes | 44 |
|
||||||
| **Frontend build** | ✅ Clean |
|
| Homepage tool cards surfaced | 33 |
|
||||||
| **Blueprints** | 18 |
|
| Backend test files | 38 |
|
||||||
| **Celery task modules** | 10 |
|
| Frontend test files | 6 |
|
||||||
| **Service files** | 15 |
|
|
||||||
| **i18n languages** | 3 (en, ar, fr) |
|
|
||||||
|
|
||||||
### Competitor Parity Score
|
### Plans & quotas
|
||||||
|
|
||||||
| Competitor | Their tools | We match | Coverage |
|
From `backend/app/services/policy_service.py`:
|
||||||
|---|---|---|---|
|
|
||||||
| iLovePDF | ~25 core | ~16 | 64% |
|
|
||||||
| SmallPDF | ~21 core | ~15 | 71% |
|
|
||||||
| TinyWow | ~50+ (many AI) | ~14 | 28% |
|
|
||||||
| PDF24 | ~30 core | ~17 | 57% |
|
|
||||||
|
|
||||||
### Recommended Next Sprint
|
| Plan | Web requests/month | API requests/month | History retention | Effective upload limits |
|
||||||
|
|---|---:|---:|---:|---|
|
||||||
|
| Free | 50 | — | 25 | Base file-size limits from backend config |
|
||||||
|
| Pro | 500 | 1,000 | 250 | 2x backend file-size limits |
|
||||||
|
|
||||||
**Highest ROI — 6 tools to reach 80%+ parity with SmallPDF/iLovePDF:**
|
---
|
||||||
|
|
||||||
1. Compress Image (Pillow — already installed)
|
## 2. Current Tool Inventory — 44 Direct Tool Routes
|
||||||
2. PDF to Excel (`camelot-py`)
|
|
||||||
3. HTML to PDF (`weasyprint`)
|
These are the currently wired tool routes in `frontend/src/App.tsx`.
|
||||||
4. Sign PDF (ReportLab overlay)
|
|
||||||
5. Reorder Pages (PyPDF2 — already installed)
|
### 2.1 Core PDF Tools (14)
|
||||||
6. PDF to PowerPoint (`python-pptx`)
|
|
||||||
|
1. Compress PDF
|
||||||
|
2. PDF to Word
|
||||||
|
3. Word to PDF
|
||||||
|
4. Merge PDF
|
||||||
|
5. Split PDF
|
||||||
|
6. Rotate PDF
|
||||||
|
7. PDF to Images
|
||||||
|
8. Images to PDF
|
||||||
|
9. Watermark PDF
|
||||||
|
10. Protect PDF
|
||||||
|
11. Unlock PDF
|
||||||
|
12. Add Page Numbers
|
||||||
|
13. PDF Editor
|
||||||
|
14. PDF Flowchart
|
||||||
|
|
||||||
|
### 2.2 Image Tools (6)
|
||||||
|
|
||||||
|
15. Image Converter
|
||||||
|
16. Image Resize
|
||||||
|
17. Compress Image
|
||||||
|
18. OCR
|
||||||
|
19. Remove Background
|
||||||
|
20. Image to SVG
|
||||||
|
|
||||||
|
### 2.3 Conversion Tools (2)
|
||||||
|
|
||||||
|
21. PDF to Excel
|
||||||
|
22. HTML to PDF
|
||||||
|
|
||||||
|
### 2.4 PDF Extra Tools (3)
|
||||||
|
|
||||||
|
23. Remove Watermark
|
||||||
|
24. Reorder PDF
|
||||||
|
25. Extract Pages
|
||||||
|
|
||||||
|
### 2.5 AI Tools (4)
|
||||||
|
|
||||||
|
26. Chat with PDF
|
||||||
|
27. Summarize PDF
|
||||||
|
28. Translate PDF
|
||||||
|
29. Extract Tables
|
||||||
|
|
||||||
|
### 2.6 Utility / Other Tools (1)
|
||||||
|
|
||||||
|
30. QR Code Generator
|
||||||
|
|
||||||
|
### 2.7 Video Tools (1)
|
||||||
|
|
||||||
|
31. Video to GIF
|
||||||
|
|
||||||
|
### 2.8 Text Tools (2)
|
||||||
|
|
||||||
|
32. Word Counter
|
||||||
|
33. Text Cleaner
|
||||||
|
|
||||||
|
### 2.9 Phase 2 — PDF Conversion (4)
|
||||||
|
|
||||||
|
34. PDF to PowerPoint
|
||||||
|
35. Excel to PDF
|
||||||
|
36. PowerPoint to PDF
|
||||||
|
37. Sign PDF
|
||||||
|
|
||||||
|
### 2.10 Phase 2 — PDF Extra (4)
|
||||||
|
|
||||||
|
38. Crop PDF
|
||||||
|
39. Flatten PDF
|
||||||
|
40. Repair PDF
|
||||||
|
41. PDF Metadata Editor
|
||||||
|
|
||||||
|
### 2.11 Phase 2 — Image & Utility (3)
|
||||||
|
|
||||||
|
42. Image Crop
|
||||||
|
43. Image Rotate / Flip
|
||||||
|
44. Barcode Generator
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Homepage Coverage vs Full Catalog
|
||||||
|
|
||||||
|
The codebase exposes **44** tool routes, but the homepage currently surfaces **33** tools.
|
||||||
|
|
||||||
|
### Surfaced on homepage
|
||||||
|
|
||||||
|
The homepage includes the core catalog and several growth-focused tools, including:
|
||||||
|
|
||||||
|
- Core PDF tools
|
||||||
|
- Core image tools
|
||||||
|
- OCR / remove background
|
||||||
|
- PDF to Excel
|
||||||
|
- HTML to PDF
|
||||||
|
- Chat / summarize / translate / table extraction
|
||||||
|
- QR code
|
||||||
|
- Video to GIF
|
||||||
|
- Word counter / text cleaner
|
||||||
|
|
||||||
|
### Implemented but not surfaced on homepage
|
||||||
|
|
||||||
|
The following routes exist but are not currently represented on the homepage tool grid:
|
||||||
|
|
||||||
|
- PDF to PowerPoint
|
||||||
|
- Excel to PDF
|
||||||
|
- PowerPoint to PDF
|
||||||
|
- Sign PDF
|
||||||
|
- Crop PDF
|
||||||
|
- Flatten PDF
|
||||||
|
- Repair PDF
|
||||||
|
- PDF Metadata Editor
|
||||||
|
- Image Crop
|
||||||
|
- Image Rotate / Flip
|
||||||
|
- Barcode Generator
|
||||||
|
|
||||||
|
This is an important product and growth gap: implementation breadth is currently ahead of homepage discovery.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Backend / API Coverage
|
||||||
|
|
||||||
|
### Web app coverage
|
||||||
|
|
||||||
|
The web app has direct frontend routes for all 44 tools listed above.
|
||||||
|
|
||||||
|
### B2B API coverage
|
||||||
|
|
||||||
|
`backend/app/routes/v1/tools.py` exposes a substantial subset of the platform through `/api/v1/*`, including:
|
||||||
|
|
||||||
|
- PDF conversion and compression
|
||||||
|
- Major PDF tools
|
||||||
|
- Image conversion and resize
|
||||||
|
- Video to GIF
|
||||||
|
- OCR
|
||||||
|
- Remove background
|
||||||
|
- AI PDF workflows
|
||||||
|
- PDF to Excel
|
||||||
|
- HTML to PDF
|
||||||
|
- QR and barcode generation
|
||||||
|
- PDF to PowerPoint / Excel to PDF / PowerPoint to PDF
|
||||||
|
- Sign PDF
|
||||||
|
- Crop / flatten / repair / metadata
|
||||||
|
- Image crop / image rotate-flip
|
||||||
|
|
||||||
|
This means the platform is no longer only a consumer-facing tools site; it already has real API-product potential.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Test & Quality Snapshot
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
|
||||||
|
- `38` backend test files currently exist under `backend/tests`
|
||||||
|
- Sample backend verification run on March 25, 2026:
|
||||||
|
- `backend/tests/test_file_validator.py`
|
||||||
|
- `backend/tests/test_html_to_pdf.py`
|
||||||
|
- `backend/tests/test_auth.py`
|
||||||
|
- `backend/tests/test_history.py`
|
||||||
|
- Result: `24/24` tests passed
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
|
||||||
|
- `6` frontend test files currently exist under `frontend/src`
|
||||||
|
- Verified on March 25, 2026:
|
||||||
|
- Result: `64/64` tests passed
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
|
- Frontend production build passes successfully
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. What Is No Longer Missing
|
||||||
|
|
||||||
|
The earlier inventory marked many of the following as gaps. They are now implemented in the current codebase:
|
||||||
|
|
||||||
|
- Compress Image
|
||||||
|
- PDF to Excel
|
||||||
|
- PDF to PowerPoint
|
||||||
|
- Excel to PDF
|
||||||
|
- PowerPoint to PDF
|
||||||
|
- HTML to PDF
|
||||||
|
- Reorder PDF Pages
|
||||||
|
- Extract Pages
|
||||||
|
- Sign PDF
|
||||||
|
- PDF Repair
|
||||||
|
- Flatten PDF
|
||||||
|
- Crop PDF
|
||||||
|
- QR Code Generator
|
||||||
|
- Barcode Generator
|
||||||
|
- Image Crop
|
||||||
|
- Image Rotate / Flip
|
||||||
|
- AI Chat with PDF
|
||||||
|
- PDF Summarizer
|
||||||
|
- PDF Translator
|
||||||
|
- Table Extractor
|
||||||
|
- PDF Metadata Editor
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Remaining Competitive Gaps
|
||||||
|
|
||||||
|
Compared against iLovePDF, Smallpdf, PDF24, TinyWow, and Adobe Acrobat online flows, the main remaining gaps are now more focused.
|
||||||
|
|
||||||
|
### High-value remaining gaps
|
||||||
|
|
||||||
|
1. PDF comparison / diff
|
||||||
|
2. PDF to PDF/A
|
||||||
|
3. Batch processing workflows
|
||||||
|
4. True cryptographic digital signature / eSign
|
||||||
|
5. PDF form filler
|
||||||
|
6. PDF redaction
|
||||||
|
7. Video compression
|
||||||
|
8. Audio extraction from video
|
||||||
|
9. GIF to video
|
||||||
|
10. Image filters / adjustments
|
||||||
|
|
||||||
|
### Strategic gaps, not just feature gaps
|
||||||
|
|
||||||
|
1. Homepage/catalog mismatch
|
||||||
|
2. Documentation drift
|
||||||
|
3. Full production validation for monetization and analytics
|
||||||
|
4. Sharper positioning for the strongest tool clusters
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Competitive Position Today
|
||||||
|
|
||||||
|
### Where Dociva is strong
|
||||||
|
|
||||||
|
- Unusually broad tool coverage for an early-stage codebase
|
||||||
|
- Trilingual product surface from the start
|
||||||
|
- Real SaaS primitives already in place: plans, quotas, billing, API keys
|
||||||
|
- Async processing architecture suitable for file workflows
|
||||||
|
- Stronger-than-average SEO foundation for this stage
|
||||||
|
|
||||||
|
### Where Dociva is still weaker than category leaders
|
||||||
|
|
||||||
|
- Brand trust and distribution
|
||||||
|
- Workflow polish on every tool
|
||||||
|
- Product discovery consistency
|
||||||
|
- Team/business collaboration features
|
||||||
|
- Proof of production traction
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Recommended Next Sprint
|
||||||
|
|
||||||
|
### Product quality
|
||||||
|
|
||||||
|
1. Keep frontend tests green as part of CI
|
||||||
|
2. Run a broader backend validation pass
|
||||||
|
3. Audit hidden tools and decide whether to surface or intentionally defer them
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
1. Keep this file as the current source of truth for tool counts
|
||||||
|
2. Update project status and SEO docs whenever route counts or sitemap scale change
|
||||||
|
|
||||||
|
### Growth
|
||||||
|
|
||||||
|
1. Focus on 8 high-intent landing pages first:
|
||||||
|
- Compress PDF
|
||||||
|
- PDF to Word
|
||||||
|
- Word to PDF
|
||||||
|
- Merge PDF
|
||||||
|
- PDF to Excel
|
||||||
|
- OCR
|
||||||
|
- Remove Background
|
||||||
|
- HTML to PDF
|
||||||
|
2. Improve conversion from free use to account creation to Pro/API
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Bottom Line
|
||||||
|
|
||||||
|
The project is no longer a 21-tool MVP. It is currently a **44-route document-processing platform** with a real SaaS foundation and a growing competitive surface.
|
||||||
|
|
||||||
|
The core challenge has shifted:
|
||||||
|
|
||||||
|
> The next bottleneck is no longer raw feature count. It is consistency, positioning, discovery, and turning breadth into measurable growth.
|
||||||
|
|||||||
@@ -225,6 +225,6 @@ describe('useTaskPolling', () => {
|
|||||||
await vi.runAllTimersAsync();
|
await vi.runAllTimersAsync();
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.current.error).toBe('Task failed.');
|
expect(result.current.error).toBe('Processing failed. Please try again.');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
|
|||||||
import InternalAdminPage from './InternalAdminPage';
|
import InternalAdminPage from './InternalAdminPage';
|
||||||
import { useAuthStore } from '@/stores/authStore';
|
import { useAuthStore } from '@/stores/authStore';
|
||||||
import {
|
import {
|
||||||
|
getAdminPlanInterest,
|
||||||
|
getAdminSystemHealth,
|
||||||
|
getAdminUserStats,
|
||||||
getInternalAdminContacts,
|
getInternalAdminContacts,
|
||||||
getInternalAdminOverview,
|
getInternalAdminOverview,
|
||||||
listInternalAdminUsers,
|
listInternalAdminUsers,
|
||||||
@@ -18,6 +21,9 @@ vi.mock('@/stores/authStore', () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('@/services/api', () => ({
|
vi.mock('@/services/api', () => ({
|
||||||
|
getAdminPlanInterest: vi.fn(),
|
||||||
|
getAdminSystemHealth: vi.fn(),
|
||||||
|
getAdminUserStats: vi.fn(),
|
||||||
getInternalAdminContacts: vi.fn(),
|
getInternalAdminContacts: vi.fn(),
|
||||||
getInternalAdminOverview: vi.fn(),
|
getInternalAdminOverview: vi.fn(),
|
||||||
listInternalAdminUsers: vi.fn(),
|
listInternalAdminUsers: vi.fn(),
|
||||||
@@ -37,7 +43,7 @@ const authState = {
|
|||||||
function renderPage() {
|
function renderPage() {
|
||||||
return render(
|
return render(
|
||||||
<HelmetProvider>
|
<HelmetProvider>
|
||||||
<MemoryRouter>
|
<MemoryRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
|
||||||
<InternalAdminPage />
|
<InternalAdminPage />
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
</HelmetProvider>
|
</HelmetProvider>
|
||||||
@@ -56,11 +62,49 @@ describe('InternalAdminPage', () => {
|
|||||||
(selector: (state: typeof authState) => unknown) => selector(authState)
|
(selector: (state: typeof authState) => unknown) => selector(authState)
|
||||||
);
|
);
|
||||||
(getInternalAdminOverview as Mock).mockReset();
|
(getInternalAdminOverview as Mock).mockReset();
|
||||||
|
(getAdminSystemHealth as Mock).mockReset();
|
||||||
|
(getAdminPlanInterest as Mock).mockReset();
|
||||||
|
(getAdminUserStats as Mock).mockReset();
|
||||||
(listInternalAdminUsers as Mock).mockReset();
|
(listInternalAdminUsers as Mock).mockReset();
|
||||||
(getInternalAdminContacts as Mock).mockReset();
|
(getInternalAdminContacts as Mock).mockReset();
|
||||||
(markInternalAdminContactRead as Mock).mockReset();
|
(markInternalAdminContactRead as Mock).mockReset();
|
||||||
(updateInternalAdminUserPlan as Mock).mockReset();
|
(updateInternalAdminUserPlan as Mock).mockReset();
|
||||||
(updateInternalAdminUserRole as Mock).mockReset();
|
(updateInternalAdminUserRole as Mock).mockReset();
|
||||||
|
|
||||||
|
(getAdminSystemHealth as Mock).mockResolvedValue({
|
||||||
|
ai_configured: true,
|
||||||
|
ai_model: 'nvidia/nemotron-3-super-120b-a12b:free',
|
||||||
|
ai_budget_used_percent: 25,
|
||||||
|
error_rate_1h: 0,
|
||||||
|
tasks_last_1h: 0,
|
||||||
|
failures_last_1h: 0,
|
||||||
|
database_size_mb: 1.5,
|
||||||
|
});
|
||||||
|
(getAdminPlanInterest as Mock).mockResolvedValue({
|
||||||
|
total_clicks: 0,
|
||||||
|
unique_users: 0,
|
||||||
|
clicks_last_7d: 0,
|
||||||
|
clicks_last_30d: 0,
|
||||||
|
by_plan: [],
|
||||||
|
recent: [],
|
||||||
|
});
|
||||||
|
(getAdminUserStats as Mock).mockResolvedValue({
|
||||||
|
total_users: 2,
|
||||||
|
new_last_7d: 1,
|
||||||
|
new_last_30d: 2,
|
||||||
|
pro_users: 1,
|
||||||
|
free_users: 1,
|
||||||
|
daily_registrations: [{ day: '2026-03-16', count: 1 }],
|
||||||
|
most_active_users: [
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
email: 'operator@example.com',
|
||||||
|
plan: 'free',
|
||||||
|
created_at: '2026-03-16T10:00:00Z',
|
||||||
|
total_tasks: 3,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows the admin sign-in form for anonymous users', () => {
|
it('shows the admin sign-in form for anonymous users', () => {
|
||||||
@@ -129,13 +173,19 @@ describe('InternalAdminPage', () => {
|
|||||||
renderPage();
|
renderPage();
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByText('Users and monetization')).toBeTruthy();
|
expect(screen.getByText('Top tools')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole('button', { name: 'Set admin' }));
|
fireEvent.click(screen.getByRole('button', { name: 'Users' }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('User management')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Admin' }));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(updateInternalAdminUserRole).toHaveBeenCalledWith(2, 'admin');
|
expect(updateInternalAdminUserRole).toHaveBeenCalledWith(2, 'admin');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -29,22 +29,6 @@
|
|||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* تحسين تحميل الخطوط لتقليل CLS */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Inter';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: local('Inter'), url('/fonts/Inter.woff2') format('woff2');
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Tajawal';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: local('Tajawal'), url('/fonts/Tajawal.woff2') format('woff2');
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* RTL Support */
|
/* RTL Support */
|
||||||
[dir="rtl"] body {
|
[dir="rtl"] body {
|
||||||
font-family: 'Tajawal', 'Inter', system-ui, sans-serif;
|
font-family: 'Tajawal', 'Inter', system-ui, sans-serif;
|
||||||
|
|||||||
6
frontend/src/test/setup.ts
Normal file
6
frontend/src/test/setup.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import '@testing-library/jest-dom/vitest';
|
||||||
|
import '@/i18n';
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.localStorage.setItem('i18nextLng', 'en');
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ export default defineConfig({
|
|||||||
test: {
|
test: {
|
||||||
globals: true,
|
globals: true,
|
||||||
environment: 'jsdom',
|
environment: 'jsdom',
|
||||||
|
setupFiles: ['./src/test/setup.ts'],
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
Reference in New Issue
Block a user