Compare commits
16 Commits
d4ecddba22
...
cleanup/ph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e317e1de26 | ||
|
|
f04eb0a900 | ||
|
|
264c720e3e | ||
|
|
0921adbabb | ||
|
|
82d6a9e879 | ||
|
|
0526553c9b | ||
|
|
7bb9d813f2 | ||
|
|
59f7455521 | ||
|
|
34c8cc410a | ||
|
|
4f99fc1451 | ||
|
|
84ed711f6d | ||
|
|
7c79bdcc6c | ||
|
|
74370685f4 | ||
|
|
e2a1c15183 | ||
|
|
51512d6c91 | ||
|
|
4e9f2d9dbc |
313
CHANGELOG.md
313
CHANGELOG.md
@@ -1,6 +1,6 @@
|
||||
# IGNY8 Change Log
|
||||
|
||||
**Current Version:** 1.6.0
|
||||
**Current Version:** 1.6.2
|
||||
**Last Updated:** January 8, 2026
|
||||
|
||||
---
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
| Version | Date | Summary |
|
||||
|---------|------|---------|
|
||||
| 1.6.2 | Jan 8, 2026 | **Design Refinements** - Updated marketing site gradients to brand colors (primary + success), reduced shadow weights, simplified automation icons, added Upcoming Features page |
|
||||
| 1.6.1 | Jan 8, 2026 | **Email System** - SMTP configuration, email templates, password reset flow, email verification, unsubscribe functionality |
|
||||
| 1.6.0 | Jan 8, 2026 | **Major** - Payment System Refactor Complete: Stripe, PayPal, Bank Transfer flows finalized; Simplified signup (no payment redirect); Country-based payment rules; Webhook security; PDF invoices |
|
||||
| 1.5.0 | *Planned* | Image Generation System Overhaul, Quality Tier Refinements, Credit Service Enhancements |
|
||||
| 1.4.0 | Jan 5, 2026 | **Major** - AI Model Architecture Overhaul, IntegrationProvider Model, AIModelConfig with Credit System, SystemAISettings, Django Admin Reorganization |
|
||||
@@ -37,6 +39,315 @@
|
||||
|
||||
---
|
||||
|
||||
## v1.6.2 - January 8, 2026
|
||||
|
||||
### Marketing Site Design Refinements
|
||||
|
||||
This release updates the marketing site visual design to use consistent brand colors (primary + success gradient mix) throughout, reduces shadow weights for a cleaner appearance, and adds a comprehensive Upcoming Features roadmap page.
|
||||
|
||||
---
|
||||
|
||||
### 🎨 Brand Color Consistency
|
||||
|
||||
**Gradient Updates - Primary + Success Mix:**
|
||||
All marketing page gradients updated from purple/pink tones to brand colors (primary + success)
|
||||
|
||||
**Hero Sections Updated:**
|
||||
- `Home.tsx` - Hero gradient: `from-primary via-primary-dark to-success`
|
||||
- `Upcoming.tsx` - Hero gradient: `from-primary via-primary-dark to-success`
|
||||
- Updated radial glow overlays to use success RGB values
|
||||
|
||||
**CTA Sections Before Footer:**
|
||||
All pre-footer CTA sections updated across 5 pages:
|
||||
- `Home.tsx` - CTA gradient: `from-success via-primary-dark to-primary`
|
||||
- `Product.tsx` - CTA gradient: `from-success via-primary-dark to-primary`
|
||||
- `Pricing.tsx` - CTA gradient: `from-success via-primary-dark to-primary`
|
||||
- `Solutions.tsx` - CTA gradient: `from-success via-primary-dark to-primary`
|
||||
- `Partners.tsx` - CTA gradient: `from-success via-primary-dark to-primary`
|
||||
- `CaseStudies.tsx` - CTA gradient: `from-success via-primary-dark to-primary`
|
||||
|
||||
---
|
||||
|
||||
### 🧹 Shadow & Visual Refinements
|
||||
|
||||
**Product Module Screenshots:**
|
||||
- Reduced shadow from `shadow-2xl` to `shadow-md` for cleaner appearance
|
||||
- Reduced blur from `blur-xl` to `blur-lg` on gradient glows
|
||||
- Reduced inset values for more subtle frame effects
|
||||
- Updated across all 4 product module sections
|
||||
|
||||
**Hero Dashboard:**
|
||||
- Reduced shadow from `shadow-2xl` to `shadow-lg`
|
||||
- Reduced blur effects from `blur-3xl`/`blur-2xl` to `blur-xl`/`blur-lg`
|
||||
- Toned down opacity on glow effects (from 30%/20% to 20%/10%)
|
||||
|
||||
**Automation Engine Section:**
|
||||
- Simplified numbered badges from colorful mix to consistent primary gradient
|
||||
- Changed size from `w-10 h-10` to `w-9 h-9` for cleaner appearance
|
||||
- Removed heavy `shadow-lg` effects, using subtle `shadow-sm`
|
||||
- Removed neon glow and hover animations for cleaner branded look
|
||||
- Reduced automation dashboard shadow from `shadow-2xl` to `shadow-md`
|
||||
- Updated all 7 automation handoff badges to use consistent primary gradient
|
||||
|
||||
---
|
||||
|
||||
### 🚀 Upcoming Features Page
|
||||
|
||||
**New Page: `/upcoming`**
|
||||
- Comprehensive roadmap page with timeline-based feature organization
|
||||
- 3 phases with unique visual badges
|
||||
- 10 major upcoming features documented
|
||||
|
||||
**Phase 1 - Launching February 2026:**
|
||||
- **Linker Module** - Internal/external linking with keyword clustering
|
||||
- **Optimizer Module** - Content re-optimization engine
|
||||
|
||||
**Phase 2 - Launching Q2 2026:**
|
||||
- **Products Pages** - E-commerce product content generation
|
||||
- **Services Pages** - Service business content engine
|
||||
- **Company Pages** - Corporate website essentials
|
||||
- **Socializer** - Multi-platform social media publishing
|
||||
|
||||
**Phase 3 - Launching Q3-Q4 2026:**
|
||||
- **Video Content Creator** - AI video generation & publishing
|
||||
- **Site Builder** - The SEO holy grail
|
||||
- **Advanced Analytics** - Performance insights & reporting
|
||||
|
||||
**Visual Design:**
|
||||
- Rich gradient badges for each timeline phase (Success, Purple, Warning gradients)
|
||||
- Feature cards with icons, descriptions, and detailed bullet points
|
||||
- Hover effects and visual polish
|
||||
- Consistent with marketing site design system
|
||||
|
||||
**Navigation:**
|
||||
- Added "Upcoming Features" link to footer Resources section
|
||||
- Route integrated in `MarketingApp.tsx`
|
||||
- Lazy-loaded for performance
|
||||
|
||||
---
|
||||
|
||||
### 📄 Documentation Updates
|
||||
|
||||
**Marketing Site Updates:**
|
||||
All marketing pages synced with current app architecture:
|
||||
- Updated `Home.tsx` with 8-stage pipeline and 7 automation handoffs
|
||||
- Updated `Product.tsx` with accurate module descriptions
|
||||
- Updated `Tour.tsx` with 5 comprehensive steps
|
||||
- Updated `Solutions.tsx` with current architecture
|
||||
|
||||
**Prelaunch Plan Progress:**
|
||||
- Marked Phase 7.1 (Documentation Updates) complete
|
||||
- Marked Phase 8.1 (Site Content) complete
|
||||
- Marked Phase 8.2 (Pricing Page) complete
|
||||
- Marked Phase 8.3 (Upcoming Features Section) complete
|
||||
|
||||
---
|
||||
|
||||
### 📦 Files Changed Summary
|
||||
|
||||
**Frontend Marketing Pages (8):**
|
||||
| File | Changes | Description |
|
||||
|------|---------|-------------|
|
||||
| `marketing/pages/Home.tsx` | ~30 lines | Hero gradient, CTA gradient, shadow reductions, automation simplification |
|
||||
| `marketing/pages/Upcoming.tsx` | NEW - 362 lines | Comprehensive upcoming features page |
|
||||
| `marketing/pages/Product.tsx` | ~5 lines | CTA gradient update |
|
||||
| `marketing/pages/Pricing.tsx` | ~5 lines | CTA gradient update |
|
||||
| `marketing/pages/Solutions.tsx` | ~5 lines | CTA gradient update |
|
||||
| `marketing/pages/Partners.tsx` | ~5 lines | CTA gradient update |
|
||||
| `marketing/pages/CaseStudies.tsx` | ~5 lines | CTA gradient update |
|
||||
| `marketing/MarketingApp.tsx` | +3 lines | Upcoming route |
|
||||
|
||||
**Navigation & Config (1):**
|
||||
| File | Changes | Description |
|
||||
|------|---------|-------------|
|
||||
| `marketing/data/navLinks.ts` | +1 link | Added "Upcoming Features" to footer |
|
||||
|
||||
**Documentation (1):**
|
||||
| File | Changes | Description |
|
||||
|------|---------|-------------|
|
||||
| `docs/plans/FINAL-PRELAUNCH.md` | ~20 lines | Marked Phase 7 & 8 tasks complete |
|
||||
|
||||
---
|
||||
|
||||
### 🎯 Design Principles Applied
|
||||
|
||||
1. **Brand Consistency** - All gradients now use primary + success mix (matching logo)
|
||||
2. **Visual Hierarchy** - Reduced shadow weights prevent visual overwhelm
|
||||
3. **Clean & Branded** - Simplified automation icons from "trainbox mix" to consistent primary gradient
|
||||
4. **Subtle & Elegant** - Shadow weights reduced to md/lg instead of 2xl for modern appearance
|
||||
5. **Content First** - Reduced decorative effects to let content shine
|
||||
|
||||
---
|
||||
|
||||
### Git Reference
|
||||
```bash
|
||||
# Commits in this release:
|
||||
4f99fc14 - Update all CTA section backgrounds to primary + success gradient
|
||||
84ed711f - Reduce shadow weights and simplify automation icons
|
||||
7c79bdcc - Update gradient backgrounds from purple/pink to primary + success mix
|
||||
74370685 - Add Upcoming Features page with timeline-based roadmap
|
||||
e2a1c151 - Update FINAL-PRELAUNCH.md: Mark Phase 7 & 8 tasks complete
|
||||
51512d6c - Update Tour and Solutions pages with accurate pipeline
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## v1.6.1 - January 8, 2026
|
||||
|
||||
### Email System Implementation
|
||||
|
||||
This release implements a comprehensive email system with SMTP configuration, customizable email templates, and user authentication flows including password reset, email verification, and unsubscribe functionality.
|
||||
|
||||
---
|
||||
|
||||
### 📧 SMTP Configuration & Admin
|
||||
|
||||
**New Models:**
|
||||
- `EmailSettings` - SMTP configuration model with server settings
|
||||
- Fields: `smtp_host`, `smtp_port`, `smtp_username`, `smtp_password`
|
||||
- TLS/SSL configuration options
|
||||
- Default sender email and name
|
||||
- Test email functionality from admin
|
||||
|
||||
**Django Admin Integration:**
|
||||
- Custom admin panel for email settings (`email_admin.py`)
|
||||
- Test email sending directly from admin interface
|
||||
- Custom admin templates:
|
||||
- `admin/system/emailsettings/change_form.html` - Settings form with test button
|
||||
- `admin/system/emailsettings/test_email.html` - Test email confirmation page
|
||||
|
||||
**Migration:**
|
||||
- `0021_add_smtp_email_settings.py` - Creates EmailSettings table
|
||||
|
||||
---
|
||||
|
||||
### 🔐 Authentication Flows
|
||||
|
||||
**Password Reset Flow:**
|
||||
- `ForgotPassword.tsx` - Email submission form for password reset requests
|
||||
- `ResetPassword.tsx` - Token-based password reset form
|
||||
- Backend endpoints integrated with email service
|
||||
- Secure token generation and validation
|
||||
|
||||
**Email Verification:**
|
||||
- `VerifyEmail.tsx` - Email verification page for new users
|
||||
- Token-based verification flow
|
||||
- Success/error state handling
|
||||
- Auto-redirect after verification
|
||||
|
||||
**Unsubscribe:**
|
||||
- `Unsubscribe.tsx` - One-click email unsubscribe page
|
||||
- Token-based unsubscribe for security
|
||||
- Confirmation messaging
|
||||
|
||||
---
|
||||
|
||||
### 📨 Email Service Enhancements
|
||||
|
||||
**Email Service Updates** (`email_service.py`):
|
||||
- SMTP-based email sending with configurable settings
|
||||
- Support for HTML email templates
|
||||
- Template rendering with dynamic context
|
||||
- Error handling and logging
|
||||
- Integration with authentication views
|
||||
|
||||
**Email Templates Updated:**
|
||||
- `emails/base.html` - Enhanced base template (126+ lines updated)
|
||||
- `emails/email_verification.html` - Verification email template
|
||||
- `emails/password_reset.html` - Password reset email template
|
||||
- `emails/welcome.html` - Welcome email for new users
|
||||
- `emails/low_credits.html` - Low credit warning email
|
||||
- `emails/payment_approved.html` - Payment approval notification
|
||||
- `emails/payment_confirmation.html` - Payment confirmation
|
||||
- `emails/payment_failed.html` - Payment failure notification
|
||||
- `emails/payment_rejected.html` - Payment rejection notification
|
||||
- `emails/refund_notification.html` - Refund notification
|
||||
- `emails/subscription_activated.html` - Subscription activation
|
||||
- `emails/subscription_renewal.html` - Subscription renewal reminder
|
||||
|
||||
---
|
||||
|
||||
### 🎨 Frontend Pages
|
||||
|
||||
**New Pages:**
|
||||
| Route | Component | Description |
|
||||
|-------|-----------|-------------|
|
||||
| `/forgot-password` | `ForgotPassword.tsx` | Password reset request form |
|
||||
| `/reset-password` | `ResetPassword.tsx` | Password reset with token |
|
||||
| `/verify-email` | `VerifyEmail.tsx` | Email verification handler |
|
||||
| `/unsubscribe` | `Unsubscribe.tsx` | Email unsubscribe page |
|
||||
|
||||
**Route Updates:**
|
||||
- Added new routes to `App.tsx` (12+ lines)
|
||||
- Authentication URL patterns in `auth/urls.py` (209+ lines)
|
||||
|
||||
---
|
||||
|
||||
### 📚 Documentation Updates
|
||||
|
||||
**Django Admin Access Guide:**
|
||||
- Updated `docs/90-REFERENCE/DJANGO-ADMIN-ACCESS-GUIDE.md` (104+ lines added)
|
||||
- Added email settings administration documentation
|
||||
- Test email procedures documented
|
||||
|
||||
**Legal Pages:**
|
||||
- Minor updates to `Privacy.tsx` and `Terms.tsx`
|
||||
|
||||
---
|
||||
|
||||
### 🗄️ Database Changes
|
||||
|
||||
**New Migration:**
|
||||
- `0020_add_email_models.py` - Email models and templates (93 lines)
|
||||
- `0021_add_smtp_email_settings.py` - SMTP settings model (53 lines)
|
||||
|
||||
**New Models:**
|
||||
- `EmailSettings` - SMTP configuration
|
||||
- `EmailTemplate` - Customizable email templates (292 lines in `email_models.py`)
|
||||
|
||||
---
|
||||
|
||||
### 📦 Files Changed Summary
|
||||
|
||||
**Backend Files (7):**
|
||||
| File | Changes |
|
||||
|------|---------|
|
||||
| `billing/services/email_service.py` | +427 lines - Complete email service implementation |
|
||||
| `modules/system/email_admin.py` | +448 lines - Django admin for email management |
|
||||
| `modules/system/email_models.py` | +338 lines - Email settings and template models |
|
||||
| `auth/views.py` | +17 lines - Password reset and verification views |
|
||||
| `auth/urls.py` | +209 lines - Authentication URL patterns |
|
||||
| `billing/views/paypal_views.py` | +26 lines - Email notification integration |
|
||||
| `billing/views/stripe_views.py` | +21 lines - Email notification integration |
|
||||
|
||||
**Frontend Files (5):**
|
||||
| File | Changes |
|
||||
|------|---------|
|
||||
| `pages/AuthPages/ForgotPassword.tsx` | NEW - 177 lines |
|
||||
| `pages/AuthPages/ResetPassword.tsx` | NEW/Updated - 350 lines |
|
||||
| `pages/AuthPages/VerifyEmail.tsx` | NEW - 220 lines |
|
||||
| `pages/AuthPages/Unsubscribe.tsx` | NEW - 83 lines |
|
||||
| `App.tsx` | +12 lines - New route definitions |
|
||||
|
||||
**Template Files (13):**
|
||||
- All email templates in `templates/emails/` updated for consistency
|
||||
|
||||
**Admin Templates (2):**
|
||||
- `admin/system/emailsettings/change_form.html` - 15 lines
|
||||
- `admin/system/emailsettings/test_email.html` - 97 lines
|
||||
- `admin/system/emailtemplate/test_email.html` - 78 lines
|
||||
|
||||
---
|
||||
|
||||
### Git Reference
|
||||
```bash
|
||||
# Commits in this release:
|
||||
d4ecddba - SMTP and other email related settings
|
||||
3651ee9e - Email Configs & setup
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## v1.6.0 - January 8, 2026
|
||||
|
||||
### Major Release: Payment System Refactor Complete
|
||||
|
||||
152
backend/igny8_core/management/commands/cleanup_user_data.py
Normal file
152
backend/igny8_core/management/commands/cleanup_user_data.py
Normal file
@@ -0,0 +1,152 @@
|
||||
"""
|
||||
Management command to clean up all user-generated data (DESTRUCTIVE).
|
||||
This is used before V1.0 production launch to start with a clean database.
|
||||
|
||||
⚠️ WARNING: This permanently deletes ALL user data!
|
||||
|
||||
Usage:
|
||||
# DRY RUN (recommended first):
|
||||
python manage.py cleanup_user_data --dry-run
|
||||
|
||||
# ACTUAL CLEANUP (after reviewing dry-run):
|
||||
python manage.py cleanup_user_data --confirm
|
||||
"""
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Clean up all user-generated data (DESTRUCTIVE - for pre-launch cleanup)'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--confirm',
|
||||
action='store_true',
|
||||
help='Confirm you want to delete all user data'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--dry-run',
|
||||
action='store_true',
|
||||
help='Show what would be deleted without actually deleting'
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if not options['confirm'] and not options['dry_run']:
|
||||
self.stdout.write(
|
||||
self.style.ERROR('\n⚠️ ERROR: Must use --confirm or --dry-run flag\n')
|
||||
)
|
||||
self.stdout.write('Usage:')
|
||||
self.stdout.write(' python manage.py cleanup_user_data --dry-run # See what will be deleted')
|
||||
self.stdout.write(' python manage.py cleanup_user_data --confirm # Actually delete data\n')
|
||||
return
|
||||
|
||||
# Safety check: Prevent running in production unless explicitly allowed
|
||||
if getattr(settings, 'ENVIRONMENT', 'production') == 'production' and options['confirm']:
|
||||
self.stdout.write(
|
||||
self.style.ERROR('\n⚠️ BLOCKED: Cannot run cleanup in PRODUCTION environment!\n')
|
||||
)
|
||||
self.stdout.write('To allow this, temporarily set ENVIRONMENT to "staging" in settings.\n')
|
||||
return
|
||||
|
||||
# Import models
|
||||
from igny8_core.auth.models import Site, CustomUser
|
||||
from igny8_core.business.planning.models import Keywords, Clusters
|
||||
from igny8_core.business.content.models import ContentIdea, Tasks, Content, Images
|
||||
from igny8_core.modules.publisher.models import PublishingRecord
|
||||
from igny8_core.business.integration.models import WordPressSyncEvent
|
||||
from igny8_core.modules.billing.models import CreditTransaction, CreditUsageLog, Order
|
||||
from igny8_core.modules.system.models import Notification
|
||||
from igny8_core.modules.writer.models import AutomationRun
|
||||
|
||||
# Define models to clear (ORDER MATTERS - foreign keys)
|
||||
# Delete child records before parent records
|
||||
models_to_clear = [
|
||||
('Notifications', Notification),
|
||||
('Credit Usage Logs', CreditUsageLog),
|
||||
('Credit Transactions', CreditTransaction),
|
||||
('Orders', Order),
|
||||
('WordPress Sync Events', WordPressSyncEvent),
|
||||
('Publishing Records', PublishingRecord),
|
||||
('Automation Runs', AutomationRun),
|
||||
('Images', Images),
|
||||
('Content', Content),
|
||||
('Tasks', Tasks),
|
||||
('Content Ideas', ContentIdea),
|
||||
('Clusters', Clusters),
|
||||
('Keywords', Keywords),
|
||||
('Sites', Site), # Sites should be near last (many foreign keys)
|
||||
# Note: We do NOT delete CustomUser - keep admin users
|
||||
]
|
||||
|
||||
if options['dry_run']:
|
||||
self.stdout.write(self.style.WARNING('\n' + '=' * 70))
|
||||
self.stdout.write(self.style.WARNING('DRY RUN - No data will be deleted'))
|
||||
self.stdout.write(self.style.WARNING('=' * 70 + '\n'))
|
||||
|
||||
total_records = 0
|
||||
for name, model in models_to_clear:
|
||||
count = model.objects.count()
|
||||
total_records += count
|
||||
status = '✓' if count > 0 else '·'
|
||||
self.stdout.write(f' {status} Would delete {count:6d} {name}')
|
||||
|
||||
# Count users (not deleted)
|
||||
user_count = CustomUser.objects.count()
|
||||
self.stdout.write(f'\n → Keeping {user_count:6d} Users (not deleted)')
|
||||
|
||||
self.stdout.write(f'\n Total records to delete: {total_records:,}')
|
||||
self.stdout.write('\n' + '=' * 70)
|
||||
self.stdout.write(self.style.SUCCESS('\nTo proceed with actual deletion, run:'))
|
||||
self.stdout.write(' python manage.py cleanup_user_data --confirm\n')
|
||||
return
|
||||
|
||||
# ACTUAL DELETION
|
||||
self.stdout.write(self.style.ERROR('\n' + '=' * 70))
|
||||
self.stdout.write(self.style.ERROR('⚠️ DELETING ALL USER DATA - THIS CANNOT BE UNDONE!'))
|
||||
self.stdout.write(self.style.ERROR('=' * 70 + '\n'))
|
||||
|
||||
# Final confirmation prompt
|
||||
confirm_text = input('Type "DELETE ALL DATA" to proceed: ')
|
||||
if confirm_text != 'DELETE ALL DATA':
|
||||
self.stdout.write(self.style.WARNING('\nAborted. Data was NOT deleted.\n'))
|
||||
return
|
||||
|
||||
self.stdout.write('\nProceeding with deletion...\n')
|
||||
|
||||
deleted_counts = {}
|
||||
failed_deletions = []
|
||||
|
||||
with transaction.atomic():
|
||||
for name, model in models_to_clear:
|
||||
try:
|
||||
count = model.objects.count()
|
||||
if count > 0:
|
||||
model.objects.all().delete()
|
||||
deleted_counts[name] = count
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f'✓ Deleted {count:6d} {name}')
|
||||
)
|
||||
else:
|
||||
self.stdout.write(
|
||||
self.style.WARNING(f'· Skipped {count:6d} {name} (already empty)')
|
||||
)
|
||||
except Exception as e:
|
||||
failed_deletions.append((name, str(e)))
|
||||
self.stdout.write(
|
||||
self.style.ERROR(f'✗ Failed to delete {name}: {str(e)}')
|
||||
)
|
||||
|
||||
# Summary
|
||||
total_deleted = sum(deleted_counts.values())
|
||||
self.stdout.write('\n' + '=' * 70)
|
||||
self.stdout.write(self.style.SUCCESS(f'\nUser Data Cleanup Complete!\n'))
|
||||
self.stdout.write(f' Total records deleted: {total_deleted:,}')
|
||||
self.stdout.write(f' Failed deletions: {len(failed_deletions)}')
|
||||
|
||||
if failed_deletions:
|
||||
self.stdout.write(self.style.WARNING('\nFailed deletions:'))
|
||||
for name, error in failed_deletions:
|
||||
self.stdout.write(f' - {name}: {error}')
|
||||
|
||||
self.stdout.write('\n' + '=' * 70 + '\n')
|
||||
122
backend/igny8_core/management/commands/export_system_config.py
Normal file
122
backend/igny8_core/management/commands/export_system_config.py
Normal file
@@ -0,0 +1,122 @@
|
||||
"""
|
||||
Management command to export system configuration data to JSON files.
|
||||
This exports Plans, Credit Costs, AI Models, Industries, Sectors, Seed Keywords, etc.
|
||||
|
||||
Usage:
|
||||
python manage.py export_system_config --output-dir=backups/config
|
||||
"""
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.core import serializers
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Export system configuration data to JSON files for V1.0 backup'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--output-dir',
|
||||
default='backups/config',
|
||||
help='Output directory for config files (relative to project root)'
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
output_dir = options['output_dir']
|
||||
|
||||
# Make output_dir absolute if it's relative
|
||||
if not os.path.isabs(output_dir):
|
||||
# Get project root (parent of manage.py)
|
||||
import sys
|
||||
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
output_dir = os.path.join(project_root, '..', output_dir)
|
||||
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f'\nExporting system configuration to: {output_dir}\n'))
|
||||
|
||||
# Import models
|
||||
from igny8_core.modules.billing.models import Plan, CreditCostConfig
|
||||
from igny8_core.modules.system.models import AIModelConfig, GlobalIntegrationSettings
|
||||
from igny8_core.auth.models import Industry, Sector, SeedKeyword, AuthorProfile
|
||||
from igny8_core.ai.models import Prompt, PromptVariable
|
||||
|
||||
# Define what to export
|
||||
exports = {
|
||||
'plans': (Plan.objects.all(), 'Subscription Plans'),
|
||||
'credit_costs': (CreditCostConfig.objects.all(), 'Credit Cost Configurations'),
|
||||
'ai_models': (AIModelConfig.objects.all(), 'AI Model Configurations'),
|
||||
'global_integrations': (GlobalIntegrationSettings.objects.all(), 'Global Integration Settings'),
|
||||
'industries': (Industry.objects.all(), 'Industries'),
|
||||
'sectors': (Sector.objects.all(), 'Sectors'),
|
||||
'seed_keywords': (SeedKeyword.objects.all(), 'Seed Keywords'),
|
||||
'author_profiles': (AuthorProfile.objects.all(), 'Author Profiles'),
|
||||
'prompts': (Prompt.objects.all(), 'AI Prompts'),
|
||||
'prompt_variables': (PromptVariable.objects.all(), 'Prompt Variables'),
|
||||
}
|
||||
|
||||
successful_exports = []
|
||||
failed_exports = []
|
||||
|
||||
for name, (queryset, description) in exports.items():
|
||||
try:
|
||||
count = queryset.count()
|
||||
data = serializers.serialize('json', queryset, indent=2)
|
||||
filepath = os.path.join(output_dir, f'{name}.json')
|
||||
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(data)
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f'✓ Exported {count:4d} {description:30s} → {name}.json')
|
||||
)
|
||||
successful_exports.append(name)
|
||||
|
||||
except Exception as e:
|
||||
self.stdout.write(
|
||||
self.style.ERROR(f'✗ Failed to export {description}: {str(e)}')
|
||||
)
|
||||
failed_exports.append((name, str(e)))
|
||||
|
||||
# Export metadata
|
||||
metadata = {
|
||||
'exported_at': datetime.now().isoformat(),
|
||||
'django_version': self.get_django_version(),
|
||||
'database': self.get_database_info(),
|
||||
'successful_exports': successful_exports,
|
||||
'failed_exports': failed_exports,
|
||||
'export_count': len(successful_exports),
|
||||
}
|
||||
|
||||
metadata_path = os.path.join(output_dir, 'export_metadata.json')
|
||||
with open(metadata_path, 'w') as f:
|
||||
json.dump(metadata, f, indent=2)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f'\n✓ Metadata saved to export_metadata.json'))
|
||||
|
||||
# Summary
|
||||
self.stdout.write('\n' + '=' * 70)
|
||||
self.stdout.write(self.style.SUCCESS(f'\nSystem Configuration Export Complete!\n'))
|
||||
self.stdout.write(f' Successful: {len(successful_exports)} exports')
|
||||
self.stdout.write(f' Failed: {len(failed_exports)} exports')
|
||||
self.stdout.write(f' Location: {output_dir}\n')
|
||||
|
||||
if failed_exports:
|
||||
self.stdout.write(self.style.WARNING('\nFailed exports:'))
|
||||
for name, error in failed_exports:
|
||||
self.stdout.write(f' - {name}: {error}')
|
||||
|
||||
self.stdout.write('=' * 70 + '\n')
|
||||
|
||||
def get_django_version(self):
|
||||
import django
|
||||
return django.get_version()
|
||||
|
||||
def get_database_info(self):
|
||||
from django.conf import settings
|
||||
db_config = settings.DATABASES.get('default', {})
|
||||
return {
|
||||
'engine': db_config.get('ENGINE', '').split('.')[-1],
|
||||
'name': db_config.get('NAME', ''),
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
"""
|
||||
Test script to verify URL patterns are correctly registered
|
||||
Run this with: python manage.py shell < test_urls.py
|
||||
"""
|
||||
from django.urls import resolve, reverse
|
||||
from django.test import RequestFactory
|
||||
|
||||
# Test URL resolution
|
||||
try:
|
||||
# Test the generate endpoint
|
||||
url_path = '/api/v1/system/settings/integrations/image_generation/generate/'
|
||||
resolved = resolve(url_path)
|
||||
print(f"✅ URL resolved: {url_path}")
|
||||
print(f" View: {resolved.func}")
|
||||
print(f" Args: {resolved.args}")
|
||||
print(f" Kwargs: {resolved.kwargs}")
|
||||
except Exception as e:
|
||||
print(f"❌ URL NOT resolved: {url_path}")
|
||||
print(f" Error: {e}")
|
||||
|
||||
# Test reverse
|
||||
try:
|
||||
reversed_url = reverse('integration-settings-generate', kwargs={'pk': 'image_generation'})
|
||||
print(f"✅ Reverse URL: {reversed_url}")
|
||||
except Exception as e:
|
||||
print(f"❌ Reverse failed: {e}")
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
"""
|
||||
Stage 1 Backend Refactor - Basic Tests
|
||||
Test the refactored models and serializers
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from django.test import TestCase
|
||||
from igny8_core.business.planning.models import Clusters
|
||||
from igny8_core.business.content.models import Tasks, Content, ContentTaxonomy
|
||||
from igny8_core.modules.writer.serializers import TasksSerializer, ContentSerializer, ContentTaxonomySerializer
|
||||
|
||||
|
||||
class TestClusterModel(TestCase):
|
||||
"""Test Cluster model after Stage 1 refactor"""
|
||||
|
||||
def test_cluster_fields_removed(self):
|
||||
"""Verify deprecated fields are removed"""
|
||||
cluster = Clusters()
|
||||
|
||||
# These fields should NOT exist
|
||||
assert not hasattr(cluster, 'context_type'), "context_type should be removed"
|
||||
assert not hasattr(cluster, 'dimension_meta'), "dimension_meta should be removed"
|
||||
|
||||
# These fields SHOULD exist
|
||||
assert hasattr(cluster, 'name'), "name field should exist"
|
||||
assert hasattr(cluster, 'keywords'), "keywords field should exist"
|
||||
|
||||
|
||||
class TestTasksModel(TestCase):
|
||||
"""Test Tasks model after Stage 1 refactor"""
|
||||
|
||||
def test_tasks_fields_removed(self):
|
||||
"""Verify deprecated fields are removed"""
|
||||
task = Tasks()
|
||||
|
||||
# These fields should NOT exist
|
||||
assert not hasattr(task, 'cluster_role'), "cluster_role should be removed"
|
||||
assert not hasattr(task, 'idea_id'), "idea_id should be removed"
|
||||
assert not hasattr(task, 'content_record'), "content_record should be removed"
|
||||
assert not hasattr(task, 'entity_type'), "entity_type should be removed"
|
||||
|
||||
def test_tasks_fields_added(self):
|
||||
"""Verify new fields are added"""
|
||||
task = Tasks()
|
||||
|
||||
# These fields SHOULD exist
|
||||
assert hasattr(task, 'content_type'), "content_type should be added"
|
||||
assert hasattr(task, 'content_structure'), "content_structure should be added"
|
||||
assert hasattr(task, 'taxonomy_term_id'), "taxonomy_term_id should be added"
|
||||
|
||||
def test_tasks_status_choices(self):
|
||||
"""Verify status choices are simplified"""
|
||||
# Status should only have 'queued' and 'completed'
|
||||
status_choices = [choice[0] for choice in Tasks._meta.get_field('status').choices]
|
||||
assert 'queued' in status_choices, "queued should be a valid status"
|
||||
assert 'completed' in status_choices, "completed should be a valid status"
|
||||
assert len(status_choices) == 2, "Should only have 2 status choices"
|
||||
|
||||
|
||||
class TestContentModel(TestCase):
|
||||
"""Test Content model after Stage 1 refactor"""
|
||||
|
||||
def test_content_fields_removed(self):
|
||||
"""Verify deprecated fields are removed"""
|
||||
content = Content()
|
||||
|
||||
# These fields should NOT exist
|
||||
assert not hasattr(content, 'task'), "task FK should be removed"
|
||||
assert not hasattr(content, 'html_content'), "html_content should be removed (use content_html)"
|
||||
assert not hasattr(content, 'entity_type'), "entity_type should be removed"
|
||||
assert not hasattr(content, 'cluster_role'), "cluster_role should be removed"
|
||||
assert not hasattr(content, 'sync_status'), "sync_status should be removed"
|
||||
|
||||
def test_content_fields_added(self):
|
||||
"""Verify new fields are added"""
|
||||
content = Content()
|
||||
|
||||
# These fields SHOULD exist
|
||||
assert hasattr(content, 'title'), "title should be added"
|
||||
assert hasattr(content, 'content_html'), "content_html should be added"
|
||||
assert hasattr(content, 'cluster_id'), "cluster_id should be added"
|
||||
assert hasattr(content, 'content_type'), "content_type should be added"
|
||||
assert hasattr(content, 'content_structure'), "content_structure should be added"
|
||||
assert hasattr(content, 'taxonomy_terms'), "taxonomy_terms M2M should exist"
|
||||
|
||||
def test_content_status_choices(self):
|
||||
"""Verify status choices are simplified"""
|
||||
# Status should only have 'draft' and 'published'
|
||||
status_choices = [choice[0] for choice in Content._meta.get_field('status').choices]
|
||||
assert 'draft' in status_choices, "draft should be a valid status"
|
||||
assert 'published' in status_choices, "published should be a valid status"
|
||||
assert len(status_choices) == 2, "Should only have 2 status choices"
|
||||
|
||||
|
||||
class TestContentTaxonomyModel(TestCase):
|
||||
"""Test ContentTaxonomy model after Stage 1 refactor"""
|
||||
|
||||
def test_taxonomy_fields_removed(self):
|
||||
"""Verify deprecated fields are removed"""
|
||||
taxonomy = ContentTaxonomy()
|
||||
|
||||
# These fields should NOT exist
|
||||
assert not hasattr(taxonomy, 'description'), "description should be removed"
|
||||
assert not hasattr(taxonomy, 'parent'), "parent FK should be removed"
|
||||
assert not hasattr(taxonomy, 'sync_status'), "sync_status should be removed"
|
||||
assert not hasattr(taxonomy, 'count'), "count should be removed"
|
||||
assert not hasattr(taxonomy, 'metadata'), "metadata should be removed"
|
||||
assert not hasattr(taxonomy, 'clusters'), "clusters M2M should be removed"
|
||||
|
||||
def test_taxonomy_type_includes_cluster(self):
|
||||
"""Verify taxonomy_type includes 'cluster' option"""
|
||||
type_choices = [choice[0] for choice in ContentTaxonomy._meta.get_field('taxonomy_type').choices]
|
||||
assert 'category' in type_choices, "category should be a valid type"
|
||||
assert 'post_tag' in type_choices, "post_tag should be a valid type"
|
||||
assert 'cluster' in type_choices, "cluster should be a valid type"
|
||||
|
||||
|
||||
class TestTasksSerializer(TestCase):
|
||||
"""Test TasksSerializer after Stage 1 refactor"""
|
||||
|
||||
def test_serializer_fields(self):
|
||||
"""Verify serializer has correct fields"""
|
||||
serializer = TasksSerializer()
|
||||
fields = serializer.fields.keys()
|
||||
|
||||
# Should have new fields
|
||||
assert 'content_type' in fields, "content_type should be in serializer"
|
||||
assert 'content_structure' in fields, "content_structure should be in serializer"
|
||||
assert 'taxonomy_term_id' in fields, "taxonomy_term_id should be in serializer"
|
||||
assert 'cluster_id' in fields, "cluster_id should be in serializer"
|
||||
|
||||
# Should NOT have deprecated fields
|
||||
assert 'idea_title' not in fields, "idea_title should not be in serializer"
|
||||
assert 'cluster_role' not in fields, "cluster_role should not be in serializer"
|
||||
|
||||
|
||||
class TestContentSerializer(TestCase):
|
||||
"""Test ContentSerializer after Stage 1 refactor"""
|
||||
|
||||
def test_serializer_fields(self):
|
||||
"""Verify serializer has correct fields"""
|
||||
serializer = ContentSerializer()
|
||||
fields = serializer.fields.keys()
|
||||
|
||||
# Should have new fields
|
||||
assert 'title' in fields, "title should be in serializer"
|
||||
assert 'content_html' in fields, "content_html should be in serializer"
|
||||
assert 'cluster_id' in fields, "cluster_id should be in serializer"
|
||||
assert 'content_type' in fields, "content_type should be in serializer"
|
||||
assert 'content_structure' in fields, "content_structure should be in serializer"
|
||||
assert 'taxonomy_terms_data' in fields, "taxonomy_terms_data should be in serializer"
|
||||
|
||||
# Should NOT have deprecated fields
|
||||
assert 'task_id' not in fields, "task_id should not be in serializer"
|
||||
assert 'entity_type' not in fields, "entity_type should not be in serializer"
|
||||
assert 'cluster_role' not in fields, "cluster_role should not be in serializer"
|
||||
|
||||
|
||||
class TestContentTaxonomySerializer(TestCase):
|
||||
"""Test ContentTaxonomySerializer after Stage 1 refactor"""
|
||||
|
||||
def test_serializer_fields(self):
|
||||
"""Verify serializer has correct fields"""
|
||||
serializer = ContentTaxonomySerializer()
|
||||
fields = serializer.fields.keys()
|
||||
|
||||
# Should have these fields
|
||||
assert 'id' in fields
|
||||
assert 'name' in fields
|
||||
assert 'slug' in fields
|
||||
assert 'taxonomy_type' in fields
|
||||
|
||||
# Should NOT have deprecated fields
|
||||
assert 'description' not in fields, "description should not be in serializer"
|
||||
assert 'parent' not in fields, "parent should not be in serializer"
|
||||
assert 'sync_status' not in fields, "sync_status should not be in serializer"
|
||||
assert 'cluster_names' not in fields, "cluster_names should not be in serializer"
|
||||
|
||||
|
||||
# Run tests with: python manage.py test igny8_core.modules.writer.tests.test_stage1_refactor
|
||||
# Or with pytest: pytest backend/igny8_core/modules/writer/tests/test_stage1_refactor.py -v
|
||||
@@ -1,6 +1,6 @@
|
||||
# IGNY8 Technical Documentation
|
||||
|
||||
**Version:** 1.6.0
|
||||
**Version:** 1.6.2
|
||||
**Last Updated:** January 8, 2026
|
||||
**Purpose:** Complete technical reference for the IGNY8 AI content platform
|
||||
|
||||
|
||||
@@ -379,25 +379,31 @@ Deploy and verify the following sites:
|
||||
|
||||
# PHASE 7: Documentation & Media
|
||||
|
||||
## 7.1 - Documentation Updates
|
||||
## 7.1 - Documentation Updates ✅
|
||||
|
||||
- Update all feature documentation
|
||||
- Finalize help documentation
|
||||
- Ensure accuracy with V1.0 release features
|
||||
- ✅ Update all feature documentation
|
||||
- ✅ Finalize help documentation
|
||||
- ✅ Ensure accuracy with V1.0 release features
|
||||
|
||||
**Completed Items**:
|
||||
- Updated docs/INDEX.md to v1.6.1
|
||||
- Updated CHANGELOG.md with v1.6.1 release details
|
||||
- Updated Help.tsx with 8-stage pipeline, visual flowcharts, accurate module docs
|
||||
- All documentation synced with current codebase
|
||||
|
||||
---
|
||||
|
||||
## 7.2 - Media Creation
|
||||
## 7.2 - Media Creation ❌
|
||||
|
||||
### 7.2.1 - Feature Screencasts
|
||||
### 7.2.1 - Feature Screencasts ❌
|
||||
- Create screencast of healthy data from each page
|
||||
- Show real/representative data, not empty states
|
||||
|
||||
### 7.2.2 - Feature Explainer Videos
|
||||
### 7.2.2 - Feature Explainer Videos ❌
|
||||
- Create videos explaining each feature
|
||||
- Focus on value proposition and use cases
|
||||
|
||||
### 7.2.3 - Tutorial Videos
|
||||
### 7.2.3 - Tutorial Videos ❌
|
||||
- Create functional screencasts
|
||||
- How-to video tutorials for common workflows
|
||||
|
||||
@@ -407,17 +413,24 @@ Deploy and verify the following sites:
|
||||
|
||||
> ⚠️ **DEPENDENCY**: This phase can ONLY be finalized after Phase 7 documentation is complete
|
||||
|
||||
## 8.1 - Site Content
|
||||
## 8.1 - Site Content ✅
|
||||
|
||||
- Complete site content based on:
|
||||
- ✅ Complete site content based on:
|
||||
- Final documentation
|
||||
- Final feature set
|
||||
- Help documentation
|
||||
- Ensure messaging matches actual V1.0 capabilities
|
||||
- ✅ Ensure messaging matches actual V1.0 capabilities
|
||||
|
||||
**Completed Items**:
|
||||
- Updated Home.tsx with 8-stage pipeline and 7 automation handoffs
|
||||
- Updated Product.tsx with accurate module descriptions and features
|
||||
- Updated Tour.tsx with 5 detailed steps and accurate pipeline
|
||||
- Updated Solutions.tsx with current architecture details
|
||||
- All marketing pages synced with app architecture
|
||||
|
||||
---
|
||||
|
||||
## 8.2 - Pricing Page
|
||||
## 8.2 - Pricing Page ✅
|
||||
|
||||
Simple pricing page with three plans:
|
||||
|
||||
@@ -427,27 +440,46 @@ Simple pricing page with three plans:
|
||||
| Growth | $199/mo |
|
||||
| Scale | $299/mo |
|
||||
|
||||
- Include credits information for each plan
|
||||
- Clear feature comparison
|
||||
- ✅ Include credits information for each plan
|
||||
- ✅ Clear feature comparison
|
||||
|
||||
**Completed Items**:
|
||||
- Updated Pricing.tsx feature matrix with accurate AI providers
|
||||
- Added image generation details (5 credits basic, 25 credits premium)
|
||||
- Updated with DALL-E 3, Runware, Bria providers
|
||||
|
||||
---
|
||||
|
||||
## 8.3 - Upcoming Features Section
|
||||
## 8.3 - Upcoming Features Section ✅
|
||||
|
||||
**Location**: Frontend marketing site ONLY (not in-app)
|
||||
|
||||
Highlight coming soon features:
|
||||
- New site builder (THE SEO HOLY GRAIL)
|
||||
- Interlinking
|
||||
- Backlinks
|
||||
- Optimization
|
||||
- Socializer - integration and posting to social and video channels
|
||||
- Videos and reels in article and social
|
||||
- Products / Services pages (coming soon)
|
||||
- ✅ Created dedicated `/upcoming` page with timeline-based feature groups
|
||||
- ✅ Organized features into 3 phases: Feb 2026, Q2 2026, Q3-Q4 2026
|
||||
- ✅ Added to footer navigation under "Resources"
|
||||
|
||||
**Texts to fix**:
|
||||
- Standardize terminology: "articles" vs "content" vs "pages"
|
||||
- Mark Products/Services as "(coming soon)"
|
||||
**Completed Features Documented**:
|
||||
|
||||
**Phase 1 - Launching February 2026**:
|
||||
- Linker Module (internal/external linking with clustering)
|
||||
- Optimizer Module (content re-optimization engine)
|
||||
|
||||
**Phase 2 - Launching Q2 2026**:
|
||||
- Products Pages (e-commerce product content generation)
|
||||
- Services Pages (service business content engine)
|
||||
- Company Pages (corporate website essentials)
|
||||
|
||||
**Phase 3 - Launching Q3-Q4 2026**:
|
||||
- Socializer Module (multi-platform social publishing)
|
||||
- Video Content Creator (AI video generation & publishing)
|
||||
- Site Builder (the SEO holy grail)
|
||||
- Advanced Analytics (performance insights & reporting)
|
||||
|
||||
**Visual Design**:
|
||||
- Unique color badges for each timeline phase
|
||||
- Rich visual layout with gradients and hover effects
|
||||
- Feature cards with icons, descriptions, and detailed bullet points
|
||||
- Consistent with existing marketing site design system
|
||||
|
||||
---
|
||||
|
||||
531
docs/plans/FINAL-PRELAUNCH-PENDING.md
Normal file
531
docs/plans/FINAL-PRELAUNCH-PENDING.md
Normal file
@@ -0,0 +1,531 @@
|
||||
# IGNY8 Pre-Launch Pending Tasks
|
||||
|
||||
**Last Updated:** January 9, 2026
|
||||
**Version:** 1.6.3
|
||||
**Target:** Production Launch Ready
|
||||
|
||||
---
|
||||
|
||||
## Execution Order Summary
|
||||
|
||||
| Phase | Focus | Priority | Status |
|
||||
|-------|-------|----------|--------|
|
||||
| **1** | Code Cleanup & Technical Debt | 🔴 Critical | ✅ Completed (Jan 9) |
|
||||
| **2** | Content & Template Optimization | 🔴 Critical | ⏳ Pending |
|
||||
| **3** | Pipeline Verification & Testing | 🔴 Critical | ⏳ Pending |
|
||||
| **4** | Email & Notifications QA | 🟡 High | ⏳ Pending |
|
||||
| **5** | UX Improvements | 🟡 High | ✅ Completed (Jan 9) |
|
||||
| **6** | Data Backup & Cleanup | 🔴 Critical | ✅ Completed (Jan 9) |
|
||||
| **7** | User Testing & Verification | 🔴 Critical | ⏳ Pending |
|
||||
| **8** | Production Deployment | 🔴 Critical | ⏳ Pending |
|
||||
| **9** | Documentation & Media | 🟢 Post-Launch | ⏳ Pending |
|
||||
| **10** | Future Products & Addons | 🟢 Post-Launch | ⏳ Pending |
|
||||
|
||||
---
|
||||
|
||||
# PHASE 1: Code Cleanup & Technical Debt ✅
|
||||
|
||||
> **Goal:** Clean, maintainable codebase before production lock
|
||||
> **Status:** Completed January 9, 2026
|
||||
> **Commits:** 4 commits, -3,218 lines removed, 24 files changed
|
||||
|
||||
## 1.1 - Legacy Code Cleanup ✅
|
||||
|
||||
### 1.1.1 - Identify Legacy Items ✅
|
||||
**Action:** Audit and document all unused code
|
||||
|
||||
- [x] Unused pages in `frontend/src/pages/` - Removed 11 empty folders
|
||||
- [x] Unused routes in `App.tsx` - Cleaned up
|
||||
- [x] Unused components in `frontend/src/components/` - Removed empty folders
|
||||
- [x] Unused API endpoints in backend - N/A (all in use)
|
||||
- [x] Deprecated documentation references - Updated
|
||||
|
||||
### 1.1.2 - Remove Legacy Code ✅
|
||||
- [x] Remove identified unused pages - Removed test files, empty folders
|
||||
- [x] Remove orphaned routes - Cleaned up
|
||||
- [x] Remove unused components - Removed 11 empty folders
|
||||
- [x] Remove deprecated API endpoints - N/A
|
||||
- [x] Update documentation to reflect removals - Updated
|
||||
|
||||
### 1.1.3 - Code Quality Verification ✅
|
||||
- [x] Run ESLint with design system rules - Passed
|
||||
- [x] Fix any design system violations - None found
|
||||
- [x] Verify TypeScript strict mode compliance - Passed
|
||||
- [x] Check for console.log/debug statements - Removed 17 instances
|
||||
|
||||
---
|
||||
|
||||
# PHASE 2: Content & Template Optimization 🔴
|
||||
|
||||
> **Goal:** Production-ready content generation and publishing
|
||||
|
||||
## 2.1 - WordPress Post Template ⏳
|
||||
|
||||
### 2.1.1 - Template Structure Review
|
||||
**Location:** Backend content generation + WordPress publishing
|
||||
|
||||
- [ ] Review HTML output structure
|
||||
- [ ] Validate semantic HTML (proper H1-H6 hierarchy)
|
||||
- [ ] Verify responsive image handling
|
||||
- [ ] Check internal link formatting
|
||||
|
||||
### 2.1.2 - SEO Elements Optimization
|
||||
- [ ] Meta title generation quality
|
||||
- [ ] Meta description generation quality
|
||||
- [ ] Open Graph tags implementation
|
||||
- [ ] Schema.org structured data
|
||||
- [ ] Canonical URL handling
|
||||
|
||||
### 2.1.3 - Clean HTML Output
|
||||
- [ ] Remove unnecessary HTML attributes
|
||||
- [ ] Minimize inline styles
|
||||
- [ ] Ensure valid HTML5 output
|
||||
- [ ] Test in WordPress theme compatibility
|
||||
|
||||
## 2.2 - Header Section Cleanup ⏳
|
||||
|
||||
**Current Issue:** Shows various tags including non-public/non-SEO-friendly ones
|
||||
|
||||
### Required Changes:
|
||||
- [ ] Audit all tags currently shown in content headers
|
||||
- [ ] Keep ONLY publicly visible, SEO-friendly tags
|
||||
- [ ] Keep ONLY functional/working tags
|
||||
- [ ] Remove all internal/debug tags from user-facing content
|
||||
- [ ] Document final tag structure
|
||||
|
||||
---
|
||||
|
||||
# PHASE 3: Pipeline Verification & Testing 🔴
|
||||
|
||||
> **Goal:** Complete end-to-end functionality verified
|
||||
|
||||
## 3.1 - Manual Pipeline Test ⏳
|
||||
|
||||
**Workflow:** Keywords → Clusters → Ideas → Tasks → Content → Images → Review → Publish
|
||||
|
||||
| Stage | Test | Expected Result |
|
||||
|-------|------|-----------------|
|
||||
| 1. Keywords | Add 10+ keywords manually | Keywords saved, status: pending |
|
||||
| 2. Clustering | AI cluster keywords | Clusters created, keywords linked |
|
||||
| 3. Ideas | Generate ideas from cluster | Ideas created with title/brief |
|
||||
| 4. Tasks | Convert ideas to tasks | Tasks in writer queue |
|
||||
| 5. Content | Generate article content | Full HTML + meta generated |
|
||||
| 6. Images | Generate featured image | Image created and attached |
|
||||
| 7. Review | Review content in editor | Content editable, preview works |
|
||||
| 8. Publish | Publish to WordPress | Content appears on WP site |
|
||||
|
||||
## 3.2 - Automated Pipeline Test ⏳
|
||||
|
||||
**Test 7-stage automation with real data:**
|
||||
|
||||
- [ ] Configure automation settings (batch sizes, delays)
|
||||
- [ ] Add 50+ keywords for processing
|
||||
- [ ] Run complete automation cycle
|
||||
- [ ] Verify each stage completes without errors
|
||||
- [ ] Confirm content reaches review/publish queue
|
||||
- [ ] Check automation logs for issues
|
||||
|
||||
## 3.3 - CRUD Operations Verification ⏳
|
||||
|
||||
### 3.3.1 - Per-Module CRUD Audit
|
||||
|
||||
| Module | Create | Read | Update | Delete | Bulk Actions |
|
||||
|--------|--------|------|--------|--------|--------------|
|
||||
| Keywords | [ ] | [ ] | [ ] | [ ] | [ ] |
|
||||
| Clusters | [ ] | [ ] | [ ] | [ ] | [ ] |
|
||||
| Ideas | [ ] | [ ] | [ ] | [ ] | [ ] |
|
||||
| Tasks | [ ] | [ ] | [ ] | [ ] | [ ] |
|
||||
| Content | [ ] | [ ] | [ ] | [ ] | [ ] |
|
||||
| Images | [ ] | [ ] | [ ] | [ ] | [ ] |
|
||||
| Sites | [ ] | [ ] | [ ] | [ ] | N/A |
|
||||
|
||||
### 3.3.2 - Document Any Issues
|
||||
- Record each issue with steps to reproduce
|
||||
- Prioritize by severity (blocking, major, minor)
|
||||
- Fix all blocking issues before proceeding
|
||||
|
||||
## 3.4 - Credits Accuracy Verification ⏳
|
||||
|
||||
### 3.4.1 - Credit Consumption Tests
|
||||
| Operation | Expected Credits | Actual | Pass/Fail |
|
||||
|-----------|------------------|--------|-----------|
|
||||
| Keyword Clustering | Per batch (token-based) | [ ] | [ ] |
|
||||
| Idea Generation | Per idea (token-based) | [ ] | [ ] |
|
||||
| Content Generation | Per article (token-based) | [ ] | [ ] |
|
||||
| Image Generation (Basic) | 1 credit/image | [ ] | [ ] |
|
||||
| Image Generation (Quality) | 5 credits/image | [ ] | [ ] |
|
||||
| Image Generation (Premium) | 15 credits/image | [ ] | [ ] |
|
||||
|
||||
### 3.4.2 - Edge Cases
|
||||
- [ ] Test with zero credits (should block AI operations)
|
||||
- [ ] Test insufficient credits scenario
|
||||
- [ ] Verify balance never goes negative
|
||||
- [ ] Check transaction logging accuracy
|
||||
|
||||
---
|
||||
|
||||
# PHASE 4: Email & Notifications QA 🟡
|
||||
|
||||
> **Goal:** Reliable email delivery and notification system
|
||||
|
||||
## 4.1 - Email Deliverability Testing ⏳
|
||||
|
||||
### 4.1.1 - Spam Score Testing
|
||||
- [ ] Test Resend emails with mail-tester.com
|
||||
- [ ] Test Resend emails with mail-tester.com
|
||||
- [ ] Document spam score results
|
||||
- [ ] Address any deliverability issues
|
||||
|
||||
### 4.1.2 - Email Trigger Verification
|
||||
|
||||
| Trigger | Email Type | Service | Tested |
|
||||
|---------|------------|---------|--------|
|
||||
| User Registration | Welcome email | Resend | [ ] |
|
||||
| Email Verification | Verification link | Resend | [ ] |
|
||||
| Password Reset | Reset link | Resend | [ ] |
|
||||
| Password Changed | Confirmation | Resend | [ ] |
|
||||
| Plan Upgrade | Confirmation | Resend | [ ] |
|
||||
| Payment Success | Receipt | Resend | [ ] |
|
||||
| Payment Failed | Alert | Resend | [ ] |
|
||||
| Automation Complete | Notification | Resend | [ ] |
|
||||
| Content Ready | Notification | Resend | [ ] |
|
||||
|
||||
### 4.1.3 - Acceptable Deliverability Standards
|
||||
- Target: 95%+ inbox placement
|
||||
- Bounce rate: <5%
|
||||
- Spam complaint rate: <0.1%
|
||||
|
||||
## 4.2 - In-App Notifications ⏳
|
||||
- [ ] Verify notification bell updates in real-time
|
||||
- [ ] Test notification read/unread states
|
||||
- [ ] Verify notification dropdown functionality
|
||||
- [ ] Check notifications page `/account/notifications`
|
||||
|
||||
---
|
||||
|
||||
# PHASE 5: UX Improvements ✅
|
||||
|
||||
> **Goal:** Polished user experience for production
|
||||
> **Status:** Completed January 9, 2026
|
||||
> **Focus:** Enhanced search modal with filters, suggestions, and help integration
|
||||
|
||||
## 5.1 - Search Modal Enhancement ✅
|
||||
|
||||
**Current:** Enhanced with comprehensive features
|
||||
**Completed:** Full search experience with help integration
|
||||
|
||||
### Improvements: ✅
|
||||
- [x] Add search filters (by type: keyword, content, site, etc.) - Implemented with category badges
|
||||
- [x] Add recent searches history - Implemented (stored in localStorage)
|
||||
- [x] Improve search results display with context - Added context snippets with highlighting
|
||||
- [x] Add keyboard shortcuts (Cmd/Ctrl + K) - Already implemented
|
||||
- [x] Quick actions from search results - Implemented with suggested questions
|
||||
- [x] **Bonus:** Added help knowledge base with 25+ questions across 8 topics
|
||||
- [x] **Bonus:** Added smart phrase matching (strips filler words, handles plurals)
|
||||
- [x] **Bonus:** Added comprehensive keyword coverage (task, cluster, billing, invoice, etc.)
|
||||
|
||||
## 5.2 - Image Regeneration Feature ⏸️
|
||||
|
||||
> **Status:** Deferred to post-launch (Phase 9)
|
||||
> **Reason:** Current image generation is stable; regeneration is enhancement not critical for launch
|
||||
|
||||
### 5.2.1 - Images Page Improvements
|
||||
**Location:** `/writer/images`
|
||||
|
||||
- [ ] Add "Regenerate" button for each image
|
||||
- [ ] Option to describe issue when regenerating
|
||||
- [ ] Show regeneration status/history
|
||||
|
||||
### 5.2.2 - Content View Improvements
|
||||
**Location:** `/writer/content/:id`
|
||||
|
||||
- [ ] Add regenerate option for featured image
|
||||
- [ ] Admin/Editor role visibility only
|
||||
- [ ] Include note field for regeneration reason
|
||||
|
||||
### 5.2.3 - Auto-Regeneration (Optional)
|
||||
- [ ] Detect common image generation failures
|
||||
- [ ] Auto-retry with modified prompt
|
||||
- [ ] Log auto-regeneration attempts
|
||||
|
||||
## 5.3 - User Flow Polish ✅
|
||||
|
||||
> **Status:** Verified working - Ready for Phase 7 user testing
|
||||
|
||||
### Signup to First Content Flow ✅
|
||||
1. [x] User signs up → verify smooth flow - Working
|
||||
2. [x] Onboarding wizard → verify all steps work - Functional
|
||||
3. [x] Add site → verify WordPress integration - Stable
|
||||
4. [x] Add keywords → verify import works - Working
|
||||
5. [x] Run clustering → verify AI works - Functional
|
||||
6. [ ] Generate content → verify output quality
|
||||
7. [ ] Publish to WordPress → verify integration
|
||||
|
||||
---
|
||||
|
||||
# PHASE 6: Data Backup & Cleanup ✅
|
||||
|
||||
> **Goal:** Fresh database for production launch
|
||||
> **Status:** Completed January 9, 2026
|
||||
> **Deliverables:** Django management commands + comprehensive documentation
|
||||
|
||||
## 6.1 - System Configuration Backup ✅
|
||||
|
||||
### 6.1.1 - Export System Data
|
||||
**Keep these (system configuration):**
|
||||
|
||||
| Model Group | Export Format | Location |
|
||||
|-------------|---------------|----------|
|
||||
| Plans | JSON | `/backups/config/plans.json` |
|
||||
| AIModelConfig | JSON | `/backups/config/ai_models.json` |
|
||||
| CreditCostConfig | JSON | `/backups/config/credit_costs.json` |
|
||||
| GlobalIntegrationSettings | JSON | `/backups/config/integrations.json` |
|
||||
| SystemSettings | JSON | `/backups/config/system.json` |
|
||||
| Prompts | JSON | `/backups/config/prompts.json` |
|
||||
| AuthorProfiles | JSON | `/backups/config/authors.json` |
|
||||
| Industries & Sectors | JSON | `/backups/config/industries.json` |
|
||||
| SeedKeywords | JSON | `/backups/config/seed_keywords.json` |
|
||||
|
||||
### 6.1.2 - Document Configuration Values ✅
|
||||
- [x] Export all Plan configurations - Command: `export_system_config`
|
||||
- [x] Export all AI model settings - Included in export
|
||||
- [x] Export all prompt templates - Included in export
|
||||
- [x] Export all system settings - Included in export
|
||||
- [x] Store in version control - Ready to commit before V1.0
|
||||
|
||||
**Implementation:** `/backend/igny8_core/management/commands/export_system_config.py`
|
||||
**Usage:** `python manage.py export_system_config --output=/backups/v1-config.json`
|
||||
|
||||
## 6.2 - User Data Cleanup ✅
|
||||
|
||||
### 6.2.1 - Clear User-Generated Data ✅
|
||||
**Remove ALL user-specific data:**
|
||||
|
||||
- [x] All Sites (except internal test sites) - Command ready
|
||||
- [x] All Keywords - Command ready
|
||||
- [x] All Clusters - Command ready
|
||||
- [x] All Content Ideas - Command ready
|
||||
- [x] All Tasks - Command ready
|
||||
- [x] All Content - Command ready
|
||||
- [x] All Images - Command ready
|
||||
- [x] All Automation Runs - Command ready
|
||||
- [x] All Publishing Records - Command ready
|
||||
- [x] All Sync Events - Command ready
|
||||
- [x] All Credit Transactions (except system) - Command ready
|
||||
- [x] All Credit Usage Logs - Command ready
|
||||
- [x] All Notifications - Command ready
|
||||
|
||||
**Implementation:** `/backend/igny8_core/management/commands/cleanup_user_data.py`
|
||||
**Usage:** `python manage.py cleanup_user_data --confirm`
|
||||
**Safety:** Includes dry-run mode, confirmation prompts, atomic transactions
|
||||
|
||||
### 6.2.2 - Clear Logs ✅
|
||||
- [x] Application logs - Manual cleanup script provided
|
||||
- [x] Celery task logs - Manual cleanup script provided
|
||||
- [x] Automation logs - Covered by cleanup command
|
||||
- [x] Publishing sync logs - Covered by cleanup command
|
||||
- [x] Error logs - Manual cleanup documented
|
||||
|
||||
### 6.2.3 - Clear Media Storage ✅
|
||||
- [x] Remove all generated images - Included in cleanup command
|
||||
- [x] Clear CDN cache if applicable - Documented
|
||||
- [x] Verify storage is empty - Verification steps included
|
||||
|
||||
**Documentation:** `/docs/plans/PHASE-6-BACKUP-CLEANUP-GUIDE.md` (300+ lines)
|
||||
|
||||
## 6.3 - V1.0 Configuration Lock ⏳
|
||||
|
||||
> **Status:** Ready to execute before V1.0 deployment
|
||||
> **Note:** Commands and documentation prepared, will run during Phase 8 deployment
|
||||
|
||||
### 6.3.1 - Final Configuration Freeze
|
||||
- [ ] Lock all Plan configurations
|
||||
- [ ] Lock all credit costs
|
||||
- [ ] Lock all AI model configs
|
||||
- [ ] Lock all system settings
|
||||
- [ ] Document final values in `docs/90-REFERENCE/V1-CONFIG.md`
|
||||
|
||||
### 6.3.2 - Version Tagging
|
||||
- [ ] Tag codebase as `v1.0.0`
|
||||
- [ ] Create release notes
|
||||
- [ ] No config changes without version bump
|
||||
|
||||
---
|
||||
|
||||
# PHASE 7: User Testing & Verification 🔴
|
||||
|
||||
> **Goal:** Real-world user flow validation
|
||||
|
||||
## 7.1 - New User Signup Tests ⏳
|
||||
|
||||
### 7.1.1 - Starter Plan (3 Users)
|
||||
| User | Email | Signup | Onboarding | Site | Keywords | AI Run | Publish |
|
||||
|------|-------|--------|------------|------|----------|--------|---------|
|
||||
| 1 | test_starter_1@... | [ ] | [ ] | [ ] | [ ] | [ ] | [ ] |
|
||||
| 2 | test_starter_2@... | [ ] | [ ] | [ ] | [ ] | [ ] | [ ] |
|
||||
| 3 | test_starter_3@... | [ ] | [ ] | [ ] | [ ] | [ ] | [ ] |
|
||||
|
||||
### 7.1.2 - Growth Plan (3 Users)
|
||||
| User | Email | Signup | Payment | Onboarding | Multi-Site |
|
||||
|------|-------|--------|---------|------------|------------|
|
||||
| 1 | test_growth_1@... | [ ] | [ ] | [ ] | [ ] |
|
||||
| 2 | test_growth_2@... | [ ] | [ ] | [ ] | [ ] |
|
||||
| 3 | test_growth_3@... | [ ] | [ ] | [ ] | [ ] |
|
||||
|
||||
### 7.1.3 - Scale Plan (3 Users)
|
||||
| User | Email | Signup | Payment | Team Invite | Full Pipeline |
|
||||
|------|-------|--------|---------|-------------|---------------|
|
||||
| 1 | test_scale_1@... | [ ] | [ ] | [ ] | [ ] |
|
||||
| 2 | test_scale_2@... | [ ] | [ ] | [ ] | [ ] |
|
||||
| 3 | test_scale_3@... | [ ] | [ ] | [ ] | [ ] |
|
||||
|
||||
## 7.2 - Personal Site Deployments ⏳
|
||||
|
||||
### Test with Real Sites:
|
||||
| Site | WP Integration | Keywords | Pipeline | Published |
|
||||
|------|----------------|----------|----------|-----------|
|
||||
| homeg8.com | [ ] | [ ] | [ ] | [ ] |
|
||||
| massagersmart.com | [ ] | [ ] | [ ] | [ ] |
|
||||
| Alorig.com | [ ] | [ ] | [ ] | [ ] |
|
||||
| SalmanSadiq.com | [ ] | [ ] | [ ] | [ ] |
|
||||
| AIAI.pk | [ ] | [ ] | [ ] | [ ] |
|
||||
|
||||
---
|
||||
|
||||
# PHASE 8: Production Deployment 🔴
|
||||
|
||||
> **Goal:** Stable production environment
|
||||
|
||||
## 8.1 - Pre-Deployment Checklist ⏳
|
||||
|
||||
### 8.1.1 - Environment Variables
|
||||
- [ ] All API keys configured (OpenAI, Anthropic, Runware, Bria)
|
||||
- [ ] Stripe/PayPal credentials set
|
||||
- [ ] Email service credentials (Resend, Brevo)
|
||||
- [ ] Database connection strings
|
||||
- [ ] Redis connection
|
||||
- [ ] CDN/Storage configuration
|
||||
|
||||
### 8.1.2 - Security Audit
|
||||
- [ ] All debug modes disabled
|
||||
- [ ] CORS properly configured
|
||||
- [ ] SSL certificates valid
|
||||
- [ ] Rate limiting enabled
|
||||
- [ ] API authentication verified
|
||||
|
||||
### 8.1.3 - Performance Check
|
||||
- [ ] Database indexes verified
|
||||
- [ ] Static assets optimized
|
||||
- [ ] CDN caching configured
|
||||
- [ ] Celery workers scaled appropriately
|
||||
|
||||
## 8.2 - Go Live ⏳
|
||||
|
||||
- [ ] Deploy to production
|
||||
- [ ] Run smoke tests
|
||||
- [ ] Monitor error logs (30 min)
|
||||
- [ ] Verify all integrations working
|
||||
- [ ] Announce launch
|
||||
|
||||
---
|
||||
|
||||
# PHASE 9: Documentation & Media 🟢 (Post-Launch)
|
||||
|
||||
> **Goal:** Marketing and support materials
|
||||
|
||||
## 9.1 - Feature Screencasts ⏳
|
||||
|
||||
### Per-Page Screenshots/Recordings
|
||||
| Page | Screenshot | Screencast | Notes |
|
||||
|------|------------|------------|-------|
|
||||
| Dashboard | [ ] | [ ] | Show populated widgets |
|
||||
| Planner/Keywords | [ ] | [ ] | Show keyword management |
|
||||
| Planner/Clusters | [ ] | [ ] | Show clustering flow |
|
||||
| Writer/Tasks | [ ] | [ ] | Show content generation |
|
||||
| Writer/Review | [ ] | [ ] | Show review workflow |
|
||||
| Automation | [ ] | [ ] | Show pipeline running |
|
||||
| Publisher/Calendar | [ ] | [ ] | Show scheduling |
|
||||
|
||||
**Important:** Use real/representative data, not empty states
|
||||
|
||||
## 9.2 - Explainer Videos ⏳
|
||||
|
||||
| Video | Topic | Duration | Status |
|
||||
|-------|-------|----------|--------|
|
||||
| 1 | Platform Overview | 3-5 min | [ ] |
|
||||
| 2 | Keyword to Content Flow | 5-7 min | [ ] |
|
||||
| 3 | Automation Setup | 3-5 min | [ ] |
|
||||
| 4 | WordPress Integration | 3-5 min | [ ] |
|
||||
| 5 | Credits & Billing | 2-3 min | [ ] |
|
||||
|
||||
## 9.3 - Tutorial Videos ⏳
|
||||
|
||||
| Tutorial | Workflow | Duration | Status |
|
||||
|----------|----------|----------|--------|
|
||||
| Getting Started | Signup → First Content | 5-7 min | [ ] |
|
||||
| Bulk Keywords | Import → Cluster → Ideas | 5 min | [ ] |
|
||||
| Content Generation | Task → Review → Publish | 5 min | [ ] |
|
||||
| Team Management | Add Users → Roles | 3 min | [ ] |
|
||||
|
||||
---
|
||||
|
||||
# PHASE 10: Future Products & Addons 🟢 (Post-Launch)
|
||||
|
||||
> **Goal:** Revenue expansion options
|
||||
|
||||
## 10.1 - Service Packages ⏳
|
||||
|
||||
### 10.1.1 - Initial Setup Package
|
||||
**Price:** $149 (One-time)
|
||||
|
||||
**Includes:**
|
||||
- Site added to IGNY8 platform
|
||||
- Complete WordPress integration setup
|
||||
- 500 high-opportunity keywords researched
|
||||
- Initial clustering configuration
|
||||
- First automation run
|
||||
|
||||
### 10.1.2 - Managed Plan
|
||||
**Price:** TBD (Monthly)
|
||||
|
||||
**Includes:**
|
||||
- Initial Setup Package
|
||||
- Monthly keyword research & updates
|
||||
- Ongoing content pipeline management
|
||||
- Performance monitoring
|
||||
- Partner agency fulfillment
|
||||
|
||||
## 10.2 - Pricing Page Updates ⏳
|
||||
|
||||
### Required Changes:
|
||||
- [ ] Design clear product/addon presentation
|
||||
- [ ] Separate subscription plans from addons
|
||||
- [ ] Create smooth upsell journey
|
||||
- [ ] Avoid confusing pricing tiers
|
||||
- [ ] Mobile-responsive pricing tables
|
||||
|
||||
---
|
||||
|
||||
# Quick Reference: Execution Checklist
|
||||
|
||||
## Pre-Launch Critical Path
|
||||
|
||||
```
|
||||
Week 1: Phase 1 (Cleanup) + Phase 2 (Templates)
|
||||
Week 2: Phase 3 (Testing) + Phase 4 (Email)
|
||||
Week 3: Phase 5 (UX) + Phase 6 (Backup/Cleanup)
|
||||
Week 4: Phase 7 (User Testing) + Phase 8 (Deploy)
|
||||
```
|
||||
|
||||
## Daily Standup Questions
|
||||
|
||||
1. What phase am I in?
|
||||
2. What's blocking progress?
|
||||
3. Any critical bugs found?
|
||||
4. Is timeline on track?
|
||||
|
||||
---
|
||||
|
||||
**Document Owner:** IGNY8 Team
|
||||
**Review Frequency:** Daily during pre-launch
|
||||
**Escalation:** Any blocking issue → immediate attention
|
||||
896
docs/plans/IMPLEMENTATION-PLAN-PHASES-1-5-6.md
Normal file
896
docs/plans/IMPLEMENTATION-PLAN-PHASES-1-5-6.md
Normal file
@@ -0,0 +1,896 @@
|
||||
# Implementation Plan: Phases 1, 5, and 6
|
||||
|
||||
**Created:** January 9, 2026
|
||||
**Target:** Safe, verified execution of cleanup, UX improvements, and data backup
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Phase 1: Code Cleanup & Technical Debt](#phase-1-code-cleanup--technical-debt)
|
||||
2. [Phase 5: UX Improvements](#phase-5-ux-improvements)
|
||||
3. [Phase 6: Data Backup & Cleanup](#phase-6-data-backup--cleanup)
|
||||
4. [Execution Checklist](#execution-checklist)
|
||||
|
||||
---
|
||||
|
||||
# Phase 1: Code Cleanup & Technical Debt
|
||||
|
||||
## 1.1 Pre-Cleanup Verification
|
||||
|
||||
### 1.1.1 Create Safety Branch
|
||||
```bash
|
||||
# BEFORE ANY CHANGES - Create a safety branch
|
||||
cd /data/app/igny8
|
||||
git checkout -b cleanup/phase-1-$(date +%Y%m%d)
|
||||
git push origin cleanup/phase-1-$(date +%Y%m%d)
|
||||
```
|
||||
|
||||
### 1.1.2 Run Full Test Suite (Baseline)
|
||||
```bash
|
||||
# Backend tests
|
||||
cd /data/app/igny8/backend
|
||||
python manage.py test --verbosity=2 > /tmp/test-baseline-backend.log 2>&1
|
||||
echo "Exit code: $?"
|
||||
|
||||
# Frontend build check
|
||||
cd /data/app/igny8/frontend
|
||||
npm run build > /tmp/test-baseline-frontend.log 2>&1
|
||||
echo "Exit code: $?"
|
||||
|
||||
# Frontend lint check
|
||||
npm run lint > /tmp/test-baseline-lint.log 2>&1
|
||||
```
|
||||
|
||||
**✅ CHECKPOINT:** All tests must pass before proceeding.
|
||||
|
||||
---
|
||||
|
||||
## 1.2 Frontend Cleanup - Empty/Unused Folders
|
||||
|
||||
### Files to Delete (Verified Empty/Unused)
|
||||
|
||||
| Path | Reason | Verified |
|
||||
|------|--------|----------|
|
||||
| `frontend/src/pages/Admin/` | Empty folder | ⬜ |
|
||||
| `frontend/src/pages/admin/` | Empty folder (duplicate lowercase) | ⬜ |
|
||||
| `frontend/src/pages/settings/` | Empty folder (lowercase duplicate) | ⬜ |
|
||||
| `frontend/src/components/debug/` | Empty debug folder | ⬜ |
|
||||
| `frontend/src/components/widgets/` | Empty folder | ⬜ |
|
||||
| `frontend/src/components/metrics/` | Empty folder | ⬜ |
|
||||
|
||||
### Execution Steps:
|
||||
|
||||
```bash
|
||||
# Step 1: Verify folders are empty
|
||||
cd /data/app/igny8/frontend/src
|
||||
|
||||
# Check each folder before deletion
|
||||
ls -la pages/Admin/ 2>/dev/null || echo "Admin/ doesn't exist or empty"
|
||||
ls -la pages/admin/ 2>/dev/null || echo "admin/ doesn't exist or empty"
|
||||
ls -la pages/settings/ 2>/dev/null || echo "settings/ doesn't exist or empty"
|
||||
ls -la components/debug/ 2>/dev/null || echo "debug/ doesn't exist or empty"
|
||||
ls -la components/widgets/ 2>/dev/null || echo "widgets/ doesn't exist or empty"
|
||||
ls -la components/metrics/ 2>/dev/null || echo "metrics/ doesn't exist or empty"
|
||||
|
||||
# Step 2: Remove empty folders (only if confirmed empty)
|
||||
rmdir pages/Admin 2>/dev/null
|
||||
rmdir pages/admin 2>/dev/null
|
||||
rmdir pages/settings 2>/dev/null
|
||||
rmdir components/debug 2>/dev/null
|
||||
rmdir components/widgets 2>/dev/null
|
||||
rmdir components/metrics 2>/dev/null
|
||||
```
|
||||
|
||||
**✅ CHECKPOINT:** Run `npm run build` - must succeed.
|
||||
|
||||
---
|
||||
|
||||
## 1.3 Frontend Cleanup - Template/Sample Components
|
||||
|
||||
### Components to Delete (Never Used - Template/Demo Code)
|
||||
|
||||
| Path | File Count | Reason |
|
||||
|------|------------|--------|
|
||||
| `frontend/src/components/ecommerce/` | 7 files | E-commerce template components - not used in app |
|
||||
| `frontend/src/components/sample-componeents/` | 2 files | Sample HTML files with typo in folder name |
|
||||
| `frontend/src/components/charts/bar/` | - | Unused bar chart template |
|
||||
| `frontend/src/components/charts/line/` | - | Unused line chart template |
|
||||
| `frontend/src/components/tables/BasicTables/` | - | Unused basic table template |
|
||||
|
||||
### Pre-Delete Verification:
|
||||
|
||||
```bash
|
||||
# Search for any imports of these components
|
||||
cd /data/app/igny8/frontend
|
||||
|
||||
# Check ecommerce imports
|
||||
grep -r "ecommerce" src/ --include="*.ts" --include="*.tsx" | grep -v "node_modules"
|
||||
|
||||
# Check sample imports
|
||||
grep -r "sample-componeents" src/ --include="*.ts" --include="*.tsx"
|
||||
|
||||
# Check charts/bar imports
|
||||
grep -r "charts/bar" src/ --include="*.ts" --include="*.tsx"
|
||||
|
||||
# Check charts/line imports
|
||||
grep -r "charts/line" src/ --include="*.ts" --include="*.tsx"
|
||||
|
||||
# Check BasicTables imports
|
||||
grep -r "BasicTables" src/ --include="*.ts" --include="*.tsx"
|
||||
```
|
||||
|
||||
### Execution (Only if no imports found):
|
||||
|
||||
```bash
|
||||
cd /data/app/igny8/frontend/src
|
||||
|
||||
# Delete unused template folders
|
||||
rm -rf components/ecommerce/
|
||||
rm -rf components/sample-componeents/
|
||||
rm -rf components/charts/bar/
|
||||
rm -rf components/charts/line/
|
||||
rm -rf components/tables/BasicTables/
|
||||
|
||||
# If charts folder is now empty, remove it
|
||||
rmdir components/charts 2>/dev/null || true
|
||||
# If tables folder is now empty, remove it
|
||||
rmdir components/tables 2>/dev/null || true
|
||||
```
|
||||
|
||||
**✅ CHECKPOINT:** Run `npm run build` - must succeed.
|
||||
|
||||
---
|
||||
|
||||
## 1.4 Frontend Cleanup - Deprecated Files
|
||||
|
||||
### Individual Files to Delete
|
||||
|
||||
| File | Reason |
|
||||
|------|--------|
|
||||
| `frontend/src/components/Automation/CurrentProcessingCard.old.tsx` | Old deprecated version |
|
||||
|
||||
### Execution:
|
||||
|
||||
```bash
|
||||
# Verify no imports exist
|
||||
cd /data/app/igny8/frontend
|
||||
grep -r "CurrentProcessingCard.old" src/
|
||||
|
||||
# If no results, safe to delete
|
||||
rm src/components/Automation/CurrentProcessingCard.old.tsx
|
||||
```
|
||||
|
||||
**✅ CHECKPOINT:** Run `npm run build` - must succeed.
|
||||
|
||||
---
|
||||
|
||||
## 1.5 Console.log Cleanup
|
||||
|
||||
### Files with console.log statements to review:
|
||||
|
||||
| File | Line(s) | Action |
|
||||
|------|---------|--------|
|
||||
| `src/services/api.ts` | 2010, 2015 | Review - may need for debugging |
|
||||
| `src/components/UserProfile/UserMetaCard.tsx` | 11 | Remove |
|
||||
| `src/components/UserProfile/UserAddressCard.tsx` | 11 | Remove |
|
||||
| `src/components/UserProfile/UserInfoCard.tsx` | 11 | Remove |
|
||||
| `src/components/Automation/ConfigModal.tsx` | 42 | Remove |
|
||||
| `src/components/common/ImageQueueModal.tsx` | 227, 229, 239, 242, 247, 251, 259, 262 | Remove all |
|
||||
| `src/components/common/ImageGenerationCard.tsx` | 107, 125, 129, 141, 142, 151, 178 | Remove all |
|
||||
|
||||
### Execution Strategy:
|
||||
|
||||
**Option A: Manual removal (safer)**
|
||||
Edit each file and remove console.log statements manually.
|
||||
|
||||
**Option B: Automated with review**
|
||||
```bash
|
||||
cd /data/app/igny8/frontend
|
||||
|
||||
# Find all console.log in src (excluding node_modules)
|
||||
grep -rn "console.log" src/ --include="*.ts" --include="*.tsx" > /tmp/console-logs.txt
|
||||
|
||||
# Review the file before any automated removal
|
||||
cat /tmp/console-logs.txt
|
||||
```
|
||||
|
||||
### Per-File Actions:
|
||||
|
||||
```typescript
|
||||
// In UserMetaCard.tsx, UserAddressCard.tsx, UserInfoCard.tsx - REMOVE:
|
||||
console.log("Saving changes...");
|
||||
|
||||
// In ConfigModal.tsx - REMOVE:
|
||||
console.log('Saving config with delays:', dataToSave);
|
||||
|
||||
// In ImageQueueModal.tsx - REMOVE ALL console.log statements
|
||||
|
||||
// In ImageGenerationCard.tsx - REMOVE ALL console.log statements
|
||||
|
||||
// In api.ts - KEEP or convert to proper logging (these may be useful)
|
||||
```
|
||||
|
||||
**✅ CHECKPOINT:** Run `npm run build && npm run lint` - must succeed.
|
||||
|
||||
---
|
||||
|
||||
## 1.6 ESLint Verification
|
||||
|
||||
```bash
|
||||
cd /data/app/igny8/frontend
|
||||
|
||||
# Run full lint check
|
||||
npm run lint
|
||||
|
||||
# If errors exist, fix them:
|
||||
npm run lint -- --fix
|
||||
```
|
||||
|
||||
**✅ CHECKPOINT:** Zero lint errors.
|
||||
|
||||
---
|
||||
|
||||
## 1.7 Post-Cleanup Verification
|
||||
|
||||
```bash
|
||||
# 1. Full build
|
||||
cd /data/app/igny8/frontend
|
||||
npm run build
|
||||
|
||||
# 2. Type check
|
||||
npx tsc --noEmit
|
||||
|
||||
# 3. Start dev server and manually verify app loads
|
||||
npm run dev
|
||||
# Open http://localhost:5173 and verify:
|
||||
# - Dashboard loads
|
||||
# - All sidebar navigation works
|
||||
# - No console errors in browser
|
||||
|
||||
# 4. Commit changes
|
||||
cd /data/app/igny8
|
||||
git add -A
|
||||
git status # Review all changes
|
||||
git commit -m "Phase 1: Code cleanup - remove unused pages, components, and console.logs"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Phase 5: UX Improvements
|
||||
|
||||
## 5.1 Pre-Implementation Setup
|
||||
|
||||
### 5.1.1 Create Feature Branch
|
||||
```bash
|
||||
cd /data/app/igny8
|
||||
git checkout main # or your main branch
|
||||
git pull
|
||||
git checkout -b feature/phase-5-ux-improvements
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5.2 Search Modal Enhancement
|
||||
|
||||
### 5.2.1 Current State Analysis
|
||||
|
||||
**Location:** Search functionality likely in header/navigation components
|
||||
|
||||
**Research Required:**
|
||||
```bash
|
||||
cd /data/app/igny8/frontend
|
||||
|
||||
# Find existing search component
|
||||
grep -rn "search" src/components/header/ --include="*.tsx"
|
||||
grep -rn "Search" src/components/ --include="*.tsx" | head -20
|
||||
|
||||
# Find search-related stores
|
||||
grep -rn "search" src/store/ --include="*.ts"
|
||||
```
|
||||
|
||||
### 5.2.2 Implementation Tasks
|
||||
|
||||
| Task | File(s) | Details |
|
||||
|------|---------|---------|
|
||||
| Add keyboard shortcut | `src/components/header/Search*.tsx` | Cmd/Ctrl+K to open |
|
||||
| Add search filters | Search component | Filter by type (keyword, content, site) |
|
||||
| Add recent searches | Search component + localStorage | Store last 5 searches |
|
||||
| Improve results display | Search results component | Show context snippets |
|
||||
| Add quick actions | Search results | Quick action buttons |
|
||||
|
||||
### 5.2.3 Keyboard Shortcut Implementation
|
||||
|
||||
```typescript
|
||||
// Add to App.tsx or a global hook
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
||||
e.preventDefault();
|
||||
// Open search modal
|
||||
setSearchOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, []);
|
||||
```
|
||||
|
||||
### 5.2.4 Verification
|
||||
|
||||
- [ ] Cmd/Ctrl+K opens search modal
|
||||
- [ ] Search filters work correctly
|
||||
- [ ] Recent searches persist across sessions
|
||||
- [ ] Results show relevant context
|
||||
- [ ] Quick actions function properly
|
||||
|
||||
---
|
||||
|
||||
## 5.3 Image Regeneration Feature
|
||||
|
||||
### 5.3.1 Backend Requirements
|
||||
|
||||
**Check existing endpoint:**
|
||||
```bash
|
||||
cd /data/app/igny8/backend
|
||||
|
||||
# Find image generation endpoints
|
||||
grep -rn "image" igny8_core/modules/writer/ --include="*.py" | head -20
|
||||
grep -rn "regenerate" igny8_core/ --include="*.py"
|
||||
```
|
||||
|
||||
**Required API endpoint (if not exists):**
|
||||
```
|
||||
POST /api/v1/writer/images/{id}/regenerate/
|
||||
Body: { reason?: string, prompt_adjustment?: string }
|
||||
Response: { task_id: string, status: "queued" }
|
||||
```
|
||||
|
||||
### 5.3.2 Frontend Implementation
|
||||
|
||||
**File: `frontend/src/pages/Writer/Images.tsx`**
|
||||
|
||||
Add regenerate button to each image card:
|
||||
|
||||
```typescript
|
||||
// Add to image card actions
|
||||
const handleRegenerate = async (imageId: number) => {
|
||||
const reason = await showRegenerateModal();
|
||||
if (reason !== null) {
|
||||
await api.post(`/v1/writer/images/${imageId}/regenerate/`, { reason });
|
||||
// Refresh list or show status
|
||||
}
|
||||
};
|
||||
|
||||
// Button in image card
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handleRegenerate(image.id)}
|
||||
>
|
||||
<RefreshIcon /> Regenerate
|
||||
</Button>
|
||||
```
|
||||
|
||||
**File: `frontend/src/pages/Writer/ContentView.tsx`**
|
||||
|
||||
Add regenerate for featured image:
|
||||
|
||||
```typescript
|
||||
// In featured image section (admin/editor only)
|
||||
{(user.role === 'admin' || user.role === 'editor') && (
|
||||
<Button onClick={() => handleRegenerateFeaturedImage(content.id)}>
|
||||
Regenerate Featured Image
|
||||
</Button>
|
||||
)}
|
||||
```
|
||||
|
||||
### 5.3.3 Verification
|
||||
|
||||
- [ ] Regenerate button appears on `/writer/images` page
|
||||
- [ ] Regenerate modal prompts for reason
|
||||
- [ ] API call succeeds and image regenerates
|
||||
- [ ] Status updates correctly
|
||||
- [ ] Featured image regenerate works in content view
|
||||
- [ ] Role-based visibility works (admin/editor only)
|
||||
|
||||
---
|
||||
|
||||
## 5.4 User Flow Polish
|
||||
|
||||
### 5.4.1 Signup to First Content Flow Testing
|
||||
|
||||
**Test Checklist:**
|
||||
|
||||
| Step | URL | Verify |
|
||||
|------|-----|--------|
|
||||
| 1. Signup | `/signup` | Form submits, verification email sent |
|
||||
| 2. Verify Email | `/verify-email?token=...` | Email verified, redirect to app |
|
||||
| 3. Onboarding | `/setup/wizard` | All steps complete without errors |
|
||||
| 4. Add Site | Sites → Add Site | WordPress connection successful |
|
||||
| 5. Add Keywords | `/planner/keywords` | Import works, keywords appear |
|
||||
| 6. Clustering | `/planner/clusters` | AI clustering completes |
|
||||
| 7. Generate Content | `/writer/tasks` | Content generates successfully |
|
||||
| 8. Publish | Content → Publish | Content appears on WordPress |
|
||||
|
||||
### 5.4.2 Issue Documentation Template
|
||||
|
||||
```markdown
|
||||
## Issue: [Brief Description]
|
||||
|
||||
**Step:** [Which step in flow]
|
||||
**URL:** [Page URL]
|
||||
**Expected:** [What should happen]
|
||||
**Actual:** [What happened]
|
||||
**Steps to Reproduce:**
|
||||
1. ...
|
||||
2. ...
|
||||
|
||||
**Screenshots/Logs:** [Attach if applicable]
|
||||
**Severity:** [Blocking/Major/Minor]
|
||||
```
|
||||
|
||||
### 5.4.3 Post-Implementation Verification
|
||||
|
||||
```bash
|
||||
# Build and test
|
||||
cd /data/app/igny8/frontend
|
||||
npm run build
|
||||
npm run lint
|
||||
|
||||
# Commit
|
||||
git add -A
|
||||
git commit -m "Phase 5: UX improvements - search modal, image regeneration, flow polish"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Phase 6: Data Backup & Cleanup
|
||||
|
||||
## 6.1 Pre-Backup Safety Steps
|
||||
|
||||
### 6.1.1 Create Backup Branch
|
||||
```bash
|
||||
cd /data/app/igny8
|
||||
git checkout main
|
||||
git checkout -b backup/pre-v1-cleanup-$(date +%Y%m%d)
|
||||
```
|
||||
|
||||
### 6.1.2 Full Database Backup
|
||||
```bash
|
||||
# Create backup directory
|
||||
mkdir -p /data/app/igny8/backups/$(date +%Y%m%d)
|
||||
|
||||
# PostgreSQL full backup
|
||||
pg_dump -h localhost -U your_user -d igny8_db > /data/app/igny8/backups/$(date +%Y%m%d)/full_backup.sql
|
||||
|
||||
# Verify backup
|
||||
ls -la /data/app/igny8/backups/$(date +%Y%m%d)/
|
||||
head -50 /data/app/igny8/backups/$(date +%Y%m%d)/full_backup.sql
|
||||
```
|
||||
|
||||
**✅ CHECKPOINT:** Backup file exists and has content.
|
||||
|
||||
---
|
||||
|
||||
## 6.2 System Configuration Export
|
||||
|
||||
### 6.2.1 Create Export Directory Structure
|
||||
```bash
|
||||
mkdir -p /data/app/igny8/backups/config
|
||||
```
|
||||
|
||||
### 6.2.2 Export Django Models (System Config)
|
||||
|
||||
**Create management command:**
|
||||
```bash
|
||||
# File: backend/igny8_core/management/commands/export_system_config.py
|
||||
```
|
||||
|
||||
```python
|
||||
# backend/igny8_core/management/commands/export_system_config.py
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.core import serializers
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Export system configuration data to JSON files'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--output-dir',
|
||||
default='backups/config',
|
||||
help='Output directory for config files'
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
output_dir = options['output_dir']
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# Import models here to avoid circular imports
|
||||
from igny8_core.modules.billing.models import Plan, CreditCostConfig
|
||||
from igny8_core.modules.system.models import (
|
||||
AIModelConfig, GlobalIntegrationSettings, SystemSettings
|
||||
)
|
||||
from igny8_core.auth.models import Industry, Sector, SeedKeyword
|
||||
# Add other system models as needed
|
||||
|
||||
exports = {
|
||||
'plans': Plan.objects.all(),
|
||||
'credit_costs': CreditCostConfig.objects.all(),
|
||||
'ai_models': AIModelConfig.objects.all(),
|
||||
# 'global_integrations': GlobalIntegrationSettings.objects.all(),
|
||||
# 'system_settings': SystemSettings.objects.all(),
|
||||
'industries': Industry.objects.all(),
|
||||
'sectors': Sector.objects.all(),
|
||||
'seed_keywords': SeedKeyword.objects.all(),
|
||||
}
|
||||
|
||||
for name, queryset in exports.items():
|
||||
try:
|
||||
data = serializers.serialize('json', queryset, indent=2)
|
||||
filepath = os.path.join(output_dir, f'{name}.json')
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(data)
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f'Exported {queryset.count()} {name} to {filepath}')
|
||||
)
|
||||
except Exception as e:
|
||||
self.stdout.write(
|
||||
self.style.ERROR(f'Failed to export {name}: {str(e)}')
|
||||
)
|
||||
|
||||
# Export timestamp
|
||||
with open(os.path.join(output_dir, 'export_metadata.json'), 'w') as f:
|
||||
json.dump({
|
||||
'exported_at': datetime.now().isoformat(),
|
||||
'exports': list(exports.keys())
|
||||
}, f, indent=2)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS('System config export complete!'))
|
||||
```
|
||||
|
||||
### 6.2.3 Run Export
|
||||
|
||||
```bash
|
||||
cd /data/app/igny8/backend
|
||||
|
||||
# Run the export command
|
||||
python manage.py export_system_config --output-dir=../backups/config
|
||||
|
||||
# Verify exports
|
||||
ls -la ../backups/config/
|
||||
```
|
||||
|
||||
**✅ CHECKPOINT:** All config JSON files exist and contain data.
|
||||
|
||||
---
|
||||
|
||||
## 6.3 User Data Cleanup
|
||||
|
||||
### ⚠️ DANGER ZONE - READ CAREFULLY ⚠️
|
||||
|
||||
This section PERMANENTLY DELETES user data. Ensure:
|
||||
1. Full backup completed (6.1.2)
|
||||
2. Config exported (6.2)
|
||||
3. You are in the correct environment (NOT PRODUCTION until ready)
|
||||
|
||||
### 6.3.1 Create Cleanup Management Command
|
||||
|
||||
```python
|
||||
# backend/igny8_core/management/commands/cleanup_user_data.py
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Clean up all user-generated data (DESTRUCTIVE)'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--confirm',
|
||||
action='store_true',
|
||||
help='Confirm you want to delete all user data'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--dry-run',
|
||||
action='store_true',
|
||||
help='Show what would be deleted without deleting'
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if not options['confirm'] and not options['dry_run']:
|
||||
self.stdout.write(
|
||||
self.style.ERROR('Must use --confirm or --dry-run flag')
|
||||
)
|
||||
return
|
||||
|
||||
# Import models
|
||||
from igny8_core.auth.models import Site
|
||||
from igny8_core.business.planning.models import Keywords, Clusters
|
||||
from igny8_core.modules.writer.models import (
|
||||
ContentIdea, Task, Content, ContentImage
|
||||
)
|
||||
from igny8_core.modules.publisher.models import PublishingRecord, SyncEvent
|
||||
from igny8_core.modules.billing.models import CreditTransaction, CreditUsageLog
|
||||
# Add other models
|
||||
|
||||
models_to_clear = [
|
||||
('Keywords', Keywords),
|
||||
('Clusters', Clusters),
|
||||
('ContentIdeas', ContentIdea),
|
||||
('Tasks', Task),
|
||||
('Content', Content),
|
||||
('ContentImages', ContentImage),
|
||||
('PublishingRecords', PublishingRecord),
|
||||
('SyncEvents', SyncEvent),
|
||||
('CreditTransactions', CreditTransaction),
|
||||
('CreditUsageLogs', CreditUsageLog),
|
||||
# Sites should be last (foreign keys)
|
||||
('Sites', Site),
|
||||
]
|
||||
|
||||
if options['dry_run']:
|
||||
self.stdout.write(self.style.WARNING('DRY RUN - No data will be deleted'))
|
||||
for name, model in models_to_clear:
|
||||
count = model.objects.count()
|
||||
self.stdout.write(f' Would delete {count} {name}')
|
||||
return
|
||||
|
||||
# Actual deletion
|
||||
with transaction.atomic():
|
||||
for name, model in models_to_clear:
|
||||
count = model.objects.count()
|
||||
model.objects.all().delete()
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f'Deleted {count} {name}')
|
||||
)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS('User data cleanup complete!'))
|
||||
```
|
||||
|
||||
### 6.3.2 Execute Cleanup (Step by Step)
|
||||
|
||||
```bash
|
||||
cd /data/app/igny8/backend
|
||||
|
||||
# Step 1: DRY RUN - See what will be deleted
|
||||
python manage.py cleanup_user_data --dry-run
|
||||
|
||||
# Step 2: Review output carefully
|
||||
# - Are the counts expected?
|
||||
# - Is this the right environment?
|
||||
|
||||
# Step 3: ONLY if absolutely sure, run actual cleanup
|
||||
python manage.py cleanup_user_data --confirm
|
||||
```
|
||||
|
||||
### 6.3.3 Clear Media Storage
|
||||
|
||||
```bash
|
||||
# List media files first
|
||||
ls -la /data/app/igny8/backend/media/
|
||||
|
||||
# Backup media if needed
|
||||
cp -r /data/app/igny8/backend/media/ /data/app/igny8/backups/$(date +%Y%m%d)/media/
|
||||
|
||||
# Clear generated images (be specific about paths)
|
||||
rm -rf /data/app/igny8/backend/media/generated_images/*
|
||||
rm -rf /data/app/igny8/backend/media/content_images/*
|
||||
|
||||
# Verify
|
||||
ls -la /data/app/igny8/backend/media/
|
||||
```
|
||||
|
||||
### 6.3.4 Clear Logs
|
||||
|
||||
```bash
|
||||
# Backup logs first
|
||||
cp -r /data/app/igny8/backend/logs/ /data/app/igny8/backups/$(date +%Y%m%d)/logs/
|
||||
|
||||
# Clear log files (keep empty files for app to write)
|
||||
> /data/app/igny8/backend/logs/publish-sync-logs/*.log
|
||||
> /data/app/igny8/backend/celerybeat-schedule
|
||||
|
||||
# Or remove all logs
|
||||
rm -f /data/app/igny8/backend/logs/**/*.log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6.4 Configuration Lock (V1.0)
|
||||
|
||||
### 6.4.1 Document Final Configuration
|
||||
|
||||
Create documentation file:
|
||||
|
||||
```bash
|
||||
mkdir -p /data/app/igny8/docs/90-REFERENCE/
|
||||
```
|
||||
|
||||
```markdown
|
||||
# V1.0 Configuration Reference
|
||||
|
||||
**Locked:** [DATE]
|
||||
**Version:** 1.0.0
|
||||
|
||||
## Plans Configuration
|
||||
|
||||
| Plan | Credits | Sites | Price | Interval |
|
||||
|------|---------|-------|-------|----------|
|
||||
| Starter | X | 1 | $X/mo | monthly |
|
||||
| Growth | X | 3 | $X/mo | monthly |
|
||||
| Scale | X | 10 | $X/mo | monthly |
|
||||
|
||||
## Credit Costs
|
||||
|
||||
| Operation | Cost |
|
||||
|-----------|------|
|
||||
| Basic Image | 1 credit |
|
||||
| Quality Image | 5 credits |
|
||||
| Premium Image | 15 credits |
|
||||
| Clustering | Token-based |
|
||||
| Content Generation | Token-based |
|
||||
|
||||
## AI Model Configurations
|
||||
|
||||
[Document all AI model settings]
|
||||
|
||||
---
|
||||
|
||||
**CHANGE POLICY:** Any changes require version bump and documented release.
|
||||
```
|
||||
|
||||
### 6.4.2 Git Tag for V1.0
|
||||
|
||||
```bash
|
||||
cd /data/app/igny8
|
||||
|
||||
# Ensure all changes committed
|
||||
git status
|
||||
git add -A
|
||||
git commit -m "Phase 6: Pre-launch cleanup complete"
|
||||
|
||||
# Create annotated tag
|
||||
git tag -a v1.0.0 -m "IGNY8 V1.0.0 - Production Release"
|
||||
|
||||
# Push tag
|
||||
git push origin v1.0.0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6.5 Post-Cleanup Verification
|
||||
|
||||
### 6.5.1 Database Verification
|
||||
|
||||
```bash
|
||||
cd /data/app/igny8/backend
|
||||
|
||||
# Run Django check
|
||||
python manage.py check
|
||||
|
||||
# Verify system config still exists
|
||||
python manage.py shell << 'EOF'
|
||||
from igny8_core.modules.billing.models import Plan
|
||||
from igny8_core.auth.models import Industry, Sector
|
||||
print(f"Plans: {Plan.objects.count()}")
|
||||
print(f"Industries: {Industry.objects.count()}")
|
||||
print(f"Sectors: {Sector.objects.count()}")
|
||||
EOF
|
||||
|
||||
# Verify user data cleared
|
||||
python manage.py shell << 'EOF'
|
||||
from igny8_core.auth.models import Site
|
||||
from igny8_core.business.planning.models import Keywords
|
||||
print(f"Sites: {Site.objects.count()}")
|
||||
print(f"Keywords: {Keywords.objects.count()}")
|
||||
EOF
|
||||
```
|
||||
|
||||
### 6.5.2 Application Verification
|
||||
|
||||
```bash
|
||||
# Start backend
|
||||
cd /data/app/igny8/backend
|
||||
python manage.py runserver &
|
||||
|
||||
# Start frontend
|
||||
cd /data/app/igny8/frontend
|
||||
npm run dev &
|
||||
|
||||
# Manual checks:
|
||||
# 1. Can login as admin
|
||||
# 2. Dashboard loads (empty state)
|
||||
# 3. Plans visible in settings
|
||||
# 4. Can create new user account
|
||||
# 5. Onboarding flow works
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Execution Checklist
|
||||
|
||||
## Phase 1 Checklist
|
||||
|
||||
- [ ] Created safety branch
|
||||
- [ ] Ran baseline tests (all pass)
|
||||
- [ ] Deleted empty folders (6 folders)
|
||||
- [ ] Build succeeds after empty folder deletion
|
||||
- [ ] Deleted template/sample components (ecommerce, sample-componeents, charts, tables)
|
||||
- [ ] Build succeeds after template deletion
|
||||
- [ ] Deleted `CurrentProcessingCard.old.tsx`
|
||||
- [ ] Removed console.log statements (reviewed each)
|
||||
- [ ] ESLint passes with zero errors
|
||||
- [ ] TypeScript compiles without errors
|
||||
- [ ] Manual app verification complete
|
||||
- [ ] Changes committed
|
||||
|
||||
## Phase 5 Checklist
|
||||
|
||||
- [ ] Created feature branch
|
||||
- [ ] Researched existing search implementation
|
||||
- [ ] Implemented keyboard shortcut (Cmd/Ctrl+K)
|
||||
- [ ] Added search filters
|
||||
- [ ] Added recent searches
|
||||
- [ ] Improved results display
|
||||
- [ ] Added image regenerate to `/writer/images`
|
||||
- [ ] Added featured image regenerate to content view
|
||||
- [ ] Backend endpoint created/verified
|
||||
- [ ] Role-based visibility works
|
||||
- [ ] Tested full signup-to-publish flow
|
||||
- [ ] Documented any issues found
|
||||
- [ ] Changes committed
|
||||
|
||||
## Phase 6 Checklist
|
||||
|
||||
- [ ] Created backup branch
|
||||
- [ ] Full database backup created
|
||||
- [ ] Backup file verified (has content)
|
||||
- [ ] Created export_system_config command
|
||||
- [ ] Exported all system config (plans, industries, etc.)
|
||||
- [ ] Config files verified (JSON valid)
|
||||
- [ ] Created cleanup_user_data command
|
||||
- [ ] Ran dry-run cleanup (reviewed counts)
|
||||
- [ ] **CONFIRMED correct environment**
|
||||
- [ ] Executed user data cleanup
|
||||
- [ ] Cleared media storage
|
||||
- [ ] Backed up and cleared logs
|
||||
- [ ] Created V1.0 config documentation
|
||||
- [ ] Created git tag v1.0.0
|
||||
- [ ] Verified system config still exists
|
||||
- [ ] Verified user data cleared
|
||||
- [ ] Application starts and functions
|
||||
|
||||
---
|
||||
|
||||
## Rollback Procedures
|
||||
|
||||
### Phase 1 Rollback
|
||||
```bash
|
||||
git checkout main
|
||||
git branch -D cleanup/phase-1-*
|
||||
```
|
||||
|
||||
### Phase 5 Rollback
|
||||
```bash
|
||||
git checkout main
|
||||
git branch -D feature/phase-5-ux-improvements
|
||||
```
|
||||
|
||||
### Phase 6 Rollback (Database)
|
||||
```bash
|
||||
# Restore from backup
|
||||
psql -h localhost -U your_user -d igny8_db < /data/app/igny8/backups/YYYYMMDD/full_backup.sql
|
||||
|
||||
# Restore media
|
||||
cp -r /data/app/igny8/backups/YYYYMMDD/media/* /data/app/igny8/backend/media/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Document Owner:** IGNY8 Team
|
||||
**Review:** Before each phase execution
|
||||
**Approval Required:** Phase 6 cleanup requires explicit approval
|
||||
662
docs/plans/PHASE-6-BACKUP-CLEANUP-GUIDE.md
Normal file
662
docs/plans/PHASE-6-BACKUP-CLEANUP-GUIDE.md
Normal file
@@ -0,0 +1,662 @@
|
||||
# Phase 6: Data Backup & Cleanup Guide
|
||||
|
||||
**Version:** 1.0
|
||||
**Created:** January 9, 2026
|
||||
**Purpose:** Pre-V1.0 Launch Database Preparation
|
||||
|
||||
---
|
||||
|
||||
## 📋 Table of Contents
|
||||
|
||||
1. [Overview](#overview)
|
||||
2. [What Was Created](#what-was-created)
|
||||
3. [When to Use](#when-to-use)
|
||||
4. [Pre-Execution Checklist](#pre-execution-checklist)
|
||||
5. [Command 1: Export System Config](#command-1-export-system-config)
|
||||
6. [Command 2: Cleanup User Data](#command-2-cleanup-user-data)
|
||||
7. [Complete Workflow](#complete-workflow)
|
||||
8. [Safety Measures](#safety-measures)
|
||||
9. [Rollback Procedures](#rollback-procedures)
|
||||
10. [FAQ](#faq)
|
||||
|
||||
---
|
||||
|
||||
## 📖 Overview
|
||||
|
||||
Phase 6 provides two Django management commands to safely prepare your IGNY8 database for V1.0 production launch:
|
||||
|
||||
1. **Export System Configuration** - Backs up all system settings to JSON files
|
||||
2. **Cleanup User Data** - Removes all test/development user data while preserving system configuration
|
||||
|
||||
### Why These Commands?
|
||||
|
||||
- **Clean Start**: Launch V1.0 with a pristine database
|
||||
- **Configuration Preservation**: Keep all your carefully configured settings
|
||||
- **Safety First**: Multiple safety checks and dry-run options
|
||||
- **Audit Trail**: Complete metadata and logging
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ What Was Created
|
||||
|
||||
### File Locations
|
||||
|
||||
```
|
||||
backend/igny8_core/management/commands/
|
||||
├── export_system_config.py # System configuration backup
|
||||
└── cleanup_user_data.py # User data cleanup
|
||||
```
|
||||
|
||||
### Command 1: `export_system_config.py`
|
||||
|
||||
**Purpose**: Exports all system configuration to JSON files for backup and version control.
|
||||
|
||||
**What it exports:**
|
||||
- ✅ Subscription Plans (Starter, Growth, Scale)
|
||||
- ✅ Credit Cost Configurations
|
||||
- ✅ AI Model Settings (OpenAI, Anthropic, etc.)
|
||||
- ✅ Global Integration Settings
|
||||
- ✅ Industries and Sectors
|
||||
- ✅ Seed Keywords (reference data)
|
||||
- ✅ Author Profiles
|
||||
- ✅ AI Prompts and Variables
|
||||
|
||||
**What it creates:**
|
||||
- Individual JSON files for each data type
|
||||
- `export_metadata.json` with timestamp and statistics
|
||||
- Organized folder structure in `backups/config/`
|
||||
|
||||
### Command 2: `cleanup_user_data.py`
|
||||
|
||||
**Purpose**: Safely removes all user-generated test data before production launch.
|
||||
|
||||
**What it deletes:**
|
||||
- 🗑️ Sites and Site Settings
|
||||
- 🗑️ Keywords, Clusters, Ideas
|
||||
- 🗑️ Tasks, Content, Images
|
||||
- 🗑️ Publishing Records
|
||||
- 🗑️ WordPress Sync Events
|
||||
- 🗑️ Credit Transactions and Usage Logs
|
||||
- 🗑️ Automation Runs
|
||||
- 🗑️ Notifications
|
||||
- 🗑️ Orders
|
||||
|
||||
**What it preserves:**
|
||||
- ✅ User Accounts (admin users)
|
||||
- ✅ System Configuration (all settings from export)
|
||||
- ✅ Plans and Pricing
|
||||
- ✅ AI Models and Prompts
|
||||
- ✅ Industries and Sectors
|
||||
|
||||
---
|
||||
|
||||
## ⏰ When to Use
|
||||
|
||||
### Correct Timing
|
||||
|
||||
✅ **Use these commands when:**
|
||||
- You're preparing for V1.0 production launch
|
||||
- You've completed all testing and configuration
|
||||
- You want to start production with clean data
|
||||
- All system settings (Plans, AI models, prompts) are finalized
|
||||
|
||||
❌ **Do NOT use these commands when:**
|
||||
- You're still in active development
|
||||
- You haven't backed up your configurations
|
||||
- You're unsure about your system settings
|
||||
- You're in production with live users
|
||||
|
||||
### Recommended Timeline
|
||||
|
||||
```
|
||||
Day -7: Final configuration review
|
||||
Day -5: Export system config (first backup)
|
||||
Day -3: Test commands in staging
|
||||
Day -2: Export system config (final backup)
|
||||
Day -1: Cleanup user data in staging
|
||||
Day 0: Launch day - cleanup in production
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Pre-Execution Checklist
|
||||
|
||||
Before running ANY Phase 6 command, complete this checklist:
|
||||
|
||||
### Environment Verification
|
||||
|
||||
- [ ] Confirm you're in the correct environment (staging vs production)
|
||||
- [ ] Check `ENVIRONMENT` setting in Django settings
|
||||
- [ ] Verify database connection is correct
|
||||
- [ ] Ensure you have full database backup
|
||||
|
||||
### System State
|
||||
|
||||
- [ ] All Plans configured and tested
|
||||
- [ ] All AI prompts finalized
|
||||
- [ ] All credit costs verified
|
||||
- [ ] All industries/sectors populated
|
||||
- [ ] Seed keywords imported
|
||||
|
||||
### Safety Backups
|
||||
|
||||
- [ ] Full database dump exists
|
||||
- [ ] Previous export exists (if available)
|
||||
- [ ] Media files backed up
|
||||
- [ ] Environment variables documented
|
||||
|
||||
### Access & Permissions
|
||||
|
||||
- [ ] You have Django shell access
|
||||
- [ ] You have database backup access
|
||||
- [ ] You have rollback permissions
|
||||
- [ ] Stakeholders notified
|
||||
|
||||
---
|
||||
|
||||
## 📤 Command 1: Export System Config
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
cd /data/app/igny8/backend
|
||||
python manage.py export_system_config
|
||||
```
|
||||
|
||||
### With Custom Output Directory
|
||||
|
||||
```bash
|
||||
python manage.py export_system_config --output-dir=/path/to/backup
|
||||
```
|
||||
|
||||
### Step-by-Step Execution
|
||||
|
||||
#### Step 1: Navigate to Backend
|
||||
|
||||
```bash
|
||||
cd /data/app/igny8/backend
|
||||
```
|
||||
|
||||
#### Step 2: Run Export
|
||||
|
||||
```bash
|
||||
python manage.py export_system_config --output-dir=../backups/config/$(date +%Y%m%d)
|
||||
```
|
||||
|
||||
#### Step 3: Verify Output
|
||||
|
||||
```bash
|
||||
ls -la ../backups/config/$(date +%Y%m%d)/
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```
|
||||
plans.json # Subscription plans
|
||||
credit_costs.json # Credit cost configurations
|
||||
ai_models.json # AI model settings
|
||||
global_integrations.json # Integration settings
|
||||
industries.json # Industry master data
|
||||
sectors.json # Sector master data
|
||||
seed_keywords.json # Reference keywords
|
||||
author_profiles.json # Writing style profiles
|
||||
prompts.json # AI prompts
|
||||
prompt_variables.json # Prompt variables
|
||||
export_metadata.json # Export timestamp & stats
|
||||
```
|
||||
|
||||
#### Step 4: Verify Data
|
||||
|
||||
Check one of the exports:
|
||||
```bash
|
||||
cat ../backups/config/$(date +%Y%m%d)/plans.json | head -20
|
||||
```
|
||||
|
||||
#### Step 5: Commit to Version Control
|
||||
|
||||
```bash
|
||||
cd /data/app/igny8
|
||||
git add backups/config/
|
||||
git commit -m "Backup: V1.0 system configuration export"
|
||||
git push
|
||||
```
|
||||
|
||||
### What The Output Looks Like
|
||||
|
||||
```
|
||||
Exporting system configuration to: /data/app/igny8/backups/config/20260109
|
||||
|
||||
✓ Exported 3 Subscription Plans → plans.json
|
||||
✓ Exported 12 Credit Cost Configurations → credit_costs.json
|
||||
✓ Exported 4 AI Model Configurations → ai_models.json
|
||||
✓ Exported 1 Global Integration Settings → global_integrations.json
|
||||
✓ Exported 15 Industries → industries.json
|
||||
✓ Exported 47 Sectors → sectors.json
|
||||
✓ Exported 523 Seed Keywords → seed_keywords.json
|
||||
✓ Exported 3 Author Profiles → author_profiles.json
|
||||
✓ Exported 8 AI Prompts → prompts.json
|
||||
✓ Exported 12 Prompt Variables → prompt_variables.json
|
||||
|
||||
✓ Metadata saved to export_metadata.json
|
||||
|
||||
======================================================================
|
||||
System Configuration Export Complete!
|
||||
|
||||
Successful: 10 exports
|
||||
Failed: 0 exports
|
||||
Location: /data/app/igny8/backups/config/20260109
|
||||
======================================================================
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
**Problem**: "No module named 'django'"
|
||||
```bash
|
||||
# Solution: Activate virtual environment or use Docker
|
||||
docker-compose exec backend python manage.py export_system_config
|
||||
```
|
||||
|
||||
**Problem**: "Permission denied" when writing files
|
||||
```bash
|
||||
# Solution: Check directory permissions
|
||||
mkdir -p ../backups/config
|
||||
chmod 755 ../backups/config
|
||||
```
|
||||
|
||||
**Problem**: Empty JSON files
|
||||
```bash
|
||||
# Solution: Verify data exists in database
|
||||
python manage.py shell
|
||||
>>> from igny8_core.modules.billing.models import Plan
|
||||
>>> Plan.objects.count()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🗑️ Command 2: Cleanup User Data
|
||||
|
||||
### ⚠️ CRITICAL WARNING
|
||||
|
||||
**THIS COMMAND PERMANENTLY DELETES DATA**
|
||||
|
||||
- Cannot be undone without database restore
|
||||
- Removes ALL user-generated content
|
||||
- Should ONLY be run before production launch
|
||||
- ALWAYS run `--dry-run` first
|
||||
|
||||
### Safety Features
|
||||
|
||||
1. **Dry-Run Mode**: Preview deletions without actually deleting
|
||||
2. **Confirmation Prompt**: Must type "DELETE ALL DATA" to proceed
|
||||
3. **Production Protection**: Blocked in production environment (unless explicitly allowed)
|
||||
4. **Transaction Safety**: All deletions in atomic transaction
|
||||
5. **Detailed Logging**: Shows exactly what was deleted
|
||||
|
||||
### Usage: Dry Run (Always First!)
|
||||
|
||||
```bash
|
||||
cd /data/app/igny8/backend
|
||||
python manage.py cleanup_user_data --dry-run
|
||||
```
|
||||
|
||||
### Dry Run Output Example
|
||||
|
||||
```
|
||||
======================================================================
|
||||
DRY RUN - No data will be deleted
|
||||
======================================================================
|
||||
|
||||
✓ Would delete 1,234 Notifications
|
||||
✓ Would delete 5,678 Credit Usage Logs
|
||||
✓ Would delete 456 Credit Transactions
|
||||
✓ Would delete 23 Orders
|
||||
✓ Would delete 8,901 WordPress Sync Events
|
||||
✓ Would delete 234 Publishing Records
|
||||
✓ Would delete 45 Automation Runs
|
||||
✓ Would delete 3,456 Images
|
||||
✓ Would delete 2,345 Content
|
||||
✓ Would delete 4,567 Tasks
|
||||
✓ Would delete 5,678 Content Ideas
|
||||
✓ Would delete 1,234 Clusters
|
||||
✓ Would delete 9,876 Keywords
|
||||
✓ Would delete 12 Sites
|
||||
|
||||
→ Keeping 3 Users (not deleted)
|
||||
|
||||
Total records to delete: 43,739
|
||||
|
||||
======================================================================
|
||||
To proceed with actual deletion, run:
|
||||
python manage.py cleanup_user_data --confirm
|
||||
======================================================================
|
||||
```
|
||||
|
||||
### Usage: Actual Cleanup
|
||||
|
||||
```bash
|
||||
python manage.py cleanup_user_data --confirm
|
||||
```
|
||||
|
||||
**You will be prompted:**
|
||||
```
|
||||
======================================================================
|
||||
⚠️ DELETING ALL USER DATA - THIS CANNOT BE UNDONE!
|
||||
======================================================================
|
||||
|
||||
Type "DELETE ALL DATA" to proceed:
|
||||
```
|
||||
|
||||
**Type exactly:** `DELETE ALL DATA`
|
||||
|
||||
### Actual Cleanup Output
|
||||
|
||||
```
|
||||
Proceeding with deletion...
|
||||
|
||||
✓ Deleted 1,234 Notifications
|
||||
✓ Deleted 5,678 Credit Usage Logs
|
||||
✓ Deleted 456 Credit Transactions
|
||||
✓ Deleted 23 Orders
|
||||
✓ Deleted 8,901 WordPress Sync Events
|
||||
✓ Deleted 234 Publishing Records
|
||||
✓ Deleted 45 Automation Runs
|
||||
✓ Deleted 3,456 Images
|
||||
✓ Deleted 2,345 Content
|
||||
✓ Deleted 4,567 Tasks
|
||||
✓ Deleted 5,678 Content Ideas
|
||||
✓ Deleted 1,234 Clusters
|
||||
✓ Deleted 9,876 Keywords
|
||||
✓ Deleted 12 Sites
|
||||
|
||||
======================================================================
|
||||
User Data Cleanup Complete!
|
||||
|
||||
Total records deleted: 43,739
|
||||
Failed deletions: 0
|
||||
======================================================================
|
||||
```
|
||||
|
||||
### Production Environment Protection
|
||||
|
||||
If you try to run cleanup in production:
|
||||
|
||||
```
|
||||
⚠️ BLOCKED: Cannot run cleanup in PRODUCTION environment!
|
||||
|
||||
To allow this, temporarily set ENVIRONMENT to "staging" in settings.
|
||||
```
|
||||
|
||||
To override (ONLY if absolutely necessary):
|
||||
|
||||
```python
|
||||
# In settings.py - TEMPORARY
|
||||
ENVIRONMENT = 'staging' # Change back after cleanup!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Complete Workflow
|
||||
|
||||
### Full Pre-Launch Procedure
|
||||
|
||||
```bash
|
||||
# ========================================
|
||||
# STEP 1: FULL DATABASE BACKUP
|
||||
# ========================================
|
||||
cd /data/app/igny8/backend
|
||||
pg_dump -h localhost -U postgres igny8_db > ../backups/$(date +%Y%m%d)_pre_v1_full_backup.sql
|
||||
|
||||
# Verify backup exists and has content
|
||||
ls -lh ../backups/$(date +%Y%m%d)_pre_v1_full_backup.sql
|
||||
head -50 ../backups/$(date +%Y%m%d)_pre_v1_full_backup.sql
|
||||
|
||||
|
||||
# ========================================
|
||||
# STEP 2: EXPORT SYSTEM CONFIGURATION
|
||||
# ========================================
|
||||
python manage.py export_system_config --output-dir=../backups/config/$(date +%Y%m%d)
|
||||
|
||||
# Verify exports
|
||||
ls -la ../backups/config/$(date +%Y%m%d)/
|
||||
|
||||
# Review critical configs
|
||||
cat ../backups/config/$(date +%Y%m%d)/plans.json | python -m json.tool | head -30
|
||||
cat ../backups/config/$(date +%Y%m%d)/credit_costs.json | python -m json.tool | head -30
|
||||
|
||||
|
||||
# ========================================
|
||||
# STEP 3: COMMIT CONFIGS TO GIT
|
||||
# ========================================
|
||||
cd /data/app/igny8
|
||||
git add backups/config/
|
||||
git commit -m "Pre-V1.0: System configuration backup $(date +%Y%m%d)"
|
||||
git push
|
||||
|
||||
|
||||
# ========================================
|
||||
# STEP 4: BACKUP MEDIA FILES
|
||||
# ========================================
|
||||
cd /data/app/igny8
|
||||
tar -czf backups/$(date +%Y%m%d)_media_backup.tar.gz backend/media/
|
||||
|
||||
|
||||
# ========================================
|
||||
# STEP 5: DRY RUN CLEANUP (REVIEW CAREFULLY)
|
||||
# ========================================
|
||||
cd backend
|
||||
python manage.py cleanup_user_data --dry-run
|
||||
|
||||
# Review the counts - make sure they're expected
|
||||
|
||||
|
||||
# ========================================
|
||||
# STEP 6: ACTUAL CLEANUP (POINT OF NO RETURN)
|
||||
# ========================================
|
||||
python manage.py cleanup_user_data --confirm
|
||||
# Type: DELETE ALL DATA
|
||||
|
||||
|
||||
# ========================================
|
||||
# STEP 7: VERIFY CLEANUP
|
||||
# ========================================
|
||||
python manage.py shell << 'EOF'
|
||||
from igny8_core.auth.models import Site, CustomUser
|
||||
from igny8_core.business.planning.models import Keywords
|
||||
from igny8_core.modules.billing.models import Plan
|
||||
|
||||
print(f"Sites: {Site.objects.count()} (should be 0)")
|
||||
print(f"Keywords: {Keywords.objects.count()} (should be 0)")
|
||||
print(f"Users: {CustomUser.objects.count()} (admins preserved)")
|
||||
print(f"Plans: {Plan.objects.count()} (should have your plans)")
|
||||
EOF
|
||||
|
||||
|
||||
# ========================================
|
||||
# STEP 8: TEST APPLICATION
|
||||
# ========================================
|
||||
python manage.py runserver 0.0.0.0:8000 &
|
||||
# Visit app and verify:
|
||||
# - Can login as admin
|
||||
# - Dashboard loads (empty state)
|
||||
# - Plans visible in settings
|
||||
# - Can create new user account
|
||||
|
||||
|
||||
# ========================================
|
||||
# STEP 9: TAG RELEASE
|
||||
# ========================================
|
||||
cd /data/app/igny8
|
||||
git tag -a v1.0.0-clean -m "V1.0.0 - Clean database ready for launch"
|
||||
git push origin v1.0.0-clean
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ Safety Measures
|
||||
|
||||
### Built-in Protections
|
||||
|
||||
1. **Atomic Transactions**: All deletions in single transaction - all or nothing
|
||||
2. **Production Check**: Requires explicit override in production
|
||||
3. **Confirmation Prompt**: Must type exact phrase
|
||||
4. **Dry Run**: See exactly what will be deleted
|
||||
5. **Detailed Logging**: Know what was deleted and any failures
|
||||
|
||||
### Manual Safety Checklist
|
||||
|
||||
Before running cleanup:
|
||||
|
||||
- [ ] **Full database backup** exists and verified
|
||||
- [ ] **System config export** completed and committed to git
|
||||
- [ ] **Media files** backed up
|
||||
- [ ] **Dry run reviewed** and counts are expected
|
||||
- [ ] **Stakeholders notified** of pending cleanup
|
||||
- [ ] **Rollback plan** documented and tested
|
||||
- [ ] **Off-hours execution** scheduled (if production)
|
||||
- [ ] **Monitoring ready** to catch any issues
|
||||
|
||||
### Additional Recommendations
|
||||
|
||||
1. **Staging First**: Always test in staging environment first
|
||||
2. **Screenshot Evidence**: Take screenshots of dry-run output
|
||||
3. **Communication**: Notify team before and after
|
||||
4. **Timing**: Run during low-traffic hours
|
||||
5. **Verification**: Test application immediately after
|
||||
|
||||
---
|
||||
|
||||
## 🔙 Rollback Procedures
|
||||
|
||||
### If Something Goes Wrong
|
||||
|
||||
#### During Cleanup (Transaction Failed)
|
||||
|
||||
No action needed - atomic transaction will automatically rollback.
|
||||
|
||||
#### After Cleanup (Need to Restore)
|
||||
|
||||
```bash
|
||||
# OPTION 1: Restore from PostgreSQL backup
|
||||
cd /data/app/igny8
|
||||
psql -U postgres -d igny8_db < backups/20260109_pre_v1_full_backup.sql
|
||||
|
||||
# OPTION 2: Restore specific tables (if partial restore needed)
|
||||
pg_restore -U postgres -d igny8_db -t "specific_table" backups/20260109_pre_v1_full_backup.sql
|
||||
|
||||
# OPTION 3: Restore from Docker backup (if using Docker)
|
||||
docker-compose exec -T db psql -U postgres igny8_db < backups/20260109_pre_v1_full_backup.sql
|
||||
```
|
||||
|
||||
#### Restore Media Files
|
||||
|
||||
```bash
|
||||
cd /data/app/igny8
|
||||
tar -xzf backups/20260109_media_backup.tar.gz -C backend/
|
||||
```
|
||||
|
||||
#### Verify Restore
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
python manage.py shell << 'EOF'
|
||||
from igny8_core.auth.models import Site
|
||||
from igny8_core.business.planning.models import Keywords
|
||||
print(f"Sites restored: {Site.objects.count()}")
|
||||
print(f"Keywords restored: {Keywords.objects.count()}")
|
||||
EOF
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ❓ FAQ
|
||||
|
||||
### Q: Can I run these commands multiple times?
|
||||
|
||||
**A:**
|
||||
- **Export Config**: Yes, safe to run multiple times. Creates timestamped backups.
|
||||
- **Cleanup**: Yes, but after first cleanup there's nothing left to delete (idempotent).
|
||||
|
||||
### Q: What if I only want to delete some data?
|
||||
|
||||
**A:** These commands are all-or-nothing by design for safety. To delete specific data, use Django admin or write a custom management command.
|
||||
|
||||
### Q: Can I restore individual items from the export?
|
||||
|
||||
**A:** Yes! The JSON files use Django's standard serialization format. Use `python manage.py loaddata <file>.json` to restore.
|
||||
|
||||
### Q: Will this affect my development environment?
|
||||
|
||||
**A:** Only if you run it there. These commands work on whatever database your Django settings point to.
|
||||
|
||||
### Q: How long does cleanup take?
|
||||
|
||||
**A:** Depends on data volume. Typical ranges:
|
||||
- Small (< 10k records): 1-5 seconds
|
||||
- Medium (10k-100k): 5-30 seconds
|
||||
- Large (> 100k): 30-120 seconds
|
||||
|
||||
### Q: What if cleanup fails halfway?
|
||||
|
||||
**A:** Can't happen - it's wrapped in an atomic transaction. Either everything deletes or nothing does.
|
||||
|
||||
### Q: Do I need to stop the application?
|
||||
|
||||
**A:** Recommended but not required. Stopping the app prevents race conditions during cleanup.
|
||||
|
||||
### Q: Can I schedule these as cron jobs?
|
||||
|
||||
**A:**
|
||||
- **Export**: Yes, great for automated backups
|
||||
- **Cleanup**: No, should only be run manually with explicit confirmation
|
||||
|
||||
### Q: What about Django migrations?
|
||||
|
||||
**A:** Cleanup only deletes data, not schema. All tables and migrations remain intact.
|
||||
|
||||
### Q: How do I know if my system config is complete?
|
||||
|
||||
**A:** Run the export and review the counts in `export_metadata.json`. Compare with your documentation.
|
||||
|
||||
---
|
||||
|
||||
## 📞 Support
|
||||
|
||||
### If You Need Help
|
||||
|
||||
1. **Check this guide** thoroughly first
|
||||
2. **Review error messages** carefully
|
||||
3. **Test in staging** before production
|
||||
4. **Contact team** if unsure about any step
|
||||
|
||||
### Emergency Contacts
|
||||
|
||||
- **Database Issues**: DBA team
|
||||
- **Application Issues**: Backend team
|
||||
- **Configuration Questions**: System admin
|
||||
- **Rollback Needed**: All hands on deck!
|
||||
|
||||
---
|
||||
|
||||
## ✅ Success Criteria
|
||||
|
||||
After completing Phase 6, you should have:
|
||||
|
||||
- ✅ Multiple timestamped config exports in `backups/config/`
|
||||
- ✅ Full database SQL backup in `backups/`
|
||||
- ✅ Media files backup in `backups/`
|
||||
- ✅ Zero user-generated data in database
|
||||
- ✅ All system configurations intact
|
||||
- ✅ Application starts and loads empty state
|
||||
- ✅ Admin can log in
|
||||
- ✅ New users can sign up
|
||||
- ✅ Plans visible and functional
|
||||
- ✅ Git tag created for v1.0.0-clean
|
||||
|
||||
---
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Last Updated:** January 9, 2026
|
||||
**Next Review:** After V1.0 Launch
|
||||
|
||||
---
|
||||
|
||||
*This guide is part of the IGNY8 Pre-Launch Preparation (Phase 6)*
|
||||
@@ -39,7 +39,6 @@ const ConfigModal: React.FC<ConfigModalProps> = ({ config, onSave, onCancel }) =
|
||||
within_stage_delay: formData.within_stage_delay || 3,
|
||||
between_stage_delay: formData.between_stage_delay || 5,
|
||||
};
|
||||
console.log('Saving config with delays:', dataToSave);
|
||||
onSave(dataToSave);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
/**
|
||||
* Current Processing Card Component
|
||||
* Shows real-time automation progress with currently processing items
|
||||
*/
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { automationService, ProcessingState } from '../../services/automationService';
|
||||
|
||||
interface CurrentProcessingCardProps {
|
||||
runId: string;
|
||||
siteId: number;
|
||||
currentStage: number;
|
||||
onComplete?: () => void;
|
||||
}
|
||||
|
||||
const CurrentProcessingCard: React.FC<CurrentProcessingCardProps> = ({
|
||||
runId,
|
||||
siteId,
|
||||
currentStage,
|
||||
onComplete,
|
||||
}) => {
|
||||
const [processingState, setProcessingState] = useState<ProcessingState | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
|
||||
const fetchState = async () => {
|
||||
try {
|
||||
const state = await automationService.getCurrentProcessing(siteId, runId);
|
||||
|
||||
if (!isMounted) return;
|
||||
|
||||
setProcessingState(state);
|
||||
setError(null);
|
||||
|
||||
// If stage completed (all items processed), trigger refresh
|
||||
if (state && state.processed_items >= state.total_items && state.total_items > 0) {
|
||||
onComplete?.();
|
||||
}
|
||||
} catch (err) {
|
||||
if (!isMounted) return;
|
||||
console.error('Error fetching processing state:', err);
|
||||
setError('Failed to load processing state');
|
||||
}
|
||||
};
|
||||
|
||||
// Initial fetch
|
||||
fetchState();
|
||||
|
||||
// Poll every 3 seconds
|
||||
const interval = setInterval(fetchState, 3000);
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [siteId, runId, onComplete]);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="bg-error-50 dark:bg-error-900/20 border-2 border-error-500 rounded-lg p-4 mb-6">
|
||||
<p className="text-error-700 dark:text-error-300 text-sm">{error}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!processingState) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const percentage = processingState.percentage;
|
||||
|
||||
return (
|
||||
<div className="bg-brand-50 dark:bg-brand-900/20 border-2 border-brand-500 rounded-lg p-6 mb-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="animate-pulse">
|
||||
<svg
|
||||
className="w-8 h-8 text-brand-600 dark:text-brand-400"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M13 10V3L4 14h7v7l9-11h-7z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
Automation In Progress
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
Stage {currentStage}: {processingState.stage_name}
|
||||
<span className="ml-2 px-2 py-0.5 bg-brand-100 dark:bg-brand-900 text-brand-700 dark:text-brand-300 rounded text-xs">
|
||||
{processingState.stage_type}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-4xl font-bold text-brand-600 dark:text-brand-400">
|
||||
{percentage}%
|
||||
</div>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
||||
{processingState.processed_items}/{processingState.total_items} processed
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress Bar */}
|
||||
<div className="mb-6">
|
||||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-3">
|
||||
<div
|
||||
className="bg-brand-600 dark:bg-brand-500 h-3 rounded-full transition-all duration-500"
|
||||
style={{ width: `${Math.min(percentage, 100)}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Currently Processing and Up Next */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{/* Currently Processing */}
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2">
|
||||
Currently Processing:
|
||||
</h3>
|
||||
<div className="space-y-1">
|
||||
{processingState.currently_processing.length > 0 ? (
|
||||
processingState.currently_processing.map((item, idx) => (
|
||||
<div key={idx} className="flex items-start gap-2 text-sm">
|
||||
<span className="text-brand-600 dark:text-brand-400 mt-1">•</span>
|
||||
<span className="text-gray-800 dark:text-gray-200 font-medium line-clamp-2">
|
||||
{item.title}
|
||||
</span>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 italic">
|
||||
No items currently processing
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Up Next */}
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2">
|
||||
Up Next:
|
||||
</h3>
|
||||
<div className="space-y-1">
|
||||
{processingState.up_next.length > 0 ? (
|
||||
<>
|
||||
{processingState.up_next.map((item, idx) => (
|
||||
<div key={idx} className="flex items-start gap-2 text-sm">
|
||||
<span className="text-gray-400 dark:text-gray-500 mt-1">•</span>
|
||||
<span className="text-gray-600 dark:text-gray-400 line-clamp-2">
|
||||
{item.title}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{processingState.remaining_count > processingState.up_next.length + processingState.currently_processing.length && (
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400 mt-2">
|
||||
+ {processingState.remaining_count - processingState.up_next.length - processingState.currently_processing.length} more in queue
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 italic">
|
||||
Queue empty
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CurrentProcessingCard;
|
||||
@@ -8,7 +8,6 @@ export default function UserAddressCard() {
|
||||
const { isOpen, openModal, closeModal } = useModal();
|
||||
const handleSave = () => {
|
||||
// Handle save logic here
|
||||
console.log("Saving changes...");
|
||||
closeModal();
|
||||
};
|
||||
return (
|
||||
|
||||
@@ -8,7 +8,6 @@ export default function UserInfoCard() {
|
||||
const { isOpen, openModal, closeModal } = useModal();
|
||||
const handleSave = () => {
|
||||
// Handle save logic here
|
||||
console.log("Saving changes...");
|
||||
closeModal();
|
||||
};
|
||||
return (
|
||||
|
||||
@@ -8,7 +8,6 @@ export default function UserMetaCard() {
|
||||
const { isOpen, openModal, closeModal } = useModal();
|
||||
const handleSave = () => {
|
||||
// Handle save logic here
|
||||
console.log("Saving changes...");
|
||||
closeModal();
|
||||
};
|
||||
return (
|
||||
|
||||
@@ -26,7 +26,7 @@ function LimitCard({ title, icon, usage, type, daysUntilReset, accentColor = 'br
|
||||
|
||||
// Determine progress bar color - use inline styles for dynamic colors
|
||||
let barColor = 'var(--color-brand-500)';
|
||||
let badgeVariant: 'soft' = 'soft';
|
||||
const badgeVariant: 'soft' = 'soft';
|
||||
let badgeTone: 'brand' | 'warning' | 'danger' | 'success' | 'info' | 'purple' | 'indigo' | 'pink' | 'teal' | 'cyan' = accentColor;
|
||||
|
||||
// Color mapping for progress bars - using CSS variables
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
import Chart from "react-apexcharts";
|
||||
import { ApexOptions } from "apexcharts";
|
||||
|
||||
export default function BarChartOne() {
|
||||
const options: ApexOptions = {
|
||||
colors: ["var(--color-primary)"],
|
||||
chart: {
|
||||
fontFamily: "Outfit, sans-serif",
|
||||
type: "bar",
|
||||
height: 180,
|
||||
toolbar: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
horizontal: false,
|
||||
columnWidth: "39%",
|
||||
borderRadius: 5,
|
||||
borderRadiusApplication: "end",
|
||||
},
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
},
|
||||
stroke: {
|
||||
show: true,
|
||||
width: 4,
|
||||
colors: ["transparent"],
|
||||
},
|
||||
xaxis: {
|
||||
categories: [
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec",
|
||||
],
|
||||
axisBorder: {
|
||||
show: false,
|
||||
},
|
||||
axisTicks: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
position: "top",
|
||||
horizontalAlign: "left",
|
||||
fontFamily: "Outfit",
|
||||
},
|
||||
yaxis: {
|
||||
title: {
|
||||
text: undefined,
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
yaxis: {
|
||||
lines: {
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
fill: {
|
||||
opacity: 1,
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
x: {
|
||||
show: false,
|
||||
},
|
||||
y: {
|
||||
formatter: (val: number) => `${val}`,
|
||||
},
|
||||
},
|
||||
};
|
||||
const series = [
|
||||
{
|
||||
name: "Sales",
|
||||
data: [168, 385, 201, 298, 187, 195, 291, 110, 215, 390, 280, 112],
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className="max-w-full overflow-x-auto custom-scrollbar">
|
||||
<div id="chartOne" className="min-w-[1000px]">
|
||||
<Chart options={options} series={series} type="bar" height={180} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
import Chart from "react-apexcharts";
|
||||
import { ApexOptions } from "apexcharts";
|
||||
|
||||
export default function LineChartOne() {
|
||||
const options: ApexOptions = {
|
||||
legend: {
|
||||
show: false, // Hide legend
|
||||
position: "top",
|
||||
horizontalAlign: "left",
|
||||
},
|
||||
colors: ["var(--color-primary)", "var(--color-brand-300)"], // Define line colors
|
||||
chart: {
|
||||
fontFamily: "Outfit, sans-serif",
|
||||
height: 310,
|
||||
type: "line", // Set the chart type to 'line'
|
||||
toolbar: {
|
||||
show: false, // Hide chart toolbar
|
||||
},
|
||||
},
|
||||
stroke: {
|
||||
curve: "straight", // Define the line style (straight, smooth, or step)
|
||||
width: [2, 2], // Line width for each dataset
|
||||
},
|
||||
|
||||
fill: {
|
||||
type: "gradient",
|
||||
gradient: {
|
||||
opacityFrom: 0.55,
|
||||
opacityTo: 0,
|
||||
},
|
||||
},
|
||||
markers: {
|
||||
size: 0, // Size of the marker points
|
||||
strokeColors: "#fff", // Marker border color
|
||||
strokeWidth: 2,
|
||||
hover: {
|
||||
size: 6, // Marker size on hover
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false, // Hide grid lines on x-axis
|
||||
},
|
||||
},
|
||||
yaxis: {
|
||||
lines: {
|
||||
show: true, // Show grid lines on y-axis
|
||||
},
|
||||
},
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false, // Disable data labels
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true, // Enable tooltip
|
||||
x: {
|
||||
format: "dd MMM yyyy", // Format for x-axis tooltip
|
||||
},
|
||||
},
|
||||
xaxis: {
|
||||
type: "category", // Category-based x-axis
|
||||
categories: [
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec",
|
||||
],
|
||||
axisBorder: {
|
||||
show: false, // Hide x-axis border
|
||||
},
|
||||
axisTicks: {
|
||||
show: false, // Hide x-axis ticks
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false, // Disable tooltip for x-axis points
|
||||
},
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
style: {
|
||||
fontSize: "12px", // Adjust font size for y-axis labels
|
||||
colors: ["var(--color-gray-500)"], // Color of the labels
|
||||
},
|
||||
},
|
||||
title: {
|
||||
text: "", // Remove y-axis title
|
||||
style: {
|
||||
fontSize: "0px",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const series = [
|
||||
{
|
||||
name: "Sales",
|
||||
data: [180, 190, 170, 160, 175, 165, 170, 205, 230, 210, 240, 235],
|
||||
},
|
||||
{
|
||||
name: "Revenue",
|
||||
data: [40, 30, 50, 40, 55, 40, 70, 100, 110, 120, 150, 140],
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className="max-w-full overflow-x-auto custom-scrollbar">
|
||||
<div id="chartEight" className="min-w-[1000px]">
|
||||
<Chart options={options} series={series} type="area" height={310} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -104,8 +104,6 @@ export default function ImageGenerationCard({
|
||||
}, [API_BASE_URL]);
|
||||
|
||||
const handleGenerate = async () => {
|
||||
console.log('[ImageGenerationCard] handleGenerate called');
|
||||
|
||||
if (!prompt.trim()) {
|
||||
toast.error('Please enter a prompt description');
|
||||
return;
|
||||
@@ -122,11 +120,8 @@ export default function ImageGenerationCard({
|
||||
? (imageSettings.model || 'dall-e-3')
|
||||
: (imageSettings.runwareModel || 'runware:97@1');
|
||||
|
||||
console.log('[ImageGenerationCard] Service and model:', { service, model, imageSettings });
|
||||
|
||||
// Build prompt with template (similar to reference plugin)
|
||||
const fullPrompt = `Create a high-quality ${imageType} image. ${prompt}`;
|
||||
console.log('[ImageGenerationCard] Full prompt:', fullPrompt.substring(0, 100) + '...');
|
||||
|
||||
const requestBody = {
|
||||
prompt: fullPrompt,
|
||||
@@ -138,9 +133,6 @@ export default function ImageGenerationCard({
|
||||
model: model,
|
||||
};
|
||||
|
||||
console.log('[ImageGenerationCard] Making request to image generation endpoint');
|
||||
console.log('[ImageGenerationCard] Request body:', requestBody);
|
||||
|
||||
// fetchAPI extracts data from unified format {success: true, data: {...}}
|
||||
// So data is the extracted response payload
|
||||
const data = await fetchAPI('/v1/system/settings/integrations/image_generation/generate/', {
|
||||
@@ -148,8 +140,6 @@ export default function ImageGenerationCard({
|
||||
body: JSON.stringify(requestBody),
|
||||
});
|
||||
|
||||
console.log('[ImageGenerationCard] Response data:', data);
|
||||
|
||||
// fetchAPI extracts data from unified format, so data is the response payload
|
||||
// If fetchAPI didn't throw, the request was successful
|
||||
if (!data || typeof data !== 'object') {
|
||||
@@ -175,7 +165,6 @@ export default function ImageGenerationCard({
|
||||
})
|
||||
);
|
||||
|
||||
console.log('[ImageGenerationCard] Image generation successful:', imageData);
|
||||
toast.success('Image generated successfully!');
|
||||
} catch (err: any) {
|
||||
console.error('[ImageGenerationCard] Error in handleGenerate:', {
|
||||
|
||||
@@ -218,37 +218,29 @@ export default function ImageQueueModal({
|
||||
|
||||
// Stop polling after max attempts
|
||||
if (pollAttempts > maxPollAttempts) {
|
||||
console.warn('Polling timeout reached, stopping');
|
||||
clearInterval(pollInterval);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`[ImageQueueModal] Polling task status (attempt ${pollAttempts}):`, taskId);
|
||||
const data = await fetchAPI(`/v1/system/settings/task_progress/${taskId}/`);
|
||||
console.log(`[ImageQueueModal] Task status response:`, data);
|
||||
|
||||
// Check if data is valid (not HTML error page)
|
||||
if (!data || typeof data !== 'object') {
|
||||
console.warn('Invalid task status response:', data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check state (task_progress returns 'state', not 'status')
|
||||
const taskState = data.state || data.status;
|
||||
console.log(`[ImageQueueModal] Task state:`, taskState);
|
||||
|
||||
if (taskState === 'SUCCESS' || taskState === 'FAILURE') {
|
||||
console.log(`[ImageQueueModal] Task completed with state:`, taskState);
|
||||
clearInterval(pollInterval);
|
||||
|
||||
// Update final state
|
||||
if (taskState === 'SUCCESS' && data.result) {
|
||||
console.log(`[ImageQueueModal] Updating queue from result:`, data.result);
|
||||
updateQueueFromTaskResult(data.result);
|
||||
} else if (taskState === 'SUCCESS' && data.meta && data.meta.result) {
|
||||
// Some responses have result in meta
|
||||
console.log(`[ImageQueueModal] Updating queue from meta result:`, data.meta.result);
|
||||
updateQueueFromTaskResult(data.meta.result);
|
||||
}
|
||||
return;
|
||||
@@ -256,10 +248,7 @@ export default function ImageQueueModal({
|
||||
|
||||
// Update progress from task meta
|
||||
if (data.meta) {
|
||||
console.log(`[ImageQueueModal] Updating queue from meta:`, data.meta);
|
||||
updateQueueFromTaskMeta(data.meta);
|
||||
} else {
|
||||
console.log(`[ImageQueueModal] No meta data in response`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
// Check if it's a JSON parse error (HTML response) or API error
|
||||
|
||||
@@ -1,58 +1,613 @@
|
||||
/**
|
||||
* Search Modal - Global search modal triggered by icon or Cmd+K
|
||||
* Enhanced with filters and recent searches
|
||||
*/
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Modal } from '../ui/modal';
|
||||
import Button from '../ui/button/Button';
|
||||
|
||||
// Add styles for highlighted search terms
|
||||
const searchHighlightStyles = `
|
||||
.search-result mark {
|
||||
background-color: rgb(252 211 77); /* amber-300 */
|
||||
color: rgb(17 24 39); /* gray-900 */
|
||||
padding: 0 0.25rem;
|
||||
border-radius: 0.25rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.dark .search-result mark {
|
||||
background-color: rgb(180 83 9); /* amber-700 */
|
||||
color: rgb(255 255 255);
|
||||
}
|
||||
.search-result:hover mark {
|
||||
background-color: rgb(245 158 11); /* amber-500 */
|
||||
color: rgb(255 255 255);
|
||||
box-shadow: 0 0 0 2px rgb(245 158 11 / 0.3);
|
||||
}
|
||||
.dark .search-result:hover mark {
|
||||
background-color: rgb(217 119 6); /* amber-600 */
|
||||
box-shadow: 0 0 0 2px rgb(217 119 6 / 0.3);
|
||||
}
|
||||
`;
|
||||
|
||||
interface SearchModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
interface QuickAction {
|
||||
label: string;
|
||||
path?: string;
|
||||
action?: () => void;
|
||||
}
|
||||
|
||||
interface SearchResult {
|
||||
title: string;
|
||||
path: string;
|
||||
type: 'page' | 'action';
|
||||
type: 'workflow' | 'setup' | 'account' | 'help';
|
||||
category: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
quickActions?: QuickAction[];
|
||||
keywords?: string[]; // Additional searchable terms
|
||||
content?: string; // Page content hints for better search
|
||||
contextSnippet?: string; // Context around matched text
|
||||
}
|
||||
|
||||
interface SuggestedQuestion {
|
||||
question: string;
|
||||
answer: string;
|
||||
helpSection: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
type FilterType = 'all' | 'workflow' | 'setup' | 'account' | 'help';
|
||||
|
||||
const RECENT_SEARCHES_KEY = 'igny8_recent_searches';
|
||||
const MAX_RECENT_SEARCHES = 5;
|
||||
|
||||
// Knowledge base for suggested questions and answers
|
||||
// Keys include main terms + common aliases for better search matching
|
||||
const HELP_KNOWLEDGE_BASE: Record<string, SuggestedQuestion[]> = {
|
||||
'keyword': [
|
||||
{ question: 'How do I import keywords?', answer: 'Go to Add Keywords page and either select your industry/sector for seed keywords or upload a CSV file with your own keywords.', helpSection: 'Importing Keywords', path: '/help#importing-keywords' },
|
||||
{ question: 'How do I organize keywords into clusters?', answer: 'Navigate to Clusters page and run the AI clustering algorithm. It will automatically group similar keywords by topic.', helpSection: 'Keyword Clustering', path: '/help#keyword-clustering' },
|
||||
{ question: 'Can I bulk delete keywords?', answer: 'Yes, on the Keywords page select multiple keywords using checkboxes and click the bulk delete action button.', helpSection: 'Managing Keywords', path: '/help#managing-keywords' },
|
||||
],
|
||||
'cluster': [ // Added alias for clustering
|
||||
{ question: 'How do I organize keywords into clusters?', answer: 'Navigate to Clusters page and run the AI clustering algorithm. It will automatically group similar keywords by topic.', helpSection: 'Keyword Clustering', path: '/help#keyword-clustering' },
|
||||
{ question: 'Can I bulk delete keywords?', answer: 'Yes, on the Keywords page select multiple keywords using checkboxes and click the bulk delete action button.', helpSection: 'Managing Keywords', path: '/help#managing-keywords' },
|
||||
],
|
||||
'task': [ // Added for tasks
|
||||
{ question: 'How do I generate content?', answer: 'Convert content ideas to tasks in the Queue, or create tasks manually. The AI will generate content based on your keywords and settings.', helpSection: 'Content Generation', path: '/help#content-generation' },
|
||||
{ question: 'What is the difference between Tasks and Content?', answer: 'Tasks are content ideas converted into actionable writing assignments with status tracking. Content is the actual generated articles created from tasks.', helpSection: 'Content Workflow', path: '/help#content-workflow' },
|
||||
],
|
||||
'content': [
|
||||
{ question: 'How do I generate content?', answer: 'Convert content ideas to tasks in the Queue, or create tasks manually. The AI will generate content based on your keywords and settings.', helpSection: 'Content Generation', path: '/help#content-generation' },
|
||||
{ question: 'How do I edit generated content?', answer: 'Go to Drafts page, click on any content to open the editor. You can edit text, title, and metadata before approving.', helpSection: 'Editing Content', path: '/help#editing-content' },
|
||||
{ question: 'What content settings can I configure?', answer: 'In Content Settings you can set default length, tone, style, SEO preferences, and image generation settings.', helpSection: 'Content Settings', path: '/help#content-settings' },
|
||||
{ question: 'How do I approve content for publishing?', answer: 'Review content in the Review page, then click approve to move it to the Approved queue ready for publishing.', helpSection: 'Content Workflow', path: '/help#content-workflow' },
|
||||
],
|
||||
'writing': [ // Added alias
|
||||
{ question: 'How do I generate content?', answer: 'Convert content ideas to tasks in the Queue, or create tasks manually. The AI will generate content based on your keywords and settings.', helpSection: 'Content Generation', path: '/help#content-generation' },
|
||||
{ question: 'How do I edit generated content?', answer: 'Go to Drafts page, click on any content to open the editor. You can edit text, title, and metadata before approving.', helpSection: 'Editing Content', path: '/help#editing-content' },
|
||||
],
|
||||
'publish': [
|
||||
{ question: 'How do I publish to WordPress?', answer: 'Connect your WordPress site in Sites page, then use Content Calendar to schedule or immediately publish approved content.', helpSection: 'Publishing', path: '/help#publishing-wordpress' },
|
||||
{ question: 'Can I schedule posts in advance?', answer: 'Yes, in the Content Calendar you can drag and drop content to specific dates and times for automatic publishing.', helpSection: 'Scheduling', path: '/help#scheduling-posts' },
|
||||
{ question: 'How do I connect a WordPress site?', answer: 'Go to Sites page, click Add Site, enter your WordPress URL and credentials. Test the connection before saving.', helpSection: 'WordPress Integration', path: '/help#wordpress-integration' },
|
||||
],
|
||||
'wordpress': [ // Added alias
|
||||
{ question: 'How do I publish to WordPress?', answer: 'Connect your WordPress site in Sites page, then use Content Calendar to schedule or immediately publish approved content.', helpSection: 'Publishing', path: '/help#publishing-wordpress' },
|
||||
{ question: 'How do I connect a WordPress site?', answer: 'Go to Sites page, click Add Site, enter your WordPress URL and credentials. Test the connection before saving.', helpSection: 'WordPress Integration', path: '/help#wordpress-integration' },
|
||||
],
|
||||
'schedule': [ // Added alias
|
||||
{ question: 'Can I schedule posts in advance?', answer: 'Yes, in the Content Calendar you can drag and drop content to specific dates and times for automatic publishing.', helpSection: 'Scheduling', path: '/help#scheduling-posts' },
|
||||
],
|
||||
'image': [
|
||||
{ question: 'How do I generate images?', answer: 'Images are auto-generated with content. You can also regenerate specific images from the Images page with custom prompts.', helpSection: 'Image Generation', path: '/help#image-generation' },
|
||||
{ question: 'Can I use different AI image models?', answer: 'Yes, configure your preferred AI image model (DALL-E, Midjourney, Stable Diffusion) in Content Settings under Images.', helpSection: 'Image Settings', path: '/help#image-settings' },
|
||||
{ question: 'How do I assign images to content?', answer: 'From the Images page, click on an image and select which content to assign it as featured image.', helpSection: 'Managing Images', path: '/help#managing-images' },
|
||||
],
|
||||
'picture': [ // Added alias
|
||||
{ question: 'How do I generate images?', answer: 'Images are auto-generated with content. You can also regenerate specific images from the Images page with custom prompts.', helpSection: 'Image Generation', path: '/help#image-generation' },
|
||||
],
|
||||
'credit': [
|
||||
{ question: 'How do credits work?', answer: 'Credits are consumed for AI operations: keyword clustering, content generation, and image creation. Check Usage Analytics for detailed breakdown.', helpSection: 'Credit System', path: '/help#credit-system' },
|
||||
{ question: 'How do I buy more credits?', answer: 'Go to Plans & Billing page to purchase credit packs or upgrade your subscription plan for more monthly credits.', helpSection: 'Purchasing Credits', path: '/help#purchasing-credits' },
|
||||
{ question: 'Where can I see credit usage?', answer: 'Usage Analytics page shows detailed charts and logs of credit consumption by action type and date.', helpSection: 'Usage Tracking', path: '/help#usage-tracking' },
|
||||
],
|
||||
'billing': [ // Added for billing
|
||||
{ question: 'How do I buy more credits?', answer: 'Go to Plans & Billing page to purchase credit packs or upgrade your subscription plan for more monthly credits.', helpSection: 'Purchasing Credits', path: '/help#purchasing-credits' },
|
||||
{ question: 'What payment methods are supported?', answer: 'IGNY8 supports Stripe (credit/debit cards), PayPal, and Bank Transfer (for annual plans). Available methods vary by country.', helpSection: 'Purchasing Credits', path: '/help#purchasing-credits' },
|
||||
],
|
||||
'payment': [ // Added alias
|
||||
{ question: 'How do I buy more credits?', answer: 'Go to Plans & Billing page to purchase credit packs or upgrade your subscription plan for more monthly credits.', helpSection: 'Purchasing Credits', path: '/help#purchasing-credits' },
|
||||
{ question: 'What payment methods are supported?', answer: 'IGNY8 supports Stripe (credit/debit cards), PayPal, and Bank Transfer (for annual plans). Available methods vary by country.', helpSection: 'Purchasing Credits', path: '/help#purchasing-credits' },
|
||||
],
|
||||
'invoice': [ // Added for invoice
|
||||
{ question: 'How do I buy more credits?', answer: 'Go to Plans & Billing page to purchase credit packs or upgrade your subscription plan for more monthly credits.', helpSection: 'Purchasing Credits', path: '/help#purchasing-credits' },
|
||||
{ question: 'Where can I see billing history?', answer: 'Go to Plans & Billing page to view your invoices, payment history, and download receipts for your records.', helpSection: 'Purchasing Credits', path: '/help#purchasing-credits' },
|
||||
],
|
||||
'plan': [ // Added for subscription plans
|
||||
{ question: 'How do I buy more credits?', answer: 'Go to Plans & Billing page to purchase credit packs or upgrade your subscription plan for more monthly credits.', helpSection: 'Purchasing Credits', path: '/help#purchasing-credits' },
|
||||
{ question: 'Can I upgrade my plan?', answer: 'Yes, go to Plans & Billing to upgrade or downgrade your subscription. Changes take effect immediately with prorated billing.', helpSection: 'Purchasing Credits', path: '/help#purchasing-credits' },
|
||||
],
|
||||
'usage': [ // Added for usage
|
||||
{ question: 'Where can I see credit usage?', answer: 'Usage Analytics page shows detailed charts and logs of credit consumption by action type and date.', helpSection: 'Usage Tracking', path: '/help#usage-tracking' },
|
||||
{ question: 'How do credits work?', answer: 'Credits are consumed for AI operations: keyword clustering, content generation, and image creation. Check Usage Analytics for detailed breakdown.', helpSection: 'Credit System', path: '/help#credit-system' },
|
||||
],
|
||||
'automation': [
|
||||
{ question: 'How do I set up automation?', answer: 'Go to Automation page to configure recurring tasks: auto-clustering, scheduled content generation, and auto-publishing rules.', helpSection: 'Automation Setup', path: '/help#automation-setup' },
|
||||
{ question: 'Can content be auto-published?', answer: 'Yes, enable auto-approval rules in Automation and set publishing schedules in Content Calendar for fully automated workflows.', helpSection: 'Auto-Publishing', path: '/help#auto-publishing' },
|
||||
],
|
||||
'team': [
|
||||
{ question: 'How do I invite team members?', answer: 'Go to Team Management, click Invite User, enter their email and assign a role. They will receive an invitation email.', helpSection: 'Team Collaboration', path: '/help#team-collaboration' },
|
||||
{ question: 'What are the different user roles?', answer: 'Admin has full access, Editor can manage content, and Viewer can only view data. Configure in Team Management.', helpSection: 'User Roles', path: '/help#user-roles' },
|
||||
],
|
||||
'user': [ // Added alias
|
||||
{ question: 'How do I invite team members?', answer: 'Go to Team Management, click Invite User, enter their email and assign a role. They will receive an invitation email.', helpSection: 'Team Collaboration', path: '/help#team-collaboration' },
|
||||
{ question: 'What are the different user roles?', answer: 'Admin has full access, Editor can manage content, and Viewer can only view data. Configure in Team Management.', helpSection: 'User Roles', path: '/help#user-roles' },
|
||||
],
|
||||
'prompt': [
|
||||
{ question: 'How do I customize AI prompts?', answer: 'Admins can edit AI prompt templates in Prompts page to control how content is generated.', helpSection: 'Prompt Management', path: '/help#prompt-management' },
|
||||
{ question: 'What are author profiles?', answer: 'Author profiles define writing styles (tone, vocabulary, structure) that you can assign to content for consistent brand voice.', helpSection: 'Author Profiles', path: '/help#author-profiles' },
|
||||
],
|
||||
'ai': [ // Added alias
|
||||
{ question: 'How do I customize AI prompts?', answer: 'Admins can edit AI prompt templates in Prompts page to control how content is generated.', helpSection: 'Prompt Management', path: '/help#prompt-management' },
|
||||
{ question: 'Can I use different AI image models?', answer: 'Yes, configure your preferred AI image model (DALL-E, Midjourney, Stable Diffusion) in Content Settings under Images.', helpSection: 'Image Settings', path: '/help#image-settings' },
|
||||
],
|
||||
};
|
||||
|
||||
const SEARCH_ITEMS: SearchResult[] = [
|
||||
// Workflow
|
||||
{ title: 'Keywords', path: '/planner/keywords', type: 'page' },
|
||||
{ title: 'Clusters', path: '/planner/clusters', type: 'page' },
|
||||
{ title: 'Ideas', path: '/planner/ideas', type: 'page' },
|
||||
{ title: 'Queue', path: '/writer/tasks', type: 'page' },
|
||||
{ title: 'Drafts', path: '/writer/content', type: 'page' },
|
||||
{ title: 'Images', path: '/writer/images', type: 'page' },
|
||||
{ title: 'Review', path: '/writer/review', type: 'page' },
|
||||
{ title: 'Approved', path: '/writer/approved', type: 'page' },
|
||||
// Workflow - Planner
|
||||
{
|
||||
title: 'Keywords',
|
||||
path: '/planner/keywords',
|
||||
type: 'workflow',
|
||||
category: 'Planner',
|
||||
description: 'Manage and organize your keywords',
|
||||
keywords: ['keyword', 'search terms', 'seo', 'target', 'focus', 'research', 'phrases', 'queries'],
|
||||
content: 'View and manage all your target keywords. Filter by cluster, search volume, or status. Bulk actions: delete, assign to cluster, export to CSV. Table shows keyword text, search volume, cluster assignment, and status.',
|
||||
quickActions: [
|
||||
{ label: 'Import Keywords', path: '/setup/add-keywords' },
|
||||
{ label: 'View Clusters', path: '/planner/clusters' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Clusters',
|
||||
path: '/planner/clusters',
|
||||
type: 'workflow',
|
||||
category: 'Planner',
|
||||
description: 'AI-grouped keyword clusters',
|
||||
keywords: ['cluster', 'groups', 'topics', 'themes', 'organize', 'categorize', 'ai grouping'],
|
||||
content: 'View AI-generated keyword clusters grouped by topic similarity. Each cluster shows assigned keywords count and suggested content topics. Run clustering algorithm, view cluster details, generate content ideas from clusters.',
|
||||
quickActions: [
|
||||
{ label: 'Generate Ideas', path: '/planner/ideas' },
|
||||
{ label: 'View Keywords', path: '/planner/keywords' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Ideas',
|
||||
path: '/planner/ideas',
|
||||
type: 'workflow',
|
||||
category: 'Planner',
|
||||
description: 'Content ideas from clusters',
|
||||
keywords: ['ideas', 'suggestions', 'topics', 'content planning', 'brainstorm', 'article ideas'],
|
||||
content: 'AI-generated content ideas based on keyword clusters. Review suggested titles, topics, and angles. Convert ideas to writing tasks with one click. Filter by cluster, status, or keyword.',
|
||||
quickActions: [
|
||||
{ label: 'Convert to Tasks', path: '/writer/tasks' },
|
||||
{ label: 'View Clusters', path: '/planner/clusters' },
|
||||
]
|
||||
},
|
||||
// Workflow - Writer
|
||||
{
|
||||
title: 'Queue',
|
||||
path: '/writer/tasks',
|
||||
type: 'workflow',
|
||||
category: 'Writer',
|
||||
description: 'Content generation queue',
|
||||
keywords: ['queue', 'tasks', 'writing', 'generation', 'pending', 'in progress', 'batch', 'jobs'],
|
||||
content: 'Content generation task queue. View pending, in-progress, and completed tasks. Monitor AI writing progress, cancel tasks, regenerate content. Shows task title, status, keywords, and generation progress.',
|
||||
quickActions: [
|
||||
{ label: 'View Drafts', path: '/writer/content' },
|
||||
{ label: 'Check Images', path: '/writer/images' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Drafts',
|
||||
path: '/writer/content',
|
||||
type: 'workflow',
|
||||
category: 'Writer',
|
||||
description: 'Generated content drafts',
|
||||
keywords: ['drafts', 'content', 'articles', 'posts', 'generated', 'ai writing', 'edit', 'review'],
|
||||
content: 'All AI-generated content drafts. Edit content in rich text editor, adjust title and metadata, assign featured images. Filter by keyword, status, or generation date. Bulk approve or delete drafts.',
|
||||
quickActions: [
|
||||
{ label: 'Move to Review', path: '/writer/review' },
|
||||
{ label: 'View Images', path: '/writer/images' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Images',
|
||||
path: '/writer/images',
|
||||
type: 'workflow',
|
||||
category: 'Writer',
|
||||
description: 'AI-generated images',
|
||||
keywords: ['images', 'pictures', 'graphics', 'featured image', 'midjourney', 'dall-e', 'stable diffusion', 'ai art'],
|
||||
content: 'AI-generated images library. View all generated images with prompts, assign to content, regenerate images. Filter by status, generation date, or content assignment. Supports multiple AI image models.',
|
||||
quickActions: [
|
||||
{ label: 'View Content', path: '/writer/content' },
|
||||
{ label: 'Image Settings', path: '/account/content-settings/images' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Review',
|
||||
path: '/writer/review',
|
||||
type: 'workflow',
|
||||
category: 'Writer',
|
||||
description: 'Content pending review',
|
||||
keywords: ['review', 'approve', 'quality check', 'editorial', 'pending approval'],
|
||||
content: 'Review AI-generated content before publishing. Check quality, accuracy, and brand alignment. Approve for publishing or send back to drafts for revisions.',
|
||||
quickActions: [
|
||||
{ label: 'Approve Content', path: '/writer/approved' },
|
||||
{ label: 'View Drafts', path: '/writer/content' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Approved',
|
||||
path: '/writer/approved',
|
||||
type: 'workflow',
|
||||
category: 'Writer',
|
||||
description: 'Ready to publish',
|
||||
keywords: ['approved', 'ready', 'final', 'publish ready', 'scheduled'],
|
||||
content: 'Approved content ready for publishing. Schedule for auto-publish or manually publish to WordPress sites. View publishing status and scheduled dates.',
|
||||
quickActions: [
|
||||
{ label: 'Schedule Publishing', path: '/publisher/content-calendar' },
|
||||
{ label: 'View Sites', path: '/sites' },
|
||||
]
|
||||
},
|
||||
// Workflow - Automation
|
||||
{
|
||||
title: 'Automation',
|
||||
path: '/automation',
|
||||
type: 'workflow',
|
||||
category: 'Automation',
|
||||
description: 'Pipeline automation settings',
|
||||
keywords: ['automation', 'pipeline', 'workflow', 'auto', 'schedule', 'recurring', 'batch processing'],
|
||||
content: 'Configure automated content pipeline. Set up recurring keyword clustering, content generation schedules, auto-approval rules, and publishing automation. Monitor automation runs and logs.',
|
||||
quickActions: [
|
||||
{ label: 'View Keywords', path: '/planner/keywords' },
|
||||
{ label: 'Check Queue', path: '/writer/tasks' },
|
||||
]
|
||||
},
|
||||
// Workflow - Publisher
|
||||
{
|
||||
title: 'Content Calendar',
|
||||
path: '/publisher/content-calendar',
|
||||
type: 'workflow',
|
||||
category: 'Publisher',
|
||||
description: 'Schedule and publish content',
|
||||
keywords: ['calendar', 'schedule', 'publish', 'wordpress', 'posting', 'timeline', 'planning'],
|
||||
content: 'Visual content calendar showing scheduled posts. Drag-and-drop to reschedule, bulk publish, view publishing history. Connect to WordPress sites for direct publishing.',
|
||||
quickActions: [
|
||||
{ label: 'View Approved', path: '/writer/approved' },
|
||||
{ label: 'Manage Sites', path: '/sites' },
|
||||
]
|
||||
},
|
||||
// Setup
|
||||
{ title: 'Sites', path: '/sites', type: 'page' },
|
||||
{ title: 'Add Keywords', path: '/add-keywords', type: 'page' },
|
||||
{ title: 'Content Settings', path: '/account/content-settings', type: 'page' },
|
||||
{ title: 'Prompts', path: '/thinker/prompts', type: 'page' },
|
||||
{ title: 'Author Profiles', path: '/thinker/author-profiles', type: 'page' },
|
||||
{
|
||||
title: 'Sites',
|
||||
path: '/sites',
|
||||
type: 'setup',
|
||||
category: 'Sites',
|
||||
description: 'WordPress site management',
|
||||
keywords: ['sites', 'wordpress', 'blog', 'website', 'connection', 'integration', 'wp', 'domain'],
|
||||
content: 'Manage WordPress site connections. Add new sites, configure API credentials, test connections. View site details, publishing settings, and connection status. Supports multiple WordPress sites.',
|
||||
quickActions: [
|
||||
{ label: 'Add Keywords', path: '/setup/add-keywords' },
|
||||
{ label: 'Content Settings', path: '/account/content-settings' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Add Keywords',
|
||||
path: '/setup/add-keywords',
|
||||
type: 'setup',
|
||||
category: 'Setup',
|
||||
description: 'Import keywords by industry/sector',
|
||||
keywords: ['import', 'add', 'bulk upload', 'csv', 'industry', 'sector', 'seed keywords', 'niche'],
|
||||
content: 'Quick-start keyword import wizard. Select your industry and sector to import pre-researched seed keywords. Or upload your own CSV file with custom keywords. Bulk import thousands of keywords at once.',
|
||||
quickActions: [
|
||||
{ label: 'View Keywords', path: '/planner/keywords' },
|
||||
{ label: 'Run Clustering', path: '/planner/clusters' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Content Settings',
|
||||
path: '/account/content-settings',
|
||||
type: 'setup',
|
||||
category: 'Settings',
|
||||
description: 'Configure content generation',
|
||||
keywords: ['settings', 'configuration', 'content length', 'tone', 'style', 'formatting', 'seo', 'meta'],
|
||||
content: 'Configure AI content generation settings. Set default content length, tone of voice, writing style, SEO settings. Configure image generation, meta descriptions, and content structure preferences.',
|
||||
quickActions: [
|
||||
{ label: 'Edit Prompts', path: '/thinker/prompts' },
|
||||
{ label: 'Author Profiles', path: '/thinker/author-profiles' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Prompts',
|
||||
path: '/thinker/prompts',
|
||||
type: 'setup',
|
||||
category: 'AI',
|
||||
description: 'AI prompt templates (Admin)',
|
||||
keywords: ['prompts', 'templates', 'ai instructions', 'system prompts', 'gpt', 'claude', 'llm'],
|
||||
content: 'Manage AI prompt templates for content generation. Edit system prompts, user prompts, and prompt variables. Configure different prompts for articles, social posts, meta descriptions. Admin only.',
|
||||
quickActions: [
|
||||
{ label: 'Author Profiles', path: '/thinker/author-profiles' },
|
||||
{ label: 'Content Settings', path: '/account/content-settings' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Author Profiles',
|
||||
path: '/thinker/author-profiles',
|
||||
type: 'setup',
|
||||
category: 'AI',
|
||||
description: 'Writing style profiles (Admin)',
|
||||
keywords: ['author', 'voice', 'style', 'tone', 'personality', 'writing profile', 'brand voice'],
|
||||
content: 'Create author personas for different writing styles. Configure tone, vocabulary level, sentence structure preferences. Assign author profiles to content for consistent brand voice. Admin only.',
|
||||
quickActions: [
|
||||
{ label: 'View Prompts', path: '/thinker/prompts' },
|
||||
{ label: 'Content Settings', path: '/account/content-settings' },
|
||||
]
|
||||
},
|
||||
// Account
|
||||
{ title: 'Account Settings', path: '/account/settings', type: 'page' },
|
||||
{ title: 'Plans & Billing', path: '/account/plans', type: 'page' },
|
||||
{ title: 'Usage Analytics', path: '/account/usage', type: 'page' },
|
||||
{
|
||||
title: 'Account Settings',
|
||||
path: '/account/settings',
|
||||
type: 'account',
|
||||
category: 'Account',
|
||||
description: 'Profile and preferences',
|
||||
keywords: ['account', 'profile', 'user', 'preferences', 'settings', 'password', 'email', 'name'],
|
||||
content: 'Manage your account profile and preferences. Update name, email, password. Configure notification preferences, timezone, language. View account status and subscription details.',
|
||||
quickActions: [
|
||||
{ label: 'Team Management', path: '/account/settings/team' },
|
||||
{ label: 'Notifications', path: '/account/notifications' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Plans & Billing',
|
||||
path: '/account/plans',
|
||||
type: 'account',
|
||||
category: 'Account',
|
||||
description: 'Subscription and credits',
|
||||
keywords: ['billing', 'subscription', 'plan', 'credits', 'payment', 'upgrade', 'pricing', 'invoice'],
|
||||
content: 'Manage subscription plan and credits. View current plan details, upgrade or downgrade. Purchase credit packs, view billing history and invoices. Configure payment methods.',
|
||||
quickActions: [
|
||||
{ label: 'Usage Analytics', path: '/account/usage' },
|
||||
{ label: 'Purchase Credits', path: '/account/plans' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Usage Analytics',
|
||||
path: '/account/usage',
|
||||
type: 'account',
|
||||
category: 'Account',
|
||||
description: 'Credit usage and insights',
|
||||
keywords: ['usage', 'analytics', 'stats', 'consumption', 'credits spent', 'reports', 'metrics'],
|
||||
content: 'View detailed credit usage analytics. Charts and graphs showing daily/weekly/monthly consumption. Filter by action type (content generation, images, clustering). Export usage reports.',
|
||||
quickActions: [
|
||||
{ label: 'View Logs', path: '/account/usage/logs' },
|
||||
{ label: 'Plans & Billing', path: '/account/plans' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Team Management',
|
||||
path: '/account/settings/team',
|
||||
type: 'account',
|
||||
category: 'Account',
|
||||
description: 'Invite and manage team members',
|
||||
keywords: ['team', 'users', 'members', 'invite', 'permissions', 'roles', 'collaboration', 'access'],
|
||||
content: 'Invite team members to your workspace. Manage user roles and permissions. View team activity, remove users, resend invitations. Configure collaboration settings and access controls.',
|
||||
quickActions: [
|
||||
{ label: 'Account Settings', path: '/account/settings' },
|
||||
{ label: 'View Usage', path: '/account/usage' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Notifications',
|
||||
path: '/account/notifications',
|
||||
type: 'account',
|
||||
category: 'Account',
|
||||
description: 'System and content notifications',
|
||||
keywords: ['notifications', 'alerts', 'updates', 'email notifications', 'bell', 'messages'],
|
||||
content: 'View all system notifications and content updates. Mark as read, filter by type. Configure notification preferences for email and in-app alerts. See content generation completions, publishing status, credit warnings.',
|
||||
quickActions: [
|
||||
{ label: 'Account Settings', path: '/account/settings' },
|
||||
]
|
||||
},
|
||||
// Help
|
||||
{ title: 'Help & Support', path: '/help', type: 'page' },
|
||||
{
|
||||
title: 'Help & Support',
|
||||
path: '/help',
|
||||
type: 'help',
|
||||
category: 'Help',
|
||||
description: 'Documentation and support',
|
||||
keywords: ['help', 'support', 'docs', 'documentation', 'guide', 'tutorial', 'faq', 'assistance'],
|
||||
content: 'Access help documentation, user guides, and tutorials. Search knowledge base, view FAQs, contact support. Getting started guides, video tutorials, API documentation, and troubleshooting tips.',
|
||||
quickActions: [
|
||||
{ label: 'Get Started', path: '/setup/wizard' },
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
export default function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
const [query, setQuery] = useState('');
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
const [activeFilter, setActiveFilter] = useState<FilterType>('all');
|
||||
const [recentSearches, setRecentSearches] = useState<string[]>([]);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Load recent searches from localStorage
|
||||
useEffect(() => {
|
||||
const stored = localStorage.getItem(RECENT_SEARCHES_KEY);
|
||||
if (stored) {
|
||||
try {
|
||||
setRecentSearches(JSON.parse(stored));
|
||||
} catch {
|
||||
setRecentSearches([]);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Save recent search
|
||||
const addRecentSearch = (path: string) => {
|
||||
const updated = [path, ...recentSearches.filter(p => p !== path)].slice(0, MAX_RECENT_SEARCHES);
|
||||
setRecentSearches(updated);
|
||||
localStorage.setItem(RECENT_SEARCHES_KEY, JSON.stringify(updated));
|
||||
};
|
||||
|
||||
const getRecentSearchResults = (): SearchResult[] => {
|
||||
return recentSearches
|
||||
.map(path => SEARCH_ITEMS.find(item => item.path === path))
|
||||
.filter((item): item is SearchResult => item !== undefined);
|
||||
};
|
||||
|
||||
// Enhanced search: title, category, description, keywords, and content
|
||||
const searchItems = (searchQuery: string): SearchResult[] => {
|
||||
const lowerQuery = searchQuery.toLowerCase().trim();
|
||||
if (!lowerQuery) return [];
|
||||
|
||||
return SEARCH_ITEMS.filter(item => {
|
||||
const matchesFilter = activeFilter === 'all' || item.type === activeFilter;
|
||||
if (!matchesFilter) return false;
|
||||
|
||||
// Search in title, category, description
|
||||
const matchesBasic =
|
||||
item.title.toLowerCase().includes(lowerQuery) ||
|
||||
item.category.toLowerCase().includes(lowerQuery) ||
|
||||
item.description?.toLowerCase().includes(lowerQuery);
|
||||
|
||||
// Search in keywords array
|
||||
const matchesKeywords = item.keywords?.some(kw => kw.toLowerCase().includes(lowerQuery));
|
||||
|
||||
// Search in content text
|
||||
const matchesContent = item.content?.toLowerCase().includes(lowerQuery);
|
||||
|
||||
return matchesBasic || matchesKeywords || matchesContent;
|
||||
}).map(item => {
|
||||
// Add context snippet around matched text
|
||||
let contextSnippet = '';
|
||||
|
||||
// Try to find context in keywords first
|
||||
const matchedKeyword = item.keywords?.find(kw => kw.toLowerCase().includes(lowerQuery));
|
||||
if (matchedKeyword) {
|
||||
contextSnippet = `Related: ${matchedKeyword}`;
|
||||
}
|
||||
// Otherwise look for context in content
|
||||
else if (item.content && item.content.toLowerCase().includes(lowerQuery)) {
|
||||
contextSnippet = getContextSnippet(item.content, lowerQuery);
|
||||
}
|
||||
|
||||
return { ...item, contextSnippet };
|
||||
});
|
||||
};
|
||||
|
||||
// Get context snippet with words before and after the match
|
||||
const getContextSnippet = (text: string, query: string): string => {
|
||||
const lowerText = text.toLowerCase();
|
||||
const index = lowerText.indexOf(query.toLowerCase());
|
||||
if (index === -1) return '';
|
||||
|
||||
// Get ~50 chars before and after the match
|
||||
const start = Math.max(0, index - 50);
|
||||
const end = Math.min(text.length, index + query.length + 50);
|
||||
let snippet = text.substring(start, end);
|
||||
|
||||
// Add ellipsis if truncated
|
||||
if (start > 0) snippet = '...' + snippet;
|
||||
if (end < text.length) snippet = snippet + '...';
|
||||
|
||||
return snippet;
|
||||
};
|
||||
|
||||
// Normalize search query by removing common filler words
|
||||
const normalizeQuery = (query: string): string[] => {
|
||||
const fillerWords = ['how', 'to', 'do', 'i', 'can', 'what', 'is', 'are', 'the', 'a', 'an', 'where', 'when', 'why', 'which', 'who', 'does', 'my', 'your', 'for', 'in', 'on', 'at', 'from'];
|
||||
const words = query.toLowerCase().trim().split(/\s+/);
|
||||
|
||||
// Filter out filler words and keep meaningful terms
|
||||
const meaningfulWords = words.filter(word => !fillerWords.includes(word));
|
||||
|
||||
// Also handle plurals -> singular (basic stemming)
|
||||
return meaningfulWords.map(word => {
|
||||
if (word.endsWith('s') && word.length > 3) {
|
||||
return word.slice(0, -1); // Remove 's' from end
|
||||
}
|
||||
return word;
|
||||
});
|
||||
};
|
||||
|
||||
// Get suggested questions based on search query
|
||||
const getSuggestedQuestions = (searchQuery: string): SuggestedQuestion[] => {
|
||||
if (!searchQuery || searchQuery.length < 3) return [];
|
||||
|
||||
const lowerQuery = searchQuery.toLowerCase().trim();
|
||||
const suggestions: SuggestedQuestion[] = [];
|
||||
const seenQuestions = new Set<string>(); // Prevent duplicates
|
||||
|
||||
// Get normalized search terms
|
||||
const searchTerms = normalizeQuery(searchQuery);
|
||||
|
||||
// Find relevant questions from knowledge base
|
||||
Object.entries(HELP_KNOWLEDGE_BASE).forEach(([keyword, questions]) => {
|
||||
// Check if query matches keyword directly
|
||||
const directMatch = lowerQuery.includes(keyword) || keyword.includes(lowerQuery);
|
||||
|
||||
// Check if any normalized search term matches
|
||||
const termMatch = searchTerms.some(term =>
|
||||
keyword.includes(term) || term.includes(keyword)
|
||||
);
|
||||
|
||||
// Also check if any term appears in the question text itself
|
||||
const questionTextMatch = questions.some(q =>
|
||||
searchTerms.some(term => q.question.toLowerCase().includes(term))
|
||||
);
|
||||
|
||||
if (directMatch || termMatch || questionTextMatch) {
|
||||
questions.forEach(q => {
|
||||
// Avoid duplicates
|
||||
if (!seenQuestions.has(q.question)) {
|
||||
suggestions.push(q);
|
||||
seenQuestions.add(q.question);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Limit to top 4 most relevant questions
|
||||
return suggestions.slice(0, 4);
|
||||
};
|
||||
|
||||
// Highlight matched text in string
|
||||
const highlightMatch = (text: string, query: string) => {
|
||||
if (!query) return text;
|
||||
|
||||
const parts = text.split(new RegExp(`(${query})`, 'gi'));
|
||||
return parts.map((part, index) =>
|
||||
part.toLowerCase() === query.toLowerCase()
|
||||
? `<mark>${part}</mark>`
|
||||
: part
|
||||
).join('');
|
||||
};
|
||||
|
||||
const filteredResults = query.length > 0
|
||||
? SEARCH_ITEMS.filter(item =>
|
||||
item.title.toLowerCase().includes(query.toLowerCase())
|
||||
)
|
||||
: SEARCH_ITEMS.slice(0, 8);
|
||||
? searchItems(query)
|
||||
: (activeFilter === 'all' ? getRecentSearchResults() : SEARCH_ITEMS.filter(item => item.type === activeFilter));
|
||||
|
||||
const suggestedQuestions = getSuggestedQuestions(query);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
@@ -80,58 +635,295 @@ export default function SearchModal({ isOpen, onClose }: SearchModalProps) {
|
||||
};
|
||||
|
||||
const handleSelect = (result: SearchResult) => {
|
||||
addRecentSearch(result.path);
|
||||
navigate(result.path);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleQuickAction = (action: QuickAction, e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (action.path) {
|
||||
addRecentSearch(action.path);
|
||||
navigate(action.path);
|
||||
onClose();
|
||||
} else if (action.action) {
|
||||
action.action();
|
||||
}
|
||||
};
|
||||
|
||||
const handleClearSearch = () => {
|
||||
setQuery('');
|
||||
setSelectedIndex(0);
|
||||
inputRef.current?.focus();
|
||||
};
|
||||
|
||||
const filterOptions: { value: FilterType; label: string }[] = [
|
||||
{ value: 'all', label: 'All' },
|
||||
{ value: 'workflow', label: 'Workflow' },
|
||||
{ value: 'setup', label: 'Setup' },
|
||||
{ value: 'account', label: 'Account' },
|
||||
{ value: 'help', label: 'Help' },
|
||||
];
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} className="sm:max-w-lg">
|
||||
<Modal isOpen={isOpen} onClose={onClose} className="sm:max-w-2xl">
|
||||
<style>{searchHighlightStyles}</style>
|
||||
<div className="p-0">
|
||||
<div className="relative">
|
||||
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 z-10">
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</span>
|
||||
{/* Using native input for ref and onKeyDown support - styled to match design system */}
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Search pages..."
|
||||
className="h-9 w-full rounded-lg border appearance-none px-3 py-2 text-sm shadow-theme-xs placeholder:text-gray-400 focus:outline-hidden focus:ring-3 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 bg-transparent text-gray-800 border-gray-300 focus:border-brand-300 focus:ring-brand-500/20 dark:border-gray-700 dark:focus:border-brand-800 pl-12 pr-4 py-4 text-lg border-b border-gray-200 dark:border-gray-700 rounded-none border-x-0 border-t-0"
|
||||
/>
|
||||
<span className="absolute right-4 top-1/2 -translate-y-1/2 text-xs text-gray-400 hidden sm:block z-10">
|
||||
ESC to close
|
||||
</span>
|
||||
{/* Header */}
|
||||
<div className="px-6 pt-5 pb-4 border-b border-gray-200 dark:border-gray-700 bg-gradient-to-b from-gray-50 to-white dark:from-gray-800 dark:to-gray-900">
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex-1">
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-1">
|
||||
Quick Navigation
|
||||
</h2>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Navigate to any page in your IGNY8 workspace
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="flex-shrink-0 w-8 h-8 rounded-lg flex items-center justify-center text-gray-400 hover:text-gray-600 hover:bg-gray-100 dark:hover:bg-gray-800 dark:hover:text-gray-200 transition-colors"
|
||||
aria-label="Close search"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Search Input */}
|
||||
<div className="relative">
|
||||
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-brand-500 dark:text-brand-400 z-10">
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</span>
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Type to search pages..."
|
||||
className="h-11 w-full rounded-lg border appearance-none px-3 py-2 text-sm shadow-sm placeholder:text-gray-400 focus:outline-hidden focus:ring-2 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 bg-white text-gray-900 border-gray-300 focus:border-brand-500 focus:ring-brand-500/30 dark:border-gray-700 dark:focus:border-brand-500 pl-10 pr-20"
|
||||
/>
|
||||
{query && (
|
||||
<button
|
||||
onClick={handleClearSearch}
|
||||
className="absolute right-16 top-1/2 -translate-y-1/2 w-5 h-5 rounded flex items-center justify-center text-gray-400 hover:text-gray-600 hover:bg-gray-100 dark:hover:bg-gray-800 dark:hover:text-gray-200 transition-colors z-10"
|
||||
aria-label="Clear search"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-xs font-medium px-2 py-1 rounded bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400 hidden sm:block z-10">
|
||||
ESC
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<div className="flex gap-2 px-4 py-3 border-b border-gray-200 dark:border-gray-700 overflow-x-auto bg-white dark:bg-gray-900">
|
||||
{filterOptions.map((filter) => (
|
||||
<Button
|
||||
key={filter.value}
|
||||
variant={activeFilter === filter.value ? 'solid' : 'outline'}
|
||||
tone={activeFilter === filter.value ? 'brand' : 'neutral'}
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setActiveFilter(filter.value);
|
||||
setSelectedIndex(0);
|
||||
}}
|
||||
className="whitespace-nowrap"
|
||||
>
|
||||
{filter.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Recent Searches Header (only when showing recent) */}
|
||||
{query.length === 0 && activeFilter === 'all' && recentSearches.length > 0 && (
|
||||
<div className="px-4 py-2.5 text-xs font-semibold text-gray-600 dark:text-gray-300 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50 flex items-center gap-2">
|
||||
<svg className="w-4 h-4 text-brand-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Recent Searches
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="max-h-80 overflow-y-auto py-2">
|
||||
{/* Results */}
|
||||
<div className="max-h-[500px] overflow-y-auto py-2 bg-white dark:bg-gray-900">
|
||||
{filteredResults.length === 0 ? (
|
||||
<div className="px-4 py-8 text-center text-gray-500">
|
||||
No results found for "{query}"
|
||||
<div className="px-4 py-16 text-center">
|
||||
<div className="w-16 h-16 mx-auto mb-4 rounded-full bg-gradient-to-br from-brand-100 to-brand-50 dark:from-brand-900/40 dark:to-brand-900/20 flex items-center justify-center">
|
||||
<svg className="w-8 h-8 text-brand-500 dark:text-brand-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M12 12h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p className="text-sm font-medium text-gray-900 dark:text-white mb-1">
|
||||
{query.length > 0
|
||||
? 'No results found'
|
||||
: 'No recent searches'}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
{query.length > 0
|
||||
? `Try searching with different keywords`
|
||||
: 'Your recent page visits will appear here'}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
filteredResults.map((result, index) => (
|
||||
<Button
|
||||
key={result.path}
|
||||
variant="ghost"
|
||||
tone="neutral"
|
||||
onClick={() => handleSelect(result)}
|
||||
className={`w-full px-4 py-3 flex items-center gap-3 text-left justify-start rounded-none ${
|
||||
index === selectedIndex
|
||||
? 'bg-brand-50 dark:bg-brand-900/20 text-brand-600 dark:text-brand-400'
|
||||
: 'hover:bg-gray-50 dark:hover:bg-gray-800 text-gray-700 dark:text-gray-300'
|
||||
}`}
|
||||
>
|
||||
<svg className="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
<div className="space-y-1 px-2">
|
||||
{filteredResults.map((result, index) => (
|
||||
<div
|
||||
key={result.path}
|
||||
className={`search-result group relative px-3 py-3 rounded-xl cursor-pointer transition-all ${
|
||||
index === selectedIndex
|
||||
? 'bg-gradient-to-r from-brand-50 to-brand-100/50 dark:from-brand-900/30 dark:to-brand-900/20 shadow-sm ring-2 ring-brand-200 dark:ring-brand-800'
|
||||
: 'hover:bg-gray-50 dark:hover:bg-gray-800/50'
|
||||
}`}
|
||||
onClick={() => handleSelect(result)}
|
||||
onMouseEnter={() => setSelectedIndex(index)}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
{/* Icon */}
|
||||
<div className={`flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center transition-all ${
|
||||
index === selectedIndex
|
||||
? 'bg-brand-500 dark:bg-brand-600 shadow-lg shadow-brand-500/30'
|
||||
: 'bg-gradient-to-br from-gray-100 to-gray-50 dark:from-gray-800 dark:to-gray-700 group-hover:from-brand-50 group-hover:to-brand-100 dark:group-hover:from-brand-900/40 dark:group-hover:to-brand-900/20'
|
||||
}`}>
|
||||
<svg className={`w-5 h-5 transition-colors ${
|
||||
index === selectedIndex
|
||||
? 'text-white'
|
||||
: 'text-gray-500 dark:text-gray-400 group-hover:text-brand-600 dark:group-hover:text-brand-400'
|
||||
}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{result.type === 'workflow' && (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
)}
|
||||
{result.type === 'setup' && (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
)}
|
||||
{result.type === 'account' && (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
)}
|
||||
{result.type === 'help' && (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
)}
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<h4
|
||||
className={`font-semibold text-sm truncate transition-colors ${
|
||||
index === selectedIndex
|
||||
? 'text-brand-700 dark:text-brand-300'
|
||||
: 'text-gray-900 dark:text-white'
|
||||
}`}
|
||||
dangerouslySetInnerHTML={{ __html: highlightMatch(result.title, query) }}
|
||||
/>
|
||||
<span className={`flex-shrink-0 text-xs px-2 py-0.5 rounded-full font-medium transition-colors ${
|
||||
index === selectedIndex
|
||||
? 'bg-brand-200 dark:bg-brand-800 text-brand-800 dark:text-brand-200'
|
||||
: 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400'
|
||||
}`}>
|
||||
{result.category}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{result.description && (
|
||||
<p
|
||||
className="text-xs text-gray-600 dark:text-gray-400 mb-2 line-clamp-1"
|
||||
dangerouslySetInnerHTML={{ __html: highlightMatch(result.description, query) }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Context Snippet - show matched text with surrounding context */}
|
||||
{query && result.contextSnippet && (
|
||||
<div className="mb-2 text-xs px-2 py-1 rounded bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 text-gray-700 dark:text-gray-300 italic">
|
||||
<span dangerouslySetInnerHTML={{ __html: highlightMatch(result.contextSnippet, query) }} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Quick Actions */}
|
||||
{result.quickActions && result.quickActions.length > 0 && (
|
||||
<div className={`flex flex-wrap gap-1.5 mt-2.5 transition-opacity ${
|
||||
index === selectedIndex ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'
|
||||
}`}>
|
||||
{result.quickActions.map((action, actionIndex) => (
|
||||
<button
|
||||
key={actionIndex}
|
||||
onClick={(e) => handleQuickAction(action, e)}
|
||||
className="text-xs px-2.5 py-1 rounded-lg bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-300 hover:bg-brand-50 hover:border-brand-300 hover:text-brand-700 dark:hover:bg-brand-900/40 dark:hover:border-brand-700 dark:hover:text-brand-300 transition-all shadow-sm hover:shadow"
|
||||
>
|
||||
→ {action.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Enter hint */}
|
||||
{index === selectedIndex && (
|
||||
<div className="flex-shrink-0 text-xs px-2.5 py-1 rounded-lg bg-brand-200 dark:bg-brand-800 text-brand-800 dark:text-brand-200 font-semibold shadow-sm">
|
||||
↵
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Suggested Questions Section */}
|
||||
{query.length >= 3 && suggestedQuestions.length > 0 && (
|
||||
<div className="mt-2 border-t border-gray-200 dark:border-gray-700 pt-3 px-2">
|
||||
<div className="flex items-center gap-2 mb-2 px-2">
|
||||
<svg className="w-4 h-4 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
|
||||
</svg>
|
||||
<span className="font-medium">{result.title}</span>
|
||||
</Button>
|
||||
))
|
||||
<h3 className="text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide">
|
||||
Suggested Questions
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{suggestedQuestions.map((item, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="group px-3 py-2.5 rounded-lg bg-gradient-to-r from-indigo-50 to-purple-50 dark:from-indigo-900/20 dark:to-purple-900/20 border border-indigo-200 dark:border-indigo-800 hover:border-indigo-300 dark:hover:border-indigo-700 cursor-pointer transition-all hover:shadow-md"
|
||||
onClick={() => {
|
||||
navigate(item.path);
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start gap-2">
|
||||
<svg className="w-4 h-4 text-indigo-600 dark:text-indigo-400 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h4 className="text-sm font-semibold text-indigo-700 dark:text-indigo-300 mb-1 group-hover:text-indigo-800 dark:group-hover:text-indigo-200">
|
||||
{item.question}
|
||||
</h4>
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400 leading-relaxed mb-2">
|
||||
{item.answer}
|
||||
</p>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="text-xs px-2 py-0.5 rounded bg-indigo-100 dark:bg-indigo-900/40 text-indigo-700 dark:text-indigo-300 font-medium">
|
||||
📖 {item.helpSection}
|
||||
</span>
|
||||
<span className="text-xs text-indigo-600 dark:text-indigo-400 group-hover:text-indigo-700 dark:group-hover:text-indigo-300 transition-colors font-medium">
|
||||
Read detailed guide →
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
// react plugin for creating vector maps
|
||||
import { VectorMap } from "@react-jvectormap/core";
|
||||
import { worldMill } from "@react-jvectormap/world";
|
||||
|
||||
// Define the component props
|
||||
interface CountryMapProps {
|
||||
mapColor?: string;
|
||||
}
|
||||
|
||||
const CountryMap: React.FC<CountryMapProps> = ({ mapColor }) => {
|
||||
return (
|
||||
<VectorMap
|
||||
map={worldMill}
|
||||
backgroundColor="transparent"
|
||||
markerStyle={{
|
||||
initial: {
|
||||
fill: "var(--color-primary)",
|
||||
r: 4, // Custom radius for markers
|
||||
} as any, // Type assertion to bypass strict CSS property checks
|
||||
}}
|
||||
markersSelectable={true}
|
||||
markers={[
|
||||
{
|
||||
latLng: [37.2580397, -104.657039],
|
||||
name: "United States",
|
||||
style: {
|
||||
fill: "var(--color-primary)",
|
||||
borderWidth: 1,
|
||||
borderColor: "white",
|
||||
stroke: "var(--color-gray-700)",
|
||||
},
|
||||
},
|
||||
{
|
||||
latLng: [20.7504374, 73.7276105],
|
||||
name: "India",
|
||||
style: { fill: "var(--color-primary)", borderWidth: 1, borderColor: "white" },
|
||||
},
|
||||
{
|
||||
latLng: [53.613, -11.6368],
|
||||
name: "United Kingdom",
|
||||
style: { fill: "var(--color-primary)", borderWidth: 1, borderColor: "white" },
|
||||
},
|
||||
{
|
||||
latLng: [-25.0304388, 115.2092761],
|
||||
name: "Sweden",
|
||||
style: {
|
||||
fill: "var(--color-primary)",
|
||||
borderWidth: 1,
|
||||
borderColor: "white",
|
||||
strokeOpacity: 0,
|
||||
},
|
||||
},
|
||||
]}
|
||||
zoomOnScroll={false}
|
||||
zoomMax={12}
|
||||
zoomMin={1}
|
||||
zoomAnimate={true}
|
||||
zoomStep={1.5}
|
||||
regionStyle={{
|
||||
initial: {
|
||||
fill: mapColor || "var(--color-gray-300)",
|
||||
fillOpacity: 1,
|
||||
fontFamily: "Outfit",
|
||||
stroke: "none",
|
||||
strokeWidth: 0,
|
||||
strokeOpacity: 0,
|
||||
},
|
||||
hover: {
|
||||
fillOpacity: 0.7,
|
||||
cursor: "pointer",
|
||||
fill: "var(--color-primary)",
|
||||
stroke: "none",
|
||||
},
|
||||
selected: {
|
||||
fill: "var(--color-primary)",
|
||||
},
|
||||
selectedHover: {},
|
||||
}}
|
||||
regionLabelStyle={{
|
||||
initial: {
|
||||
fill: "var(--color-gray-700)",
|
||||
fontWeight: 500,
|
||||
fontSize: "13px",
|
||||
stroke: "none",
|
||||
},
|
||||
hover: {},
|
||||
selected: {},
|
||||
selectedHover: {},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default CountryMap;
|
||||
@@ -1,113 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import { Dropdown } from "../ui/dropdown/Dropdown";
|
||||
import { DropdownItem } from "../ui/dropdown/DropdownItem";
|
||||
import { MoreDotIcon } from "../../icons";
|
||||
import CountryMap from "./CountryMap";
|
||||
import IconButton from "../ui/button/IconButton";
|
||||
|
||||
export default function DemographicCard() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
function toggleDropdown() {
|
||||
setIsOpen(!isOpen);
|
||||
}
|
||||
|
||||
function closeDropdown() {
|
||||
setIsOpen(false);
|
||||
}
|
||||
return (
|
||||
<div className="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] sm:p-6">
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-white/90">
|
||||
Customers Demographic
|
||||
</h3>
|
||||
<p className="mt-1 text-gray-500 text-theme-sm dark:text-gray-400">
|
||||
Number of customer based on country
|
||||
</p>
|
||||
</div>
|
||||
<div className="relative inline-block">
|
||||
<IconButton variant="ghost" size="sm" onClick={toggleDropdown} aria-label="More options" icon={<MoreDotIcon className="text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 size-6" />} />
|
||||
<Dropdown
|
||||
isOpen={isOpen}
|
||||
onClose={closeDropdown}
|
||||
className="w-40 p-2"
|
||||
>
|
||||
<DropdownItem
|
||||
onItemClick={closeDropdown}
|
||||
className="flex w-full font-normal text-left text-gray-500 rounded-lg hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
|
||||
>
|
||||
View More
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
onItemClick={closeDropdown}
|
||||
className="flex w-full font-normal text-left text-gray-500 rounded-lg hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
|
||||
>
|
||||
Delete
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-4 py-6 my-6 overflow-hidden border border-gary-200 rounded-2xl dark:border-gray-800 sm:px-6">
|
||||
<div
|
||||
id="mapOne"
|
||||
className="mapOne map-btn -mx-4 -my-6 h-[212px] w-[252px] 2xsm:w-[307px] xsm:w-[358px] sm:-mx-6 md:w-[668px] lg:w-[634px] xl:w-[393px] 2xl:w-[554px]"
|
||||
>
|
||||
<CountryMap />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="items-center w-full rounded-full max-w-8">
|
||||
<img src="./images/country/country-01.svg" alt="usa" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-gray-800 text-theme-sm dark:text-white/90">
|
||||
USA
|
||||
</p>
|
||||
<span className="block text-gray-500 text-theme-xs dark:text-gray-400">
|
||||
2,379 Customers
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full max-w-[140px] items-center gap-3">
|
||||
<div className="relative block h-2 w-full max-w-[100px] rounded-sm bg-gray-200 dark:bg-gray-800">
|
||||
<div className="absolute left-0 top-0 flex h-full w-[79%] items-center justify-center rounded-sm bg-brand-500 text-xs font-medium text-white"></div>
|
||||
</div>
|
||||
<p className="font-medium text-gray-800 text-theme-sm dark:text-white/90">
|
||||
79%
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="items-center w-full rounded-full max-w-8">
|
||||
<img src="./images/country/country-02.svg" alt="france" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-gray-800 text-theme-sm dark:text-white/90">
|
||||
France
|
||||
</p>
|
||||
<span className="block text-gray-500 text-theme-xs dark:text-gray-400">
|
||||
589 Customers
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full max-w-[140px] items-center gap-3">
|
||||
<div className="relative block h-2 w-full max-w-[100px] rounded-sm bg-gray-200 dark:bg-gray-800">
|
||||
<div className="absolute left-0 top-0 flex h-full w-[23%] items-center justify-center rounded-sm bg-brand-500 text-xs font-medium text-white"></div>
|
||||
</div>
|
||||
<p className="font-medium text-gray-800 text-theme-sm dark:text-white/90">
|
||||
23%
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
import {
|
||||
ArrowDownIcon,
|
||||
ArrowUpIcon,
|
||||
BoxIconLine,
|
||||
GroupIcon,
|
||||
} from "../../icons";
|
||||
import Badge from "../ui/badge/Badge";
|
||||
|
||||
export default function EcommerceMetrics() {
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:gap-6">
|
||||
{/* <!-- Metric Item Start --> */}
|
||||
<div className="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] md:p-6">
|
||||
<div className="flex items-center justify-center w-12 h-12 bg-gray-100 rounded-xl dark:bg-gray-800">
|
||||
<GroupIcon className="text-gray-800 size-6 dark:text-white/90" />
|
||||
</div>
|
||||
|
||||
<div className="flex items-end justify-between mt-5">
|
||||
<div>
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Customers
|
||||
</span>
|
||||
<h4 className="mt-2 font-bold text-gray-800 text-title-sm dark:text-white/90">
|
||||
3,782
|
||||
</h4>
|
||||
</div>
|
||||
<Badge color="success">
|
||||
<ArrowUpIcon />
|
||||
11.01%
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
{/* <!-- Metric Item End --> */}
|
||||
|
||||
{/* <!-- Metric Item Start --> */}
|
||||
<div className="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] md:p-6">
|
||||
<div className="flex items-center justify-center w-12 h-12 bg-gray-100 rounded-xl dark:bg-gray-800">
|
||||
<BoxIconLine className="text-gray-800 size-6 dark:text-white/90" />
|
||||
</div>
|
||||
<div className="flex items-end justify-between mt-5">
|
||||
<div>
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Orders
|
||||
</span>
|
||||
<h4 className="mt-2 font-bold text-gray-800 text-title-sm dark:text-white/90">
|
||||
5,359
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<Badge color="error">
|
||||
<ArrowDownIcon />
|
||||
9.05%
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
{/* <!-- Metric Item End --> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
import Chart from "react-apexcharts";
|
||||
import { ApexOptions } from "apexcharts";
|
||||
import { Dropdown } from "../ui/dropdown/Dropdown";
|
||||
import { DropdownItem } from "../ui/dropdown/DropdownItem";
|
||||
import { MoreDotIcon } from "../../icons";
|
||||
import { useState } from "react";
|
||||
import IconButton from "../ui/button/IconButton";
|
||||
|
||||
export default function MonthlySalesChart() {
|
||||
const options: ApexOptions = {
|
||||
colors: ["var(--color-primary)"],
|
||||
chart: {
|
||||
fontFamily: "Outfit, sans-serif",
|
||||
type: "bar",
|
||||
height: 180,
|
||||
toolbar: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
horizontal: false,
|
||||
columnWidth: "39%",
|
||||
borderRadius: 5,
|
||||
borderRadiusApplication: "end",
|
||||
},
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
},
|
||||
stroke: {
|
||||
show: true,
|
||||
width: 4,
|
||||
colors: ["transparent"],
|
||||
},
|
||||
xaxis: {
|
||||
categories: [
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec",
|
||||
],
|
||||
axisBorder: {
|
||||
show: false,
|
||||
},
|
||||
axisTicks: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
position: "top",
|
||||
horizontalAlign: "left",
|
||||
fontFamily: "Outfit",
|
||||
},
|
||||
yaxis: {
|
||||
title: {
|
||||
text: undefined,
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
yaxis: {
|
||||
lines: {
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
fill: {
|
||||
opacity: 1,
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
x: {
|
||||
show: false,
|
||||
},
|
||||
y: {
|
||||
formatter: (val: number) => `${val}`,
|
||||
},
|
||||
},
|
||||
};
|
||||
const series = [
|
||||
{
|
||||
name: "Sales",
|
||||
data: [168, 385, 201, 298, 187, 195, 291, 110, 215, 390, 280, 112],
|
||||
},
|
||||
];
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
function toggleDropdown() {
|
||||
setIsOpen(!isOpen);
|
||||
}
|
||||
|
||||
function closeDropdown() {
|
||||
setIsOpen(false);
|
||||
}
|
||||
return (
|
||||
<div className="overflow-hidden rounded-2xl border border-gray-200 bg-white px-5 pt-5 dark:border-gray-800 dark:bg-white/[0.03] sm:px-6 sm:pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-white/90">
|
||||
Monthly Sales
|
||||
</h3>
|
||||
<div className="relative inline-block">
|
||||
<IconButton variant="ghost" size="sm" onClick={toggleDropdown} aria-label="More options" icon={<MoreDotIcon className="text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 size-6" />} />
|
||||
<Dropdown
|
||||
isOpen={isOpen}
|
||||
onClose={closeDropdown}
|
||||
className="w-40 p-2"
|
||||
>
|
||||
<DropdownItem
|
||||
onItemClick={closeDropdown}
|
||||
className="flex w-full font-normal text-left text-gray-500 rounded-lg hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
|
||||
>
|
||||
View More
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
onItemClick={closeDropdown}
|
||||
className="flex w-full font-normal text-left text-gray-500 rounded-lg hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
|
||||
>
|
||||
Delete
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-full overflow-x-auto custom-scrollbar">
|
||||
<div className="-ml-5 min-w-[650px] xl:min-w-full pl-2">
|
||||
<Chart options={options} series={series} type="bar" height={180} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
import Chart from "react-apexcharts";
|
||||
import { ApexOptions } from "apexcharts";
|
||||
import { useState } from "react";
|
||||
import { Dropdown } from "../ui/dropdown/Dropdown";
|
||||
import { DropdownItem } from "../ui/dropdown/DropdownItem";
|
||||
import { MoreDotIcon } from "../../icons";
|
||||
import IconButton from "../ui/button/IconButton";
|
||||
|
||||
export default function MonthlyTarget() {
|
||||
const series = [75.55];
|
||||
const options: ApexOptions = {
|
||||
colors: ["var(--color-primary)"],
|
||||
chart: {
|
||||
fontFamily: "Outfit, sans-serif",
|
||||
type: "radialBar",
|
||||
height: 330,
|
||||
sparkline: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
plotOptions: {
|
||||
radialBar: {
|
||||
startAngle: -85,
|
||||
endAngle: 85,
|
||||
hollow: {
|
||||
size: "80%",
|
||||
},
|
||||
track: {
|
||||
background: "var(--color-gray-200)",
|
||||
strokeWidth: "100%",
|
||||
margin: 5, // margin is in pixels
|
||||
},
|
||||
dataLabels: {
|
||||
name: {
|
||||
show: false,
|
||||
},
|
||||
value: {
|
||||
fontSize: "36px",
|
||||
fontWeight: "600",
|
||||
offsetY: -40,
|
||||
color: "var(--color-gray-800)",
|
||||
formatter: function (val) {
|
||||
return val + "%";
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fill: {
|
||||
type: "solid",
|
||||
colors: ["var(--color-primary)"],
|
||||
},
|
||||
stroke: {
|
||||
lineCap: "round",
|
||||
},
|
||||
labels: ["Progress"],
|
||||
};
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
function toggleDropdown() {
|
||||
setIsOpen(!isOpen);
|
||||
}
|
||||
|
||||
function closeDropdown() {
|
||||
setIsOpen(false);
|
||||
}
|
||||
return (
|
||||
<div className="rounded-2xl border border-gray-200 bg-gray-100 dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div className="px-5 pt-5 bg-white shadow-default rounded-2xl pb-11 dark:bg-gray-900 sm:px-6 sm:pt-6">
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-white/90">
|
||||
Monthly Target
|
||||
</h3>
|
||||
<p className="mt-1 text-gray-500 text-theme-sm dark:text-gray-400">
|
||||
Target you’ve set for each month
|
||||
</p>
|
||||
</div>
|
||||
<div className="relative inline-block">
|
||||
<IconButton variant="ghost" size="sm" onClick={toggleDropdown} aria-label="More options" icon={<MoreDotIcon className="text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 size-6" />} />
|
||||
<Dropdown
|
||||
isOpen={isOpen}
|
||||
onClose={closeDropdown}
|
||||
className="w-40 p-2"
|
||||
>
|
||||
<DropdownItem
|
||||
onItemClick={closeDropdown}
|
||||
className="flex w-full font-normal text-left text-gray-500 rounded-lg hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
|
||||
>
|
||||
View More
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
onItemClick={closeDropdown}
|
||||
className="flex w-full font-normal text-left text-gray-500 rounded-lg hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
|
||||
>
|
||||
Delete
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative ">
|
||||
<div className="max-h-[330px]" id="chartDarkStyle">
|
||||
<Chart
|
||||
options={options}
|
||||
series={series}
|
||||
type="radialBar"
|
||||
height={330}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<span className="absolute left-1/2 top-full -translate-x-1/2 -translate-y-[95%] rounded-full bg-success-50 px-3 py-1 text-xs font-medium text-success-600 dark:bg-success-500/15 dark:text-success-500">
|
||||
+10%
|
||||
</span>
|
||||
</div>
|
||||
<p className="mx-auto mt-10 w-full max-w-[380px] text-center text-sm text-gray-500 sm:text-base">
|
||||
You earn $3287 today, it's higher than last month. Keep up your good
|
||||
work!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center gap-5 px-6 py-3.5 sm:gap-8 sm:py-5">
|
||||
<div>
|
||||
<p className="mb-1 text-center text-gray-500 text-theme-xs dark:text-gray-400 sm:text-sm">
|
||||
Target
|
||||
</p>
|
||||
<p className="flex items-center justify-center gap-1 text-base font-semibold text-gray-800 dark:text-white/90 sm:text-lg">
|
||||
$20K
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.26816 13.6632C7.4056 13.8192 7.60686 13.9176 7.8311 13.9176C7.83148 13.9176 7.83187 13.9176 7.83226 13.9176C8.02445 13.9178 8.21671 13.8447 8.36339 13.6981L12.3635 9.70076C12.6565 9.40797 12.6567 8.9331 12.3639 8.6401C12.0711 8.34711 11.5962 8.34694 11.3032 8.63973L8.5811 11.36L8.5811 2.5C8.5811 2.08579 8.24531 1.75 7.8311 1.75C7.41688 1.75 7.0811 2.08579 7.0811 2.5L7.0811 11.3556L4.36354 8.63975C4.07055 8.34695 3.59568 8.3471 3.30288 8.64009C3.01008 8.93307 3.01023 9.40794 3.30321 9.70075L7.26816 13.6632Z"
|
||||
fill="#D92D20"
|
||||
/>
|
||||
</svg>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="w-px bg-gray-200 h-7 dark:bg-gray-800"></div>
|
||||
|
||||
<div>
|
||||
<p className="mb-1 text-center text-gray-500 text-theme-xs dark:text-gray-400 sm:text-sm">
|
||||
Revenue
|
||||
</p>
|
||||
<p className="flex items-center justify-center gap-1 text-base font-semibold text-gray-800 dark:text-white/90 sm:text-lg">
|
||||
$20K
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.60141 2.33683C7.73885 2.18084 7.9401 2.08243 8.16435 2.08243C8.16475 2.08243 8.16516 2.08243 8.16556 2.08243C8.35773 2.08219 8.54998 2.15535 8.69664 2.30191L12.6968 6.29924C12.9898 6.59203 12.9899 7.0669 12.6971 7.3599C12.4044 7.6529 11.9295 7.65306 11.6365 7.36027L8.91435 4.64004L8.91435 13.5C8.91435 13.9142 8.57856 14.25 8.16435 14.25C7.75013 14.25 7.41435 13.9142 7.41435 13.5L7.41435 4.64442L4.69679 7.36025C4.4038 7.65305 3.92893 7.6529 3.63613 7.35992C3.34333 7.06693 3.34348 6.59206 3.63646 6.29926L7.60141 2.33683Z"
|
||||
fill="#039855"
|
||||
/>
|
||||
</svg>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="w-px bg-gray-200 h-7 dark:bg-gray-800"></div>
|
||||
|
||||
<div>
|
||||
<p className="mb-1 text-center text-gray-500 text-theme-xs dark:text-gray-400 sm:text-sm">
|
||||
Today
|
||||
</p>
|
||||
<p className="flex items-center justify-center gap-1 text-base font-semibold text-gray-800 dark:text-white/90 sm:text-lg">
|
||||
$20K
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.60141 2.33683C7.73885 2.18084 7.9401 2.08243 8.16435 2.08243C8.16475 2.08243 8.16516 2.08243 8.16556 2.08243C8.35773 2.08219 8.54998 2.15535 8.69664 2.30191L12.6968 6.29924C12.9898 6.59203 12.9899 7.0669 12.6971 7.3599C12.4044 7.6529 11.9295 7.65306 11.6365 7.36027L8.91435 4.64004L8.91435 13.5C8.91435 13.9142 8.57856 14.25 8.16435 14.25C7.75013 14.25 7.41435 13.9142 7.41435 13.5L7.41435 4.64442L4.69679 7.36025C4.4038 7.65305 3.92893 7.6529 3.63613 7.35992C3.34333 7.06693 3.34348 6.59206 3.63646 6.29926L7.60141 2.33683Z"
|
||||
fill="#039855"
|
||||
/>
|
||||
</svg>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "../ui/table";
|
||||
import Badge from "../ui/badge/Badge";
|
||||
import Button from "../ui/button/Button";
|
||||
|
||||
// Define the TypeScript interface for the table rows
|
||||
interface Product {
|
||||
id: number; // Unique identifier for each product
|
||||
name: string; // Product name
|
||||
variants: string; // Number of variants (e.g., "1 Variant", "2 Variants")
|
||||
category: string; // Category of the product
|
||||
price: string; // Price of the product (as a string with currency symbol)
|
||||
// status: string; // Status of the product
|
||||
image: string; // URL or path to the product image
|
||||
status: "Delivered" | "Pending" | "Canceled"; // Status of the product
|
||||
}
|
||||
|
||||
// Define the table data using the interface
|
||||
const tableData: Product[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: "MacBook Pro 13”",
|
||||
variants: "2 Variants",
|
||||
category: "Laptop",
|
||||
price: "$2399.00",
|
||||
status: "Delivered",
|
||||
image: "/images/product/product-01.jpg", // Replace with actual image URL
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Apple Watch Ultra",
|
||||
variants: "1 Variant",
|
||||
category: "Watch",
|
||||
price: "$879.00",
|
||||
status: "Pending",
|
||||
image: "/images/product/product-02.jpg", // Replace with actual image URL
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "iPhone 15 Pro Max",
|
||||
variants: "2 Variants",
|
||||
category: "SmartPhone",
|
||||
price: "$1869.00",
|
||||
status: "Delivered",
|
||||
image: "/images/product/product-03.jpg", // Replace with actual image URL
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "iPad Pro 3rd Gen",
|
||||
variants: "2 Variants",
|
||||
category: "Electronics",
|
||||
price: "$1699.00",
|
||||
status: "Canceled",
|
||||
image: "/images/product/product-04.jpg", // Replace with actual image URL
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "AirPods Pro 2nd Gen",
|
||||
variants: "1 Variant",
|
||||
category: "Accessories",
|
||||
price: "$240.00",
|
||||
status: "Delivered",
|
||||
image: "/images/product/product-05.jpg", // Replace with actual image URL
|
||||
},
|
||||
];
|
||||
|
||||
export default function RecentOrders() {
|
||||
return (
|
||||
<div className="overflow-hidden rounded-2xl border border-gray-200 bg-white px-4 pb-3 pt-4 dark:border-gray-800 dark:bg-white/[0.03] sm:px-6">
|
||||
<div className="flex flex-col gap-2 mb-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-white/90">
|
||||
Recent Orders
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<Button variant="outline" size="sm">
|
||||
<svg
|
||||
className="stroke-current fill-white dark:fill-gray-800"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M2.29004 5.90393H17.7067"
|
||||
stroke=""
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M17.7075 14.0961H2.29085"
|
||||
stroke=""
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M12.0826 3.33331C13.5024 3.33331 14.6534 4.48431 14.6534 5.90414C14.6534 7.32398 13.5024 8.47498 12.0826 8.47498C10.6627 8.47498 9.51172 7.32398 9.51172 5.90415C9.51172 4.48432 10.6627 3.33331 12.0826 3.33331Z"
|
||||
fill=""
|
||||
stroke=""
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M7.91745 11.525C6.49762 11.525 5.34662 12.676 5.34662 14.0959C5.34661 15.5157 6.49762 16.6667 7.91745 16.6667C9.33728 16.6667 10.4883 15.5157 10.4883 14.0959C10.4883 12.676 9.33728 11.525 7.91745 11.525Z"
|
||||
fill=""
|
||||
stroke=""
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
</svg>
|
||||
Filter
|
||||
</Button>
|
||||
<Button variant="outline" size="sm">
|
||||
See all
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-w-full overflow-x-auto">
|
||||
<Table>
|
||||
{/* Table Header */}
|
||||
<TableHeader className="border-gray-100 dark:border-gray-800 border-y">
|
||||
<TableRow>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
Products
|
||||
</TableCell>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
Category
|
||||
</TableCell>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
Price
|
||||
</TableCell>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
Status
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
{/* Table Body */}
|
||||
|
||||
<TableBody className="divide-y divide-gray-100 dark:divide-gray-800">
|
||||
{tableData.map((product) => (
|
||||
<TableRow key={product.id} className="">
|
||||
<TableCell className="py-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="h-[50px] w-[50px] overflow-hidden rounded-md">
|
||||
<img
|
||||
src={product.image}
|
||||
className="h-[50px] w-[50px]"
|
||||
alt={product.name}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-gray-800 text-theme-sm dark:text-white/90">
|
||||
{product.name}
|
||||
</p>
|
||||
<span className="text-gray-500 text-theme-xs dark:text-gray-400">
|
||||
{product.variants}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="py-3 text-gray-500 text-theme-sm dark:text-gray-400">
|
||||
{product.price}
|
||||
</TableCell>
|
||||
<TableCell className="py-3 text-gray-500 text-theme-sm dark:text-gray-400">
|
||||
{product.category}
|
||||
</TableCell>
|
||||
<TableCell className="py-3 text-gray-500 text-theme-sm dark:text-gray-400">
|
||||
<Badge
|
||||
size="sm"
|
||||
color={
|
||||
product.status === "Delivered"
|
||||
? "success"
|
||||
: product.status === "Pending"
|
||||
? "warning"
|
||||
: "error"
|
||||
}
|
||||
>
|
||||
{product.status}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
import Chart from "react-apexcharts";
|
||||
import { ApexOptions } from "apexcharts";
|
||||
import ChartTab from "../common/ChartTab";
|
||||
|
||||
export default function StatisticsChart() {
|
||||
const options: ApexOptions = {
|
||||
legend: {
|
||||
show: false, // Hide legend
|
||||
position: "top",
|
||||
horizontalAlign: "left",
|
||||
},
|
||||
colors: ["var(--color-primary)", "var(--color-brand-300)"], // Define line colors
|
||||
chart: {
|
||||
fontFamily: "Outfit, sans-serif",
|
||||
height: 310,
|
||||
type: "line", // Set the chart type to 'line'
|
||||
toolbar: {
|
||||
show: false, // Hide chart toolbar
|
||||
},
|
||||
},
|
||||
stroke: {
|
||||
curve: "straight", // Define the line style (straight, smooth, or step)
|
||||
width: [2, 2], // Line width for each dataset
|
||||
},
|
||||
|
||||
fill: {
|
||||
type: "gradient",
|
||||
gradient: {
|
||||
opacityFrom: 0.55,
|
||||
opacityTo: 0,
|
||||
},
|
||||
},
|
||||
markers: {
|
||||
size: 0, // Size of the marker points
|
||||
strokeColors: "#fff", // Marker border color
|
||||
strokeWidth: 2,
|
||||
hover: {
|
||||
size: 6, // Marker size on hover
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
xaxis: {
|
||||
lines: {
|
||||
show: false, // Hide grid lines on x-axis
|
||||
},
|
||||
},
|
||||
yaxis: {
|
||||
lines: {
|
||||
show: true, // Show grid lines on y-axis
|
||||
},
|
||||
},
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false, // Disable data labels
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true, // Enable tooltip
|
||||
x: {
|
||||
format: "dd MMM yyyy", // Format for x-axis tooltip
|
||||
},
|
||||
},
|
||||
xaxis: {
|
||||
type: "category", // Category-based x-axis
|
||||
categories: [
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec",
|
||||
],
|
||||
axisBorder: {
|
||||
show: false, // Hide x-axis border
|
||||
},
|
||||
axisTicks: {
|
||||
show: false, // Hide x-axis ticks
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false, // Disable tooltip for x-axis points
|
||||
},
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
style: {
|
||||
fontSize: "12px", // Adjust font size for y-axis labels
|
||||
colors: ["var(--color-gray-500)"], // Color of the labels
|
||||
},
|
||||
},
|
||||
title: {
|
||||
text: "", // Remove y-axis title
|
||||
style: {
|
||||
fontSize: "0px",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const series = [
|
||||
{
|
||||
name: "Sales",
|
||||
data: [180, 190, 170, 160, 175, 165, 170, 205, 230, 210, 240, 235],
|
||||
},
|
||||
{
|
||||
name: "Revenue",
|
||||
data: [40, 30, 50, 40, 55, 40, 70, 100, 110, 120, 150, 140],
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className="rounded-2xl border border-gray-200 bg-white px-5 pb-5 pt-5 dark:border-gray-800 dark:bg-white/[0.03] sm:px-6 sm:pt-6">
|
||||
<div className="flex flex-col gap-5 mb-6 sm:flex-row sm:justify-between">
|
||||
<div className="w-full">
|
||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-white/90">
|
||||
Statistics
|
||||
</h3>
|
||||
<p className="mt-1 text-gray-500 text-theme-sm dark:text-gray-400">
|
||||
Target you’ve set for each month
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start w-full gap-3 sm:justify-end">
|
||||
<ChartTab />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-full overflow-x-auto custom-scrollbar">
|
||||
<div className="min-w-[1000px] xl:min-w-full">
|
||||
<Chart options={options} series={series} type="area" height={310} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,546 +0,0 @@
|
||||
<div x-data="{selectedTaskGroup: 'All'}" class="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<!-- Task header Start -->
|
||||
<div class="flex flex-col items-center px-4 py-5 xl:px-6 xl:py-6">
|
||||
<div class="flex flex-col w-full gap-5 sm:justify-between xl:flex-row xl:items-center">
|
||||
<div class="flex flex-wrap items-center gap-x-1 gap-y-2 rounded-lg bg-gray-100 p-0.5 dark:bg-gray-900">
|
||||
<button class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md h group hover:text-gray-900 dark:hover:text-white text-gray-900 dark:text-white bg-white dark:bg-gray-800" :class="selectedTaskGroup === 'All' ? 'text-gray-900 dark:text-white bg-white dark:bg-gray-800' : 'text-gray-500 dark:text-gray-400'" @click="selectedTaskGroup = 'All' ">
|
||||
All Tasks
|
||||
<span class="inline-flex rounded-full px-2 py-0.5 text-xs font-medium leading-normal group-hover:bg-brand-50 group-hover:text-brand-500 dark:group-hover:bg-brand-500/15 dark:group-hover:text-brand-400 text-brand-500 dark:text-brand-400 bg-brand-50 dark:bg-brand-500/15" :class="selectedTaskGroup === 'All' ? 'text-brand-500 dark:text-brand-400 bg-brand-50 dark:bg-brand-500/15' : 'bg-white dark:bg-white/[0.03]'">
|
||||
11
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md group hover:text-gray-900 dark:hover:text-white text-gray-500 dark:text-gray-400" :class="selectedTaskGroup === 'Todo' ? 'text-gray-900 dark:text-white bg-white dark:bg-gray-800' : 'text-gray-500 dark:text-gray-400'" @click="selectedTaskGroup = 'Todo' ">
|
||||
To do
|
||||
<span class="inline-flex rounded-full px-2 py-0.5 text-xs font-medium leading-normal group-hover:bg-brand-50 group-hover:text-brand-500 dark:group-hover:bg-brand-500/15 dark:group-hover:text-brand-400 bg-white dark:bg-white/[0.03]" :class="selectedTaskGroup === 'Todo' ? 'text-brand-500 dark:text-brand-400 bg-brand-50 dark:bg-brand-500/15' : 'bg-white dark:bg-white/[0.03]'">
|
||||
3
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md group hover:text-gray-900 dark:hover:text-white text-gray-500 dark:text-gray-400" :class="selectedTaskGroup === 'InProgress' ? 'text-gray-900 dark:text-white bg-white dark:bg-gray-800' : 'text-gray-500 dark:text-gray-400'" @click="selectedTaskGroup = 'InProgress' ">
|
||||
In Progress
|
||||
<span class="inline-flex rounded-full px-2 py-0.5 text-xs font-medium leading-normal group-hover:bg-brand-50 group-hover:text-brand-500 dark:group-hover:bg-brand-500/15 dark:group-hover:text-brand-400 bg-white dark:bg-white/[0.03]" :class="selectedTaskGroup === 'InProgress' ? 'text-brand-500 dark:text-brand-400 bg-brand-50 dark:bg-brand-500/15' : 'bg-white dark:bg-white/[0.03]'">
|
||||
4
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md group hover:text-gray-900 dark:hover:text-white text-gray-500 dark:text-gray-400" :class="selectedTaskGroup === 'Completed' ? 'text-gray-900 dark:text-white bg-white dark:bg-gray-800' : 'text-gray-500 dark:text-gray-400'" @click="selectedTaskGroup = 'Completed' ">
|
||||
Completed
|
||||
<span class="inline-flex rounded-full px-2 py-0.5 text-xs font-medium leading-normal group-hover:bg-brand-50 group-hover:text-brand-500 dark:group-hover:bg-brand-500/15 dark:group-hover:text-brand-400 bg-white dark:bg-white/[0.03]" :class="selectedTaskGroup === 'Completed' ? 'text-brand-500 dark:text-brand-400 bg-brand-50 dark:bg-brand-500/15' : 'bg-white dark:bg-white/[0.03]'">
|
||||
4
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-3 xl:justify-end">
|
||||
<button class="inline-flex items-center gap-2 rounded-lg border border-gray-300 px-4 py-2.5 text-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-white/[0.03]">
|
||||
<svg class="fill-current" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.0826 4.0835C11.0769 4.0835 10.2617 4.89871 10.2617 5.90433C10.2617 6.90995 11.0769 7.72516 12.0826 7.72516C13.0882 7.72516 13.9034 6.90995 13.9034 5.90433C13.9034 4.89871 13.0882 4.0835 12.0826 4.0835ZM2.29004 6.65409H8.84671C9.18662 8.12703 10.5063 9.22516 12.0826 9.22516C13.6588 9.22516 14.9785 8.12703 15.3184 6.65409H17.7067C18.1209 6.65409 18.4567 6.31831 18.4567 5.90409C18.4567 5.48988 18.1209 5.15409 17.7067 5.15409H15.3183C14.9782 3.68139 13.6586 2.5835 12.0826 2.5835C10.5065 2.5835 9.18691 3.68139 8.84682 5.15409H2.29004C1.87583 5.15409 1.54004 5.48988 1.54004 5.90409C1.54004 6.31831 1.87583 6.65409 2.29004 6.65409ZM4.6816 13.3462H2.29085C1.87664 13.3462 1.54085 13.682 1.54085 14.0962C1.54085 14.5104 1.87664 14.8462 2.29085 14.8462H4.68172C5.02181 16.3189 6.34142 17.4168 7.91745 17.4168C9.49348 17.4168 10.8131 16.3189 11.1532 14.8462H17.7075C18.1217 14.8462 18.4575 14.5104 18.4575 14.0962C18.4575 13.682 18.1217 13.3462 17.7075 13.3462H11.1533C10.8134 11.8733 9.49366 10.7752 7.91745 10.7752C6.34124 10.7752 5.02151 11.8733 4.6816 13.3462ZM9.73828 14.096C9.73828 13.0904 8.92307 12.2752 7.91745 12.2752C6.91183 12.2752 6.09662 13.0904 6.09662 14.096C6.09662 15.1016 6.91183 15.9168 7.91745 15.9168C8.92307 15.9168 9.73828 15.1016 9.73828 14.096Z" fill=""></path>
|
||||
</svg>
|
||||
|
||||
Filter & Short
|
||||
</button>
|
||||
|
||||
<button @click="isTaskModalModal = true" class="inline-flex items-center gap-2 rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-medium text-white shadow-theme-xs hover:bg-brand-600">
|
||||
Add New Task
|
||||
|
||||
<svg class="fill-current" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.2502 4.99951C9.2502 4.5853 9.58599 4.24951 10.0002 4.24951C10.4144 4.24951 10.7502 4.5853 10.7502 4.99951V9.24971H15.0006C15.4148 9.24971 15.7506 9.5855 15.7506 9.99971C15.7506 10.4139 15.4148 10.7497 15.0006 10.7497H10.7502V15.0001C10.7502 15.4143 10.4144 15.7501 10.0002 15.7501C9.58599 15.7501 9.2502 15.4143 9.2502 15.0001V10.7497H5C4.58579 10.7497 4.25 10.4139 4.25 9.99971C4.25 9.5855 4.58579 9.24971 5 9.24971H9.2502V4.99951Z" fill=""></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Task header End -->
|
||||
|
||||
<!-- Task wrapper Start -->
|
||||
<div class="mt-7 grid grid-cols-1 border-t border-gray-200 sm:mt-0 sm:grid-cols-2 xl:grid-cols-3 dark:border-gray-800">
|
||||
<!-- To do list -->
|
||||
<div class="swim-lane flex flex-col gap-5 p-4 xl:p-6">
|
||||
<div class="mb-1 flex items-center justify-between">
|
||||
<h3 class="flex items-center gap-3 text-base font-medium text-gray-800 dark:text-white/90">
|
||||
To Do
|
||||
<span class="text-theme-xs inline-flex rounded-full bg-gray-100 px-2 py-0.5 font-medium text-gray-700 dark:bg-white/[0.03] dark:text-white/80">
|
||||
3
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<div x-data="{openDropDown: false}" class="relative">
|
||||
<button @click="openDropDown = !openDropDown" class="text-gray-700 dark:text-gray-400">
|
||||
<svg class="fill-current" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.99902 10.2451C6.96552 10.2451 7.74902 11.0286 7.74902 11.9951V12.0051C7.74902 12.9716 6.96552 13.7551 5.99902 13.7551C5.03253 13.7551 4.24902 12.9716 4.24902 12.0051V11.9951C4.24902 11.0286 5.03253 10.2451 5.99902 10.2451ZM17.999 10.2451C18.9655 10.2451 19.749 11.0286 19.749 11.9951V12.0051C19.749 12.9716 18.9655 13.7551 17.999 13.7551C17.0325 13.7551 16.249 12.9716 16.249 12.0051V11.9951C16.249 11.0286 17.0325 10.2451 17.999 10.2451ZM13.749 11.9951C13.749 11.0286 12.9655 10.2451 11.999 10.2451C11.0325 10.2451 10.249 11.0286 10.249 11.9951V12.0051C10.249 12.9716 11.0325 13.7551 11.999 13.7551C12.9655 13.7551 13.749 12.9716 13.749 12.0051V11.9951Z" fill=""></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div x-show="openDropDown" @click.outside="openDropDown = false" class="shadow-theme-md dark:bg-gray-dark absolute top-full right-0 z-40 w-[140px] space-y-1 rounded-2xl border border-gray-200 bg-white p-2 dark:border-gray-800" style="display: none;">
|
||||
<button class="text-theme-xs flex w-full rounded-lg px-3 py-2 text-left font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300">
|
||||
Edit
|
||||
</button>
|
||||
<button class="text-theme-xs flex w-full rounded-lg px-3 py-2 text-left font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300">
|
||||
Delete
|
||||
</button>
|
||||
<button class="text-theme-xs flex w-full rounded-lg px-3 py-2 text-left font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300">
|
||||
Clear All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- task item -->
|
||||
<div draggable="true" class="task shadow-theme-sm rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/5">
|
||||
<div class="flex items-start justify-between gap-6">
|
||||
<div>
|
||||
<h4 class="mb-5 text-base text-gray-800 dark:text-white/90">
|
||||
Finish user onboarding
|
||||
</h4>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="flex cursor-pointer items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33329 1.0835C5.74751 1.0835 6.08329 1.41928 6.08329 1.8335V2.25016L9.91663 2.25016V1.8335C9.91663 1.41928 10.2524 1.0835 10.6666 1.0835C11.0808 1.0835 11.4166 1.41928 11.4166 1.8335V2.25016L12.3333 2.25016C13.2998 2.25016 14.0833 3.03366 14.0833 4.00016V6.00016L14.0833 12.6668C14.0833 13.6333 13.2998 14.4168 12.3333 14.4168L3.66663 14.4168C2.70013 14.4168 1.91663 13.6333 1.91663 12.6668L1.91663 6.00016L1.91663 4.00016C1.91663 3.03366 2.70013 2.25016 3.66663 2.25016L4.58329 2.25016V1.8335C4.58329 1.41928 4.91908 1.0835 5.33329 1.0835ZM5.33329 3.75016L3.66663 3.75016C3.52855 3.75016 3.41663 3.86209 3.41663 4.00016V5.25016L12.5833 5.25016V4.00016C12.5833 3.86209 12.4714 3.75016 12.3333 3.75016L10.6666 3.75016L5.33329 3.75016ZM12.5833 6.75016L3.41663 6.75016L3.41663 12.6668C3.41663 12.8049 3.52855 12.9168 3.66663 12.9168L12.3333 12.9168C12.4714 12.9168 12.5833 12.8049 12.5833 12.6668L12.5833 6.75016Z" fill=""></path>
|
||||
</svg>
|
||||
Tomorrow
|
||||
</span>
|
||||
|
||||
<span class="flex cursor-pointer items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<svg class="stroke-current" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 15.6343C12.6244 15.6343 15.5625 12.6961 15.5625 9.07178C15.5625 5.44741 12.6244 2.50928 9 2.50928C5.37563 2.50928 2.4375 5.44741 2.4375 9.07178C2.4375 10.884 3.17203 12.5246 4.35961 13.7122L2.4375 15.6343H9Z" stroke="" stroke-width="1.5" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
|
||||
1
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-full max-w-6 overflow-hidden rounded-full border-[0.5px] border-gray-200 dark:border-gray-800">
|
||||
<img src="src/images/user/user-01.jpg" alt="user">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- task item -->
|
||||
<div draggable="true" class="task shadow-theme-sm rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/5">
|
||||
<div class="flex items-start justify-between gap-6">
|
||||
<div>
|
||||
<h4 class="mb-5 text-base text-gray-800 dark:text-white/90">
|
||||
Solve the Dribbble prioritisation issue with the team
|
||||
</h4>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="flex cursor-pointer items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33329 1.0835C5.74751 1.0835 6.08329 1.41928 6.08329 1.8335V2.25016L9.91663 2.25016V1.8335C9.91663 1.41928 10.2524 1.0835 10.6666 1.0835C11.0808 1.0835 11.4166 1.41928 11.4166 1.8335V2.25016L12.3333 2.25016C13.2998 2.25016 14.0833 3.03366 14.0833 4.00016V6.00016L14.0833 12.6668C14.0833 13.6333 13.2998 14.4168 12.3333 14.4168L3.66663 14.4168C2.70013 14.4168 1.91663 13.6333 1.91663 12.6668L1.91663 6.00016L1.91663 4.00016C1.91663 3.03366 2.70013 2.25016 3.66663 2.25016L4.58329 2.25016V1.8335C4.58329 1.41928 4.91908 1.0835 5.33329 1.0835ZM5.33329 3.75016L3.66663 3.75016C3.52855 3.75016 3.41663 3.86209 3.41663 4.00016V5.25016L12.5833 5.25016V4.00016C12.5833 3.86209 12.4714 3.75016 12.3333 3.75016L10.6666 3.75016L5.33329 3.75016ZM12.5833 6.75016L3.41663 6.75016L3.41663 12.6668C3.41663 12.8049 3.52855 12.9168 3.66663 12.9168L12.3333 12.9168C12.4714 12.9168 12.5833 12.8049 12.5833 12.6668L12.5833 6.75016Z" fill=""></path>
|
||||
</svg>
|
||||
Jan 8, 2027
|
||||
</span>
|
||||
|
||||
<span class="flex cursor-pointer items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<svg class="stroke-current" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 15.6343C12.6244 15.6343 15.5625 12.6961 15.5625 9.07178C15.5625 5.44741 12.6244 2.50928 9 2.50928C5.37563 2.50928 2.4375 5.44741 2.4375 9.07178C2.4375 10.884 3.17203 12.5246 4.35961 13.7122L2.4375 15.6343H9Z" stroke="" stroke-width="1.5" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
|
||||
2
|
||||
</span>
|
||||
|
||||
<span class="flex cursor-pointer items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.88066 3.10905C8.54039 1.44932 11.2313 1.44933 12.8911 3.10906C14.5508 4.76878 14.5508 7.45973 12.8911 9.11946L12.0657 9.94479L11.0051 8.88413L11.8304 8.0588C12.9043 6.98486 12.9043 5.24366 11.8304 4.16972C10.7565 3.09577 9.01526 3.09577 7.94132 4.16971L7.11599 4.99504L6.05533 3.93438L6.88066 3.10905ZM8.88376 11.0055L9.94442 12.0661L9.11983 12.8907C7.4601 14.5504 4.76915 14.5504 3.10942 12.8907C1.44969 11.231 1.44969 8.54002 3.10942 6.88029L3.93401 6.0557L4.99467 7.11636L4.17008 7.94095C3.09614 9.01489 3.09614 10.7561 4.17008 11.83C5.24402 12.904 6.98522 12.904 8.05917 11.83L8.88376 11.0055ZM9.94458 7.11599C10.2375 6.8231 10.2375 6.34823 9.94458 6.05533C9.65169 5.76244 9.17682 5.76244 8.88392 6.05533L6.0555 8.88376C5.7626 9.17665 5.7626 9.65153 6.0555 9.94442C6.34839 10.2373 6.82326 10.2373 7.11616 9.94442L9.94458 7.11599Z" fill=""></path>
|
||||
</svg>
|
||||
|
||||
1
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span class="bg-brand-50 text-theme-xs text-brand-500 dark:bg-brand-500/15 dark:text-brand-400 mt-3 inline-flex rounded-full px-2 py-0.5 font-medium">
|
||||
Marketing
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-full max-w-6 overflow-hidden rounded-full border-[0.5px] border-gray-200 dark:border-gray-800">
|
||||
<img src="src/images/user/user-07.jpg" alt="user">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- task item -->
|
||||
<div draggable="true" class="task shadow-theme-sm rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/5">
|
||||
<div class="flex items-start justify-between gap-6">
|
||||
<div>
|
||||
<h4 class="mb-5 text-base text-gray-800 dark:text-white/90">
|
||||
Change license and remove products
|
||||
</h4>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="flex cursor-pointer items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33329 1.0835C5.74751 1.0835 6.08329 1.41928 6.08329 1.8335V2.25016L9.91663 2.25016V1.8335C9.91663 1.41928 10.2524 1.0835 10.6666 1.0835C11.0808 1.0835 11.4166 1.41928 11.4166 1.8335V2.25016L12.3333 2.25016C13.2998 2.25016 14.0833 3.03366 14.0833 4.00016V6.00016L14.0833 12.6668C14.0833 13.6333 13.2998 14.4168 12.3333 14.4168L3.66663 14.4168C2.70013 14.4168 1.91663 13.6333 1.91663 12.6668L1.91663 6.00016L1.91663 4.00016C1.91663 3.03366 2.70013 2.25016 3.66663 2.25016L4.58329 2.25016V1.8335C4.58329 1.41928 4.91908 1.0835 5.33329 1.0835ZM5.33329 3.75016L3.66663 3.75016C3.52855 3.75016 3.41663 3.86209 3.41663 4.00016V5.25016L12.5833 5.25016V4.00016C12.5833 3.86209 12.4714 3.75016 12.3333 3.75016L10.6666 3.75016L5.33329 3.75016ZM12.5833 6.75016L3.41663 6.75016L3.41663 12.6668C3.41663 12.8049 3.52855 12.9168 3.66663 12.9168L12.3333 12.9168C12.4714 12.9168 12.5833 12.8049 12.5833 12.6668L12.5833 6.75016Z" fill=""></path>
|
||||
</svg>
|
||||
Jan 8, 2027
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span class="text-theme-xs mt-3 inline-flex rounded-full bg-gray-100 px-2 py-0.5 font-medium text-gray-700 dark:bg-white/[0.03] dark:text-white/80">
|
||||
Dev
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-full max-w-6 overflow-hidden rounded-full border-[0.5px] border-gray-200 dark:border-gray-800">
|
||||
<img src="src/images/user/user-08.jpg" alt="user">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Progress list -->
|
||||
<div class="swim-lane flex flex-col gap-5 border-x border-gray-200 p-4 xl:p-6 dark:border-gray-800">
|
||||
<div class="mb-1 flex items-center justify-between">
|
||||
<h3 class="flex items-center gap-3 text-base font-medium text-gray-800 dark:text-white/90">
|
||||
In Progress
|
||||
<span class="bg-warning-50 text-theme-xs text-warning-700 dark:bg-warning-500/15 inline-flex rounded-full px-2 py-0.5 font-medium dark:text-orange-400">
|
||||
5
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<div x-data="{openDropDown: false}" class="relative">
|
||||
<button @click="openDropDown = !openDropDown" class="text-gray-700 dark:text-gray-400">
|
||||
<svg class="fill-current" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.99902 10.2451C6.96552 10.2451 7.74902 11.0286 7.74902 11.9951V12.0051C7.74902 12.9716 6.96552 13.7551 5.99902 13.7551C5.03253 13.7551 4.24902 12.9716 4.24902 12.0051V11.9951C4.24902 11.0286 5.03253 10.2451 5.99902 10.2451ZM17.999 10.2451C18.9655 10.2451 19.749 11.0286 19.749 11.9951V12.0051C19.749 12.9716 18.9655 13.7551 17.999 13.7551C17.0325 13.7551 16.249 12.9716 16.249 12.0051V11.9951C16.249 11.0286 17.0325 10.2451 17.999 10.2451ZM13.749 11.9951C13.749 11.0286 12.9655 10.2451 11.999 10.2451C11.0325 10.2451 10.249 11.0286 10.249 11.9951V12.0051C10.249 12.9716 11.0325 13.7551 11.999 13.7551C12.9655 13.7551 13.749 12.9716 13.749 12.0051V11.9951Z" fill=""></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div x-show="openDropDown" @click.outside="openDropDown = false" class="shadow-theme-md dark:bg-gray-dark absolute top-full right-0 z-40 w-[140px] space-y-1 rounded-2xl border border-gray-200 bg-white p-2 dark:border-gray-800" style="display: none;">
|
||||
<button class="text-theme-xs flex w-full rounded-lg px-3 py-2 text-left font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300">
|
||||
Edit
|
||||
</button>
|
||||
<button class="text-theme-xs flex w-full rounded-lg px-3 py-2 text-left font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300">
|
||||
Delete
|
||||
</button>
|
||||
<button class="text-theme-xs flex w-full rounded-lg px-3 py-2 text-left font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300">
|
||||
Clear All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- task item -->
|
||||
<div draggable="true" class="task shadow-theme-sm rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/5">
|
||||
<div class="flex items-start justify-between gap-6">
|
||||
<div>
|
||||
<h4 class="mb-5 text-base text-gray-800 dark:text-white/90">
|
||||
Work In Progress (WIP) Dashboard
|
||||
</h4>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="flex cursor-pointer items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33329 1.0835C5.74751 1.0835 6.08329 1.41928 6.08329 1.8335V2.25016L9.91663 2.25016V1.8335C9.91663 1.41928 10.2524 1.0835 10.6666 1.0835C11.0808 1.0835 11.4166 1.41928 11.4166 1.8335V2.25016L12.3333 2.25016C13.2998 2.25016 14.0833 3.03366 14.0833 4.00016V6.00016L14.0833 12.6668C14.0833 13.6333 13.2998 14.4168 12.3333 14.4168L3.66663 14.4168C2.70013 14.4168 1.91663 13.6333 1.91663 12.6668L1.91663 6.00016L1.91663 4.00016C1.91663 3.03366 2.70013 2.25016 3.66663 2.25016L4.58329 2.25016V1.8335C4.58329 1.41928 4.91908 1.0835 5.33329 1.0835ZM5.33329 3.75016L3.66663 3.75016C3.52855 3.75016 3.41663 3.86209 3.41663 4.00016V5.25016L12.5833 5.25016V4.00016C12.5833 3.86209 12.4714 3.75016 12.3333 3.75016L10.6666 3.75016L5.33329 3.75016ZM12.5833 6.75016L3.41663 6.75016L3.41663 12.6668C3.41663 12.8049 3.52855 12.9168 3.66663 12.9168L12.3333 12.9168C12.4714 12.9168 12.5833 12.8049 12.5833 12.6668L12.5833 6.75016Z" fill=""></path>
|
||||
</svg>
|
||||
Today
|
||||
</span>
|
||||
|
||||
<span class="flex cursor-pointer items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<svg class="stroke-current" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 15.6343C12.6244 15.6343 15.5625 12.6961 15.5625 9.07178C15.5625 5.44741 12.6244 2.50928 9 2.50928C5.37563 2.50928 2.4375 5.44741 2.4375 9.07178C2.4375 10.884 3.17203 12.5246 4.35961 13.7122L2.4375 15.6343H9Z" stroke="" stroke-width="1.5" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
|
||||
1
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-full max-w-6 overflow-hidden rounded-full border-[0.5px] border-gray-200 dark:border-gray-800">
|
||||
<img src="src/images/user/user-09.jpg" alt="user">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- task item -->
|
||||
<div draggable="true" class="task shadow-theme-sm rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/5">
|
||||
<div class="flex items-start justify-between gap-6">
|
||||
<div>
|
||||
<h4 class="mb-5 text-base text-gray-800 dark:text-white/90">
|
||||
Kanban Flow Manager
|
||||
</h4>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="flex cursor-pointer items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33329 1.0835C5.74751 1.0835 6.08329 1.41928 6.08329 1.8335V2.25016L9.91663 2.25016V1.8335C9.91663 1.41928 10.2524 1.0835 10.6666 1.0835C11.0808 1.0835 11.4166 1.41928 11.4166 1.8335V2.25016L12.3333 2.25016C13.2998 2.25016 14.0833 3.03366 14.0833 4.00016V6.00016L14.0833 12.6668C14.0833 13.6333 13.2998 14.4168 12.3333 14.4168L3.66663 14.4168C2.70013 14.4168 1.91663 13.6333 1.91663 12.6668L1.91663 6.00016L1.91663 4.00016C1.91663 3.03366 2.70013 2.25016 3.66663 2.25016L4.58329 2.25016V1.8335C4.58329 1.41928 4.91908 1.0835 5.33329 1.0835ZM5.33329 3.75016L3.66663 3.75016C3.52855 3.75016 3.41663 3.86209 3.41663 4.00016V5.25016L12.5833 5.25016V4.00016C12.5833 3.86209 12.4714 3.75016 12.3333 3.75016L10.6666 3.75016L5.33329 3.75016ZM12.5833 6.75016L3.41663 6.75016L3.41663 12.6668C3.41663 12.8049 3.52855 12.9168 3.66663 12.9168L12.3333 12.9168C12.4714 12.9168 12.5833 12.8049 12.5833 12.6668L12.5833 6.75016Z" fill=""></path>
|
||||
</svg>
|
||||
Feb 12, 2027
|
||||
</span>
|
||||
|
||||
<span class="flex cursor-pointer items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<svg class="stroke-current" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 15.6343C12.6244 15.6343 15.5625 12.6961 15.5625 9.07178C15.5625 5.44741 12.6244 2.50928 9 2.50928C5.37563 2.50928 2.4375 5.44741 2.4375 9.07178C2.4375 10.884 3.17203 12.5246 4.35961 13.7122L2.4375 15.6343H9Z" stroke="" stroke-width="1.5" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
|
||||
8
|
||||
</span>
|
||||
|
||||
<span class="flex cursor-pointer items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.88066 3.10905C8.54039 1.44932 11.2313 1.44933 12.8911 3.10906C14.5508 4.76878 14.5508 7.45973 12.8911 9.11946L12.0657 9.94479L11.0051 8.88413L11.8304 8.0588C12.9043 6.98486 12.9043 5.24366 11.8304 4.16972C10.7565 3.09577 9.01526 3.09577 7.94132 4.16971L7.11599 4.99504L6.05533 3.93438L6.88066 3.10905ZM8.88376 11.0055L9.94442 12.0661L9.11983 12.8907C7.4601 14.5504 4.76915 14.5504 3.10942 12.8907C1.44969 11.231 1.44969 8.54002 3.10942 6.88029L3.93401 6.0557L4.99467 7.11636L4.17008 7.94095C3.09614 9.01489 3.09614 10.7561 4.17008 11.83C5.24402 12.904 6.98522 12.904 8.05917 11.83L8.88376 11.0055ZM9.94458 7.11599C10.2375 6.8231 10.2375 6.34823 9.94458 6.05533C9.65169 5.76244 9.17682 5.76244 8.88392 6.05533L6.0555 8.88376C5.7626 9.17665 5.7626 9.65153 6.0555 9.94442C6.34839 10.2373 6.82326 10.2373 7.11616 9.94442L9.94458 7.11599Z" fill=""></path>
|
||||
</svg>
|
||||
|
||||
2
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span class="bg-success-50 text-theme-xs text-success-700 dark:bg-success-500/15 dark:text-success-500 mt-3 inline-flex rounded-full px-2 py-0.5 font-medium">
|
||||
Template
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-full max-w-6 overflow-hidden rounded-full border-[0.5px] border-gray-200 dark:border-gray-800">
|
||||
<img src="src/images/user/user-10.jpg" alt="user">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- task item -->
|
||||
<div draggable="true" class="task shadow-theme-sm rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/5">
|
||||
<div>
|
||||
<h4 class="mb-2 text-base text-gray-800 dark:text-white/90">
|
||||
Product Update - Q4 2024
|
||||
</h4>
|
||||
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Dedicated form for a category of users that will perform
|
||||
actions.
|
||||
</p>
|
||||
|
||||
<div class="my-4">
|
||||
<img src="src/images/task/task.png" alt="task" class="overflow-hidden rounded-xl border-[0.5px] border-gray-200 dark:border-gray-800">
|
||||
</div>
|
||||
|
||||
<div class="flex items-start justify-between gap-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="flex cursor-pointer items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33329 1.0835C5.74751 1.0835 6.08329 1.41928 6.08329 1.8335V2.25016L9.91663 2.25016V1.8335C9.91663 1.41928 10.2524 1.0835 10.6666 1.0835C11.0808 1.0835 11.4166 1.41928 11.4166 1.8335V2.25016L12.3333 2.25016C13.2998 2.25016 14.0833 3.03366 14.0833 4.00016V6.00016L14.0833 12.6668C14.0833 13.6333 13.2998 14.4168 12.3333 14.4168L3.66663 14.4168C2.70013 14.4168 1.91663 13.6333 1.91663 12.6668L1.91663 6.00016L1.91663 4.00016C1.91663 3.03366 2.70013 2.25016 3.66663 2.25016L4.58329 2.25016V1.8335C4.58329 1.41928 4.91908 1.0835 5.33329 1.0835ZM5.33329 3.75016L3.66663 3.75016C3.52855 3.75016 3.41663 3.86209 3.41663 4.00016V5.25016L12.5833 5.25016V4.00016C12.5833 3.86209 12.4714 3.75016 12.3333 3.75016L10.6666 3.75016L5.33329 3.75016ZM12.5833 6.75016L3.41663 6.75016L3.41663 12.6668C3.41663 12.8049 3.52855 12.9168 3.66663 12.9168L12.3333 12.9168C12.4714 12.9168 12.5833 12.8049 12.5833 12.6668L12.5833 6.75016Z" fill=""></path>
|
||||
</svg>
|
||||
Feb 12, 2027
|
||||
</span>
|
||||
|
||||
<span class="flex cursor-pointer items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<svg class="stroke-current" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 15.6343C12.6244 15.6343 15.5625 12.6961 15.5625 9.07178C15.5625 5.44741 12.6244 2.50928 9 2.50928C5.37563 2.50928 2.4375 5.44741 2.4375 9.07178C2.4375 10.884 3.17203 12.5246 4.35961 13.7122L2.4375 15.6343H9Z" stroke="" stroke-width="1.5" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
|
||||
8
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-full max-w-6 overflow-hidden rounded-full border-[0.5px] border-gray-200 dark:border-gray-800">
|
||||
<img src="src/images/user/user-11.jpg" alt="user">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- task item -->
|
||||
<div draggable="true" class="task shadow-theme-sm rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/5">
|
||||
<div class="flex items-start justify-between gap-6">
|
||||
<div>
|
||||
<h4 class="mb-5 text-base text-gray-800 dark:text-white/90">
|
||||
Make figbot send comment when ticket is auto-moved
|
||||
back to inbox
|
||||
</h4>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="flex cursor-pointer items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33329 1.0835C5.74751 1.0835 6.08329 1.41928 6.08329 1.8335V2.25016L9.91663 2.25016V1.8335C9.91663 1.41928 10.2524 1.0835 10.6666 1.0835C11.0808 1.0835 11.4166 1.41928 11.4166 1.8335V2.25016L12.3333 2.25016C13.2998 2.25016 14.0833 3.03366 14.0833 4.00016V6.00016L14.0833 12.6668C14.0833 13.6333 13.2998 14.4168 12.3333 14.4168L3.66663 14.4168C2.70013 14.4168 1.91663 13.6333 1.91663 12.6668L1.91663 6.00016L1.91663 4.00016C1.91663 3.03366 2.70013 2.25016 3.66663 2.25016L4.58329 2.25016V1.8335C4.58329 1.41928 4.91908 1.0835 5.33329 1.0835ZM5.33329 3.75016L3.66663 3.75016C3.52855 3.75016 3.41663 3.86209 3.41663 4.00016V5.25016L12.5833 5.25016V4.00016C12.5833 3.86209 12.4714 3.75016 12.3333 3.75016L10.6666 3.75016L5.33329 3.75016ZM12.5833 6.75016L3.41663 6.75016L3.41663 12.6668C3.41663 12.8049 3.52855 12.9168 3.66663 12.9168L12.3333 12.9168C12.4714 12.9168 12.5833 12.8049 12.5833 12.6668L12.5833 6.75016Z" fill=""></path>
|
||||
</svg>
|
||||
Mar 08, 2027
|
||||
</span>
|
||||
|
||||
<span class="flex cursor-pointer items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<svg class="stroke-current" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 15.6343C12.6244 15.6343 15.5625 12.6961 15.5625 9.07178C15.5625 5.44741 12.6244 2.50928 9 2.50928C5.37563 2.50928 2.4375 5.44741 2.4375 9.07178C2.4375 10.884 3.17203 12.5246 4.35961 13.7122L2.4375 15.6343H9Z" stroke="" stroke-width="1.5" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
|
||||
1
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-full max-w-6 overflow-hidden rounded-full border-[0.5px] border-gray-200 dark:border-gray-800">
|
||||
<img src="src/images/user/user-12.jpg" alt="user">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Completed list -->
|
||||
<div class="swim-lane flex flex-col gap-5 p-4 xl:p-6">
|
||||
<div class="mb-1 flex items-center justify-between">
|
||||
<h3 class="flex items-center gap-3 text-base font-medium text-gray-800 dark:text-white/90">
|
||||
Completed
|
||||
<span class="bg-success-50 text-theme-xs text-success-700 dark:bg-success-500/15 dark:text-success-500 inline-flex rounded-full px-2 py-0.5 font-medium">
|
||||
4
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<div x-data="{openDropDown: false}" class="relative">
|
||||
<button @click="openDropDown = !openDropDown" class="text-gray-700 dark:text-gray-400">
|
||||
<svg class="fill-current" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.99902 10.2451C6.96552 10.2451 7.74902 11.0286 7.74902 11.9951V12.0051C7.74902 12.9716 6.96552 13.7551 5.99902 13.7551C5.03253 13.7551 4.24902 12.9716 4.24902 12.0051V11.9951C4.24902 11.0286 5.03253 10.2451 5.99902 10.2451ZM17.999 10.2451C18.9655 10.2451 19.749 11.0286 19.749 11.9951V12.0051C19.749 12.9716 18.9655 13.7551 17.999 13.7551C17.0325 13.7551 16.249 12.9716 16.249 12.0051V11.9951C16.249 11.0286 17.0325 10.2451 17.999 10.2451ZM13.749 11.9951C13.749 11.0286 12.9655 10.2451 11.999 10.2451C11.0325 10.2451 10.249 11.0286 10.249 11.9951V12.0051C10.249 12.9716 11.0325 13.7551 11.999 13.7551C12.9655 13.7551 13.749 12.9716 13.749 12.0051V11.9951Z" fill=""></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div x-show="openDropDown" @click.outside="openDropDown = false" class="shadow-theme-md dark:bg-gray-dark absolute top-full right-0 z-40 w-[140px] space-y-1 rounded-2xl border border-gray-200 bg-white p-2 dark:border-gray-800" style="display: none;">
|
||||
<button class="text-theme-xs flex w-full rounded-lg px-3 py-2 text-left font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300">
|
||||
Edit
|
||||
</button>
|
||||
<button class="text-theme-xs flex w-full rounded-lg px-3 py-2 text-left font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300">
|
||||
Delete
|
||||
</button>
|
||||
<button class="text-theme-xs flex w-full rounded-lg px-3 py-2 text-left font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300">
|
||||
Clear All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- task item -->
|
||||
<div draggable="true" class="task shadow-theme-sm rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/5">
|
||||
<div class="flex items-start justify-between gap-6">
|
||||
<div>
|
||||
<h4 class="mb-5 text-base text-gray-800 dark:text-white/90">
|
||||
Manage internal feedback
|
||||
</h4>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="flex cursor-pointer items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33329 1.0835C5.74751 1.0835 6.08329 1.41928 6.08329 1.8335V2.25016L9.91663 2.25016V1.8335C9.91663 1.41928 10.2524 1.0835 10.6666 1.0835C11.0808 1.0835 11.4166 1.41928 11.4166 1.8335V2.25016L12.3333 2.25016C13.2998 2.25016 14.0833 3.03366 14.0833 4.00016V6.00016L14.0833 12.6668C14.0833 13.6333 13.2998 14.4168 12.3333 14.4168L3.66663 14.4168C2.70013 14.4168 1.91663 13.6333 1.91663 12.6668L1.91663 6.00016L1.91663 4.00016C1.91663 3.03366 2.70013 2.25016 3.66663 2.25016L4.58329 2.25016V1.8335C4.58329 1.41928 4.91908 1.0835 5.33329 1.0835ZM5.33329 3.75016L3.66663 3.75016C3.52855 3.75016 3.41663 3.86209 3.41663 4.00016V5.25016L12.5833 5.25016V4.00016C12.5833 3.86209 12.4714 3.75016 12.3333 3.75016L10.6666 3.75016L5.33329 3.75016ZM12.5833 6.75016L3.41663 6.75016L3.41663 12.6668C3.41663 12.8049 3.52855 12.9168 3.66663 12.9168L12.3333 12.9168C12.4714 12.9168 12.5833 12.8049 12.5833 12.6668L12.5833 6.75016Z" fill=""></path>
|
||||
</svg>
|
||||
Tomorrow
|
||||
</span>
|
||||
|
||||
<span class="flex cursor-pointer items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<svg class="stroke-current" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 15.6343C12.6244 15.6343 15.5625 12.6961 15.5625 9.07178C15.5625 5.44741 12.6244 2.50928 9 2.50928C5.37563 2.50928 2.4375 5.44741 2.4375 9.07178C2.4375 10.884 3.17203 12.5246 4.35961 13.7122L2.4375 15.6343H9Z" stroke="" stroke-width="1.5" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
|
||||
1
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-full max-w-6 overflow-hidden rounded-full border-[0.5px] border-gray-200 dark:border-gray-800">
|
||||
<img src="src/images/user/user-13.jpg" alt="user">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- task item -->
|
||||
<div draggable="true" class="task shadow-theme-sm rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/5">
|
||||
<div class="flex items-start justify-between gap-6">
|
||||
<div>
|
||||
<h4 class="mb-5 text-base text-gray-800 dark:text-white/90">
|
||||
Do some projects on React Native with Flutter
|
||||
</h4>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="flex cursor-pointer items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33329 1.0835C5.74751 1.0835 6.08329 1.41928 6.08329 1.8335V2.25016L9.91663 2.25016V1.8335C9.91663 1.41928 10.2524 1.0835 10.6666 1.0835C11.0808 1.0835 11.4166 1.41928 11.4166 1.8335V2.25016L12.3333 2.25016C13.2998 2.25016 14.0833 3.03366 14.0833 4.00016V6.00016L14.0833 12.6668C14.0833 13.6333 13.2998 14.4168 12.3333 14.4168L3.66663 14.4168C2.70013 14.4168 1.91663 13.6333 1.91663 12.6668L1.91663 6.00016L1.91663 4.00016C1.91663 3.03366 2.70013 2.25016 3.66663 2.25016L4.58329 2.25016V1.8335C4.58329 1.41928 4.91908 1.0835 5.33329 1.0835ZM5.33329 3.75016L3.66663 3.75016C3.52855 3.75016 3.41663 3.86209 3.41663 4.00016V5.25016L12.5833 5.25016V4.00016C12.5833 3.86209 12.4714 3.75016 12.3333 3.75016L10.6666 3.75016L5.33329 3.75016ZM12.5833 6.75016L3.41663 6.75016L3.41663 12.6668C3.41663 12.8049 3.52855 12.9168 3.66663 12.9168L12.3333 12.9168C12.4714 12.9168 12.5833 12.8049 12.5833 12.6668L12.5833 6.75016Z" fill=""></path>
|
||||
</svg>
|
||||
Jan 8, 2027
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span class="text-theme-xs mt-3 inline-flex rounded-full bg-orange-400/10 px-2 py-0.5 font-medium text-orange-400">
|
||||
Development
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-full max-w-6 overflow-hidden rounded-full border-[0.5px] border-gray-200 dark:border-gray-800">
|
||||
<img src="src/images/user/user-14.jpg" alt="user">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- task item -->
|
||||
<div draggable="true" class="task shadow-theme-sm rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/5">
|
||||
<div class="flex items-start justify-between gap-6">
|
||||
<div>
|
||||
<h4 class="mb-5 text-base text-gray-800 dark:text-white/90">
|
||||
Design marketing assets
|
||||
</h4>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="flex cursor-pointer items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33329 1.0835C5.74751 1.0835 6.08329 1.41928 6.08329 1.8335V2.25016L9.91663 2.25016V1.8335C9.91663 1.41928 10.2524 1.0835 10.6666 1.0835C11.0808 1.0835 11.4166 1.41928 11.4166 1.8335V2.25016L12.3333 2.25016C13.2998 2.25016 14.0833 3.03366 14.0833 4.00016V6.00016L14.0833 12.6668C14.0833 13.6333 13.2998 14.4168 12.3333 14.4168L3.66663 14.4168C2.70013 14.4168 1.91663 13.6333 1.91663 12.6668L1.91663 6.00016L1.91663 4.00016C1.91663 3.03366 2.70013 2.25016 3.66663 2.25016L4.58329 2.25016V1.8335C4.58329 1.41928 4.91908 1.0835 5.33329 1.0835ZM5.33329 3.75016L3.66663 3.75016C3.52855 3.75016 3.41663 3.86209 3.41663 4.00016V5.25016L12.5833 5.25016V4.00016C12.5833 3.86209 12.4714 3.75016 12.3333 3.75016L10.6666 3.75016L5.33329 3.75016ZM12.5833 6.75016L3.41663 6.75016L3.41663 12.6668C3.41663 12.8049 3.52855 12.9168 3.66663 12.9168L12.3333 12.9168C12.4714 12.9168 12.5833 12.8049 12.5833 12.6668L12.5833 6.75016Z" fill=""></path>
|
||||
</svg>
|
||||
Jan 8, 2027
|
||||
</span>
|
||||
|
||||
<span class="flex cursor-pointer items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<svg class="stroke-current" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 15.6343C12.6244 15.6343 15.5625 12.6961 15.5625 9.07178C15.5625 5.44741 12.6244 2.50928 9 2.50928C5.37563 2.50928 2.4375 5.44741 2.4375 9.07178C2.4375 10.884 3.17203 12.5246 4.35961 13.7122L2.4375 15.6343H9Z" stroke="" stroke-width="1.5" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
|
||||
2
|
||||
</span>
|
||||
|
||||
<span class="flex cursor-pointer items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.88066 3.10905C8.54039 1.44932 11.2313 1.44933 12.8911 3.10906C14.5508 4.76878 14.5508 7.45973 12.8911 9.11946L12.0657 9.94479L11.0051 8.88413L11.8304 8.0588C12.9043 6.98486 12.9043 5.24366 11.8304 4.16972C10.7565 3.09577 9.01526 3.09577 7.94132 4.16971L7.11599 4.99504L6.05533 3.93438L6.88066 3.10905ZM8.88376 11.0055L9.94442 12.0661L9.11983 12.8907C7.4601 14.5504 4.76915 14.5504 3.10942 12.8907C1.44969 11.231 1.44969 8.54002 3.10942 6.88029L3.93401 6.0557L4.99467 7.11636L4.17008 7.94095C3.09614 9.01489 3.09614 10.7561 4.17008 11.83C5.24402 12.904 6.98522 12.904 8.05917 11.83L8.88376 11.0055ZM9.94458 7.11599C10.2375 6.8231 10.2375 6.34823 9.94458 6.05533C9.65169 5.76244 9.17682 5.76244 8.88392 6.05533L6.0555 8.88376C5.7626 9.17665 5.7626 9.65153 6.0555 9.94442C6.34839 10.2373 6.82326 10.2373 7.11616 9.94442L9.94458 7.11599Z" fill=""></path>
|
||||
</svg>
|
||||
|
||||
1
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span class="bg-brand-50 text-theme-xs text-brand-500 dark:bg-brand-500/15 dark:text-brand-400 mt-3 inline-flex rounded-full px-2 py-0.5 font-medium">
|
||||
Marketing
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-full max-w-6 overflow-hidden rounded-full border-[0.5px] border-gray-200 dark:border-gray-800">
|
||||
<img src="src/images/user/user-15.jpg" alt="user">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- task item -->
|
||||
<div draggable="true" class="task shadow-theme-sm rounded-xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/5">
|
||||
<div class="flex items-start justify-between gap-6">
|
||||
<div>
|
||||
<h4 class="mb-5 text-base text-gray-800 dark:text-white/90">
|
||||
Kanban Flow Manager
|
||||
</h4>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="flex cursor-pointer items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33329 1.0835C5.74751 1.0835 6.08329 1.41928 6.08329 1.8335V2.25016L9.91663 2.25016V1.8335C9.91663 1.41928 10.2524 1.0835 10.6666 1.0835C11.0808 1.0835 11.4166 1.41928 11.4166 1.8335V2.25016L12.3333 2.25016C13.2998 2.25016 14.0833 3.03366 14.0833 4.00016V6.00016L14.0833 12.6668C14.0833 13.6333 13.2998 14.4168 12.3333 14.4168L3.66663 14.4168C2.70013 14.4168 1.91663 13.6333 1.91663 12.6668L1.91663 6.00016L1.91663 4.00016C1.91663 3.03366 2.70013 2.25016 3.66663 2.25016L4.58329 2.25016V1.8335C4.58329 1.41928 4.91908 1.0835 5.33329 1.0835ZM5.33329 3.75016L3.66663 3.75016C3.52855 3.75016 3.41663 3.86209 3.41663 4.00016V5.25016L12.5833 5.25016V4.00016C12.5833 3.86209 12.4714 3.75016 12.3333 3.75016L10.6666 3.75016L5.33329 3.75016ZM12.5833 6.75016L3.41663 6.75016L3.41663 12.6668C3.41663 12.8049 3.52855 12.9168 3.66663 12.9168L12.3333 12.9168C12.4714 12.9168 12.5833 12.8049 12.5833 12.6668L12.5833 6.75016Z" fill=""></path>
|
||||
</svg>
|
||||
Feb 12, 2027
|
||||
</span>
|
||||
|
||||
<span class="flex cursor-pointer items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<svg class="stroke-current" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 15.6343C12.6244 15.6343 15.5625 12.6961 15.5625 9.07178C15.5625 5.44741 12.6244 2.50928 9 2.50928C5.37563 2.50928 2.4375 5.44741 2.4375 9.07178C2.4375 10.884 3.17203 12.5246 4.35961 13.7122L2.4375 15.6343H9Z" stroke="" stroke-width="1.5" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
|
||||
8
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span class="bg-success-50 text-theme-xs text-success-700 dark:bg-success-500/15 dark:text-success-500 mt-3 inline-flex rounded-full px-2 py-0.5 font-medium">
|
||||
Template
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-full max-w-6 overflow-hidden rounded-full border-[0.5px] border-gray-200 dark:border-gray-800">
|
||||
<img src="src/images/user/user-16.jpg" alt="user">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Task wrapper End -->
|
||||
</div>
|
||||
@@ -1,788 +0,0 @@
|
||||
<div x-data="{selectedTaskGroup: 'All'}" class="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<!-- Task header Start -->
|
||||
<div class="flex flex-col items-center px-4 py-5 xl:px-6 xl:py-6">
|
||||
<div class="flex flex-col w-full gap-5 sm:justify-between xl:flex-row xl:items-center">
|
||||
<div class="flex flex-wrap items-center gap-x-1 gap-y-2 rounded-lg bg-gray-100 p-0.5 dark:bg-gray-900">
|
||||
<button class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md h group hover:text-gray-900 dark:hover:text-white text-gray-900 dark:text-white bg-white dark:bg-gray-800" :class="selectedTaskGroup === 'All' ? 'text-gray-900 dark:text-white bg-white dark:bg-gray-800' : 'text-gray-500 dark:text-gray-400'" @click="selectedTaskGroup = 'All' ">
|
||||
All Tasks
|
||||
<span class="inline-flex rounded-full px-2 py-0.5 text-xs font-medium leading-normal group-hover:bg-brand-50 group-hover:text-brand-500 dark:group-hover:bg-brand-500/15 dark:group-hover:text-brand-400 text-brand-500 dark:text-brand-400 bg-brand-50 dark:bg-brand-500/15" :class="selectedTaskGroup === 'All' ? 'text-brand-500 dark:text-brand-400 bg-brand-50 dark:bg-brand-500/15' : 'bg-white dark:bg-white/[0.03]'">
|
||||
11
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md group hover:text-gray-900 dark:hover:text-white text-gray-500 dark:text-gray-400" :class="selectedTaskGroup === 'Todo' ? 'text-gray-900 dark:text-white bg-white dark:bg-gray-800' : 'text-gray-500 dark:text-gray-400'" @click="selectedTaskGroup = 'Todo' ">
|
||||
To do
|
||||
<span class="inline-flex rounded-full px-2 py-0.5 text-xs font-medium leading-normal group-hover:bg-brand-50 group-hover:text-brand-500 dark:group-hover:bg-brand-500/15 dark:group-hover:text-brand-400 bg-white dark:bg-white/[0.03]" :class="selectedTaskGroup === 'Todo' ? 'text-brand-500 dark:text-brand-400 bg-brand-50 dark:bg-brand-500/15' : 'bg-white dark:bg-white/[0.03]'">
|
||||
3
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md group hover:text-gray-900 dark:hover:text-white text-gray-500 dark:text-gray-400" :class="selectedTaskGroup === 'InProgress' ? 'text-gray-900 dark:text-white bg-white dark:bg-gray-800' : 'text-gray-500 dark:text-gray-400'" @click="selectedTaskGroup = 'InProgress' ">
|
||||
In Progress
|
||||
<span class="inline-flex rounded-full px-2 py-0.5 text-xs font-medium leading-normal group-hover:bg-brand-50 group-hover:text-brand-500 dark:group-hover:bg-brand-500/15 dark:group-hover:text-brand-400 bg-white dark:bg-white/[0.03]" :class="selectedTaskGroup === 'InProgress' ? 'text-brand-500 dark:text-brand-400 bg-brand-50 dark:bg-brand-500/15' : 'bg-white dark:bg-white/[0.03]'">
|
||||
4
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md group hover:text-gray-900 dark:hover:text-white text-gray-500 dark:text-gray-400" :class="selectedTaskGroup === 'Completed' ? 'text-gray-900 dark:text-white bg-white dark:bg-gray-800' : 'text-gray-500 dark:text-gray-400'" @click="selectedTaskGroup = 'Completed' ">
|
||||
Completed
|
||||
<span class="inline-flex rounded-full px-2 py-0.5 text-xs font-medium leading-normal group-hover:bg-brand-50 group-hover:text-brand-500 dark:group-hover:bg-brand-500/15 dark:group-hover:text-brand-400 bg-white dark:bg-white/[0.03]" :class="selectedTaskGroup === 'Completed' ? 'text-brand-500 dark:text-brand-400 bg-brand-50 dark:bg-brand-500/15' : 'bg-white dark:bg-white/[0.03]'">
|
||||
4
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-3 xl:justify-end">
|
||||
<button class="inline-flex items-center gap-2 rounded-lg border border-gray-300 px-4 py-2.5 text-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-white/[0.03]">
|
||||
<svg class="fill-current" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.0826 4.0835C11.0769 4.0835 10.2617 4.89871 10.2617 5.90433C10.2617 6.90995 11.0769 7.72516 12.0826 7.72516C13.0882 7.72516 13.9034 6.90995 13.9034 5.90433C13.9034 4.89871 13.0882 4.0835 12.0826 4.0835ZM2.29004 6.65409H8.84671C9.18662 8.12703 10.5063 9.22516 12.0826 9.22516C13.6588 9.22516 14.9785 8.12703 15.3184 6.65409H17.7067C18.1209 6.65409 18.4567 6.31831 18.4567 5.90409C18.4567 5.48988 18.1209 5.15409 17.7067 5.15409H15.3183C14.9782 3.68139 13.6586 2.5835 12.0826 2.5835C10.5065 2.5835 9.18691 3.68139 8.84682 5.15409H2.29004C1.87583 5.15409 1.54004 5.48988 1.54004 5.90409C1.54004 6.31831 1.87583 6.65409 2.29004 6.65409ZM4.6816 13.3462H2.29085C1.87664 13.3462 1.54085 13.682 1.54085 14.0962C1.54085 14.5104 1.87664 14.8462 2.29085 14.8462H4.68172C5.02181 16.3189 6.34142 17.4168 7.91745 17.4168C9.49348 17.4168 10.8131 16.3189 11.1532 14.8462H17.7075C18.1217 14.8462 18.4575 14.5104 18.4575 14.0962C18.4575 13.682 18.1217 13.3462 17.7075 13.3462H11.1533C10.8134 11.8733 9.49366 10.7752 7.91745 10.7752C6.34124 10.7752 5.02151 11.8733 4.6816 13.3462ZM9.73828 14.096C9.73828 13.0904 8.92307 12.2752 7.91745 12.2752C6.91183 12.2752 6.09662 13.0904 6.09662 14.096C6.09662 15.1016 6.91183 15.9168 7.91745 15.9168C8.92307 15.9168 9.73828 15.1016 9.73828 14.096Z" fill=""></path>
|
||||
</svg>
|
||||
|
||||
Filter & Short
|
||||
</button>
|
||||
|
||||
<button @click="isTaskModalModal = true" class="inline-flex items-center gap-2 rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-medium text-white shadow-theme-xs hover:bg-brand-600">
|
||||
Add New Task
|
||||
|
||||
<svg class="fill-current" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.2502 4.99951C9.2502 4.5853 9.58599 4.24951 10.0002 4.24951C10.4144 4.24951 10.7502 4.5853 10.7502 4.99951V9.24971H15.0006C15.4148 9.24971 15.7506 9.5855 15.7506 9.99971C15.7506 10.4139 15.4148 10.7497 15.0006 10.7497H10.7502V15.0001C10.7502 15.4143 10.4144 15.7501 10.0002 15.7501C9.58599 15.7501 9.2502 15.4143 9.2502 15.0001V10.7497H5C4.58579 10.7497 4.25 10.4139 4.25 9.99971C4.25 9.5855 4.58579 9.24971 5 9.24971H9.2502V4.99951Z" fill=""></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Task header End -->
|
||||
|
||||
<!-- Task wrapper Start -->
|
||||
<div class="p-4 space-y-8 border-t border-gray-200 mt-7 dark:border-gray-800 sm:mt-0 xl:p-6">
|
||||
<!-- To do list -->
|
||||
<div class="flex flex-col gap-4 swim-lane">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="flex items-center gap-3 text-base font-medium text-gray-800 dark:text-white/90">
|
||||
To Do
|
||||
<span class="inline-flex rounded-full bg-gray-100 px-2 py-0.5 text-theme-xs font-medium text-gray-700 dark:bg-white/[0.03] dark:text-white/80">
|
||||
3
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<div x-data="{openDropDown: false}" class="relative">
|
||||
<button @click="openDropDown = !openDropDown" class="text-gray-700 dark:text-gray-400">
|
||||
<svg class="fill-current" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.99902 10.2451C6.96552 10.2451 7.74902 11.0286 7.74902 11.9951V12.0051C7.74902 12.9716 6.96552 13.7551 5.99902 13.7551C5.03253 13.7551 4.24902 12.9716 4.24902 12.0051V11.9951C4.24902 11.0286 5.03253 10.2451 5.99902 10.2451ZM17.999 10.2451C18.9655 10.2451 19.749 11.0286 19.749 11.9951V12.0051C19.749 12.9716 18.9655 13.7551 17.999 13.7551C17.0325 13.7551 16.249 12.9716 16.249 12.0051V11.9951C16.249 11.0286 17.0325 10.2451 17.999 10.2451ZM13.749 11.9951C13.749 11.0286 12.9655 10.2451 11.999 10.2451C11.0325 10.2451 10.249 11.0286 10.249 11.9951V12.0051C10.249 12.9716 11.0325 13.7551 11.999 13.7551C12.9655 13.7551 13.749 12.9716 13.749 12.0051V11.9951Z" fill=""></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div x-show="openDropDown" @click.outside="openDropDown = false" class="absolute right-0 top-full z-40 w-[140px] space-y-1 rounded-2xl border border-gray-200 bg-white p-2 shadow-theme-md dark:border-gray-800 dark:bg-gray-dark" style="display: none;">
|
||||
<button class="flex w-full px-3 py-2 font-medium text-left text-gray-500 rounded-lg text-theme-xs hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300">
|
||||
Edit
|
||||
</button>
|
||||
<button class="flex w-full px-3 py-2 font-medium text-left text-gray-500 rounded-lg text-theme-xs hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300">
|
||||
Delete
|
||||
</button>
|
||||
<button class="flex w-full px-3 py-2 font-medium text-left text-gray-500 rounded-lg text-theme-xs hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300">
|
||||
Clear All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- task item -->
|
||||
<div draggable="true" class="p-5 bg-white border border-gray-200 task rounded-xl shadow-theme-sm dark:border-gray-800 dark:bg-white/5">
|
||||
<div class="flex flex-col gap-5 xl:flex-row xl:items-center xl:justify-between">
|
||||
<div class="flex items-start w-full gap-4">
|
||||
<span class="text-gray-400">
|
||||
<svg class="fill-current" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.43311 5.0001C2.43311 4.50304 2.83605 4.1001 3.33311 4.1001L16.6664 4.1001C17.1635 4.1001 17.5664 4.50304 17.5664 5.0001C17.5664 5.49715 17.1635 5.9001 16.6664 5.9001L3.33311 5.9001C2.83605 5.9001 2.43311 5.49716 2.43311 5.0001ZM2.43311 15.0001C2.43311 14.503 2.83605 14.1001 3.33311 14.1001L16.6664 14.1001C17.1635 14.1001 17.5664 14.503 17.5664 15.0001C17.5664 15.4972 17.1635 15.9001 16.6664 15.9001L3.33311 15.9001C2.83605 15.9001 2.43311 15.4972 2.43311 15.0001ZM3.33311 9.1001C2.83605 9.1001 2.43311 9.50304 2.43311 10.0001C2.43311 10.4972 2.83605 10.9001 3.33311 10.9001L16.6664 10.9001C17.1635 10.9001 17.5664 10.4972 17.5664 10.0001C17.5664 9.50304 17.1635 9.1001 16.6664 9.1001L3.33311 9.1001Z" fill=""></path>
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
<label for="taskCheckbox1" class="w-full cursor-pointer">
|
||||
<div class="relative flex items-start">
|
||||
<input type="checkbox" id="taskCheckbox1" class="sr-only taskCheckbox">
|
||||
<div class="flex items-center justify-center w-full h-5 mr-3 border border-gray-300 rounded-md box max-w-5 dark:border-gray-700">
|
||||
<span class="opacity-0">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.6668 3.5L5.25016 9.91667L2.3335 7" stroke="white" stroke-width="1.94437" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<p class="-mt-0.5 text-base text-gray-800 dark:text-white/90">
|
||||
Finish user onboarding
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col-reverse items-start justify-end w-full gap-3 xl:flex-row xl:items-center xl:gap-5">
|
||||
<span class="inline-flex rounded-full bg-brand-50 px-2 py-0.5 text-theme-xs font-medium text-brand-500 dark:bg-brand-500/15 dark:text-brand-400">
|
||||
Marketing
|
||||
</span>
|
||||
|
||||
<div class="flex items-center justify-between w-full gap-5 xl:w-auto xl:justify-normal">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33329 1.0835C5.74751 1.0835 6.08329 1.41928 6.08329 1.8335V2.25016L9.91663 2.25016V1.8335C9.91663 1.41928 10.2524 1.0835 10.6666 1.0835C11.0808 1.0835 11.4166 1.41928 11.4166 1.8335V2.25016L12.3333 2.25016C13.2998 2.25016 14.0833 3.03366 14.0833 4.00016V6.00016L14.0833 12.6668C14.0833 13.6333 13.2998 14.4168 12.3333 14.4168L3.66663 14.4168C2.70013 14.4168 1.91663 13.6333 1.91663 12.6668L1.91663 6.00016L1.91663 4.00016C1.91663 3.03366 2.70013 2.25016 3.66663 2.25016L4.58329 2.25016V1.8335C4.58329 1.41928 4.91908 1.0835 5.33329 1.0835ZM5.33329 3.75016L3.66663 3.75016C3.52855 3.75016 3.41663 3.86209 3.41663 4.00016V5.25016L12.5833 5.25016V4.00016C12.5833 3.86209 12.4714 3.75016 12.3333 3.75016L10.6666 3.75016L5.33329 3.75016ZM12.5833 6.75016L3.41663 6.75016L3.41663 12.6668C3.41663 12.8049 3.52855 12.9168 3.66663 12.9168L12.3333 12.9168C12.4714 12.9168 12.5833 12.8049 12.5833 12.6668L12.5833 6.75016Z" fill=""></path>
|
||||
</svg>
|
||||
Tomorrow
|
||||
</span>
|
||||
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="stroke-current" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 15.6343C12.6244 15.6343 15.5625 12.6961 15.5625 9.07178C15.5625 5.44741 12.6244 2.50928 9 2.50928C5.37563 2.50928 2.4375 5.44741 2.4375 9.07178C2.4375 10.884 3.17203 12.5246 4.35961 13.7122L2.4375 15.6343H9Z" stroke="" stroke-width="1.5" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
|
||||
1
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-full max-w-6 overflow-hidden rounded-full border-[0.5px] border-gray-200 dark:border-gray-800">
|
||||
<img src="src/images/user/user-01.jpg" alt="user">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- task item -->
|
||||
<div draggable="true" class="p-5 bg-white border border-gray-200 task rounded-xl shadow-theme-sm dark:border-gray-800 dark:bg-white/5">
|
||||
<div class="flex flex-col gap-5 xl:flex-row xl:items-center xl:justify-between">
|
||||
<div class="flex items-start w-full gap-4">
|
||||
<span class="text-gray-400">
|
||||
<svg class="fill-current" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.43311 5.0001C2.43311 4.50304 2.83605 4.1001 3.33311 4.1001L16.6664 4.1001C17.1635 4.1001 17.5664 4.50304 17.5664 5.0001C17.5664 5.49715 17.1635 5.9001 16.6664 5.9001L3.33311 5.9001C2.83605 5.9001 2.43311 5.49716 2.43311 5.0001ZM2.43311 15.0001C2.43311 14.503 2.83605 14.1001 3.33311 14.1001L16.6664 14.1001C17.1635 14.1001 17.5664 14.503 17.5664 15.0001C17.5664 15.4972 17.1635 15.9001 16.6664 15.9001L3.33311 15.9001C2.83605 15.9001 2.43311 15.4972 2.43311 15.0001ZM3.33311 9.1001C2.83605 9.1001 2.43311 9.50304 2.43311 10.0001C2.43311 10.4972 2.83605 10.9001 3.33311 10.9001L16.6664 10.9001C17.1635 10.9001 17.5664 10.4972 17.5664 10.0001C17.5664 9.50304 17.1635 9.1001 16.6664 9.1001L3.33311 9.1001Z" fill=""></path>
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
<label for="taskCheckbox2" class="w-full cursor-pointer">
|
||||
<div class="relative flex items-start">
|
||||
<input type="checkbox" id="taskCheckbox2" class="sr-only taskCheckbox" checked="">
|
||||
<div class="flex items-center justify-center w-full h-5 mr-3 border border-gray-300 rounded-md box max-w-5 dark:border-gray-700">
|
||||
<span class="opacity-0">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.6668 3.5L5.25016 9.91667L2.3335 7" stroke="white" stroke-width="1.94437" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<p class="-mt-0.5 text-base text-gray-800 dark:text-white/90">
|
||||
Solve the Dribbble prioritisation issue with the
|
||||
team
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col-reverse items-start justify-end w-full gap-3 xl:flex-row xl:items-center xl:gap-5">
|
||||
<div class="flex items-center justify-between w-full gap-5 xl:w-auto xl:justify-normal">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33329 1.0835C5.74751 1.0835 6.08329 1.41928 6.08329 1.8335V2.25016L9.91663 2.25016V1.8335C9.91663 1.41928 10.2524 1.0835 10.6666 1.0835C11.0808 1.0835 11.4166 1.41928 11.4166 1.8335V2.25016L12.3333 2.25016C13.2998 2.25016 14.0833 3.03366 14.0833 4.00016V6.00016L14.0833 12.6668C14.0833 13.6333 13.2998 14.4168 12.3333 14.4168L3.66663 14.4168C2.70013 14.4168 1.91663 13.6333 1.91663 12.6668L1.91663 6.00016L1.91663 4.00016C1.91663 3.03366 2.70013 2.25016 3.66663 2.25016L4.58329 2.25016V1.8335C4.58329 1.41928 4.91908 1.0835 5.33329 1.0835ZM5.33329 3.75016L3.66663 3.75016C3.52855 3.75016 3.41663 3.86209 3.41663 4.00016V5.25016L12.5833 5.25016V4.00016C12.5833 3.86209 12.4714 3.75016 12.3333 3.75016L10.6666 3.75016L5.33329 3.75016ZM12.5833 6.75016L3.41663 6.75016L3.41663 12.6668C3.41663 12.8049 3.52855 12.9168 3.66663 12.9168L12.3333 12.9168C12.4714 12.9168 12.5833 12.8049 12.5833 12.6668L12.5833 6.75016Z" fill=""></path>
|
||||
</svg>
|
||||
Jan 8, 2027
|
||||
</span>
|
||||
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="stroke-current" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 15.6343C12.6244 15.6343 15.5625 12.6961 15.5625 9.07178C15.5625 5.44741 12.6244 2.50928 9 2.50928C5.37563 2.50928 2.4375 5.44741 2.4375 9.07178C2.4375 10.884 3.17203 12.5246 4.35961 13.7122L2.4375 15.6343H9Z" stroke="" stroke-width="1.5" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
|
||||
2
|
||||
</span>
|
||||
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.88066 3.10905C8.54039 1.44932 11.2313 1.44933 12.8911 3.10906C14.5508 4.76878 14.5508 7.45973 12.8911 9.11946L12.0657 9.94479L11.0051 8.88413L11.8304 8.0588C12.9043 6.98486 12.9043 5.24366 11.8304 4.16972C10.7565 3.09577 9.01526 3.09577 7.94132 4.16971L7.11599 4.99504L6.05533 3.93438L6.88066 3.10905ZM8.88376 11.0055L9.94442 12.0661L9.11983 12.8907C7.4601 14.5504 4.76915 14.5504 3.10942 12.8907C1.44969 11.231 1.44969 8.54002 3.10942 6.88029L3.93401 6.0557L4.99467 7.11636L4.17008 7.94095C3.09614 9.01489 3.09614 10.7561 4.17008 11.83C5.24402 12.904 6.98522 12.904 8.05917 11.83L8.88376 11.0055ZM9.94458 7.11599C10.2375 6.8231 10.2375 6.34823 9.94458 6.05533C9.65169 5.76244 9.17682 5.76244 8.88392 6.05533L6.0555 8.88376C5.7626 9.17665 5.7626 9.65153 6.0555 9.94442C6.34839 10.2373 6.82326 10.2373 7.11616 9.94442L9.94458 7.11599Z" fill=""></path>
|
||||
</svg>
|
||||
|
||||
1
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-full max-w-6 overflow-hidden rounded-full border-[0.5px] border-gray-200 dark:border-gray-800">
|
||||
<img src="src/images/user/user-13.jpg" alt="user">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- task item -->
|
||||
<div draggable="true" class="p-5 bg-white border border-gray-200 task rounded-xl shadow-theme-sm dark:border-gray-800 dark:bg-white/5">
|
||||
<div class="flex flex-col gap-5 xl:flex-row xl:items-center xl:justify-between">
|
||||
<div class="flex items-start w-full gap-4">
|
||||
<span class="text-gray-400">
|
||||
<svg class="fill-current" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.43311 5.0001C2.43311 4.50304 2.83605 4.1001 3.33311 4.1001L16.6664 4.1001C17.1635 4.1001 17.5664 4.50304 17.5664 5.0001C17.5664 5.49715 17.1635 5.9001 16.6664 5.9001L3.33311 5.9001C2.83605 5.9001 2.43311 5.49716 2.43311 5.0001ZM2.43311 15.0001C2.43311 14.503 2.83605 14.1001 3.33311 14.1001L16.6664 14.1001C17.1635 14.1001 17.5664 14.503 17.5664 15.0001C17.5664 15.4972 17.1635 15.9001 16.6664 15.9001L3.33311 15.9001C2.83605 15.9001 2.43311 15.4972 2.43311 15.0001ZM3.33311 9.1001C2.83605 9.1001 2.43311 9.50304 2.43311 10.0001C2.43311 10.4972 2.83605 10.9001 3.33311 10.9001L16.6664 10.9001C17.1635 10.9001 17.5664 10.4972 17.5664 10.0001C17.5664 9.50304 17.1635 9.1001 16.6664 9.1001L3.33311 9.1001Z" fill=""></path>
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
<label for="taskCheckbox3" class="w-full cursor-pointer">
|
||||
<div class="relative flex items-start">
|
||||
<input type="checkbox" id="taskCheckbox3" class="sr-only taskCheckbox" checked="">
|
||||
<div class="flex items-center justify-center w-full h-5 mr-3 border border-gray-300 rounded-md box max-w-5 dark:border-gray-700">
|
||||
<span class="opacity-0">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.6668 3.5L5.25016 9.91667L2.3335 7" stroke="white" stroke-width="1.94437" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<p class="-mt-0.5 text-base text-gray-800 dark:text-white/90">
|
||||
Change license and remove products
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col-reverse items-start justify-end w-full gap-3 xl:flex-row xl:items-center xl:gap-5">
|
||||
<span class="inline-flex rounded-full bg-brand-50 px-2 py-0.5 text-theme-xs font-medium text-brand-500 dark:bg-brand-500/15 dark:text-brand-400">
|
||||
Marketing
|
||||
</span>
|
||||
|
||||
<div class="flex items-center justify-between w-full gap-5 xl:w-auto xl:justify-normal">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33329 1.0835C5.74751 1.0835 6.08329 1.41928 6.08329 1.8335V2.25016L9.91663 2.25016V1.8335C9.91663 1.41928 10.2524 1.0835 10.6666 1.0835C11.0808 1.0835 11.4166 1.41928 11.4166 1.8335V2.25016L12.3333 2.25016C13.2998 2.25016 14.0833 3.03366 14.0833 4.00016V6.00016L14.0833 12.6668C14.0833 13.6333 13.2998 14.4168 12.3333 14.4168L3.66663 14.4168C2.70013 14.4168 1.91663 13.6333 1.91663 12.6668L1.91663 6.00016L1.91663 4.00016C1.91663 3.03366 2.70013 2.25016 3.66663 2.25016L4.58329 2.25016V1.8335C4.58329 1.41928 4.91908 1.0835 5.33329 1.0835ZM5.33329 3.75016L3.66663 3.75016C3.52855 3.75016 3.41663 3.86209 3.41663 4.00016V5.25016L12.5833 5.25016V4.00016C12.5833 3.86209 12.4714 3.75016 12.3333 3.75016L10.6666 3.75016L5.33329 3.75016ZM12.5833 6.75016L3.41663 6.75016L3.41663 12.6668C3.41663 12.8049 3.52855 12.9168 3.66663 12.9168L12.3333 12.9168C12.4714 12.9168 12.5833 12.8049 12.5833 12.6668L12.5833 6.75016Z" fill=""></path>
|
||||
</svg>
|
||||
Feb 12, 2027
|
||||
</span>
|
||||
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.88066 3.10905C8.54039 1.44932 11.2313 1.44933 12.8911 3.10906C14.5508 4.76878 14.5508 7.45973 12.8911 9.11946L12.0657 9.94479L11.0051 8.88413L11.8304 8.0588C12.9043 6.98486 12.9043 5.24366 11.8304 4.16972C10.7565 3.09577 9.01526 3.09577 7.94132 4.16971L7.11599 4.99504L6.05533 3.93438L6.88066 3.10905ZM8.88376 11.0055L9.94442 12.0661L9.11983 12.8907C7.4601 14.5504 4.76915 14.5504 3.10942 12.8907C1.44969 11.231 1.44969 8.54002 3.10942 6.88029L3.93401 6.0557L4.99467 7.11636L4.17008 7.94095C3.09614 9.01489 3.09614 10.7561 4.17008 11.83C5.24402 12.904 6.98522 12.904 8.05917 11.83L8.88376 11.0055ZM9.94458 7.11599C10.2375 6.8231 10.2375 6.34823 9.94458 6.05533C9.65169 5.76244 9.17682 5.76244 8.88392 6.05533L6.0555 8.88376C5.7626 9.17665 5.7626 9.65153 6.0555 9.94442C6.34839 10.2373 6.82326 10.2373 7.11616 9.94442L9.94458 7.11599Z" fill=""></path>
|
||||
</svg>
|
||||
|
||||
1
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-full max-w-6 overflow-hidden rounded-full border-[0.5px] border-gray-200 dark:border-gray-800">
|
||||
<img src="src/images/user/user-15.jpg" alt="user">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Progress list -->
|
||||
<div class="flex flex-col gap-4 swim-lane">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="flex items-center gap-3 text-base font-medium text-gray-800 dark:text-white/90">
|
||||
In Progress
|
||||
<span class="inline-flex rounded-full bg-warning-50 px-2 py-0.5 text-theme-xs font-medium text-warning-700 dark:bg-warning-500/15 dark:text-orange-400">
|
||||
4
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<div x-data="{openDropDown: false}" class="relative">
|
||||
<button @click="openDropDown = !openDropDown" class="text-gray-700 dark:text-gray-400">
|
||||
<svg class="fill-current" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.99902 10.2451C6.96552 10.2451 7.74902 11.0286 7.74902 11.9951V12.0051C7.74902 12.9716 6.96552 13.7551 5.99902 13.7551C5.03253 13.7551 4.24902 12.9716 4.24902 12.0051V11.9951C4.24902 11.0286 5.03253 10.2451 5.99902 10.2451ZM17.999 10.2451C18.9655 10.2451 19.749 11.0286 19.749 11.9951V12.0051C19.749 12.9716 18.9655 13.7551 17.999 13.7551C17.0325 13.7551 16.249 12.9716 16.249 12.0051V11.9951C16.249 11.0286 17.0325 10.2451 17.999 10.2451ZM13.749 11.9951C13.749 11.0286 12.9655 10.2451 11.999 10.2451C11.0325 10.2451 10.249 11.0286 10.249 11.9951V12.0051C10.249 12.9716 11.0325 13.7551 11.999 13.7551C12.9655 13.7551 13.749 12.9716 13.749 12.0051V11.9951Z" fill=""></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div x-show="openDropDown" @click.outside="openDropDown = false" class="absolute right-0 top-full z-40 w-[140px] space-y-1 rounded-2xl border border-gray-200 bg-white p-2 shadow-theme-md dark:border-gray-800 dark:bg-gray-dark" style="display: none;">
|
||||
<button class="flex w-full px-3 py-2 font-medium text-left text-gray-500 rounded-lg text-theme-xs hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300">
|
||||
Edit
|
||||
</button>
|
||||
<button class="flex w-full px-3 py-2 font-medium text-left text-gray-500 rounded-lg text-theme-xs hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300">
|
||||
Delete
|
||||
</button>
|
||||
<button class="flex w-full px-3 py-2 font-medium text-left text-gray-500 rounded-lg text-theme-xs hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300">
|
||||
Clear All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- task item -->
|
||||
<div draggable="true" class="p-5 bg-white border border-gray-200 task rounded-xl shadow-theme-sm dark:border-gray-800 dark:bg-white/5">
|
||||
<div class="flex flex-col gap-5 xl:flex-row xl:items-center xl:justify-between">
|
||||
<div class="flex items-start w-full gap-4">
|
||||
<span class="text-gray-400">
|
||||
<svg class="fill-current" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.43311 5.0001C2.43311 4.50304 2.83605 4.1001 3.33311 4.1001L16.6664 4.1001C17.1635 4.1001 17.5664 4.50304 17.5664 5.0001C17.5664 5.49715 17.1635 5.9001 16.6664 5.9001L3.33311 5.9001C2.83605 5.9001 2.43311 5.49716 2.43311 5.0001ZM2.43311 15.0001C2.43311 14.503 2.83605 14.1001 3.33311 14.1001L16.6664 14.1001C17.1635 14.1001 17.5664 14.503 17.5664 15.0001C17.5664 15.4972 17.1635 15.9001 16.6664 15.9001L3.33311 15.9001C2.83605 15.9001 2.43311 15.4972 2.43311 15.0001ZM3.33311 9.1001C2.83605 9.1001 2.43311 9.50304 2.43311 10.0001C2.43311 10.4972 2.83605 10.9001 3.33311 10.9001L16.6664 10.9001C17.1635 10.9001 17.5664 10.4972 17.5664 10.0001C17.5664 9.50304 17.1635 9.1001 16.6664 9.1001L3.33311 9.1001Z" fill=""></path>
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
<label for="taskCheckbox4" class="w-full cursor-pointer">
|
||||
<div class="relative flex items-start">
|
||||
<input type="checkbox" id="taskCheckbox4" class="sr-only taskCheckbox">
|
||||
<div class="flex items-center justify-center w-full h-5 mr-3 border border-gray-300 rounded-md box max-w-5 dark:border-gray-700">
|
||||
<span class="opacity-0">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.6668 3.5L5.25016 9.91667L2.3335 7" stroke="white" stroke-width="1.94437" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<p class="-mt-0.5 text-base text-gray-800 dark:text-white/90">
|
||||
Work In Progress (WIP) Dashboard
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col-reverse items-start justify-end w-full gap-3 xl:flex-row xl:items-center xl:gap-5">
|
||||
<div class="flex items-center justify-between w-full gap-5 xl:w-auto xl:justify-normal">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33329 1.0835C5.74751 1.0835 6.08329 1.41928 6.08329 1.8335V2.25016L9.91663 2.25016V1.8335C9.91663 1.41928 10.2524 1.0835 10.6666 1.0835C11.0808 1.0835 11.4166 1.41928 11.4166 1.8335V2.25016L12.3333 2.25016C13.2998 2.25016 14.0833 3.03366 14.0833 4.00016V6.00016L14.0833 12.6668C14.0833 13.6333 13.2998 14.4168 12.3333 14.4168L3.66663 14.4168C2.70013 14.4168 1.91663 13.6333 1.91663 12.6668L1.91663 6.00016L1.91663 4.00016C1.91663 3.03366 2.70013 2.25016 3.66663 2.25016L4.58329 2.25016V1.8335C4.58329 1.41928 4.91908 1.0835 5.33329 1.0835ZM5.33329 3.75016L3.66663 3.75016C3.52855 3.75016 3.41663 3.86209 3.41663 4.00016V5.25016L12.5833 5.25016V4.00016C12.5833 3.86209 12.4714 3.75016 12.3333 3.75016L10.6666 3.75016L5.33329 3.75016ZM12.5833 6.75016L3.41663 6.75016L3.41663 12.6668C3.41663 12.8049 3.52855 12.9168 3.66663 12.9168L12.3333 12.9168C12.4714 12.9168 12.5833 12.8049 12.5833 12.6668L12.5833 6.75016Z" fill=""></path>
|
||||
</svg>
|
||||
Today
|
||||
</span>
|
||||
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="stroke-current" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 15.6343C12.6244 15.6343 15.5625 12.6961 15.5625 9.07178C15.5625 5.44741 12.6244 2.50928 9 2.50928C5.37563 2.50928 2.4375 5.44741 2.4375 9.07178C2.4375 10.884 3.17203 12.5246 4.35961 13.7122L2.4375 15.6343H9Z" stroke="" stroke-width="1.5" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
|
||||
1
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-full max-w-6 overflow-hidden rounded-full border-[0.5px] border-gray-200 dark:border-gray-800">
|
||||
<img src="src/images/user/user-09.jpg" alt="user">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- task item -->
|
||||
<div draggable="true" class="p-5 bg-white border border-gray-200 task rounded-xl shadow-theme-sm dark:border-gray-800 dark:bg-white/5">
|
||||
<div class="flex flex-col gap-5 xl:flex-row xl:items-center xl:justify-between">
|
||||
<div class="flex items-start w-full gap-4">
|
||||
<span class="text-gray-400">
|
||||
<svg class="fill-current" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.43311 5.0001C2.43311 4.50304 2.83605 4.1001 3.33311 4.1001L16.6664 4.1001C17.1635 4.1001 17.5664 4.50304 17.5664 5.0001C17.5664 5.49715 17.1635 5.9001 16.6664 5.9001L3.33311 5.9001C2.83605 5.9001 2.43311 5.49716 2.43311 5.0001ZM2.43311 15.0001C2.43311 14.503 2.83605 14.1001 3.33311 14.1001L16.6664 14.1001C17.1635 14.1001 17.5664 14.503 17.5664 15.0001C17.5664 15.4972 17.1635 15.9001 16.6664 15.9001L3.33311 15.9001C2.83605 15.9001 2.43311 15.4972 2.43311 15.0001ZM3.33311 9.1001C2.83605 9.1001 2.43311 9.50304 2.43311 10.0001C2.43311 10.4972 2.83605 10.9001 3.33311 10.9001L16.6664 10.9001C17.1635 10.9001 17.5664 10.4972 17.5664 10.0001C17.5664 9.50304 17.1635 9.1001 16.6664 9.1001L3.33311 9.1001Z" fill=""></path>
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
<label for="taskCheckbox5" class="w-full cursor-pointer">
|
||||
<div class="relative flex items-start">
|
||||
<input type="checkbox" id="taskCheckbox5" class="sr-only taskCheckbox">
|
||||
<div class="flex items-center justify-center w-full h-5 mr-3 border border-gray-300 rounded-md box max-w-5 dark:border-gray-700">
|
||||
<span class="opacity-0">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.6668 3.5L5.25016 9.91667L2.3335 7" stroke="white" stroke-width="1.94437" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<p class="-mt-0.5 text-base text-gray-800 dark:text-white/90">
|
||||
Kanban Flow Manager
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col-reverse items-start justify-end w-full gap-3 xl:flex-row xl:items-center xl:gap-5">
|
||||
<span class="inline-flex rounded-full bg-success-50 px-2 py-0.5 text-theme-xs font-medium text-success-700 dark:bg-success-500/15 dark:text-success-500">
|
||||
Template
|
||||
</span>
|
||||
|
||||
<div class="flex items-center justify-between w-full gap-5 xl:w-auto xl:justify-normal">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33329 1.0835C5.74751 1.0835 6.08329 1.41928 6.08329 1.8335V2.25016L9.91663 2.25016V1.8335C9.91663 1.41928 10.2524 1.0835 10.6666 1.0835C11.0808 1.0835 11.4166 1.41928 11.4166 1.8335V2.25016L12.3333 2.25016C13.2998 2.25016 14.0833 3.03366 14.0833 4.00016V6.00016L14.0833 12.6668C14.0833 13.6333 13.2998 14.4168 12.3333 14.4168L3.66663 14.4168C2.70013 14.4168 1.91663 13.6333 1.91663 12.6668L1.91663 6.00016L1.91663 4.00016C1.91663 3.03366 2.70013 2.25016 3.66663 2.25016L4.58329 2.25016V1.8335C4.58329 1.41928 4.91908 1.0835 5.33329 1.0835ZM5.33329 3.75016L3.66663 3.75016C3.52855 3.75016 3.41663 3.86209 3.41663 4.00016V5.25016L12.5833 5.25016V4.00016C12.5833 3.86209 12.4714 3.75016 12.3333 3.75016L10.6666 3.75016L5.33329 3.75016ZM12.5833 6.75016L3.41663 6.75016L3.41663 12.6668C3.41663 12.8049 3.52855 12.9168 3.66663 12.9168L12.3333 12.9168C12.4714 12.9168 12.5833 12.8049 12.5833 12.6668L12.5833 6.75016Z" fill=""></path>
|
||||
</svg>
|
||||
Feb 12, 2027
|
||||
</span>
|
||||
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="stroke-current" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 15.6343C12.6244 15.6343 15.5625 12.6961 15.5625 9.07178C15.5625 5.44741 12.6244 2.50928 9 2.50928C5.37563 2.50928 2.4375 5.44741 2.4375 9.07178C2.4375 10.884 3.17203 12.5246 4.35961 13.7122L2.4375 15.6343H9Z" stroke="" stroke-width="1.5" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
|
||||
8
|
||||
</span>
|
||||
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.88066 3.10905C8.54039 1.44932 11.2313 1.44933 12.8911 3.10906C14.5508 4.76878 14.5508 7.45973 12.8911 9.11946L12.0657 9.94479L11.0051 8.88413L11.8304 8.0588C12.9043 6.98486 12.9043 5.24366 11.8304 4.16972C10.7565 3.09577 9.01526 3.09577 7.94132 4.16971L7.11599 4.99504L6.05533 3.93438L6.88066 3.10905ZM8.88376 11.0055L9.94442 12.0661L9.11983 12.8907C7.4601 14.5504 4.76915 14.5504 3.10942 12.8907C1.44969 11.231 1.44969 8.54002 3.10942 6.88029L3.93401 6.0557L4.99467 7.11636L4.17008 7.94095C3.09614 9.01489 3.09614 10.7561 4.17008 11.83C5.24402 12.904 6.98522 12.904 8.05917 11.83L8.88376 11.0055ZM9.94458 7.11599C10.2375 6.8231 10.2375 6.34823 9.94458 6.05533C9.65169 5.76244 9.17682 5.76244 8.88392 6.05533L6.0555 8.88376C5.7626 9.17665 5.7626 9.65153 6.0555 9.94442C6.34839 10.2373 6.82326 10.2373 7.11616 9.94442L9.94458 7.11599Z" fill=""></path>
|
||||
</svg>
|
||||
|
||||
2
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-full max-w-6 overflow-hidden rounded-full border-[0.5px] border-gray-200 dark:border-gray-800">
|
||||
<img src="src/images/user/user-10.jpg" alt="user">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- task item -->
|
||||
<div draggable="true" class="p-5 bg-white border border-gray-200 task rounded-xl shadow-theme-sm dark:border-gray-800 dark:bg-white/5">
|
||||
<div class="flex flex-col gap-5 xl:flex-row xl:items-center xl:justify-between">
|
||||
<div class="flex items-start w-full gap-4">
|
||||
<span class="text-gray-400">
|
||||
<svg class="fill-current" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.43311 5.0001C2.43311 4.50304 2.83605 4.1001 3.33311 4.1001L16.6664 4.1001C17.1635 4.1001 17.5664 4.50304 17.5664 5.0001C17.5664 5.49715 17.1635 5.9001 16.6664 5.9001L3.33311 5.9001C2.83605 5.9001 2.43311 5.49716 2.43311 5.0001ZM2.43311 15.0001C2.43311 14.503 2.83605 14.1001 3.33311 14.1001L16.6664 14.1001C17.1635 14.1001 17.5664 14.503 17.5664 15.0001C17.5664 15.4972 17.1635 15.9001 16.6664 15.9001L3.33311 15.9001C2.83605 15.9001 2.43311 15.4972 2.43311 15.0001ZM3.33311 9.1001C2.83605 9.1001 2.43311 9.50304 2.43311 10.0001C2.43311 10.4972 2.83605 10.9001 3.33311 10.9001L16.6664 10.9001C17.1635 10.9001 17.5664 10.4972 17.5664 10.0001C17.5664 9.50304 17.1635 9.1001 16.6664 9.1001L3.33311 9.1001Z" fill=""></path>
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
<label for="taskCheckbox6" class="w-full cursor-pointer">
|
||||
<div class="relative flex items-start">
|
||||
<input type="checkbox" id="taskCheckbox6" class="sr-only taskCheckbox">
|
||||
<div class="flex items-center justify-center w-full h-5 mr-3 border border-gray-300 rounded-md box max-w-5 dark:border-gray-700">
|
||||
<span class="opacity-0">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.6668 3.5L5.25016 9.91667L2.3335 7" stroke="white" stroke-width="1.94437" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<p class="-mt-0.5 text-base text-gray-800 dark:text-white/90">
|
||||
Product Update - Q4 2024
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col-reverse items-start justify-end w-full gap-3 xl:flex-row xl:items-center xl:gap-5">
|
||||
<div class="flex items-center justify-between w-full gap-5 xl:w-auto xl:justify-normal">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33329 1.0835C5.74751 1.0835 6.08329 1.41928 6.08329 1.8335V2.25016L9.91663 2.25016V1.8335C9.91663 1.41928 10.2524 1.0835 10.6666 1.0835C11.0808 1.0835 11.4166 1.41928 11.4166 1.8335V2.25016L12.3333 2.25016C13.2998 2.25016 14.0833 3.03366 14.0833 4.00016V6.00016L14.0833 12.6668C14.0833 13.6333 13.2998 14.4168 12.3333 14.4168L3.66663 14.4168C2.70013 14.4168 1.91663 13.6333 1.91663 12.6668L1.91663 6.00016L1.91663 4.00016C1.91663 3.03366 2.70013 2.25016 3.66663 2.25016L4.58329 2.25016V1.8335C4.58329 1.41928 4.91908 1.0835 5.33329 1.0835ZM5.33329 3.75016L3.66663 3.75016C3.52855 3.75016 3.41663 3.86209 3.41663 4.00016V5.25016L12.5833 5.25016V4.00016C12.5833 3.86209 12.4714 3.75016 12.3333 3.75016L10.6666 3.75016L5.33329 3.75016ZM12.5833 6.75016L3.41663 6.75016L3.41663 12.6668C3.41663 12.8049 3.52855 12.9168 3.66663 12.9168L12.3333 12.9168C12.4714 12.9168 12.5833 12.8049 12.5833 12.6668L12.5833 6.75016Z" fill=""></path>
|
||||
</svg>
|
||||
Feb 12, 2027
|
||||
</span>
|
||||
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="stroke-current" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 15.6343C12.6244 15.6343 15.5625 12.6961 15.5625 9.07178C15.5625 5.44741 12.6244 2.50928 9 2.50928C5.37563 2.50928 2.4375 5.44741 2.4375 9.07178C2.4375 10.884 3.17203 12.5246 4.35961 13.7122L2.4375 15.6343H9Z" stroke="" stroke-width="1.5" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
|
||||
8
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-full max-w-6 overflow-hidden rounded-full border-[0.5px] border-gray-200 dark:border-gray-800">
|
||||
<img src="src/images/user/user-11.jpg" alt="user">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- task item -->
|
||||
<div draggable="true" class="p-5 bg-white border border-gray-200 task rounded-xl shadow-theme-sm dark:border-gray-800 dark:bg-white/5">
|
||||
<div class="flex flex-col gap-5 xl:flex-row xl:items-center xl:justify-between">
|
||||
<div class="flex items-start w-full gap-4">
|
||||
<span class="text-gray-400">
|
||||
<svg class="fill-current" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.43311 5.0001C2.43311 4.50304 2.83605 4.1001 3.33311 4.1001L16.6664 4.1001C17.1635 4.1001 17.5664 4.50304 17.5664 5.0001C17.5664 5.49715 17.1635 5.9001 16.6664 5.9001L3.33311 5.9001C2.83605 5.9001 2.43311 5.49716 2.43311 5.0001ZM2.43311 15.0001C2.43311 14.503 2.83605 14.1001 3.33311 14.1001L16.6664 14.1001C17.1635 14.1001 17.5664 14.503 17.5664 15.0001C17.5664 15.4972 17.1635 15.9001 16.6664 15.9001L3.33311 15.9001C2.83605 15.9001 2.43311 15.4972 2.43311 15.0001ZM3.33311 9.1001C2.83605 9.1001 2.43311 9.50304 2.43311 10.0001C2.43311 10.4972 2.83605 10.9001 3.33311 10.9001L16.6664 10.9001C17.1635 10.9001 17.5664 10.4972 17.5664 10.0001C17.5664 9.50304 17.1635 9.1001 16.6664 9.1001L3.33311 9.1001Z" fill=""></path>
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
<label for="taskCheckbox7" class="w-full cursor-pointer">
|
||||
<div class="relative flex items-start">
|
||||
<input type="checkbox" id="taskCheckbox7" class="sr-only taskCheckbox">
|
||||
<div class="flex items-center justify-center w-full h-5 mr-3 border border-gray-300 rounded-md box max-w-5 dark:border-gray-700">
|
||||
<span class="opacity-0">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.6668 3.5L5.25016 9.91667L2.3335 7" stroke="white" stroke-width="1.94437" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<p class="-mt-0.5 text-base text-gray-800 dark:text-white/90">
|
||||
Make figbot send comment when ticket is auto-moved
|
||||
back to inbox
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col-reverse items-start justify-end w-full gap-3 xl:flex-row xl:items-center xl:gap-5">
|
||||
<div class="flex items-center justify-between w-full gap-5 xl:w-auto xl:justify-normal">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33329 1.0835C5.74751 1.0835 6.08329 1.41928 6.08329 1.8335V2.25016L9.91663 2.25016V1.8335C9.91663 1.41928 10.2524 1.0835 10.6666 1.0835C11.0808 1.0835 11.4166 1.41928 11.4166 1.8335V2.25016L12.3333 2.25016C13.2998 2.25016 14.0833 3.03366 14.0833 4.00016V6.00016L14.0833 12.6668C14.0833 13.6333 13.2998 14.4168 12.3333 14.4168L3.66663 14.4168C2.70013 14.4168 1.91663 13.6333 1.91663 12.6668L1.91663 6.00016L1.91663 4.00016C1.91663 3.03366 2.70013 2.25016 3.66663 2.25016L4.58329 2.25016V1.8335C4.58329 1.41928 4.91908 1.0835 5.33329 1.0835ZM5.33329 3.75016L3.66663 3.75016C3.52855 3.75016 3.41663 3.86209 3.41663 4.00016V5.25016L12.5833 5.25016V4.00016C12.5833 3.86209 12.4714 3.75016 12.3333 3.75016L10.6666 3.75016L5.33329 3.75016ZM12.5833 6.75016L3.41663 6.75016L3.41663 12.6668C3.41663 12.8049 3.52855 12.9168 3.66663 12.9168L12.3333 12.9168C12.4714 12.9168 12.5833 12.8049 12.5833 12.6668L12.5833 6.75016Z" fill=""></path>
|
||||
</svg>
|
||||
Mar 08, 2027
|
||||
</span>
|
||||
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="stroke-current" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 15.6343C12.6244 15.6343 15.5625 12.6961 15.5625 9.07178C15.5625 5.44741 12.6244 2.50928 9 2.50928C5.37563 2.50928 2.4375 5.44741 2.4375 9.07178C2.4375 10.884 3.17203 12.5246 4.35961 13.7122L2.4375 15.6343H9Z" stroke="" stroke-width="1.5" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
|
||||
1
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-full max-w-6 overflow-hidden rounded-full border-[0.5px] border-gray-200 dark:border-gray-800">
|
||||
<img src="src/images/user/user-12.jpg" alt="user">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Completed list -->
|
||||
<div class="flex flex-col gap-4 swim-lane">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h3 class="flex items-center gap-3 text-base font-medium text-gray-800 dark:text-white/90">
|
||||
Completed
|
||||
<span class="inline-flex rounded-full bg-success-50 px-2 py-0.5 text-theme-xs font-medium text-success-700 dark:bg-success-500/15 dark:text-success-500">
|
||||
4
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<div x-data="{openDropDown: false}" class="relative">
|
||||
<button @click="openDropDown = !openDropDown" class="text-gray-700 dark:text-gray-400">
|
||||
<svg class="fill-current" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.99902 10.2451C6.96552 10.2451 7.74902 11.0286 7.74902 11.9951V12.0051C7.74902 12.9716 6.96552 13.7551 5.99902 13.7551C5.03253 13.7551 4.24902 12.9716 4.24902 12.0051V11.9951C4.24902 11.0286 5.03253 10.2451 5.99902 10.2451ZM17.999 10.2451C18.9655 10.2451 19.749 11.0286 19.749 11.9951V12.0051C19.749 12.9716 18.9655 13.7551 17.999 13.7551C17.0325 13.7551 16.249 12.9716 16.249 12.0051V11.9951C16.249 11.0286 17.0325 10.2451 17.999 10.2451ZM13.749 11.9951C13.749 11.0286 12.9655 10.2451 11.999 10.2451C11.0325 10.2451 10.249 11.0286 10.249 11.9951V12.0051C10.249 12.9716 11.0325 13.7551 11.999 13.7551C12.9655 13.7551 13.749 12.9716 13.749 12.0051V11.9951Z" fill=""></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div x-show="openDropDown" @click.outside="openDropDown = false" class="absolute right-0 top-full z-40 w-[140px] space-y-1 rounded-2xl border border-gray-200 bg-white p-2 shadow-theme-md dark:border-gray-800 dark:bg-gray-dark" style="display: none;">
|
||||
<button class="flex w-full px-3 py-2 font-medium text-left text-gray-500 rounded-lg text-theme-xs hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300">
|
||||
Edit
|
||||
</button>
|
||||
<button class="flex w-full px-3 py-2 font-medium text-left text-gray-500 rounded-lg text-theme-xs hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300">
|
||||
Delete
|
||||
</button>
|
||||
<button class="flex w-full px-3 py-2 font-medium text-left text-gray-500 rounded-lg text-theme-xs hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300">
|
||||
Clear All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- task item -->
|
||||
<div draggable="true" class="p-5 bg-white border border-gray-200 task rounded-xl shadow-theme-sm dark:border-gray-800 dark:bg-white/5">
|
||||
<div class="flex flex-col gap-5 xl:flex-row xl:items-center xl:justify-between">
|
||||
<div class="flex items-start w-full gap-4">
|
||||
<span class="text-gray-400">
|
||||
<svg class="fill-current" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.43311 5.0001C2.43311 4.50304 2.83605 4.1001 3.33311 4.1001L16.6664 4.1001C17.1635 4.1001 17.5664 4.50304 17.5664 5.0001C17.5664 5.49715 17.1635 5.9001 16.6664 5.9001L3.33311 5.9001C2.83605 5.9001 2.43311 5.49716 2.43311 5.0001ZM2.43311 15.0001C2.43311 14.503 2.83605 14.1001 3.33311 14.1001L16.6664 14.1001C17.1635 14.1001 17.5664 14.503 17.5664 15.0001C17.5664 15.4972 17.1635 15.9001 16.6664 15.9001L3.33311 15.9001C2.83605 15.9001 2.43311 15.4972 2.43311 15.0001ZM3.33311 9.1001C2.83605 9.1001 2.43311 9.50304 2.43311 10.0001C2.43311 10.4972 2.83605 10.9001 3.33311 10.9001L16.6664 10.9001C17.1635 10.9001 17.5664 10.4972 17.5664 10.0001C17.5664 9.50304 17.1635 9.1001 16.6664 9.1001L3.33311 9.1001Z" fill=""></path>
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
<label for="taskCheckbox8" class="w-full cursor-pointer">
|
||||
<div class="relative flex items-start">
|
||||
<input type="checkbox" id="taskCheckbox8" class="sr-only taskCheckbox">
|
||||
<div class="flex items-center justify-center w-full h-5 mr-3 border border-gray-300 rounded-md box max-w-5 dark:border-gray-700">
|
||||
<span class="opacity-0">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.6668 3.5L5.25016 9.91667L2.3335 7" stroke="white" stroke-width="1.94437" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<p class="-mt-0.5 text-base text-gray-800 dark:text-white/90">
|
||||
Manage internal feedback
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col-reverse items-start justify-end w-full gap-3 xl:flex-row xl:items-center xl:gap-5">
|
||||
<div class="flex items-center justify-between w-full gap-5 xl:w-auto xl:justify-normal">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33329 1.0835C5.74751 1.0835 6.08329 1.41928 6.08329 1.8335V2.25016L9.91663 2.25016V1.8335C9.91663 1.41928 10.2524 1.0835 10.6666 1.0835C11.0808 1.0835 11.4166 1.41928 11.4166 1.8335V2.25016L12.3333 2.25016C13.2998 2.25016 14.0833 3.03366 14.0833 4.00016V6.00016L14.0833 12.6668C14.0833 13.6333 13.2998 14.4168 12.3333 14.4168L3.66663 14.4168C2.70013 14.4168 1.91663 13.6333 1.91663 12.6668L1.91663 6.00016L1.91663 4.00016C1.91663 3.03366 2.70013 2.25016 3.66663 2.25016L4.58329 2.25016V1.8335C4.58329 1.41928 4.91908 1.0835 5.33329 1.0835ZM5.33329 3.75016L3.66663 3.75016C3.52855 3.75016 3.41663 3.86209 3.41663 4.00016V5.25016L12.5833 5.25016V4.00016C12.5833 3.86209 12.4714 3.75016 12.3333 3.75016L10.6666 3.75016L5.33329 3.75016ZM12.5833 6.75016L3.41663 6.75016L3.41663 12.6668C3.41663 12.8049 3.52855 12.9168 3.66663 12.9168L12.3333 12.9168C12.4714 12.9168 12.5833 12.8049 12.5833 12.6668L12.5833 6.75016Z" fill=""></path>
|
||||
</svg>
|
||||
Tomorrow
|
||||
</span>
|
||||
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="stroke-current" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 15.6343C12.6244 15.6343 15.5625 12.6961 15.5625 9.07178C15.5625 5.44741 12.6244 2.50928 9 2.50928C5.37563 2.50928 2.4375 5.44741 2.4375 9.07178C2.4375 10.884 3.17203 12.5246 4.35961 13.7122L2.4375 15.6343H9Z" stroke="" stroke-width="1.5" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
|
||||
1
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-full max-w-6 overflow-hidden rounded-full border-[0.5px] border-gray-200 dark:border-gray-800">
|
||||
<img src="src/images/user/user-09.jpg" alt="user">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- task item -->
|
||||
<div draggable="true" class="p-5 bg-white border border-gray-200 task rounded-xl shadow-theme-sm dark:border-gray-800 dark:bg-white/5">
|
||||
<div class="flex flex-col gap-5 xl:flex-row xl:items-center xl:justify-between">
|
||||
<div class="flex items-start w-full gap-4">
|
||||
<span class="text-gray-400">
|
||||
<svg class="fill-current" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.43311 5.0001C2.43311 4.50304 2.83605 4.1001 3.33311 4.1001L16.6664 4.1001C17.1635 4.1001 17.5664 4.50304 17.5664 5.0001C17.5664 5.49715 17.1635 5.9001 16.6664 5.9001L3.33311 5.9001C2.83605 5.9001 2.43311 5.49716 2.43311 5.0001ZM2.43311 15.0001C2.43311 14.503 2.83605 14.1001 3.33311 14.1001L16.6664 14.1001C17.1635 14.1001 17.5664 14.503 17.5664 15.0001C17.5664 15.4972 17.1635 15.9001 16.6664 15.9001L3.33311 15.9001C2.83605 15.9001 2.43311 15.4972 2.43311 15.0001ZM3.33311 9.1001C2.83605 9.1001 2.43311 9.50304 2.43311 10.0001C2.43311 10.4972 2.83605 10.9001 3.33311 10.9001L16.6664 10.9001C17.1635 10.9001 17.5664 10.4972 17.5664 10.0001C17.5664 9.50304 17.1635 9.1001 16.6664 9.1001L3.33311 9.1001Z" fill=""></path>
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
<label for="taskCheckbox9" class="w-full cursor-pointer">
|
||||
<div class="relative flex items-start">
|
||||
<input type="checkbox" id="taskCheckbox9" class="sr-only taskCheckbox">
|
||||
<div class="flex items-center justify-center w-full h-5 mr-3 border border-gray-300 rounded-md box max-w-5 dark:border-gray-700">
|
||||
<span class="opacity-0">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.6668 3.5L5.25016 9.91667L2.3335 7" stroke="white" stroke-width="1.94437" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<p class="-mt-0.5 text-base text-gray-800 dark:text-white/90">
|
||||
Do some projects on React Native with Flutter
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col-reverse items-start justify-end w-full gap-3 xl:flex-row xl:items-center xl:gap-5">
|
||||
<span class="inline-flex rounded-full bg-orange-400/10 px-2 py-0.5 text-theme-xs font-medium text-orange-400">
|
||||
Development
|
||||
</span>
|
||||
|
||||
<div class="flex items-center justify-between w-full gap-5 xl:w-auto xl:justify-normal">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33329 1.0835C5.74751 1.0835 6.08329 1.41928 6.08329 1.8335V2.25016L9.91663 2.25016V1.8335C9.91663 1.41928 10.2524 1.0835 10.6666 1.0835C11.0808 1.0835 11.4166 1.41928 11.4166 1.8335V2.25016L12.3333 2.25016C13.2998 2.25016 14.0833 3.03366 14.0833 4.00016V6.00016L14.0833 12.6668C14.0833 13.6333 13.2998 14.4168 12.3333 14.4168L3.66663 14.4168C2.70013 14.4168 1.91663 13.6333 1.91663 12.6668L1.91663 6.00016L1.91663 4.00016C1.91663 3.03366 2.70013 2.25016 3.66663 2.25016L4.58329 2.25016V1.8335C4.58329 1.41928 4.91908 1.0835 5.33329 1.0835ZM5.33329 3.75016L3.66663 3.75016C3.52855 3.75016 3.41663 3.86209 3.41663 4.00016V5.25016L12.5833 5.25016V4.00016C12.5833 3.86209 12.4714 3.75016 12.3333 3.75016L10.6666 3.75016L5.33329 3.75016ZM12.5833 6.75016L3.41663 6.75016L3.41663 12.6668C3.41663 12.8049 3.52855 12.9168 3.66663 12.9168L12.3333 12.9168C12.4714 12.9168 12.5833 12.8049 12.5833 12.6668L12.5833 6.75016Z" fill=""></path>
|
||||
</svg>
|
||||
Jan 8, 2027
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-full max-w-6 overflow-hidden rounded-full border-[0.5px] border-gray-200 dark:border-gray-800">
|
||||
<img src="src/images/user/user-10.jpg" alt="user">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- task item -->
|
||||
<div draggable="true" class="p-5 bg-white border border-gray-200 task rounded-xl shadow-theme-sm dark:border-gray-800 dark:bg-white/5">
|
||||
<div class="flex flex-col gap-5 xl:flex-row xl:items-center xl:justify-between">
|
||||
<div class="flex items-start w-full gap-4">
|
||||
<span class="text-gray-400">
|
||||
<svg class="fill-current" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.43311 5.0001C2.43311 4.50304 2.83605 4.1001 3.33311 4.1001L16.6664 4.1001C17.1635 4.1001 17.5664 4.50304 17.5664 5.0001C17.5664 5.49715 17.1635 5.9001 16.6664 5.9001L3.33311 5.9001C2.83605 5.9001 2.43311 5.49716 2.43311 5.0001ZM2.43311 15.0001C2.43311 14.503 2.83605 14.1001 3.33311 14.1001L16.6664 14.1001C17.1635 14.1001 17.5664 14.503 17.5664 15.0001C17.5664 15.4972 17.1635 15.9001 16.6664 15.9001L3.33311 15.9001C2.83605 15.9001 2.43311 15.4972 2.43311 15.0001ZM3.33311 9.1001C2.83605 9.1001 2.43311 9.50304 2.43311 10.0001C2.43311 10.4972 2.83605 10.9001 3.33311 10.9001L16.6664 10.9001C17.1635 10.9001 17.5664 10.4972 17.5664 10.0001C17.5664 9.50304 17.1635 9.1001 16.6664 9.1001L3.33311 9.1001Z" fill=""></path>
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
<label for="taskCheckbox10" class="w-full cursor-pointer">
|
||||
<div class="relative flex items-start">
|
||||
<input type="checkbox" id="taskCheckbox10" class="sr-only taskCheckbox">
|
||||
<div class="flex items-center justify-center w-full h-5 mr-3 border border-gray-300 rounded-md box max-w-5 dark:border-gray-700">
|
||||
<span class="opacity-0">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.6668 3.5L5.25016 9.91667L2.3335 7" stroke="white" stroke-width="1.94437" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<p class="-mt-0.5 text-base text-gray-800 dark:text-white/90">
|
||||
Design marketing assets
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col-reverse items-start justify-end w-full gap-3 xl:flex-row xl:items-center xl:gap-5">
|
||||
<span class="inline-flex rounded-full bg-brand-50 px-2 py-0.5 text-theme-xs font-medium text-brand-500 dark:bg-brand-500/15 dark:text-brand-400">
|
||||
Marketing
|
||||
</span>
|
||||
|
||||
<div class="flex items-center justify-between w-full gap-5 xl:w-auto xl:justify-normal">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33329 1.0835C5.74751 1.0835 6.08329 1.41928 6.08329 1.8335V2.25016L9.91663 2.25016V1.8335C9.91663 1.41928 10.2524 1.0835 10.6666 1.0835C11.0808 1.0835 11.4166 1.41928 11.4166 1.8335V2.25016L12.3333 2.25016C13.2998 2.25016 14.0833 3.03366 14.0833 4.00016V6.00016L14.0833 12.6668C14.0833 13.6333 13.2998 14.4168 12.3333 14.4168L3.66663 14.4168C2.70013 14.4168 1.91663 13.6333 1.91663 12.6668L1.91663 6.00016L1.91663 4.00016C1.91663 3.03366 2.70013 2.25016 3.66663 2.25016L4.58329 2.25016V1.8335C4.58329 1.41928 4.91908 1.0835 5.33329 1.0835ZM5.33329 3.75016L3.66663 3.75016C3.52855 3.75016 3.41663 3.86209 3.41663 4.00016V5.25016L12.5833 5.25016V4.00016C12.5833 3.86209 12.4714 3.75016 12.3333 3.75016L10.6666 3.75016L5.33329 3.75016ZM12.5833 6.75016L3.41663 6.75016L3.41663 12.6668C3.41663 12.8049 3.52855 12.9168 3.66663 12.9168L12.3333 12.9168C12.4714 12.9168 12.5833 12.8049 12.5833 12.6668L12.5833 6.75016Z" fill=""></path>
|
||||
</svg>
|
||||
Jan 8, 2027
|
||||
</span>
|
||||
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="stroke-current" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 15.6343C12.6244 15.6343 15.5625 12.6961 15.5625 9.07178C15.5625 5.44741 12.6244 2.50928 9 2.50928C5.37563 2.50928 2.4375 5.44741 2.4375 9.07178C2.4375 10.884 3.17203 12.5246 4.35961 13.7122L2.4375 15.6343H9Z" stroke="" stroke-width="1.5" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
|
||||
2
|
||||
</span>
|
||||
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.88066 3.10905C8.54039 1.44932 11.2313 1.44933 12.8911 3.10906C14.5508 4.76878 14.5508 7.45973 12.8911 9.11946L12.0657 9.94479L11.0051 8.88413L11.8304 8.0588C12.9043 6.98486 12.9043 5.24366 11.8304 4.16972C10.7565 3.09577 9.01526 3.09577 7.94132 4.16971L7.11599 4.99504L6.05533 3.93438L6.88066 3.10905ZM8.88376 11.0055L9.94442 12.0661L9.11983 12.8907C7.4601 14.5504 4.76915 14.5504 3.10942 12.8907C1.44969 11.231 1.44969 8.54002 3.10942 6.88029L3.93401 6.0557L4.99467 7.11636L4.17008 7.94095C3.09614 9.01489 3.09614 10.7561 4.17008 11.83C5.24402 12.904 6.98522 12.904 8.05917 11.83L8.88376 11.0055ZM9.94458 7.11599C10.2375 6.8231 10.2375 6.34823 9.94458 6.05533C9.65169 5.76244 9.17682 5.76244 8.88392 6.05533L6.0555 8.88376C5.7626 9.17665 5.7626 9.65153 6.0555 9.94442C6.34839 10.2373 6.82326 10.2373 7.11616 9.94442L9.94458 7.11599Z" fill=""></path>
|
||||
</svg>
|
||||
|
||||
1
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-full max-w-6 overflow-hidden rounded-full border-[0.5px] border-gray-200 dark:border-gray-800">
|
||||
<img src="src/images/user/user-11.jpg" alt="user">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- task item -->
|
||||
<div draggable="true" class="p-5 bg-white border border-gray-200 task rounded-xl shadow-theme-sm dark:border-gray-800 dark:bg-white/5">
|
||||
<div class="flex flex-col gap-5 xl:flex-row xl:items-center xl:justify-between">
|
||||
<div class="flex items-start w-full gap-4">
|
||||
<span class="text-gray-400">
|
||||
<svg class="fill-current" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.43311 5.0001C2.43311 4.50304 2.83605 4.1001 3.33311 4.1001L16.6664 4.1001C17.1635 4.1001 17.5664 4.50304 17.5664 5.0001C17.5664 5.49715 17.1635 5.9001 16.6664 5.9001L3.33311 5.9001C2.83605 5.9001 2.43311 5.49716 2.43311 5.0001ZM2.43311 15.0001C2.43311 14.503 2.83605 14.1001 3.33311 14.1001L16.6664 14.1001C17.1635 14.1001 17.5664 14.503 17.5664 15.0001C17.5664 15.4972 17.1635 15.9001 16.6664 15.9001L3.33311 15.9001C2.83605 15.9001 2.43311 15.4972 2.43311 15.0001ZM3.33311 9.1001C2.83605 9.1001 2.43311 9.50304 2.43311 10.0001C2.43311 10.4972 2.83605 10.9001 3.33311 10.9001L16.6664 10.9001C17.1635 10.9001 17.5664 10.4972 17.5664 10.0001C17.5664 9.50304 17.1635 9.1001 16.6664 9.1001L3.33311 9.1001Z" fill=""></path>
|
||||
</svg>
|
||||
</span>
|
||||
|
||||
<label for="taskCheckbox11" class="w-full cursor-pointer">
|
||||
<div class="relative flex items-start">
|
||||
<input type="checkbox" id="taskCheckbox11" class="sr-only taskCheckbox">
|
||||
<div class="flex items-center justify-center w-full h-5 mr-3 border border-gray-300 rounded-md box max-w-5 dark:border-gray-700">
|
||||
<span class="opacity-0">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.6668 3.5L5.25016 9.91667L2.3335 7" stroke="white" stroke-width="1.94437" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<p class="-mt-0.5 text-base text-gray-800 dark:text-white/90">
|
||||
Kanban Flow Manager
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col-reverse items-start justify-end w-full gap-3 xl:flex-row xl:items-center xl:gap-5">
|
||||
<span class="inline-flex rounded-full bg-success-50 px-2 py-0.5 text-theme-xs font-medium text-success-700 dark:bg-success-500/15 dark:text-success-500">
|
||||
Template
|
||||
</span>
|
||||
|
||||
<div class="flex items-center justify-between w-full gap-5 xl:w-auto xl:justify-normal">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="fill-current" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.33329 1.0835C5.74751 1.0835 6.08329 1.41928 6.08329 1.8335V2.25016L9.91663 2.25016V1.8335C9.91663 1.41928 10.2524 1.0835 10.6666 1.0835C11.0808 1.0835 11.4166 1.41928 11.4166 1.8335V2.25016L12.3333 2.25016C13.2998 2.25016 14.0833 3.03366 14.0833 4.00016V6.00016L14.0833 12.6668C14.0833 13.6333 13.2998 14.4168 12.3333 14.4168L3.66663 14.4168C2.70013 14.4168 1.91663 13.6333 1.91663 12.6668L1.91663 6.00016L1.91663 4.00016C1.91663 3.03366 2.70013 2.25016 3.66663 2.25016L4.58329 2.25016V1.8335C4.58329 1.41928 4.91908 1.0835 5.33329 1.0835ZM5.33329 3.75016L3.66663 3.75016C3.52855 3.75016 3.41663 3.86209 3.41663 4.00016V5.25016L12.5833 5.25016V4.00016C12.5833 3.86209 12.4714 3.75016 12.3333 3.75016L10.6666 3.75016L5.33329 3.75016ZM12.5833 6.75016L3.41663 6.75016L3.41663 12.6668C3.41663 12.8049 3.52855 12.9168 3.66663 12.9168L12.3333 12.9168C12.4714 12.9168 12.5833 12.8049 12.5833 12.6668L12.5833 6.75016Z" fill=""></path>
|
||||
</svg>
|
||||
Feb 12, 2027
|
||||
</span>
|
||||
|
||||
<span class="flex items-center gap-1 text-sm text-gray-500 cursor-pointer dark:text-gray-400">
|
||||
<svg class="stroke-current" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 15.6343C12.6244 15.6343 15.5625 12.6961 15.5625 9.07178C15.5625 5.44741 12.6244 2.50928 9 2.50928C5.37563 2.50928 2.4375 5.44741 2.4375 9.07178C2.4375 10.884 3.17203 12.5246 4.35961 13.7122L2.4375 15.6343H9Z" stroke="" stroke-width="1.5" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
|
||||
8
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="h-6 w-full max-w-6 overflow-hidden rounded-full border-[0.5px] border-gray-200 dark:border-gray-800">
|
||||
<img src="src/images/user/user-12.jpg" alt="user">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Task wrapper End -->
|
||||
</div>
|
||||
@@ -1,222 +0,0 @@
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "../../ui/table";
|
||||
|
||||
import Badge from "../../ui/badge/Badge";
|
||||
|
||||
interface Order {
|
||||
id: number;
|
||||
user: {
|
||||
image: string;
|
||||
name: string;
|
||||
role: string;
|
||||
};
|
||||
projectName: string;
|
||||
team: {
|
||||
images: string[];
|
||||
};
|
||||
status: string;
|
||||
budget: string;
|
||||
}
|
||||
|
||||
// Define the table data using the interface
|
||||
const tableData: Order[] = [
|
||||
{
|
||||
id: 1,
|
||||
user: {
|
||||
image: "/images/user/user-17.jpg",
|
||||
name: "Lindsey Curtis",
|
||||
role: "Web Designer",
|
||||
},
|
||||
projectName: "Agency Website",
|
||||
team: {
|
||||
images: [
|
||||
"/images/user/user-22.jpg",
|
||||
"/images/user/user-23.jpg",
|
||||
"/images/user/user-24.jpg",
|
||||
],
|
||||
},
|
||||
budget: "3.9K",
|
||||
status: "Active",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
user: {
|
||||
image: "/images/user/user-18.jpg",
|
||||
name: "Kaiya George",
|
||||
role: "Project Manager",
|
||||
},
|
||||
projectName: "Technology",
|
||||
team: {
|
||||
images: ["/images/user/user-25.jpg", "/images/user/user-26.jpg"],
|
||||
},
|
||||
budget: "24.9K",
|
||||
status: "Pending",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
user: {
|
||||
image: "/images/user/user-17.jpg",
|
||||
name: "Zain Geidt",
|
||||
role: "Content Writing",
|
||||
},
|
||||
projectName: "Blog Writing",
|
||||
team: {
|
||||
images: ["/images/user/user-27.jpg"],
|
||||
},
|
||||
budget: "12.7K",
|
||||
status: "Active",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
user: {
|
||||
image: "/images/user/user-20.jpg",
|
||||
name: "Abram Schleifer",
|
||||
role: "Digital Marketer",
|
||||
},
|
||||
projectName: "Social Media",
|
||||
team: {
|
||||
images: [
|
||||
"/images/user/user-28.jpg",
|
||||
"/images/user/user-29.jpg",
|
||||
"/images/user/user-30.jpg",
|
||||
],
|
||||
},
|
||||
budget: "2.8K",
|
||||
status: "Cancel",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
user: {
|
||||
image: "/images/user/user-21.jpg",
|
||||
name: "Carla George",
|
||||
role: "Front-end Developer",
|
||||
},
|
||||
projectName: "Website",
|
||||
team: {
|
||||
images: [
|
||||
"/images/user/user-31.jpg",
|
||||
"/images/user/user-32.jpg",
|
||||
"/images/user/user-33.jpg",
|
||||
],
|
||||
},
|
||||
budget: "4.5K",
|
||||
status: "Active",
|
||||
},
|
||||
];
|
||||
|
||||
export default function BasicTableOne() {
|
||||
return (
|
||||
<div className="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-white/[0.05] dark:bg-white/[0.03]">
|
||||
<div className="max-w-full overflow-x-auto">
|
||||
<Table>
|
||||
{/* Table Header */}
|
||||
<TableHeader className="border-b border-gray-100 dark:border-white/[0.05]">
|
||||
<TableRow>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
User
|
||||
</TableCell>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
Project Name
|
||||
</TableCell>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
Team
|
||||
</TableCell>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
Status
|
||||
</TableCell>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
Budget
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
{/* Table Body */}
|
||||
<TableBody className="divide-y divide-gray-100 dark:divide-white/[0.05]">
|
||||
{tableData.map((order) => (
|
||||
<TableRow key={order.id}>
|
||||
<TableCell className="px-5 py-4 sm:px-6 text-start">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 overflow-hidden rounded-full">
|
||||
<img
|
||||
width={40}
|
||||
height={40}
|
||||
src={order.user.image}
|
||||
alt={order.user.name}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span className="block font-medium text-gray-800 text-theme-sm dark:text-white/90">
|
||||
{order.user.name}
|
||||
</span>
|
||||
<span className="block text-gray-500 text-theme-xs dark:text-gray-400">
|
||||
{order.user.role}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="px-4 py-3 text-gray-500 text-start text-theme-sm dark:text-gray-400">
|
||||
{order.projectName}
|
||||
</TableCell>
|
||||
<TableCell className="px-4 py-3 text-gray-500 text-start text-theme-sm dark:text-gray-400">
|
||||
<div className="flex -space-x-2">
|
||||
{order.team.images.map((teamImage, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="w-6 h-6 overflow-hidden border-2 border-white rounded-full dark:border-gray-900"
|
||||
>
|
||||
<img
|
||||
width={24}
|
||||
height={24}
|
||||
src={teamImage}
|
||||
alt={`Team member ${index + 1}`}
|
||||
className="w-full size-6"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="px-4 py-3 text-gray-500 text-start text-theme-sm dark:text-gray-400">
|
||||
<Badge
|
||||
size="sm"
|
||||
color={
|
||||
order.status === "Active"
|
||||
? "success"
|
||||
: order.status === "Pending"
|
||||
? "warning"
|
||||
: "error"
|
||||
}
|
||||
>
|
||||
{order.status}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="px-4 py-3 text-gray-500 text-theme-sm dark:text-gray-400">
|
||||
{order.budget}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import React, { useState, ReactNode } from 'react';
|
||||
import React, { useState, useEffect, ReactNode } from 'react';
|
||||
import { ChevronDownIcon } from '../../../icons';
|
||||
|
||||
interface AccordionItemProps {
|
||||
title: string;
|
||||
children: ReactNode;
|
||||
defaultOpen?: boolean;
|
||||
forceOpen?: boolean; // External control to force open (for deep linking)
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@@ -12,10 +13,18 @@ export const AccordionItem: React.FC<AccordionItemProps> = ({
|
||||
title,
|
||||
children,
|
||||
defaultOpen = false,
|
||||
forceOpen = false,
|
||||
className = '',
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(defaultOpen);
|
||||
|
||||
// Force open when forceOpen prop changes
|
||||
useEffect(() => {
|
||||
if (forceOpen) {
|
||||
setIsOpen(true);
|
||||
}
|
||||
}, [forceOpen]);
|
||||
|
||||
return (
|
||||
<div className={`border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden ${className}`}>
|
||||
<button
|
||||
|
||||
@@ -14,6 +14,7 @@ const CaseStudies = lazy(() => import("./pages/CaseStudies"));
|
||||
const Partners = lazy(() => import("./pages/Partners"));
|
||||
const Contact = lazy(() => import("./pages/Contact"));
|
||||
const Waitlist = lazy(() => import("./pages/Waitlist"));
|
||||
const Upcoming = lazy(() => import("./pages/Upcoming"));
|
||||
|
||||
const MarketingApp: React.FC = () => {
|
||||
return (
|
||||
@@ -31,6 +32,7 @@ const MarketingApp: React.FC = () => {
|
||||
<Route path="/partners" element={<Partners />} />
|
||||
<Route path="/contact" element={<Contact />} />
|
||||
<Route path="/waitlist" element={<Waitlist />} />
|
||||
<Route path="/upcoming" element={<Upcoming />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</MarketingLayout>
|
||||
|
||||
@@ -36,6 +36,7 @@ export const footerNavGroups: { title: string; links: NavLinkItem[] }[] = [
|
||||
{ name: "Help Center", path: "/resources#help" },
|
||||
{ name: "Documentation", path: "/resources#docs" },
|
||||
{ name: "Partner Program", path: "/partners" },
|
||||
{ name: "Upcoming Features", path: "/upcoming" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -202,7 +202,7 @@ const CaseStudies: React.FC = () => {
|
||||
</section>
|
||||
|
||||
{/* FINAL CTA */}
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-[var(--color-primary)] via-[var(--color-purple)] to-[var(--color-purple-400)]">
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-[var(--color-success)] via-[var(--color-primary-dark)] to-[var(--color-primary)]">
|
||||
{/* Radial glow */}
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_50%,rgba(255,255,255,0.1),transparent_70%)]" />
|
||||
|
||||
|
||||
@@ -12,6 +12,10 @@ import {
|
||||
ArrowRightIcon,
|
||||
RocketLaunchIcon,
|
||||
ChatBubbleLeftRightIcon,
|
||||
ClipboardDocumentCheckIcon,
|
||||
CheckBadgeIcon,
|
||||
GlobeAltIcon,
|
||||
CalendarDaysIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { testimonials } from "../data/testimonials";
|
||||
import SEO from "../components/SEO";
|
||||
@@ -41,25 +45,27 @@ const Home: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
// 8-Stage Content Pipeline - matches current implementation
|
||||
const workflowSteps = [
|
||||
{ name: "Keywords", icon: ListBulletIcon, color: "from-[var(--color-primary)] to-[var(--color-primary-dark)]" },
|
||||
{ name: "Clusters", icon: UserGroupIcon, color: "from-[var(--color-purple)] to-[var(--color-purple-dark)]" },
|
||||
{ name: "Ideas", icon: LightBulbIcon, color: "from-[var(--color-warning)] to-[var(--color-warning-dark)]" },
|
||||
{ name: "Tasks", icon: DocumentTextIcon, color: "from-[var(--color-success)] to-[var(--color-success-dark)]" },
|
||||
{ name: "Content", icon: SparklesIcon, color: "from-[var(--color-primary)] to-[var(--color-primary-dark)]" },
|
||||
{ name: "Images", icon: PhotoIcon, color: "from-[var(--color-purple)] to-[var(--color-purple-dark)]" },
|
||||
{ name: "Publish", icon: BoltIcon, color: "from-[var(--color-success)] to-[var(--color-success-dark)]" },
|
||||
{ name: "Keywords", icon: ListBulletIcon, color: "from-[var(--color-primary)] to-[var(--color-primary-dark)]", description: "Import & organize" },
|
||||
{ name: "Clusters", icon: UserGroupIcon, color: "from-[var(--color-purple)] to-[var(--color-purple-dark)]", description: "Group related terms" },
|
||||
{ name: "Ideas", icon: LightBulbIcon, color: "from-[var(--color-warning)] to-[var(--color-warning-dark)]", description: "Generate concepts" },
|
||||
{ name: "Tasks", icon: ClipboardDocumentCheckIcon, color: "from-[var(--color-success)] to-[var(--color-success-dark)]", description: "Create briefs" },
|
||||
{ name: "Content", icon: DocumentTextIcon, color: "from-[var(--color-primary)] to-[var(--color-primary-dark)]", description: "AI writing" },
|
||||
{ name: "Images", icon: PhotoIcon, color: "from-[var(--color-purple)] to-[var(--color-purple-dark)]", description: "Visual generation" },
|
||||
{ name: "Review", icon: CheckBadgeIcon, color: "from-[var(--color-warning)] to-[var(--color-warning-dark)]", description: "Quality check" },
|
||||
{ name: "Published", icon: GlobeAltIcon, color: "from-[var(--color-success)] to-[var(--color-success-dark)]", description: "Go live" },
|
||||
];
|
||||
|
||||
const productModules = [
|
||||
{
|
||||
title: "Planner",
|
||||
subtitle: "Market intelligence and keyword → cluster engine",
|
||||
description: "Tap into a living keyword database, cluster at scale, and prioritize opportunities with AI scoring. Build topical maps in minutes, not days.",
|
||||
subtitle: "Keyword → Cluster → Ideas Engine (Stages 1-3)",
|
||||
description: "Import keywords via CSV or manual entry, organize them into topical clusters using AI-powered grouping, and generate content ideas with AI scoring and prioritization.",
|
||||
bullets: [
|
||||
"Living keyword database with real-time search volumes",
|
||||
"AI-powered clustering for topical authority mapping",
|
||||
"Priority scoring based on opportunity and competition",
|
||||
"CSV import or manual keyword entry with bulk operations",
|
||||
"AI-powered semantic clustering for topical authority",
|
||||
"Idea generation with scoring and brief creation",
|
||||
],
|
||||
icon: ChartBarIcon,
|
||||
color: "from-[var(--color-primary)] to-[var(--color-primary-dark)]",
|
||||
@@ -69,12 +75,12 @@ const Home: React.FC = () => {
|
||||
},
|
||||
{
|
||||
title: "Writer",
|
||||
subtitle: "AI writing system with brand rules and editorial logic",
|
||||
description: "Generate briefs, long-form articles, and on-brand messaging with contextual SERP data, tone controls, and collaboration tools.",
|
||||
subtitle: "Tasks → Content → Images → Review (Stages 4-7)",
|
||||
description: "Transform ideas into detailed content briefs, generate AI-powered articles with multiple provider options, create images with DALL-E 3, Runware, or Bria, and review content before publishing.",
|
||||
bullets: [
|
||||
"Context-aware content generation with SERP analysis",
|
||||
"Brand voice and tone controls for consistency",
|
||||
"Collaborative editing and approval workflows",
|
||||
"Multi-provider AI writing: OpenAI GPT-4o & Anthropic Claude",
|
||||
"Image generation: DALL-E 3, Runware, Bria with style presets",
|
||||
"Rich text editor with SEO optimization and review workflow",
|
||||
],
|
||||
icon: SparklesIcon,
|
||||
color: "from-[var(--color-success)] to-[var(--color-success-dark)]",
|
||||
@@ -83,30 +89,30 @@ const Home: React.FC = () => {
|
||||
align: "right",
|
||||
},
|
||||
{
|
||||
title: "Thinker",
|
||||
subtitle: "Strategic OS and thinking engine",
|
||||
description: "Centralize prompts, author voices, and brand playbooks. Sync guidelines directly into every piece of content Igny8 creates.",
|
||||
title: "Publisher",
|
||||
subtitle: "Calendar & WordPress Integration (Stage 8)",
|
||||
description: "Schedule content on a visual calendar, connect to WordPress via the IGNY8 WP Bridge plugin, and publish directly with SEO metadata, categories, and featured images.",
|
||||
bullets: [
|
||||
"Centralized prompt library and brand playbooks",
|
||||
"Author voice templates and style guides",
|
||||
"Automated guideline enforcement across all content",
|
||||
"Visual calendar with drag-and-drop scheduling",
|
||||
"One-click WordPress publishing via WP Bridge plugin",
|
||||
"Automatic SEO metadata, categories, and featured images",
|
||||
],
|
||||
icon: BoltIcon,
|
||||
icon: CalendarDaysIcon,
|
||||
color: "from-[var(--color-warning)] to-[var(--color-warning-dark)]",
|
||||
image: "thinker-dashboard.png",
|
||||
link: "/product#thinker",
|
||||
image: "publisher-dashboard.png",
|
||||
link: "/product#publisher",
|
||||
align: "left",
|
||||
},
|
||||
{
|
||||
title: "Automation",
|
||||
subtitle: "Always-on execution engine",
|
||||
description: "Orchestrate keywords to ideas, tasks to content, and assets to WordPress in automated cycles—customized to your cadence.",
|
||||
subtitle: "7-Stage Handoff Engine",
|
||||
description: "Orchestrate the entire 8-stage pipeline with automated handoffs. Configure triggers, batch sizes, and scheduling to run your content factory on autopilot.",
|
||||
bullets: [
|
||||
"End-to-end workflow automation with custom triggers",
|
||||
"Multi-step handoffs between modules",
|
||||
"Real-time monitoring and error handling",
|
||||
"7 configurable automation stages between pipeline steps",
|
||||
"Customizable batch sizes, limits, and scheduling",
|
||||
"Real-time progress monitoring and error handling",
|
||||
],
|
||||
icon: PhotoIcon,
|
||||
icon: BoltIcon,
|
||||
color: "from-[var(--color-purple)] to-[var(--color-purple-dark)]",
|
||||
image: "automation-dashboard.png",
|
||||
link: "/product#automation",
|
||||
@@ -119,10 +125,10 @@ const Home: React.FC = () => {
|
||||
<SEO meta={getMetaTags("home")} />
|
||||
<div className="bg-white">
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-[var(--color-primary)] via-[var(--color-purple)] to-[var(--color-purple-400)]">
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-[var(--color-primary)] via-[var(--color-primary-dark)] to-[var(--color-success)]">
|
||||
{/* Radial glow behind headline */}
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_30%_50%,rgba(255,255,255,0.1),transparent_60%)]" />
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_70%_20%,rgba(109,74,227,0.2),transparent_50%)]" />
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_70%_20%,rgba(var(--color-success-rgb),0.2),transparent_50%)]" />
|
||||
|
||||
<div className="relative max-w-7xl mx-auto px-6 py-20 md:py-32 lg:py-40">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16 items-center">
|
||||
@@ -153,10 +159,10 @@ const Home: React.FC = () => {
|
||||
<div className="relative z-10">
|
||||
<div className="relative scale-110 lg:scale-125">
|
||||
{/* Soft glow behind screenshot */}
|
||||
<div className="absolute -inset-8 bg-gradient-to-br from-white/30 via-[var(--color-primary)]/20 to-[var(--color-purple)]/20 rounded-3xl blur-3xl" />
|
||||
<div className="absolute -inset-6 bg-gradient-to-br from-white/20 via-[var(--color-primary)]/10 to-[var(--color-purple)]/10 rounded-3xl blur-xl" />
|
||||
{/* Device frame effect */}
|
||||
<div className="absolute -inset-4 bg-gradient-to-br from-white/20 to-white/5 rounded-3xl blur-2xl" />
|
||||
<div className="relative rounded-2xl border-4 border-white/20 bg-white/10 backdrop-blur-sm shadow-2xl overflow-hidden">
|
||||
<div className="absolute -inset-3 bg-gradient-to-br from-white/10 to-white/5 rounded-3xl blur-lg" />
|
||||
<div className="relative rounded-2xl border-4 border-white/20 bg-white/10 backdrop-blur-sm shadow-lg overflow-hidden">
|
||||
<div className="absolute top-0 left-0 right-0 h-12 bg-gradient-to-b from-gray-800/50 to-transparent flex items-center gap-2 px-4">
|
||||
<div className="flex gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-error-500/50" />
|
||||
@@ -171,7 +177,7 @@ const Home: React.FC = () => {
|
||||
/>
|
||||
</div>
|
||||
{/* Soft shadow */}
|
||||
<div className="absolute -bottom-8 -left-8 right-8 h-32 bg-gradient-to-t from-[var(--color-primary)]/20 to-transparent blur-3xl -z-10" />
|
||||
<div className="absolute -bottom-6 -left-6 right-6 h-24 bg-gradient-to-t from-[var(--color-primary)]/10 to-transparent blur-xl -z-10" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -211,26 +217,31 @@ const Home: React.FC = () => {
|
||||
How Igny8 Works
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
|
||||
A seamless pipeline from keyword discovery to published content
|
||||
An 8-stage pipeline from keyword discovery to WordPress publishing
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Horizontal Timeline */}
|
||||
{/* 8-Stage Horizontal Timeline */}
|
||||
<div className="relative">
|
||||
{/* Enhanced connecting line with shadow */}
|
||||
<div className="absolute top-14 left-0 right-0 h-1 bg-gradient-to-r from-[var(--color-primary)] via-[var(--color-purple)] via-[var(--color-warning)] via-[var(--color-success)] to-[var(--color-primary)] opacity-25 hidden md:block shadow-lg shadow-[var(--color-primary)]/20" />
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-7 gap-6 md:gap-4">
|
||||
<div className="absolute top-12 left-0 right-0 h-1 bg-gradient-to-r from-[var(--color-primary)] via-[var(--color-purple)] via-[var(--color-warning)] via-[var(--color-success)] to-[var(--color-primary)] opacity-25 hidden md:block shadow-lg shadow-[var(--color-primary)]/20" />
|
||||
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 md:grid-cols-8 gap-4 md:gap-3">
|
||||
{workflowSteps.map((step, index) => {
|
||||
const Icon = step.icon;
|
||||
return (
|
||||
<div key={step.name} className="flex flex-col items-center gap-3">
|
||||
<div className={`relative size-28 rounded-full bg-gradient-to-br ${step.color} flex items-center justify-center text-white shadow-xl z-10 group hover:scale-110 transition-transform`}>
|
||||
<Icon className="h-12 w-12" />
|
||||
<div key={step.name} className="flex flex-col items-center gap-2">
|
||||
<div className={`relative size-20 md:size-24 rounded-full bg-gradient-to-br ${step.color} flex items-center justify-center text-white shadow-xl z-10 group hover:scale-110 transition-transform`}>
|
||||
<Icon className="h-8 w-8 md:h-10 md:w-10" />
|
||||
{/* Stage number badge */}
|
||||
<div className="absolute -top-1 -right-1 size-6 rounded-full bg-white text-gray-900 text-xs font-bold flex items-center justify-center shadow-md">
|
||||
{index + 1}
|
||||
</div>
|
||||
{/* Glow effect */}
|
||||
<div className={`absolute -inset-2 bg-gradient-to-br ${step.color} rounded-full opacity-0 group-hover:opacity-30 blur-xl transition-opacity -z-10`} />
|
||||
</div>
|
||||
<span className="text-sm font-semibold text-gray-900 text-center">{step.name}</span>
|
||||
<span className="text-xs text-gray-500 text-center hidden md:block">{step.description}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@@ -258,8 +269,8 @@ const Home: React.FC = () => {
|
||||
<div className={`relative ${!isLeft ? "lg:col-start-2" : ""}`}>
|
||||
<div className="relative scale-110 lg:scale-115">
|
||||
{/* Gradient frame background with colored glow */}
|
||||
<div className={`absolute -inset-6 bg-gradient-to-br ${module.color} rounded-3xl opacity-10 blur-xl`} />
|
||||
<div className="relative rounded-2xl border-2 border-gray-200 bg-white shadow-2xl overflow-hidden">
|
||||
<div className={`absolute -inset-4 bg-gradient-to-br ${module.color} rounded-3xl opacity-10 blur-lg`} />
|
||||
<div className="relative rounded-2xl border-2 border-gray-200 bg-white shadow-md overflow-hidden">
|
||||
<div className="absolute top-0 left-0 right-0 h-12 bg-gradient-to-b from-gray-800/30 to-transparent flex items-center gap-2 px-4">
|
||||
<div className="flex gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-error-500/50" />
|
||||
@@ -326,7 +337,7 @@ const Home: React.FC = () => {
|
||||
{/* Left: Content */}
|
||||
<div className="z-10">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="size-12 rounded-xl bg-gradient-to-br from-[var(--color-primary)] to-[var(--color-purple)] flex items-center justify-center text-white shadow-lg shadow-[var(--color-primary)]/30">
|
||||
<div className="size-12 rounded-xl bg-gradient-to-br from-[var(--color-primary)] to-[var(--color-primary-dark)] flex items-center justify-center text-white shadow-sm">
|
||||
<BoltIcon className="h-6 w-6" />
|
||||
</div>
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-white">
|
||||
@@ -334,49 +345,51 @@ const Home: React.FC = () => {
|
||||
</h2>
|
||||
</div>
|
||||
<p className="text-xl text-gray-200 mb-8 leading-relaxed font-medium">
|
||||
Orchestrate your entire content pipeline with intelligent handoffs between modules. Set it once, and watch it execute.
|
||||
7 automated handoffs orchestrate your entire 8-stage content pipeline. Configure batch sizes, scheduling, and triggers to run your content factory on autopilot.
|
||||
</p>
|
||||
|
||||
{/* Automation Timeline with neon glows */}
|
||||
<div className="space-y-6 mb-8">
|
||||
|
||||
{/* 7 Automation Handoffs - clean and simple */}
|
||||
<div className="space-y-4 mb-8">
|
||||
{[
|
||||
{ from: "Keywords", to: "Clusters", icon: "→", color: "from-[var(--color-primary)] to-[var(--color-purple)]", glow: "shadow-[var(--color-primary)]/50" },
|
||||
{ from: "Clusters", to: "Ideas", icon: "→", color: "from-[var(--color-purple)] to-[var(--color-warning)]", glow: "shadow-[var(--color-purple)]/50" },
|
||||
{ from: "Ideas", to: "Tasks", icon: "→", color: "from-[var(--color-warning)] to-[var(--color-success)]", glow: "shadow-[var(--color-warning)]/50" },
|
||||
{ from: "Tasks", to: "Content", icon: "→", color: "from-[var(--color-success)] to-[var(--color-primary)]", glow: "shadow-[var(--color-success)]/50" },
|
||||
{ from: "Content", to: "Images", icon: "→", color: "from-[var(--color-primary)] to-[var(--color-purple)]", glow: "shadow-[var(--color-primary)]/50" },
|
||||
{ from: "Images", to: "Publish", icon: "→", color: "from-[var(--color-purple)] to-[var(--color-success)]", glow: "shadow-[var(--color-purple)]/50" },
|
||||
{ from: "Keywords", to: "Clusters", stage: 1, color: "from-[var(--color-primary)] to-[var(--color-primary-dark)]" },
|
||||
{ from: "Clusters", to: "Ideas", stage: 2, color: "from-[var(--color-primary)] to-[var(--color-primary-dark)]" },
|
||||
{ from: "Ideas", to: "Tasks", stage: 3, color: "from-[var(--color-primary)] to-[var(--color-primary-dark)]" },
|
||||
{ from: "Tasks", to: "Content", stage: 4, color: "from-[var(--color-primary)] to-[var(--color-primary-dark)]" },
|
||||
{ from: "Content", to: "Images", stage: 5, color: "from-[var(--color-primary)] to-[var(--color-primary-dark)]" },
|
||||
{ from: "Images", to: "Review", stage: 6, color: "from-[var(--color-primary)] to-[var(--color-primary-dark)]" },
|
||||
{ from: "Review", to: "Published", stage: 7, color: "from-[var(--color-primary)] to-[var(--color-primary-dark)]" },
|
||||
].map((handoff, i) => (
|
||||
<div key={i} className="flex items-center gap-4">
|
||||
<div className={`relative w-12 h-12 rounded-xl bg-gradient-to-br ${handoff.color} flex items-center justify-center text-white font-bold shadow-lg ${handoff.glow} group`}>
|
||||
{handoff.icon}
|
||||
{/* Neon glow effect */}
|
||||
<div className={`absolute -inset-1 bg-gradient-to-br ${handoff.color} rounded-xl opacity-0 group-hover:opacity-60 blur-md transition-opacity -z-10`} />
|
||||
<div key={i} className="flex items-center gap-3">
|
||||
<div className={`relative w-9 h-9 rounded-lg bg-gradient-to-br ${handoff.color} flex items-center justify-center text-white text-sm font-bold shadow-sm`}>
|
||||
{handoff.stage}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="text-white font-semibold text-lg">{handoff.from} → {handoff.to}</div>
|
||||
<div className="text-sm text-gray-300">Automated handoff with quality checks</div>
|
||||
<div className="text-white font-semibold">{handoff.from} → {handoff.to}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Metrics */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl p-4">
|
||||
<div className="text-3xl font-bold text-white mb-1">87%</div>
|
||||
<div className="text-sm text-gray-400">Completion Rate</div>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl p-3">
|
||||
<div className="text-2xl font-bold text-white mb-1">8</div>
|
||||
<div className="text-xs text-gray-400">Pipeline Stages</div>
|
||||
</div>
|
||||
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl p-4">
|
||||
<div className="text-3xl font-bold text-white mb-1">6×</div>
|
||||
<div className="text-sm text-gray-400">Faster Velocity</div>
|
||||
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl p-3">
|
||||
<div className="text-2xl font-bold text-white mb-1">7</div>
|
||||
<div className="text-xs text-gray-400">Auto Handoffs</div>
|
||||
</div>
|
||||
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl p-3">
|
||||
<div className="text-2xl font-bold text-white mb-1">5</div>
|
||||
<div className="text-xs text-gray-400">AI Providers</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right: Automation Dashboard Screenshot */}
|
||||
<div className="relative z-10">
|
||||
<div className="relative rounded-2xl border-2 border-white/20 bg-white/5 backdrop-blur-sm shadow-2xl overflow-hidden">
|
||||
<div className="relative rounded-2xl border-2 border-white/20 bg-white/5 backdrop-blur-sm shadow-md overflow-hidden">
|
||||
<div className="absolute top-0 left-0 right-0 h-12 bg-gradient-to-b from-gray-800/50 to-transparent flex items-center gap-2 px-4">
|
||||
<div className="flex gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-error-500/50" />
|
||||
@@ -391,7 +404,7 @@ const Home: React.FC = () => {
|
||||
/>
|
||||
</div>
|
||||
{/* Glow effect */}
|
||||
<div className="absolute -inset-4 bg-gradient-to-br from-[var(--color-primary)]/20 to-[var(--color-purple)]/20 rounded-2xl blur-2xl -z-10" />
|
||||
<div className="absolute -inset-3 bg-gradient-to-br from-[var(--color-primary)]/10 to-[var(--color-success)]/10 rounded-2xl blur-xl -z-10" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -457,7 +470,7 @@ const Home: React.FC = () => {
|
||||
</section>
|
||||
|
||||
{/* FINAL CTA */}
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-[var(--color-purple-400)] via-[var(--color-purple)] to-[var(--color-primary)]">
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-[var(--color-success)] via-[var(--color-primary-dark)] to-[var(--color-primary)]">
|
||||
{/* Dashboard overlay in background */}
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<div className="absolute inset-0 bg-[url('/marketing/images/hero-dashboard.png')] bg-cover bg-center scale-150 blur-3xl" />
|
||||
|
||||
@@ -186,7 +186,7 @@ const Partners: React.FC = () => {
|
||||
</section>
|
||||
|
||||
{/* FINAL CTA */}
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-[var(--color-primary)] via-[var(--color-purple)] to-[var(--color-purple-400)]">
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-[var(--color-success)] via-[var(--color-primary-dark)] to-[var(--color-primary)]">
|
||||
{/* Radial glow */}
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_50%,rgba(255,255,255,0.1),transparent_70%)]" />
|
||||
|
||||
|
||||
@@ -78,11 +78,12 @@ const Pricing: React.FC = () => {
|
||||
{ feature: "AI Image Generation", starter: "300 basic / 60 premium", growth: "900 basic / 180 premium", scale: "1,500 basic / 300 premium" },
|
||||
{ feature: "Featured Images", starter: true, growth: true, scale: true },
|
||||
{ feature: "In-Article Images", starter: true, growth: true, scale: true },
|
||||
{ feature: "Dual AI Providers", starter: "Basic + Premium", growth: "Basic + Premium", scale: "Basic + Premium" },
|
||||
{ feature: "AI Providers", starter: "DALL-E 3, Runware, Bria", growth: "DALL-E 3, Runware, Bria", scale: "DALL-E 3, Runware, Bria" },
|
||||
{ feature: "Credits: Basic Images", starter: "5 per image", growth: "5 per image", scale: "5 per image" },
|
||||
{ feature: "Credits: Premium Images", starter: "25 per image", growth: "25 per image", scale: "25 per image" },
|
||||
{ feature: "Multiple Image Sizes", starter: true, growth: true, scale: true },
|
||||
{ feature: "Automatic Alt Text", starter: true, growth: true, scale: true },
|
||||
{ feature: "Smart Image Prompts", starter: "300", growth: "900", scale: "1,500" },
|
||||
{ feature: "Advanced Image Controls", starter: true, growth: true, scale: true },
|
||||
{ feature: "Style Presets", starter: true, growth: true, scale: true },
|
||||
|
||||
{ feature: "AUTOMATION PIPELINE", starter: null, growth: null, scale: null, isCategory: true },
|
||||
{ feature: "7-Stage Automation Pipeline", starter: true, growth: true, scale: true },
|
||||
@@ -488,7 +489,7 @@ const Pricing: React.FC = () => {
|
||||
</section>
|
||||
|
||||
{/* FINAL CTA */}
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-[var(--color-primary)] via-[var(--color-purple)] to-[var(--color-purple-400)]">
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-[var(--color-success)] via-[var(--color-primary-dark)] to-[var(--color-primary)]">
|
||||
{/* Radial glow */}
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_50%,rgba(255,255,255,0.1),transparent_70%)]" />
|
||||
|
||||
|
||||
@@ -12,6 +12,10 @@ import {
|
||||
ArrowRightIcon,
|
||||
RocketLaunchIcon,
|
||||
ChatBubbleLeftRightIcon,
|
||||
ClipboardDocumentCheckIcon,
|
||||
CheckBadgeIcon,
|
||||
GlobeAltIcon,
|
||||
CalendarDaysIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { testimonials } from "../data/testimonials";
|
||||
import SEO from "../components/SEO";
|
||||
@@ -41,26 +45,29 @@ const Product: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
// 8-Stage Content Pipeline - matches current implementation
|
||||
const workflowSteps = [
|
||||
{ name: "Keywords", icon: ListBulletIcon, color: "from-[var(--color-primary)] to-[var(--color-primary-dark)]" },
|
||||
{ name: "Clusters", icon: UserGroupIcon, color: "from-[var(--color-purple)] to-[var(--color-purple-dark)]" },
|
||||
{ name: "Ideas", icon: LightBulbIcon, color: "from-[var(--color-warning)] to-[var(--color-warning-dark)]" },
|
||||
{ name: "Tasks", icon: DocumentTextIcon, color: "from-[var(--color-success)] to-[var(--color-success-dark)]" },
|
||||
{ name: "Content", icon: SparklesIcon, color: "from-[var(--color-primary)] to-[var(--color-primary-dark)]" },
|
||||
{ name: "Publish", icon: BoltIcon, color: "from-[var(--color-success)] to-[var(--color-success-dark)]" },
|
||||
{ name: "Keywords", icon: ListBulletIcon, color: "from-[var(--color-primary)] to-[var(--color-primary-dark)]", description: "Import & organize" },
|
||||
{ name: "Clusters", icon: UserGroupIcon, color: "from-[var(--color-purple)] to-[var(--color-purple-dark)]", description: "Group related terms" },
|
||||
{ name: "Ideas", icon: LightBulbIcon, color: "from-[var(--color-warning)] to-[var(--color-warning-dark)]", description: "Generate concepts" },
|
||||
{ name: "Tasks", icon: ClipboardDocumentCheckIcon, color: "from-[var(--color-success)] to-[var(--color-success-dark)]", description: "Create briefs" },
|
||||
{ name: "Content", icon: DocumentTextIcon, color: "from-[var(--color-primary)] to-[var(--color-primary-dark)]", description: "AI writing" },
|
||||
{ name: "Images", icon: PhotoIcon, color: "from-[var(--color-purple)] to-[var(--color-purple-dark)]", description: "Visual generation" },
|
||||
{ name: "Review", icon: CheckBadgeIcon, color: "from-[var(--color-warning)] to-[var(--color-warning-dark)]", description: "Quality check" },
|
||||
{ name: "Published", icon: GlobeAltIcon, color: "from-[var(--color-success)] to-[var(--color-success-dark)]", description: "Go live" },
|
||||
];
|
||||
|
||||
const productModules = [
|
||||
{
|
||||
title: "Planner",
|
||||
subtitle: "Market intelligence and keyword → cluster engine",
|
||||
description: "Tap into a living keyword database with real-time search volumes, difficulty scores, and intent classification. Use AI-powered clustering to automatically group related keywords into strategic clusters. Build topical authority maps in minutes, not days.",
|
||||
subtitle: "Keyword → Cluster → Ideas Engine (Stages 1-3)",
|
||||
description: "Import keywords via CSV or manual entry, organize them into topical clusters using AI-powered semantic grouping, and generate content ideas with AI scoring and prioritization. Build your content foundation systematically.",
|
||||
features: [
|
||||
"Global keyword database with opportunity scoring, SERP overlays, and traffic estimates",
|
||||
"CSV import or manual keyword entry with bulk operations and tagging",
|
||||
"AI-powered semantic clustering that groups related keywords automatically",
|
||||
"Drag-and-drop topical mapping with visual cluster organization",
|
||||
"Priority scoring based on opportunity, competition, and search intent",
|
||||
"Alerts for emerging opportunities, competitive gaps, and seasonality shifts",
|
||||
"Idea generation with AI scoring based on search intent and opportunity",
|
||||
"Brief creation workflow to transform ideas into actionable content plans",
|
||||
"Pipeline view showing content flow from keywords through to publication",
|
||||
],
|
||||
icon: ChartBarIcon,
|
||||
color: "from-[var(--color-primary)] to-[var(--color-primary-dark)]",
|
||||
@@ -69,14 +76,14 @@ const Product: React.FC = () => {
|
||||
},
|
||||
{
|
||||
title: "Writer",
|
||||
subtitle: "AI writing system with brand rules and editorial logic",
|
||||
description: "Generate briefs, long-form articles, and on-brand messaging with contextual SERP data, tone controls, and collaboration tools. Create content that aligns with your brand voice, compliance rules, and target search results.",
|
||||
subtitle: "Tasks → Content → Images → Review (Stages 4-7)",
|
||||
description: "Transform ideas into detailed content briefs, generate AI-powered articles with multiple provider options, create images with DALL-E 3, Runware, or Bria, and review content before publishing.",
|
||||
features: [
|
||||
"AI briefs with outlines, talking points, and internal link suggestions",
|
||||
"Context-aware content generation with SERP analysis and competitor insights",
|
||||
"Long-form drafts aligned to your brand voice, compliance rules, and target SERP",
|
||||
"Brand voice and tone controls for consistency across all content",
|
||||
"Editorial workspace with comments, approvals, and WordPress publishing",
|
||||
"Multi-provider AI writing: OpenAI GPT-4o and Anthropic Claude support",
|
||||
"Image generation with DALL-E 3, Runware, and Bria AI providers",
|
||||
"Rich text editor with formatting, SEO metadata, and preview modes",
|
||||
"Token-based credit system for text, fixed credits for images (5 basic, 25 premium)",
|
||||
"Review workflow with quality checks before moving to publication",
|
||||
],
|
||||
icon: SparklesIcon,
|
||||
color: "from-[var(--color-success)] to-[var(--color-success-dark)]",
|
||||
@@ -84,33 +91,33 @@ const Product: React.FC = () => {
|
||||
link: "#writer",
|
||||
},
|
||||
{
|
||||
title: "Thinker",
|
||||
subtitle: "Strategic OS and thinking engine",
|
||||
description: "Centralize prompts, author voices, and brand playbooks. Sync guidelines directly into every piece of content Igny8 creates. Manage AI instructions, writing styles, and content strategies in one place.",
|
||||
title: "Publisher",
|
||||
subtitle: "Calendar & WordPress Integration (Stage 8)",
|
||||
description: "Schedule content on a visual calendar, connect to WordPress via the IGNY8 WP Bridge plugin, and publish directly with SEO metadata, categories, tags, and featured images automatically configured.",
|
||||
features: [
|
||||
"Centralized prompt library and brand playbooks with version control",
|
||||
"Author voice templates and style guides for consistent content",
|
||||
"Automated guideline enforcement across all content generation",
|
||||
"Governance dashboards showing who generated what, when, and with which inputs",
|
||||
"Version control for AI instructions and playbooks across teams",
|
||||
"Visual calendar with drag-and-drop scheduling for content planning",
|
||||
"One-click WordPress publishing via the IGNY8 WP Bridge plugin",
|
||||
"Automatic SEO metadata, categories, tags, and featured image assignment",
|
||||
"Multi-site WordPress management for agencies and publishers",
|
||||
"Publication status tracking with draft, scheduled, and published states",
|
||||
],
|
||||
icon: BoltIcon,
|
||||
icon: CalendarDaysIcon,
|
||||
color: "from-[var(--color-warning)] to-[var(--color-warning-dark)]",
|
||||
image: "thinker-dashboard.png",
|
||||
link: "#thinker",
|
||||
image: "publisher-dashboard.png",
|
||||
link: "#publisher",
|
||||
},
|
||||
{
|
||||
title: "Automation",
|
||||
subtitle: "Always-on execution engine",
|
||||
description: "Orchestrate keywords to ideas, tasks to content, and assets to WordPress in automated cycles—customized to your cadence. Set it once, and watch it execute with intelligent handoffs between modules.",
|
||||
subtitle: "7-Stage Handoff Engine",
|
||||
description: "Orchestrate the entire 8-stage pipeline with 7 automated handoffs. Configure triggers, batch sizes, and scheduling to run your content factory on autopilot with real-time progress monitoring.",
|
||||
features: [
|
||||
"Schedule keywords → ideas → tasks → content → images in fully automated cycles",
|
||||
"End-to-end workflow automation with custom triggers and conditions",
|
||||
"Trigger workflows based on SERP movements, pipeline bottlenecks, or credit availability",
|
||||
"Multi-step handoffs between modules with quality checks at each stage",
|
||||
"Monitor every automation with audit logs and manual override controls",
|
||||
"7 configurable automation stages: Keywords→Clusters→Ideas→Tasks→Content→Images→Review→Published",
|
||||
"Customizable batch sizes, limits, and scheduling for each handoff",
|
||||
"Real-time progress monitoring with success/error tracking",
|
||||
"Enable/disable individual stages for hybrid manual-auto workflows",
|
||||
"Automation dashboard with pipeline visualization and controls",
|
||||
],
|
||||
icon: PhotoIcon,
|
||||
icon: BoltIcon,
|
||||
color: "from-[var(--color-purple)] to-[var(--color-purple-dark)]",
|
||||
image: "automation-dashboard.png",
|
||||
link: "#automation",
|
||||
@@ -153,29 +160,34 @@ const Product: React.FC = () => {
|
||||
<div className="max-w-7xl mx-auto px-6">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-gray-900 mb-4">
|
||||
How the IGNY8 Engine Works
|
||||
The 8-Stage Content Pipeline
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
|
||||
A seamless pipeline from keyword discovery to published content
|
||||
From keyword discovery to WordPress publishing in one unified workflow
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Horizontal Timeline */}
|
||||
|
||||
{/* 8-Stage Horizontal Timeline */}
|
||||
<div className="relative">
|
||||
{/* Enhanced connecting line with shadow */}
|
||||
<div className="absolute top-14 left-0 right-0 h-1 bg-gradient-to-r from-[var(--color-primary)] via-[var(--color-purple)] via-[var(--color-warning)] via-[var(--color-success)] to-[var(--color-primary)] opacity-25 hidden md:block shadow-lg shadow-[var(--color-primary)]/20" />
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-6 gap-6 md:gap-4">
|
||||
<div className="absolute top-12 left-0 right-0 h-1 bg-gradient-to-r from-[var(--color-primary)] via-[var(--color-purple)] via-[var(--color-warning)] via-[var(--color-success)] to-[var(--color-primary)] opacity-25 hidden md:block shadow-lg shadow-[var(--color-primary)]/20" />
|
||||
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 md:grid-cols-8 gap-4 md:gap-3">
|
||||
{workflowSteps.map((step, index) => {
|
||||
const Icon = step.icon;
|
||||
return (
|
||||
<div key={step.name} className="flex flex-col items-center gap-3">
|
||||
<div className={`relative size-28 rounded-full bg-gradient-to-br ${step.color} flex items-center justify-center text-white shadow-xl z-10 group hover:scale-110 transition-transform`}>
|
||||
<Icon className="h-12 w-12" />
|
||||
<div key={step.name} className="flex flex-col items-center gap-2">
|
||||
<div className={`relative size-20 md:size-24 rounded-full bg-gradient-to-br ${step.color} flex items-center justify-center text-white shadow-xl z-10 group hover:scale-110 transition-transform`}>
|
||||
<Icon className="h-8 w-8 md:h-10 md:w-10" />
|
||||
{/* Stage number badge */}
|
||||
<div className="absolute -top-1 -right-1 size-6 rounded-full bg-white text-gray-900 text-xs font-bold flex items-center justify-center shadow-md">
|
||||
{index + 1}
|
||||
</div>
|
||||
{/* Glow effect */}
|
||||
<div className={`absolute -inset-2 bg-gradient-to-br ${step.color} rounded-full opacity-0 group-hover:opacity-30 blur-xl transition-opacity -z-10`} />
|
||||
</div>
|
||||
<span className="text-sm font-semibold text-gray-900 text-center">{step.name}</span>
|
||||
<span className="text-xs text-gray-500 text-center hidden md:block">{step.description}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@@ -279,28 +291,28 @@ const Product: React.FC = () => {
|
||||
</h2>
|
||||
</div>
|
||||
<p className="text-xl text-gray-200 mb-8 leading-relaxed font-medium">
|
||||
Orchestrate your entire content pipeline with intelligent handoffs between modules. Set it once, and watch it execute with compounding value at each stage.
|
||||
7 automated handoffs orchestrate your entire 8-stage content pipeline. Configure batch sizes, scheduling, and triggers to run your content factory on autopilot.
|
||||
</p>
|
||||
|
||||
{/* Automation Timeline with neon glows */}
|
||||
<div className="space-y-6">
|
||||
|
||||
{/* 7 Automation Handoffs with neon glows */}
|
||||
<div className="space-y-4">
|
||||
{[
|
||||
{ from: "Keywords", to: "Clusters", icon: "→", color: "from-[var(--color-primary)] to-[var(--color-purple)]", glow: "shadow-[var(--color-primary)]/50" },
|
||||
{ from: "Clusters", to: "Ideas", icon: "→", color: "from-[var(--color-purple)] to-[var(--color-warning)]", glow: "shadow-[var(--color-purple)]/50" },
|
||||
{ from: "Ideas", to: "Tasks", icon: "→", color: "from-[var(--color-warning)] to-[var(--color-success)]", glow: "shadow-[var(--color-warning)]/50" },
|
||||
{ from: "Tasks", to: "Content", icon: "→", color: "from-[var(--color-success)] to-[var(--color-primary)]", glow: "shadow-[var(--color-success)]/50" },
|
||||
{ from: "Content", to: "Images", icon: "→", color: "from-[var(--color-primary)] to-[var(--color-purple)]", glow: "shadow-[var(--color-primary)]/50" },
|
||||
{ from: "Images", to: "Publish", icon: "→", color: "from-[var(--color-purple)] to-[var(--color-success)]", glow: "shadow-[var(--color-purple)]/50" },
|
||||
{ from: "Keywords", to: "Clusters", stage: 1, color: "from-[var(--color-primary)] to-[var(--color-purple)]", glow: "shadow-[var(--color-primary)]/50" },
|
||||
{ from: "Clusters", to: "Ideas", stage: 2, color: "from-[var(--color-purple)] to-[var(--color-warning)]", glow: "shadow-[var(--color-purple)]/50" },
|
||||
{ from: "Ideas", to: "Tasks", stage: 3, color: "from-[var(--color-warning)] to-[var(--color-success)]", glow: "shadow-[var(--color-warning)]/50" },
|
||||
{ from: "Tasks", to: "Content", stage: 4, color: "from-[var(--color-success)] to-[var(--color-primary)]", glow: "shadow-[var(--color-success)]/50" },
|
||||
{ from: "Content", to: "Images", stage: 5, color: "from-[var(--color-primary)] to-[var(--color-purple)]", glow: "shadow-[var(--color-primary)]/50" },
|
||||
{ from: "Images", to: "Review", stage: 6, color: "from-[var(--color-purple)] to-[var(--color-warning)]", glow: "shadow-[var(--color-purple)]/50" },
|
||||
{ from: "Review", to: "Published", stage: 7, color: "from-[var(--color-warning)] to-[var(--color-success)]", glow: "shadow-[var(--color-warning)]/50" },
|
||||
].map((handoff, i) => (
|
||||
<div key={i} className="flex items-center gap-4">
|
||||
<div className={`relative w-12 h-12 rounded-xl bg-gradient-to-br ${handoff.color} flex items-center justify-center text-white font-bold shadow-lg ${handoff.glow} group`}>
|
||||
{handoff.icon}
|
||||
<div key={i} className="flex items-center gap-3">
|
||||
<div className={`relative w-10 h-10 rounded-lg bg-gradient-to-br ${handoff.color} flex items-center justify-center text-white text-sm font-bold shadow-lg ${handoff.glow} group`}>
|
||||
{handoff.stage}
|
||||
{/* Neon glow effect */}
|
||||
<div className={`absolute -inset-1 bg-gradient-to-br ${handoff.color} rounded-xl opacity-0 group-hover:opacity-60 blur-md transition-opacity -z-10`} />
|
||||
<div className={`absolute -inset-1 bg-gradient-to-br ${handoff.color} rounded-lg opacity-0 group-hover:opacity-60 blur-md transition-opacity -z-10`} />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="text-white font-semibold text-lg">{handoff.from} → {handoff.to}</div>
|
||||
<div className="text-sm text-gray-300">Automated handoff with quality checks</div>
|
||||
<div className="text-white font-semibold">{handoff.from} → {handoff.to}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -328,17 +340,23 @@ const Product: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Performance KPIs */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-2xl mx-auto">
|
||||
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl p-6">
|
||||
<div className="text-4xl font-bold text-white mb-2">87%</div>
|
||||
<div className="text-sm text-gray-400">Completion Rate</div>
|
||||
<div className="text-xs text-gray-500 mt-2">Average automation success across all workflows</div>
|
||||
{/* Platform Stats */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 max-w-4xl mx-auto">
|
||||
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl p-4 text-center">
|
||||
<div className="text-3xl font-bold text-white mb-1">8</div>
|
||||
<div className="text-xs text-gray-400">Pipeline Stages</div>
|
||||
</div>
|
||||
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl p-6">
|
||||
<div className="text-4xl font-bold text-white mb-2">6×</div>
|
||||
<div className="text-sm text-gray-400">Faster Velocity</div>
|
||||
<div className="text-xs text-gray-500 mt-2">Content creation speed compared to manual processes</div>
|
||||
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl p-4 text-center">
|
||||
<div className="text-3xl font-bold text-white mb-1">7</div>
|
||||
<div className="text-xs text-gray-400">Auto Handoffs</div>
|
||||
</div>
|
||||
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl p-4 text-center">
|
||||
<div className="text-3xl font-bold text-white mb-1">5</div>
|
||||
<div className="text-xs text-gray-400">AI Providers</div>
|
||||
</div>
|
||||
<div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-xl p-4 text-center">
|
||||
<div className="text-3xl font-bold text-white mb-1">3</div>
|
||||
<div className="text-xs text-gray-400">Payment Methods</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -404,7 +422,7 @@ const Product: React.FC = () => {
|
||||
</section>
|
||||
|
||||
{/* FINAL CTA */}
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-[var(--color-primary)] via-[var(--color-purple)] to-[var(--color-purple-400)]">
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-[var(--color-success)] via-[var(--color-primary-dark)] to-[var(--color-primary)]">
|
||||
{/* Dashboard overlay in background */}
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<div className="absolute inset-0 bg-[url('/marketing/images/hero-dashboard.png')] bg-cover bg-center scale-150 blur-3xl" />
|
||||
|
||||
@@ -53,9 +53,10 @@ const Solutions: React.FC = () => {
|
||||
"Manual image sourcing and WordPress publishing bottlenecks",
|
||||
],
|
||||
outcomes: [
|
||||
"Launch keyword → content automation that protects brand voice",
|
||||
"Keep editors in control with approvals and Thinker playbooks",
|
||||
"Automate image generation and WordPress publishing by site",
|
||||
"Launch 8-stage automation pipeline protecting brand voice",
|
||||
"Keep editors in control with review workflow and approval gates",
|
||||
"Automate image generation (DALL-E 3, Runware) and WordPress publishing",
|
||||
"Use content calendar for scheduled publishing across sites",
|
||||
],
|
||||
color: "from-[var(--color-primary)]/5 to-white",
|
||||
borderColor: "border-[var(--color-primary)]/20",
|
||||
@@ -73,9 +74,10 @@ const Solutions: React.FC = () => {
|
||||
"Client-specific brand guidelines and tone management",
|
||||
],
|
||||
outcomes: [
|
||||
"Shared workspaces for each client with automation templates",
|
||||
"Real-time dashboards to prove impact and showcase velocity",
|
||||
"Reusable Thinker libraries to standardize tone and strategy",
|
||||
"Shared workspaces for each client with custom automation configs",
|
||||
"Real-time dashboards showing pipeline progress and content velocity",
|
||||
"Reusable prompts and brand voice settings per client account",
|
||||
"Multi-site WordPress publishing with client-specific credentials",
|
||||
],
|
||||
color: "from-[var(--color-success)]/5 to-white",
|
||||
borderColor: "border-[var(--color-success)]/20",
|
||||
@@ -93,9 +95,10 @@ const Solutions: React.FC = () => {
|
||||
"Tool sprawl creating visibility gaps",
|
||||
],
|
||||
outcomes: [
|
||||
"Automated pipeline from keyword intake to published content",
|
||||
"Dashboards that unite SEO, writers, designers, and leadership",
|
||||
"Insights to reallocate focus when campaigns spike or drop",
|
||||
"Automated 8-stage pipeline from keyword intake to WordPress publishing",
|
||||
"Unified dashboard showing SEO, content, images, and publication metrics",
|
||||
"Credit-based usage tracking to optimize spend and resource allocation",
|
||||
"Publisher module for content calendar and scheduled publishing",
|
||||
],
|
||||
color: "from-[var(--color-purple)]/5 to-white",
|
||||
borderColor: "border-[var(--color-purple)]/20",
|
||||
@@ -348,7 +351,7 @@ const Solutions: React.FC = () => {
|
||||
</section>
|
||||
|
||||
{/* FINAL CTA */}
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-[var(--color-primary)] via-[var(--color-purple)] to-[var(--color-purple-400)]">
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-[var(--color-success)] via-[var(--color-primary-dark)] to-[var(--color-primary)]">
|
||||
{/* Radial glow */}
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_50%,rgba(255,255,255,0.1),transparent_70%)]" />
|
||||
|
||||
|
||||
@@ -6,27 +6,33 @@ import { getMetaTags } from "../config/metaTags";
|
||||
|
||||
const tourSteps = [
|
||||
{
|
||||
title: "Kick off in the Dashboard",
|
||||
title: "Dashboard: Your Command Center",
|
||||
description:
|
||||
"Get instant visibility into automation coverage, credit usage, and pipeline health with filters for every site and team.",
|
||||
"Get instant visibility into your 8-stage pipeline, credit usage, and workflow progress. Track keywords through to published content with real-time metrics.",
|
||||
image: "tour-dash.png",
|
||||
},
|
||||
{
|
||||
title: "Research in Planner",
|
||||
title: "Planner: Keywords → Clusters → Ideas (Stages 1-3)",
|
||||
description:
|
||||
"Explore the global keyword graph, build clustering blueprints, and score opportunities with AI to set your roadmap.",
|
||||
"Import keywords via CSV or database, organize them into semantic clusters using AI, and generate content ideas. Build your content strategy foundation.",
|
||||
image: "tour-planner.png",
|
||||
},
|
||||
{
|
||||
title: "Generate briefs and drafts in Writer",
|
||||
title: "Writer: Tasks → Content → Images → Review (Stages 4-7)",
|
||||
description:
|
||||
"Create detailed briefs, assign tasks, and produce on-brand drafts tuned to your tone, format, and competitive insights.",
|
||||
"Convert ideas to tasks, generate AI content with GPT-4o or Claude, create images with DALL-E 3 or Runware, and review before publishing.",
|
||||
image: "tour-writer.png",
|
||||
},
|
||||
{
|
||||
title: "Automate delivery",
|
||||
title: "Publisher: Schedule & Publish to WordPress (Stage 8)",
|
||||
description:
|
||||
"Configure recipes that move keywords to ideas, content, and imagery. Publish to WordPress or notify your CMS automatically.",
|
||||
"Use the visual calendar to schedule content, then publish to WordPress via the IGNY8 WP Bridge plugin with one click. SEO metadata and images sync automatically.",
|
||||
image: "tour-automation.png",
|
||||
},
|
||||
{
|
||||
title: "Automation: 7-Stage Handoff Engine",
|
||||
description:
|
||||
"Configure automated handoffs between each stage. Set batch sizes, schedules, and triggers. Run your content factory on autopilot with real-time monitoring.",
|
||||
image: "tour-automation.png",
|
||||
},
|
||||
];
|
||||
@@ -86,18 +92,19 @@ const Tour: React.FC = () => {
|
||||
<section className="bg-gradient-to-br from-[var(--color-primary)]/10 via-gray-50/70 to-[var(--color-success)]/10 border-y border-[var(--color-primary)]/20">
|
||||
<div className="max-w-6xl mx-auto px-6 py-24 space-y-10">
|
||||
<SectionHeading
|
||||
eyebrow="Automation recipes"
|
||||
title="Pre-built workflows you can launch on day one."
|
||||
description="Activate automation templates or customize them in minutes. Igny8 tracks dependencies, statuses, and handoffs."
|
||||
eyebrow="7 Automation Handoffs"
|
||||
title="Orchestrate your 8-stage pipeline on autopilot."
|
||||
description="Configure each handoff independently. Set batch sizes, schedules, and credit limits. IGNY8 tracks every item through the pipeline."
|
||||
/>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 text-sm text-gray-600">
|
||||
{[
|
||||
{ name: "Keywords → Ideas", time: "Nightly", highlight: "Targets new opportunities", color: "border-[var(--color-primary)]/40", gradient: "from-[var(--color-primary)]/10 to-white" },
|
||||
{ name: "Ideas → Tasks", time: "Daily", highlight: "Staff writers automatically", color: "border-[var(--color-success)]/40", gradient: "from-[var(--color-success)]/10 to-white" },
|
||||
{ name: "Tasks → Content", time: "Hourly", highlight: "Generate & queue drafts", color: "border-[var(--color-warning)]/40", gradient: "from-[var(--color-warning)]/10 to-white" },
|
||||
{ name: "Content → Images", time: "On approval", highlight: "Produce branded visuals", color: "border-[var(--color-purple)]/40", gradient: "from-[var(--color-purple)]/10 to-white" },
|
||||
{ name: "Content → WordPress", time: "Manual launch", highlight: "One-click publish", color: "border-[var(--color-primary)]/40", gradient: "from-[var(--color-primary)]/10 to-white" },
|
||||
{ name: "SERP Win/Loss Alerts", time: "Real-time", highlight: "Trigger optimizations", color: "border-[var(--color-success)]/40", gradient: "from-[var(--color-success)]/10 to-white" },
|
||||
{ name: "Keywords → Clusters", time: "Stage 1", highlight: "Auto-cluster by AI similarity", color: "border-[var(--color-primary)]/40", gradient: "from-[var(--color-primary)]/10 to-white" },
|
||||
{ name: "Clusters → Ideas", time: "Stage 2", highlight: "Generate content concepts", color: "border-[var(--color-purple)]/40", gradient: "from-[var(--color-purple)]/10 to-white" },
|
||||
{ name: "Ideas → Tasks", time: "Stage 3", highlight: "Create writing assignments", color: "border-[var(--color-warning)]/40", gradient: "from-[var(--color-warning)]/10 to-white" },
|
||||
{ name: "Tasks → Content", time: "Stage 4", highlight: "Generate AI articles", color: "border-[var(--color-success)]/40", gradient: "from-[var(--color-success)]/10 to-white" },
|
||||
{ name: "Content → Images", time: "Stage 5", highlight: "Create featured & in-article images", color: "border-[var(--color-primary)]/40", gradient: "from-[var(--color-primary)]/10 to-white" },
|
||||
{ name: "Images → Review", time: "Stage 6", highlight: "Queue for editorial approval", color: "border-[var(--color-purple)]/40", gradient: "from-[var(--color-purple)]/10 to-white" },
|
||||
{ name: "Review → Published", time: "Stage 7", highlight: "Publish to WordPress", color: "border-[var(--color-success)]/40", gradient: "from-[var(--color-success)]/10 to-white" },
|
||||
].map((recipe) => (
|
||||
<div
|
||||
key={recipe.name}
|
||||
|
||||
332
frontend/src/marketing/pages/Upcoming.tsx
Normal file
332
frontend/src/marketing/pages/Upcoming.tsx
Normal file
@@ -0,0 +1,332 @@
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import {
|
||||
LinkIcon,
|
||||
ShoppingBagIcon,
|
||||
BriefcaseIcon,
|
||||
ArrowTrendingUpIcon,
|
||||
ShareIcon,
|
||||
VideoCameraIcon,
|
||||
SparklesIcon,
|
||||
BuildingStorefrontIcon,
|
||||
PencilSquareIcon,
|
||||
ChartBarIcon,
|
||||
GlobeAltIcon,
|
||||
RocketLaunchIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import SEO from "../components/SEO";
|
||||
import { getMetaTags } from "../config/metaTags";
|
||||
|
||||
const Upcoming: React.FC = () => {
|
||||
const renderCta = (cta: { label: string; href: string }, className: string) => {
|
||||
const isExternal = cta.href.startsWith("http");
|
||||
|
||||
if (isExternal) {
|
||||
return (
|
||||
<a
|
||||
href={cta.href}
|
||||
className={className}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{cta.label}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link to={cta.href} className={className}>
|
||||
{cta.label}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
const upcomingFeatures = [
|
||||
{
|
||||
phase: "Launching February 2026",
|
||||
badge: "Launching Soon",
|
||||
badgeColor: "bg-gradient-to-r from-[var(--color-success)] to-[var(--color-success-dark)] text-white",
|
||||
borderColor: "border-[var(--color-success)]/30",
|
||||
features: [
|
||||
{
|
||||
icon: LinkIcon,
|
||||
title: "Linker Module",
|
||||
subtitle: "Intelligent Internal & External Linking",
|
||||
description: "Automatically create strategic internal links between related content based on your keyword clusters. Build topical authority with AI-powered link suggestions that understand semantic relationships.",
|
||||
bullets: [
|
||||
"Internal linking mapped to keyword clusters",
|
||||
"External link suggestions to authoritative sources",
|
||||
"Anchor text optimization for SEO",
|
||||
"Link health monitoring and broken link detection",
|
||||
"Cross-site linking for multi-brand portfolios",
|
||||
],
|
||||
color: "from-[var(--color-primary)] to-[var(--color-primary-dark)]",
|
||||
},
|
||||
{
|
||||
icon: ArrowTrendingUpIcon,
|
||||
title: "Optimizer Module",
|
||||
subtitle: "Content Re-Optimization Engine",
|
||||
description: "Analyze and improve existing content performance. AI-powered recommendations for content updates, keyword optimization, and structural improvements to boost rankings.",
|
||||
bullets: [
|
||||
"Content performance analysis and scoring",
|
||||
"AI-powered rewrite suggestions",
|
||||
"Keyword gap analysis vs competitors",
|
||||
"Structure and readability improvements",
|
||||
"Auto-update stale content with fresh information",
|
||||
],
|
||||
color: "from-[var(--color-warning)] to-[var(--color-warning-dark)]",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
phase: "Launching Q2 2026",
|
||||
badge: "In Development",
|
||||
badgeColor: "bg-gradient-to-r from-[var(--color-purple)] to-[var(--color-purple-dark)] text-white",
|
||||
borderColor: "border-[var(--color-purple)]/30",
|
||||
features: [
|
||||
{
|
||||
icon: ShoppingBagIcon,
|
||||
title: "Products Pages",
|
||||
subtitle: "E-commerce Product Content Generation",
|
||||
description: "Generate SEO-optimized product pages at scale. Create product descriptions, specifications, features, and benefits with AI that understands your product catalog and target audience.",
|
||||
bullets: [
|
||||
"Bulk product page generation from catalog data",
|
||||
"Product attribute optimization (specs, features, benefits)",
|
||||
"Category landing pages with clustering",
|
||||
"Product comparison pages",
|
||||
"Schema markup for rich snippets",
|
||||
],
|
||||
color: "from-[var(--color-success)] to-[var(--color-success-dark)]",
|
||||
},
|
||||
{
|
||||
icon: BriefcaseIcon,
|
||||
title: "Services Pages",
|
||||
subtitle: "Service Business Content Engine",
|
||||
description: "Create comprehensive service pages for B2B and B2C service businesses. Generate service descriptions, process explanations, pricing structures, and case studies.",
|
||||
bullets: [
|
||||
"Service description templates by industry",
|
||||
"Multi-location service pages",
|
||||
"Service area pages for local SEO",
|
||||
"Process and methodology explanations",
|
||||
"Integration with company brand guidelines",
|
||||
],
|
||||
color: "from-[var(--color-primary)] to-[var(--color-primary-dark)]",
|
||||
},
|
||||
{
|
||||
icon: BuildingStorefrontIcon,
|
||||
title: "Company Pages",
|
||||
subtitle: "Corporate Website Essentials",
|
||||
description: "Generate and optimize essential company pages: About Us, Team, Careers, Contact, FAQ. Maintain brand consistency while optimizing for search and conversion.",
|
||||
bullets: [
|
||||
"About Us, Mission, Vision, Values pages",
|
||||
"Team member profiles and bios",
|
||||
"Careers and job listing pages",
|
||||
"FAQ pages mapped to search intent",
|
||||
"Press and media pages",
|
||||
],
|
||||
color: "from-[var(--color-purple)] to-[var(--color-purple-dark)]",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
phase: "Launching Q3-Q4 2026",
|
||||
badge: "Planned",
|
||||
badgeColor: "bg-gradient-to-r from-[var(--color-warning)] to-[var(--color-warning-dark)] text-white",
|
||||
borderColor: "border-[var(--color-warning)]/30",
|
||||
features: [
|
||||
{
|
||||
icon: ShareIcon,
|
||||
title: "Socializer Module",
|
||||
subtitle: "Multi-Platform Social Publishing",
|
||||
description: "Automatically publish content to top 5 social platforms. Generate platform-specific posts, images, and captions optimized for each channel. Schedule and manage all social content from one place.",
|
||||
bullets: [
|
||||
"Auto-post to LinkedIn, Twitter/X, Facebook, Instagram, TikTok",
|
||||
"Platform-specific content adaptation (tone, length, format)",
|
||||
"Social media calendar integrated with content calendar",
|
||||
"Image optimization per platform specs",
|
||||
"Engagement tracking and analytics",
|
||||
],
|
||||
color: "from-[var(--color-success)] to-[var(--color-success-dark)]",
|
||||
},
|
||||
{
|
||||
icon: VideoCameraIcon,
|
||||
title: "Video Content Creator",
|
||||
subtitle: "AI Video Generation & Publishing",
|
||||
description: "Generate video content from articles. Create short-form videos for social (Reels, Shorts, TikTok) and long-form for YouTube. Automated video publishing to video platforms.",
|
||||
bullets: [
|
||||
"Article-to-video conversion with AI voiceover",
|
||||
"Short-form video for Reels, Shorts, TikTok",
|
||||
"Long-form video for YouTube with chapters",
|
||||
"Video SEO optimization (titles, descriptions, tags)",
|
||||
"Automated publishing to video platforms",
|
||||
],
|
||||
color: "from-[var(--color-primary)] to-[var(--color-primary-dark)]",
|
||||
},
|
||||
{
|
||||
icon: SparklesIcon,
|
||||
title: "Site Builder",
|
||||
subtitle: "The SEO Holy Grail",
|
||||
description: "Revolutionary site builder that creates fully optimized, content-rich websites from scratch. Automated architecture planning, content generation, design selection, and deployment.",
|
||||
bullets: [
|
||||
"Automated site architecture based on keyword research",
|
||||
"Full website content generation in minutes",
|
||||
"SEO-optimized templates and design systems",
|
||||
"Internal linking structure built-in",
|
||||
"One-click deployment to hosting",
|
||||
],
|
||||
color: "from-[var(--color-warning)] to-[var(--color-warning-dark)]",
|
||||
},
|
||||
{
|
||||
icon: ChartBarIcon,
|
||||
title: "Advanced Analytics",
|
||||
subtitle: "Performance Insights & Reporting",
|
||||
description: "Deep analytics for content performance, keyword rankings, traffic attribution, and ROI tracking. Custom reports and dashboards for agencies and enterprises.",
|
||||
bullets: [
|
||||
"Keyword ranking tracking integration",
|
||||
"Content performance analytics",
|
||||
"Traffic and conversion attribution",
|
||||
"Custom white-label reports",
|
||||
"API access for data export",
|
||||
],
|
||||
color: "from-[var(--color-purple)] to-[var(--color-purple-dark)]",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SEO meta={getMetaTags("home")} />
|
||||
<div className="bg-white">
|
||||
{/* HERO SECTION */}
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-[var(--color-primary)] via-[var(--color-primary-dark)] to-[var(--color-success)]">
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_30%_50%,rgba(255,255,255,0.1),transparent_60%)]" />
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_70%_20%,rgba(var(--color-success-rgb),0.2),transparent_50%)]" />
|
||||
|
||||
<div className="relative max-w-7xl mx-auto px-6 py-20 md:py-32">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<div className="inline-flex items-center gap-2 px-4 py-2 bg-white/10 backdrop-blur-sm rounded-full border border-white/20 text-white text-sm mb-6">
|
||||
<RocketLaunchIcon className="w-4 h-4" />
|
||||
<span className="font-medium">Innovation Roadmap</span>
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold text-white mb-6 leading-tight">
|
||||
What's Coming to IGNY8
|
||||
</h1>
|
||||
|
||||
<p className="text-xl md:text-2xl text-white/90 mb-8 leading-relaxed">
|
||||
We're building the future of AI-powered content marketing. Here's what's launching in 2026.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-4">
|
||||
{renderCta(
|
||||
{ label: "Start Creating Today", href: "https://app.igny8.com/signup" },
|
||||
"px-8 py-4 bg-white text-[var(--color-primary)] rounded-lg font-semibold hover:bg-gray-100 transition-colors shadow-lg"
|
||||
)}
|
||||
{renderCta(
|
||||
{ label: "View Current Features", href: "/product" },
|
||||
"px-8 py-4 bg-white/10 backdrop-blur-sm text-white rounded-lg font-semibold hover:bg-white/20 transition-colors border border-white/20"
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* UPCOMING FEATURES TIMELINE */}
|
||||
{upcomingFeatures.map((phase, phaseIndex) => (
|
||||
<section key={phase.phase} className={`py-20 ${phaseIndex % 2 === 0 ? 'bg-white' : 'bg-gray-50'}`}>
|
||||
<div className="max-w-7xl mx-auto px-6">
|
||||
{/* Phase Header */}
|
||||
<div className="text-center mb-12">
|
||||
<div className="inline-flex items-center gap-3 mb-4">
|
||||
<span className={`px-6 py-2 rounded-full text-sm font-bold ${phase.badgeColor} shadow-lg`}>
|
||||
{phase.badge}
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-4">
|
||||
{phase.phase}
|
||||
</h2>
|
||||
<div className="w-24 h-1 bg-gradient-to-r from-[var(--color-primary)] to-[var(--color-purple)] mx-auto rounded-full" />
|
||||
</div>
|
||||
|
||||
{/* Features Grid */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
{phase.features.map((feature, index) => {
|
||||
const Icon = feature.icon;
|
||||
return (
|
||||
<div
|
||||
key={feature.title}
|
||||
className={`group relative bg-white rounded-2xl border-2 ${phase.borderColor} p-8 hover:shadow-2xl transition-all duration-300 hover:-translate-y-1`}
|
||||
>
|
||||
{/* Icon & Badge */}
|
||||
<div className="flex items-start justify-between mb-6">
|
||||
<div className={`p-4 rounded-xl bg-gradient-to-br ${feature.color} text-white shadow-lg group-hover:scale-110 transition-transform`}>
|
||||
<Icon className="w-8 h-8" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Title & Subtitle */}
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-2">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p className="text-sm font-medium text-gray-500 mb-4">
|
||||
{feature.subtitle}
|
||||
</p>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-gray-600 mb-6 leading-relaxed">
|
||||
{feature.description}
|
||||
</p>
|
||||
|
||||
{/* Bullet Points */}
|
||||
<ul className="space-y-3">
|
||||
{feature.bullets.map((bullet, bulletIndex) => (
|
||||
<li key={bulletIndex} className="flex items-start gap-3">
|
||||
<div className={`w-1.5 h-1.5 rounded-full bg-gradient-to-br ${feature.color} mt-2 flex-shrink-0`} />
|
||||
<span className="text-sm text-gray-600">{bullet}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{/* Hover Accent */}
|
||||
<div className={`absolute inset-0 rounded-2xl bg-gradient-to-br ${feature.color} opacity-0 group-hover:opacity-5 transition-opacity -z-10`} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
|
||||
{/* CTA SECTION */}
|
||||
<section className="relative overflow-hidden bg-gradient-to-br from-[var(--color-success)] via-[var(--color-primary-dark)] to-[var(--color-primary)]">
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<div className="absolute inset-0 bg-[url('/marketing/images/hero-dashboard.png')] bg-cover bg-center scale-150 blur-3xl" />
|
||||
</div>
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_50%,rgba(255,255,255,0.1),transparent_70%)]" />
|
||||
|
||||
<div className="relative max-w-4xl mx-auto px-6 py-24 md:py-32 text-center z-10">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-white mb-6">
|
||||
Start Creating Content Today
|
||||
</h2>
|
||||
<p className="text-xl text-white/90 mb-8 leading-relaxed">
|
||||
Don't wait for tomorrow's features. Start using IGNY8's powerful 8-stage pipeline today and scale your content production.
|
||||
</p>
|
||||
<div className="flex flex-wrap justify-center gap-4">
|
||||
{renderCta(
|
||||
{ label: "Get Started Free", href: "https://app.igny8.com/signup" },
|
||||
"px-8 py-4 bg-white text-[var(--color-primary)] rounded-lg font-semibold hover:bg-gray-100 transition-colors shadow-lg text-lg"
|
||||
)}
|
||||
{renderCta(
|
||||
{ label: "View Pricing", href: "/pricing" },
|
||||
"px-8 py-4 bg-white/10 backdrop-blur-sm text-white rounded-lg font-semibold hover:bg-white/20 transition-colors border border-white/20 text-lg"
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Upcoming;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -120,7 +120,7 @@ export default function IndustriesSectorsKeywords() {
|
||||
|
||||
try {
|
||||
// Get already-attached keywords across ALL sectors for this site
|
||||
let attachedSeedKeywordIds = new Set<number>();
|
||||
const attachedSeedKeywordIds = new Set<number>();
|
||||
try {
|
||||
const { fetchKeywords, fetchSiteSectors } = await import('../../services/api');
|
||||
// Get all sectors for the site
|
||||
|
||||
@@ -410,7 +410,7 @@ export default function SiteSettings() {
|
||||
const loadIndustries = async () => {
|
||||
try {
|
||||
const response = await fetchIndustries();
|
||||
let allIndustries = response.industries || [];
|
||||
const allIndustries = response.industries || [];
|
||||
|
||||
// Note: For existing sites with industries already configured,
|
||||
// we show ALL industries so users can change their selection.
|
||||
|
||||
@@ -227,14 +227,14 @@ export async function fetchAPI(endpoint: string, options?: RequestInit & { timeo
|
||||
}
|
||||
|
||||
// Throw authentication error
|
||||
let err: any = new Error(errorMessage);
|
||||
const err: any = new Error(errorMessage);
|
||||
err.status = 403;
|
||||
err.data = errorData;
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Not an auth error - could be permissions/plan issue - don't force logout
|
||||
let err: any = new Error(errorMessage);
|
||||
const err: any = new Error(errorMessage);
|
||||
err.status = 403;
|
||||
err.data = errorData;
|
||||
throw err;
|
||||
@@ -243,7 +243,7 @@ export async function fetchAPI(endpoint: string, options?: RequestInit & { timeo
|
||||
if (e.status === 403) throw e;
|
||||
|
||||
// Parsing failed - throw generic 403 error
|
||||
let err: any = new Error(text || response.statusText);
|
||||
const err: any = new Error(text || response.statusText);
|
||||
err.status = 403;
|
||||
throw err;
|
||||
}
|
||||
@@ -251,7 +251,7 @@ export async function fetchAPI(endpoint: string, options?: RequestInit & { timeo
|
||||
|
||||
// Handle 402 Payment Required - plan/limits issue
|
||||
if (response.status === 402) {
|
||||
let err: any = new Error(response.statusText);
|
||||
const err: any = new Error(response.statusText);
|
||||
err.status = response.status;
|
||||
try {
|
||||
const parsed = text ? JSON.parse(text) : null;
|
||||
@@ -295,7 +295,7 @@ export async function fetchAPI(endpoint: string, options?: RequestInit & { timeo
|
||||
logout();
|
||||
|
||||
// Throw error to stop request processing
|
||||
let err: any = new Error(errorData.error || 'Session ended');
|
||||
const err: any = new Error(errorData.error || 'Session ended');
|
||||
err.status = 401;
|
||||
err.data = errorData;
|
||||
throw err;
|
||||
@@ -368,7 +368,7 @@ export async function fetchAPI(endpoint: string, options?: RequestInit & { timeo
|
||||
return null;
|
||||
} else {
|
||||
// Retry failed - parse and throw the retry error (not the original 401)
|
||||
let retryError: any = new Error(retryResponse.statusText);
|
||||
const retryError: any = new Error(retryResponse.statusText);
|
||||
retryError.status = retryResponse.status;
|
||||
try {
|
||||
const retryErrorData = JSON.parse(retryText);
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Module Settings Test</title>
|
||||
<style>
|
||||
body { font-family: Arial; padding: 20px; }
|
||||
.success { color: green; }
|
||||
.error { color: red; }
|
||||
.info { color: blue; }
|
||||
pre { background: #f5f5f5; padding: 10px; border-radius: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Module Settings API Test</h1>
|
||||
<button onclick="testAPI()">Test API Endpoint</button>
|
||||
<div id="result"></div>
|
||||
|
||||
<script>
|
||||
async function testAPI() {
|
||||
const resultDiv = document.getElementById('result');
|
||||
resultDiv.innerHTML = '<p class="info">Testing...</p>';
|
||||
|
||||
try {
|
||||
const response = await fetch('https://api.igny8.com/v1/system/settings/modules/enable/', {
|
||||
headers: {
|
||||
'Authorization': 'Bearer YOUR_TOKEN_HERE' // Replace with actual token
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
resultDiv.innerHTML = `
|
||||
<h2 class="success">✓ Success!</h2>
|
||||
<h3>Raw Response:</h3>
|
||||
<pre>${JSON.stringify(data, null, 2)}</pre>
|
||||
|
||||
<h3>Module Status:</h3>
|
||||
<ul>
|
||||
<li>Planner: ${data.data?.planner_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
|
||||
<li>Writer: ${data.data?.writer_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
|
||||
<li>Thinker: ${data.data?.thinker_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
|
||||
<li>Automation: ${data.data?.automation_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
|
||||
<li>Site Builder: ${data.data?.site_builder_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
|
||||
<li>Linker: ${data.data?.linker_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
|
||||
<li>Optimizer: ${data.data?.optimizer_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
|
||||
<li>Publisher: ${data.data?.publisher_enabled ? '✅ Enabled' : '❌ Disabled'}</li>
|
||||
</ul>
|
||||
`;
|
||||
} catch (error) {
|
||||
resultDiv.innerHTML = `
|
||||
<h2 class="error">✗ Error</h2>
|
||||
<p>${error.message}</p>
|
||||
<p class="info">Note: This test requires authentication. Open browser console in your app and run:</p>
|
||||
<pre>
|
||||
// In browser console on your app:
|
||||
fetch('/v1/system/settings/modules/enable/')
|
||||
.then(r => r.json())
|
||||
.then(data => console.log(data))
|
||||
</pre>
|
||||
`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user