GLobal Styling part 1
This commit is contained in:
@@ -48,12 +48,12 @@ This file contains:
|
|||||||
```css
|
```css
|
||||||
:root {
|
:root {
|
||||||
/* ===== THE ONLY 6 HEX VALUES IN THE ENTIRE SYSTEM ===== */
|
/* ===== THE ONLY 6 HEX VALUES IN THE ENTIRE SYSTEM ===== */
|
||||||
--color-primary: #0077B6; /* Brand Blue */
|
--color-primary: #2C7AA1; /* Brand Blue - main CTA, links, primary actions */
|
||||||
--color-success: #00B894; /* Success Green */
|
--color-success: #2CA18E; /* Success Green - confirmations, positive states */
|
||||||
--color-warning: #F59E0B; /* Warning Amber */
|
--color-warning: #D9A12C; /* Warning Amber - alerts, cautions */
|
||||||
--color-danger: #DC2626; /* Error Red */
|
--color-danger: #A12C40; /* Danger Red - errors, destructive actions */
|
||||||
--color-purple: #7C3AED; /* Premium Purple */
|
--color-purple: #2C40A1; /* Purple - premium features, special emphasis */
|
||||||
--color-gray-base: #667085; /* Neutral Gray Base */
|
--color-gray-base: #667085; /* Gray Base - neutral text, borders, backgrounds */
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -186,11 +186,11 @@ echo " 08-inline-components.txt (review - extract to /components/)"
|
|||||||
|
|
||||||
| Token | Hex Value | Purpose |
|
| Token | Hex Value | Purpose |
|
||||||
|-------|-----------|---------|
|
|-------|-----------|---------|
|
||||||
| `--color-primary` | `#0077B6` | Brand/Primary actions |
|
| `--color-primary` | `#2C7AA1` | Brand/Primary actions, links, CTA |
|
||||||
| `--color-success` | `#00B894` | Success states |
|
| `--color-success` | `#2CA18E` | Success states, confirmations |
|
||||||
| `--color-warning` | `#F59E0B` | Warning states |
|
| `--color-warning` | `#D9A12C` | Warning states, alerts, cautions |
|
||||||
| `--color-danger` | `#DC2626` | Error/Danger states |
|
| `--color-danger` | `#A12C40` | Error/Danger states, destructive |
|
||||||
| `--color-purple` | `#7C3AED` | Premium/Special features |
|
| `--color-purple` | `#2C40A1` | Premium/Special features |
|
||||||
| `--color-gray-base` | `#667085` | Neutral base for grays |
|
| `--color-gray-base` | `#667085` | Neutral base for grays |
|
||||||
|
|
||||||
### 2.2 Allowed Color Classes (ALL derived from 6 primaries)
|
### 2.2 Allowed Color Classes (ALL derived from 6 primaries)
|
||||||
@@ -523,23 +523,25 @@ import { Check } from 'lucide-react';
|
|||||||
```css
|
```css
|
||||||
/* ✅ ONLY in design-system.css - the 6 primary tokens */
|
/* ✅ ONLY in design-system.css - the 6 primary tokens */
|
||||||
:root {
|
:root {
|
||||||
--color-primary: #0077B6;
|
--color-primary: #2C7AA1;
|
||||||
--color-success: #00B894;
|
--color-success: #2CA18E;
|
||||||
--color-warning: #F59E0B;
|
--color-warning: #D9A12C;
|
||||||
--color-danger: #DC2626;
|
--color-danger: #A12C40;
|
||||||
--color-purple: #7C3AED;
|
--color-purple: #2C40A1;
|
||||||
--color-gray-base: #667085;
|
--color-gray-base: #667085;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ✅ All other colors derived */
|
/* ✅ All other colors derived using color-mix() */
|
||||||
|
--color-primary-dark: color-mix(in srgb, var(--color-primary) 75%, black);
|
||||||
--color-brand-600: color-mix(in srgb, var(--color-primary) 85%, black);
|
--color-brand-600: color-mix(in srgb, var(--color-primary) 85%, black);
|
||||||
|
--color-gray-700: color-mix(in srgb, var(--color-gray-base) 70%, black);
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚨 CRITICAL RULES
|
## 🚨 CRITICAL RULES
|
||||||
|
|
||||||
1. **6 PRIMARY HEX ONLY**: Only `--color-primary`, `--color-success`, `--color-warning`, `--color-danger`, `--color-purple`, `--color-gray-base` use hex values
|
1. **6 PRIMARY HEX ONLY**: Only `--color-primary`, `--color-success`, `--color-warning`, `--color-danger`, `--color-purple`, `--color-gray-base` use hex values (plus `#ffffff` for white)
|
||||||
2. **ALL SHADES DERIVED**: Every shade (25-950) is computed from primary using `color-mix()`
|
2. **ALL SHADES DERIVED**: Every shade (25-950) is computed from primary using `color-mix()`
|
||||||
3. **NO TAILWIND DEFAULTS**: blue-*, red-*, green-*, etc. are DISABLED
|
3. **NO TAILWIND DEFAULTS**: blue-*, red-*, green-*, etc. are DISABLED
|
||||||
4. **NO INLINE STYLES**: Never use `style={{}}` for colors/spacing
|
4. **NO INLINE STYLES**: Never use `style={{}}` for colors/spacing
|
||||||
@@ -582,6 +584,55 @@ The entire application must look like it was designed and built by **ONE person*
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚨 CRITICAL ISSUES - Fix First
|
||||||
|
|
||||||
|
### Issue 1: Missing Components After Changes
|
||||||
|
|
||||||
|
**Problem**: Many pages have missing components after lucide-react → local icons migration
|
||||||
|
|
||||||
|
**Affected Files** (from git diff):
|
||||||
|
- `ImportExport.tsx` - Uses `Database`, `CheckCircle` etc. from lucide-react
|
||||||
|
- `WordPressIntegrationDebug.tsx` - Uses `CheckCircle`, `XCircle`, `AlertTriangle`, etc.
|
||||||
|
- `PostEditor.tsx` - Uses `SaveIcon`, `XIcon`, `FileTextIcon`, etc.
|
||||||
|
- `NotificationsPage.tsx` - Uses `Bell`, `CheckCircle`, `AlertTriangle`, etc.
|
||||||
|
- `AccountSettingsPage.tsx` - Uses `Save`, `Loader2`, `Settings`, etc.
|
||||||
|
- `PlansAndBillingPage.tsx` - Uses `CreditCard`, `Package`, etc.
|
||||||
|
- `ContentSettingsPage.tsx` - Uses `Save`, `Loader2`, `Image`, etc.
|
||||||
|
- `UsageAnalyticsPage.tsx` - Uses `TrendingUp`, `Activity`, etc.
|
||||||
|
- `PurchaseCreditsPage.tsx` - Uses `AlertCircle`, `Check`, etc.
|
||||||
|
- Multiple components in `/components/sites/`, `/components/billing/`, `/components/auth/`
|
||||||
|
|
||||||
|
**Fix**: Replace lucide-react imports with local `/icons/` imports. Add missing aliases to `/icons/index.ts`
|
||||||
|
|
||||||
|
### Issue 2: Table Row Height Too Big (Planner & Writer)
|
||||||
|
|
||||||
|
**Problem**: Planner and Writer table font sizes too big for some columns, increasing overall row height - tables not compact anymore
|
||||||
|
|
||||||
|
**Root Cause**: Font size changes in CSS or component updates affecting table cell sizing
|
||||||
|
|
||||||
|
**Fix Locations**:
|
||||||
|
- `/src/styles/design-system.css` - `.igny8-table-compact td` rules
|
||||||
|
- Planner and Writer table components - check for increased text sizes
|
||||||
|
|
||||||
|
**Required Changes**:
|
||||||
|
```css
|
||||||
|
/* Ensure compact table styling */
|
||||||
|
.igny8-table-compact td {
|
||||||
|
padding: 6px 10px !important; /* Reduced from 8px 12px */
|
||||||
|
font-size: 13px !important; /* Reduced from 14px */
|
||||||
|
line-height: 1.3 !important; /* Tighter line height */
|
||||||
|
}
|
||||||
|
|
||||||
|
.igny8-table-compact th {
|
||||||
|
padding: 8px 12px !important; /* Reduced from 12px 16px */
|
||||||
|
font-size: 12px !important; /* Reduced from 14px */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Gray scale (`gray-25` to `gray-950`) is ALLOWED - derived from `--color-gray-base`
|
- Gray scale (`gray-25` to `gray-950`) is ALLOWED - derived from `--color-gray-base`
|
||||||
|
|||||||
62
frontend/audit-results/01-inline-styles.txt
Normal file
62
frontend/audit-results/01-inline-styles.txt
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
src/pages/account/PlansAndBillingPage.tsx:596: <div className="mx-auto" style={{ maxWidth: '1560px' }}>
|
||||||
|
src/pages/Writer/Images.tsx:661: style={{ maxHeight: '90vh' }}
|
||||||
|
src/pages/Sites/List.tsx:605: style={{ boxShadow: '0 2px 6px 3px rgba(0, 0, 0, 0.08)' }}
|
||||||
|
src/pages/Automation/AutomationPage.tsx:878: style={{ width: `${isComplete ? 100 : progressPercent}%` }}
|
||||||
|
src/pages/Automation/AutomationPage.tsx:1002: style={{ width: `${isComplete ? 100 : progressPercent}%` }}
|
||||||
|
src/pages/Automation/AutomationPage.tsx:1097: style={{ width: `${isComplete ? 100 : progressPercent}%` }}
|
||||||
|
src/pages/Settings/Status.tsx:132: style={{ width: `${status.system?.cpu?.usage_percent || 0}%` }}
|
||||||
|
src/pages/Settings/Status.tsx:154: style={{ width: `${status.system?.memory?.usage_percent || 0}%` }}
|
||||||
|
src/pages/Settings/Status.tsx:176: style={{ width: `${status.system?.disk?.usage_percent || 0}%` }}
|
||||||
|
src/components/onboarding/OnboardingWizard.tsx:264: style={{ width: `${100 / STEPS.length}%` }}
|
||||||
|
src/components/shared/blocks/QuoteBlock.tsx:26: style={{ textAlign: align }}
|
||||||
|
src/components/shared/blocks/TextBlock.tsx:24: style={{ textAlign: align }}
|
||||||
|
src/components/sites/SiteProgressWidget.tsx:233: style={{ width: `${completionPercent}%` }}
|
||||||
|
src/components/sites/SiteSetupChecklist.tsx:121: style={{ width: `${progressPercent}%` }}
|
||||||
|
src/components/auth/SignUpFormUnified.tsx:624: <div className="flex-shrink-0" style={{ minWidth: '200px' }}>
|
||||||
|
src/components/common/ImageResultCard.tsx:113: style={{ maxHeight: '400px' }}
|
||||||
|
src/components/common/ImageQueueModal.tsx:537: style={{ width: `${smoothProgress[item.index] ?? item.progress ?? 0}%` }}
|
||||||
|
src/components/common/ToggleTableRow.tsx:113: style={{
|
||||||
|
src/components/optimizer/OptimizationScores.tsx:80: style={{ width: `${scores.overall_score}%` }}
|
||||||
|
src/components/optimizer/OptimizationScores.tsx:104: style={{ width: `${scores.seo_score}%` }}
|
||||||
|
src/components/optimizer/OptimizationScores.tsx:128: style={{ width: `${scores.readability_score}%` }}
|
||||||
|
src/components/optimizer/OptimizationScores.tsx:152: style={{ width: `${scores.engagement_score}%` }}
|
||||||
|
src/components/optimizer/OptimizationScores.tsx:179: style={{ width: `${scores.metadata_completeness_score || 0}%` }}
|
||||||
|
src/components/Automation/CurrentProcessingCard.tsx:414: style={{ width: `${Math.min(percentage, 100)}%` }}
|
||||||
|
src/components/Automation/CurrentProcessingCardV2.tsx:267: style={{ width: `${Math.min(displayPercent, 100)}%` }}
|
||||||
|
src/components/Automation/CurrentProcessingCard.old.tsx:120: style={{ width: `${Math.min(percentage, 100)}%` }}
|
||||||
|
src/components/billing/UsageLimitsPanel.tsx:62: style={{
|
||||||
|
src/components/billing/UsageLimitsPanel.tsx:86: style={{
|
||||||
|
src/components/dashboard/ThreeWidgetFooter.tsx:135: style={{
|
||||||
|
src/components/dashboard/ThreeWidgetFooter.tsx:221: style={{
|
||||||
|
src/components/dashboard/ThreeWidgetFooter.tsx:274: style={{
|
||||||
|
src/components/dashboard/WorkflowCompletionWidget.tsx:106: style={{
|
||||||
|
src/components/dashboard/WorkflowCompletionWidget.tsx:178: <div className="text-xs font-bold uppercase tracking-wide mb-2" style={{ color: 'var(--color-primary)' }}>
|
||||||
|
src/components/dashboard/WorkflowCompletionWidget.tsx:197: <div className="text-xs font-bold uppercase tracking-wide mb-2" style={{ color: 'var(--color-success)' }}>
|
||||||
|
src/components/dashboard/WorkflowCompletionWidget.tsx:229: <strong className="font-bold" style={{ color: 'var(--color-primary)' }}>
|
||||||
|
src/components/dashboard/WorkflowCompletionWidget.tsx:240: <strong className="font-bold" style={{ color: 'var(--color-success)' }}>
|
||||||
|
src/components/dashboard/StandardizedModuleWidget.tsx:176: style={{ color: row.color }}
|
||||||
|
src/components/dashboard/StandardizedModuleWidget.tsx:180: <span className="text-lg font-bold tabular-nums" style={{ color: row.color }}>
|
||||||
|
src/components/dashboard/StandardizedModuleWidget.tsx:188: style={{ color: row.color }}
|
||||||
|
src/components/dashboard/StandardizedModuleWidget.tsx:196: style={{ color: row.color }}
|
||||||
|
src/components/dashboard/StandardizedModuleWidget.tsx:210: style={{
|
||||||
|
src/components/dashboard/StandardizedModuleWidget.tsx:243: <strong className="font-bold" style={{ color: 'var(--color-purple)' }}>{credits.clusteringCredits}</strong>
|
||||||
|
src/components/dashboard/StandardizedModuleWidget.tsx:249: <strong className="font-bold" style={{ color: 'var(--color-warning)' }}>{credits.ideaGenerationCredits}</strong>
|
||||||
|
src/components/dashboard/StandardizedModuleWidget.tsx:262: <strong className="font-bold" style={{ color: 'var(--color-success)' }}>{credits.contentGenerationCredits}</strong>
|
||||||
|
src/components/dashboard/StandardizedModuleWidget.tsx:268: <strong className="font-bold" style={{ color: 'var(--color-purple)' }}>{credits.imageGenerationCredits}</strong>
|
||||||
|
src/components/dashboard/StandardThreeWidgetFooter.tsx:128: style={{
|
||||||
|
src/components/dashboard/StandardThreeWidgetFooter.tsx:144: <span className="text-sm font-bold" style={{ color: 'var(--color-warning)' }}>{widget.creditsConsumed.toLocaleString()}</span>
|
||||||
|
src/components/dashboard/StandardThreeWidgetFooter.tsx:160: <span className="font-semibold" style={{ color: 'var(--color-primary)' }}>Next:</span>{' '}
|
||||||
|
src/components/dashboard/StandardThreeWidgetFooter.tsx:231: style={{
|
||||||
|
src/components/dashboard/CreditAvailabilityWidget.tsx:83: style={{ width: `${remainingPercent}%` }}
|
||||||
|
src/components/dashboard/CreditBalanceWidget.tsx:60: style={{ width: `${Math.min(usagePercentage, 100)}%` }}
|
||||||
|
src/components/dashboard/SiteConfigWidget.tsx:142: style={{ width: `${completionPercent}%` }}
|
||||||
|
src/components/ui/dropdown/Dropdown.tsx:141: style={{
|
||||||
|
src/components/ui/alert/AlertModal.tsx:42: <div className="absolute inset-0 bg-success-100 rounded-full" style={{
|
||||||
|
src/components/ui/alert/AlertModal.tsx:68: <div className="absolute inset-0 bg-blue-light-100 rounded-full blur-2xl opacity-50" style={{
|
||||||
|
src/components/ui/alert/AlertModal.tsx:82: <div className="absolute inset-0 bg-warning-100 rounded-full blur-2xl opacity-50" style={{
|
||||||
|
src/components/ui/alert/AlertModal.tsx:99: <div className="absolute inset-0 bg-error-100 rounded-full blur-2xl opacity-50" style={{
|
||||||
|
src/components/ui/progress/ProgressBar.tsx:53: style={{ width: `${clampedValue}%` }}
|
||||||
|
src/components/ui/tooltip/EnhancedTooltip.tsx:61: style={{ zIndex: 1 }}
|
||||||
|
src/components/ui/tooltip/EnhancedTooltip.tsx:76: style={{ zIndex: 99999 }}
|
||||||
|
src/components/ui/pricing-table/PricingTable.tsx:108: style={{
|
||||||
|
src/components/ui/toast/ToastContainer.tsx:75: <div key={toast.id} className="pointer-events-auto" style={{ animationDelay: `${index * 50}ms` }}>
|
||||||
0
frontend/audit-results/02-hardcoded-hex-class.txt
Normal file
0
frontend/audit-results/02-hardcoded-hex-class.txt
Normal file
36
frontend/audit-results/03-hardcoded-hex-all.txt
Normal file
36
frontend/audit-results/03-hardcoded-hex-all.txt
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
src/pages/Settings/Integration.tsx:22: <path d="M12.321 24.9775C11.9035 24.3455 12.9535 23.389 12.5264 22.5859C12.2802 22.1227 11.8779 21.8341 11.3946 21.7735C10.9308 21.7153 10.4531 21.8851 10.1486 22.2169C9.66775 22.7401 9.59299 23.4523 9.68599 23.7043C9.72002 23.7967 9.77351 23.8219 9.81181 23.8273C10.0737 23.8618 10.221 23.1897 10.3115 23.0497C10.5614 22.6639 11.129 22.5471 11.5211 22.8007C12.2643 23.2814 11.6198 24.0584 11.6754 24.7147C11.7295 25.3537 12.1277 25.6105 12.4851 25.6375C12.8328 25.6507 13.0759 25.4575 13.1373 25.3165C13.2845 24.9799 12.6644 25.4975 12.321 24.9775Z" fill="#0C111D"></path>
|
||||||
|
src/pages/Settings/Integration.tsx:23: <path d="M27.8512 19.1761C27.6563 19.1492 27.4432 19.1498 27.2215 19.1761C27.0532 18.9637 26.9024 18.6199 26.8173 18.2185C26.6659 17.5045 26.6817 16.9873 27.1042 16.9201C27.5266 16.8529 27.7309 17.2849 27.8822 17.9989C27.9837 18.4789 27.9643 18.9199 27.8512 19.1761Z" fill="#0C111D"></path>
|
||||||
|
src/pages/Settings/Integration.tsx:24: <path d="M23.2243 19.6009C23.241 19.7626 23.2469 19.9262 23.2422 20.0769C23.6509 20.1009 23.9405 20.295 24.0175 20.4182C24.0571 20.4818 24.0412 20.5232 24.0285 20.5424C23.9862 20.6082 23.8955 20.598 23.7055 20.5767C23.5396 20.5581 23.3609 20.5417 23.1752 20.5503C23.0742 20.8602 22.7745 20.8892 22.564 20.66C22.417 20.7048 22.1281 20.8892 22.0421 20.6888C22.0413 20.5893 22.1453 20.4447 22.3337 20.317C22.2051 20.0718 22.1189 19.8092 22.068 19.5373C21.801 19.5854 21.5607 19.6602 21.3725 19.7188C21.284 19.7464 20.9343 19.9044 20.8933 19.7263C20.8659 19.6033 21.0574 19.4005 21.2598 19.2541C21.486 19.0935 21.7369 18.9794 21.997 18.9135C21.9914 18.5263 22.0902 18.2561 22.3716 18.2113C22.7204 18.1558 22.9366 18.4244 23.0863 18.9218C23.5079 19.0389 23.9289 19.3294 24.1148 19.6297C24.1871 19.7461 24.2011 19.8361 24.1543 19.8835C24.0375 20.0046 23.3901 19.6535 23.2243 19.6009Z" fill="#0C111D"></path>
|
||||||
|
src/pages/Settings/Integration.tsx:25: <path d="M25.9967 21.3415C26.2641 21.4711 26.5583 21.4201 26.6544 21.2275C26.7504 21.0349 26.6112 20.7739 26.3438 20.6443C26.0763 20.5147 25.7821 20.5657 25.6861 20.7583C25.5901 20.9509 25.7292 21.2119 25.9967 21.3415Z" fill="#0C111D"></path>
|
||||||
|
src/pages/Settings/Integration.tsx:26: <path d="M27.3132 20.3851C27.3181 20.0905 27.4986 19.8553 27.7156 19.8589C27.9326 19.8631 28.1046 20.1043 28.0998 20.3983C28.0949 20.6923 27.9144 20.9275 27.6974 20.9239C27.4804 20.9203 27.3084 20.6791 27.3132 20.3851Z" fill="#0C111D"></path>
|
||||||
|
src/pages/Settings/Integration.tsx:27: <path fillRule="evenodd" clipRule="evenodd" d="M33.1978 23.9929C33.196 23.9868 33.202 24.0079 33.1978 23.9929C33.6336 23.9929 34.318 24.4873 34.318 25.6819C34.318 26.8705 33.8184 28.2169 33.7004 28.5157C31.9006 32.7733 27.605 35.1433 22.4882 34.9933C17.7179 34.8535 13.6497 32.3689 11.8693 28.3183C10.7928 28.3195 9.68291 27.8521 8.83923 27.1141C7.94996 26.3365 7.40169 25.3303 7.29471 24.2809C7.21144 23.4637 7.31294 22.7035 7.57249 22.0405L6.5726 21.2041C1.99679 17.3906 16.3084 1.68861 20.8854 5.6294C20.9085 5.6492 22.4427 7.13479 22.4463 7.13839C24.6532 6.21344 30.5665 4.45236 30.5731 8.55199C30.5755 9.91338 29.6966 11.501 28.2882 12.9416C29.9158 14.4315 29.4867 16.5479 29.7458 18.4897L30.3111 18.6445C31.3888 18.9427 32.1553 19.3405 32.5303 19.7293C32.9054 20.1175 33.0914 20.4937 33.1589 20.9347C33.2221 21.2905 33.2136 21.9187 32.7382 22.6213C32.9094 23.0746 33.0656 23.5264 33.1978 23.9929ZM11.7885 27.2137C11.9483 27.2173 12.107 27.2071 12.2626 27.1807C13.9347 26.8993 14.3724 25.1065 14.097 23.3461C13.7858 21.3583 12.4237 20.6575 11.4991 20.6077C11.242 20.5945 11.0031 20.6173 10.8062 20.6563C9.15591 20.9845 8.22409 22.3705 8.40766 24.1705C8.5736 25.7995 10.2427 27.1729 11.7885 27.2137ZM8.05086 21.1447C8.6046 20.3509 9.50967 19.7767 10.5369 19.5793C11.8444 16.115 14.0277 12.923 16.9174 10.7264C19.0619 8.96058 21.3747 7.69399 21.3747 7.69399C21.3747 7.69399 20.1292 6.26839 19.753 6.16339C17.4389 5.5466 12.4413 8.94858 9.25013 13.4438C7.95908 15.2624 6.11064 18.4831 6.99444 20.1397C7.10324 20.3449 7.7202 20.8723 8.05086 21.1447ZM26.8118 26.4181C26.8154 26.4553 26.7924 26.4937 26.7589 26.5075C26.7589 26.5075 24.8995 27.3613 21.9461 26.4595C22.0523 27.3448 23.1259 27.6803 23.8759 27.8017C27.5357 28.4233 30.9573 26.3575 31.7274 25.8373C31.8596 25.748 31.7258 25.9776 31.7019 26.0113C30.7591 27.2119 28.2244 28.6021 24.9269 28.6015C23.4888 28.6009 22.0512 28.1011 21.5236 27.3325C20.7048 26.1403 21.4829 24.3997 22.8475 24.5809C25.1541 24.838 27.5188 24.6448 29.6541 23.6665C31.5159 22.8133 32.2191 21.8749 32.1134 21.1147C31.9493 19.9366 30.2026 19.7524 29.3197 19.4659C28.9356 19.3405 28.7459 19.2403 28.7028 18.5269C28.6839 18.2155 28.6292 17.129 28.6092 16.6796C28.5739 15.893 28.4779 14.8172 27.802 14.3732C27.6257 14.2574 27.43 14.2016 27.2239 14.1908C27.0596 14.1829 26.9622 14.2055 26.9098 14.2176C26.8981 14.2204 26.8886 14.2226 26.8811 14.2238C26.5161 14.285 26.2925 14.4697 26.0287 14.6874C26.0135 14.7 25.9982 14.7126 25.9827 14.7254C25.1396 15.419 24.4279 15.5324 23.6358 15.4988C23.3892 15.4884 23.135 15.4638 22.8668 15.4378C22.6201 15.4139 22.3617 15.3889 22.0865 15.3728L21.7503 15.3536C20.424 15.2864 19.0017 16.4168 18.7652 18.0223C18.5011 19.8145 19.5028 20.9122 20.1542 21.6261C20.3152 21.8025 20.4548 21.9555 20.5486 22.0897C20.6088 22.1707 20.6787 22.2847 20.6787 22.3933C20.6787 22.5229 20.5936 22.6255 20.5103 22.7131C19.1573 24.0865 18.7245 26.2687 19.2345 28.0873C19.2983 28.3141 19.3792 28.5313 19.4746 28.7389C20.6708 31.4983 24.3823 32.7835 28.0074 31.6147C30.5481 30.7955 32.7929 28.8159 33.25 26.0851C33.3588 25.3735 33.199 25.0987 32.9814 24.9661C32.751 24.8263 32.475 24.8749 32.475 24.8749C32.475 24.8749 32.3492 24.0247 31.993 23.2519C30.936 24.0751 29.5756 24.6535 28.5399 24.9469C26.8804 25.4168 25.0879 25.5998 23.373 25.3782C22.6775 25.2883 22.2102 25.228 22.0099 25.8703C24.2972 26.6971 26.7182 26.3431 26.7182 26.3431C26.765 26.3383 26.8069 26.3719 26.8118 26.4181ZM20.2125 10.3736C18.9616 11.0054 17.5648 12.1304 16.4305 13.4246C16.3904 13.4708 16.45 13.5344 16.4992 13.499C17.479 12.7952 18.8224 12.1412 20.5821 11.7176C22.5533 11.243 24.451 11.4422 25.6101 11.7044C25.6685 11.7176 25.7049 11.6186 25.6533 11.5898C24.8874 11.1656 23.7118 10.8776 22.8779 10.8716C22.8371 10.871 22.814 10.8242 22.8384 10.7918C22.9824 10.6004 23.18 10.4114 23.3605 10.2746C23.4006 10.2434 23.3763 10.1792 23.3252 10.1822C22.2878 10.2451 19.9183 11.3255 19.9268 11.2862C19.9876 10.9982 20.1791 10.6178 20.2781 10.4402C20.3019 10.3982 20.2557 10.352 20.2125 10.3736Z" fill="#0C111D"></path>
|
||||||
|
src/pages/Settings/Integration.tsx:34: <rect width="40" height="40" rx="8" fill="#625DF5"/>
|
||||||
|
src/pages/Settings/Integration.tsx:42: <rect width="40" height="40" rx="8" fill="#4285F4"/>
|
||||||
|
src/pages/Settings/Integration.tsx:955: <rect width="40" height="40" rx="8" fill="#9333EA"/>
|
||||||
|
src/pages/Settings/Integration.tsx:987: <rect width="40" height="40" rx="8" fill="#10B981"/>
|
||||||
|
src/pages/Settings/Sites.tsx:26: <circle cx="12" cy="12" r="10" stroke="#3B82F6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
src/pages/Settings/Sites.tsx:27: <path d="M2 12h20" stroke="#3B82F6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
src/pages/Settings/Sites.tsx:28: <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" stroke="#3B82F6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
src/components/sites/TemplateCustomizer.tsx:208: value={customization.customStyles?.primaryColor || '#3b82f6'}
|
||||||
|
src/components/sites/TemplateCustomizer.tsx:218: value={customization.customStyles?.primaryColor || '#3b82f6'}
|
||||||
|
src/components/sites/TemplateCustomizer.tsx:224: placeholder="#3b82f6"
|
||||||
|
src/components/sites/TemplateCustomizer.tsx:235: value={customization.customStyles?.backgroundColor || '#ffffff'}
|
||||||
|
src/components/sites/TemplateCustomizer.tsx:245: value={customization.customStyles?.backgroundColor || '#ffffff'}
|
||||||
|
src/components/sites/TemplateCustomizer.tsx:251: placeholder="#ffffff"
|
||||||
|
src/components/sites/StyleEditor.tsx:190: value={styleSettings.colorPalette?.[colorKey as keyof typeof styleSettings.colorPalette] || '#000000'}
|
||||||
|
src/components/sites/StyleEditor.tsx:198: placeholder="#000000"
|
||||||
|
src/components/form/form-elements/DefaultInputs.tsx:102: <circle cx="6.25" cy="10" r="5.625" fill="#E80B26" />
|
||||||
|
src/components/form/form-elements/DefaultInputs.tsx:103: <circle cx="13.75" cy="10" r="5.625" fill="#F59D31" />
|
||||||
|
src/components/form/form-elements/DefaultInputs.tsx:106: fill="#FC6020"
|
||||||
|
src/components/form/input/Checkbox.tsx:65: stroke="#E4E7EC"
|
||||||
|
src/components/auth/SignInForm.tsx:127: fill="#4285F4"
|
||||||
|
src/components/auth/SignInForm.tsx:131: fill="#34A853"
|
||||||
|
src/components/auth/SignInForm.tsx:135: fill="#FBBC05"
|
||||||
|
src/components/auth/SignInForm.tsx:139: fill="#EB4335"
|
||||||
|
src/components/auth/SignUpForm.tsx:147: fill="#4285F4"
|
||||||
|
src/components/auth/SignUpForm.tsx:151: fill="#34A853"
|
||||||
|
src/components/auth/SignUpForm.tsx:155: fill="#FBBC05"
|
||||||
|
src/components/auth/SignUpForm.tsx:159: fill="#EB4335"
|
||||||
|
src/components/ecommerce/MonthlyTarget.tsx:140: fill="#D92D20"
|
||||||
|
src/components/ecommerce/MonthlyTarget.tsx:165: fill="#039855"
|
||||||
|
src/components/ecommerce/MonthlyTarget.tsx:190: fill="#039855"
|
||||||
6
frontend/audit-results/04-rgb-values.txt
Normal file
6
frontend/audit-results/04-rgb-values.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
src/pages/Sites/List.tsx:605: style={{ boxShadow: '0 2px 6px 3px rgba(0, 0, 0, 0.08)' }}
|
||||||
|
src/components/billing/UsageLimitsPanel.tsx:63: backgroundColor: isDanger ? 'rgba(239, 68, 68, 0.1)' : isWarning ? 'rgba(255, 122, 0, 0.1)' : `${barColor}15`,
|
||||||
|
src/components/ui/button/Button.tsx:97: "text-white shadow-[0_20px_45px_-30px_rgba(6,147,227,0.9)] bg-[linear-gradient(135deg,var(--color-primary)_0%,var(--color-primary-dark)_100%)]",
|
||||||
|
src/components/ui/button/Button.tsx:99: "text-white shadow-[0_20px_45px_-30px_rgba(11,191,135,0.9)] bg-[linear-gradient(135deg,var(--color-success)_0%,var(--color-success-dark)_100%)]",
|
||||||
|
src/components/ui/button/Button.tsx:101: "text-white shadow-[0_20px_45px_-30px_rgba(255,122,0,0.9)] bg-[linear-gradient(135deg,var(--color-warning)_0%,var(--color-warning-dark)_100%)]",
|
||||||
|
src/components/ui/button/Button.tsx:103: "text-white shadow-[0_20px_45px_-30px_rgba(239,68,68,0.9)] bg-[linear-gradient(135deg,var(--color-danger)_0%,var(--color-danger-dark)_100%)]",
|
||||||
49
frontend/audit-results/05-tailwind-default-colors.txt
Normal file
49
frontend/audit-results/05-tailwind-default-colors.txt
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
src/pages/Sites/PublishingQueue.tsx:169: <span className="inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300">
|
||||||
|
src/pages/Sites/PublishingQueue.tsx:176: <span className="inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300">
|
||||||
|
src/pages/Sites/PublishingQueue.tsx:231: <div className="size-10 rounded-lg bg-amber-100 dark:bg-amber-900/30 flex items-center justify-center">
|
||||||
|
src/pages/Sites/PublishingQueue.tsx:232: <ClockIcon className="w-5 h-5 text-amber-600" />
|
||||||
|
src/pages/Sites/PublishingQueue.tsx:242: <div className="size-10 rounded-lg bg-blue-100 dark:bg-blue-900/30 flex items-center justify-center">
|
||||||
|
src/pages/Sites/PublishingQueue.tsx:243: <ArrowRightIcon className="w-5 h-5 text-blue-600" />
|
||||||
|
src/pages/Sites/PublishingQueue.tsx:253: <div className="size-10 rounded-lg bg-green-100 dark:bg-green-900/30 flex items-center justify-center">
|
||||||
|
src/pages/Sites/PublishingQueue.tsx:254: <CheckCircleIcon className="w-5 h-5 text-green-600" />
|
||||||
|
src/pages/Sites/PublishingQueue.tsx:264: <div className="size-10 rounded-lg bg-red-100 dark:bg-red-900/30 flex items-center justify-center">
|
||||||
|
src/pages/Sites/PublishingQueue.tsx:265: <TrashBinIcon className="w-5 h-5 text-red-600" />
|
||||||
|
src/pages/Sites/PublishingQueue.tsx:380: className="p-2 text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20"
|
||||||
|
src/pages/Sites/PublishingQueue.tsx:425: className="text-xs p-1 bg-amber-100 dark:bg-amber-900/30 text-amber-800 dark:text-amber-200 rounded truncate cursor-pointer hover:bg-amber-200 dark:hover:bg-amber-900/50"
|
||||||
|
src/pages/Sites/Dashboard.tsx:347: <ArrowRightIcon className="h-5 w-5 text-gray-400 group-hover:text-amber-500 transition" />
|
||||||
|
src/pages/Sites/Settings.tsx:864: className="p-2 text-gray-400 hover:text-red-500 transition-colors"
|
||||||
|
src/pages/Sites/Settings.tsx:891: <div className="mt-6 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
|
||||||
|
src/pages/Sites/Settings.tsx:893: <svg className="w-5 h-5 text-blue-600 dark:text-blue-400 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
src/pages/Sites/Settings.tsx:896: <div className="text-sm text-blue-800 dark:text-blue-200">
|
||||||
|
src/pages/Sites/Settings.tsx:898: <ul className="list-disc list-inside space-y-1 text-blue-700 dark:text-blue-300">
|
||||||
|
src/pages/Components.tsx:153: className="px-4 py-3 text-sm font-medium text-white rounded-lg bg-blue-light-500 shadow-theme-xs hover:bg-blue-light-600 transition-colors"
|
||||||
|
src/components/onboarding/steps/Step4AddKeywords.tsx:219: className="text-red-500 hover:text-red-600"
|
||||||
|
src/components/onboarding/steps/Step4AddKeywords.tsx:227: <Card className="p-4 bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800 mb-6">
|
||||||
|
src/components/onboarding/steps/Step4AddKeywords.tsx:228: <h4 className="font-medium text-blue-900 dark:text-blue-100 text-sm mb-2">
|
||||||
|
src/components/onboarding/steps/Step4AddKeywords.tsx:235: className="text-xs text-blue-700 dark:text-blue-300 bg-blue-100 dark:bg-blue-800/50 px-2 py-1 rounded"
|
||||||
|
src/components/onboarding/steps/Step3ConnectIntegration.tsx:219: ? 'bg-green-100 dark:bg-green-900/50 text-green-600 dark:text-green-400'
|
||||||
|
src/components/onboarding/steps/Step3ConnectIntegration.tsx:221: ? 'bg-red-100 dark:bg-red-900/50 text-red-600 dark:text-red-400'
|
||||||
|
src/components/onboarding/steps/Step5Complete.tsx:60: <div className="inline-flex items-center justify-center size-20 rounded-full bg-green-100 dark:bg-green-900/50 text-green-600 dark:text-green-400 mb-4">
|
||||||
|
src/components/onboarding/steps/Step5Complete.tsx:78: <CheckCircleIcon className="w-4 h-4 text-green-500" />
|
||||||
|
src/components/onboarding/steps/Step5Complete.tsx:85: <CheckCircleIcon className="w-4 h-4 text-green-500" />
|
||||||
|
src/components/onboarding/steps/Step5Complete.tsx:93: <CheckCircleIcon className="w-4 h-4 text-green-500" />
|
||||||
|
src/components/onboarding/steps/Step5Complete.tsx:100: <CheckCircleIcon className="w-4 h-4 text-green-500" />
|
||||||
|
src/components/onboarding/steps/Step5Complete.tsx:106: <CheckCircleIcon className="w-4 h-4 text-green-500" />
|
||||||
|
src/components/onboarding/steps/Step2AddSite.tsx:257: <Card className="p-4 bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800 mb-6">
|
||||||
|
src/components/onboarding/steps/Step2AddSite.tsx:259: <GridIcon className="w-5 h-5 text-blue-600 dark:text-blue-400 mt-0.5" />
|
||||||
|
src/components/onboarding/steps/Step2AddSite.tsx:261: <h4 className="font-medium text-blue-900 dark:text-blue-100 text-sm mb-1">
|
||||||
|
src/components/onboarding/steps/Step2AddSite.tsx:264: <ul className="text-xs text-blue-700 dark:text-blue-300 space-y-0.5">
|
||||||
|
src/components/onboarding/steps/Step2AddSite.tsx:270: <p className="text-xs text-blue-600 dark:text-blue-400 mt-2">
|
||||||
|
src/components/onboarding/OnboardingWizard.tsx:233: ? 'bg-green-500 text-white'
|
||||||
|
src/components/onboarding/OnboardingWizard.tsx:249: ? 'bg-green-500'
|
||||||
|
src/components/ui/alert/Alert.tsx:39: "border-b-2 border-blue-light-500 bg-blue-light-50 dark:border-blue-light-500/30 dark:bg-blue-light-500/15",
|
||||||
|
src/components/ui/alert/Alert.tsx:40: icon: "text-blue-light-500",
|
||||||
|
src/components/ui/alert/AlertModal.tsx:68: <div className="absolute inset-0 bg-blue-light-100 rounded-full blur-2xl opacity-50" style={{
|
||||||
|
src/components/ui/alert/AlertModal.tsx:74: <div className="relative bg-blue-light-500 rounded-full w-16 h-16 flex items-center justify-center shadow-lg">
|
||||||
|
src/components/ui/alert/AlertModal.tsx:125: info: 'bg-blue-light-500 hover:bg-blue-light-600 text-white',
|
||||||
|
src/components/ui/spinner/Spinner.tsx:25: info: "border-blue-light-200 border-t-blue-light-500",
|
||||||
|
src/components/ui/progress/ProgressBar.tsx:31: info: "bg-blue-light-500",
|
||||||
|
src/components/ui/toast/Toast.tsx:60: <svg className="w-5 h-5 text-blue-light-500" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
src/components/ui/badge/Badge.tsx:58: solid: "bg-blue-light-500 text-white",
|
||||||
|
src/components/ui/badge/Badge.tsx:59: soft: "bg-blue-light-50 text-blue-light-700 dark:bg-blue-light-500/15 dark:text-blue-light-400",
|
||||||
|
src/components/ui/badge/Badge.tsx:61: "text-blue-light-700 ring-1 ring-blue-light-200 dark:ring-blue-light-500/30 dark:text-blue-light-400",
|
||||||
2
frontend/audit-results/06-external-ui-imports.txt
Normal file
2
frontend/audit-results/06-external-ui-imports.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
src/components/BulkWordPressPublish/BulkWordPressPublish.tsx:18:} from '@mui/material';
|
||||||
|
src/components/BulkWordPressPublish/BulkWordPressPublish.tsx:24:} from '@mui/icons-material';
|
||||||
34
frontend/audit-results/07-lucide-imports.txt
Normal file
34
frontend/audit-results/07-lucide-imports.txt
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
src/pages/account/NotificationsPage.tsx:15:} from 'lucide-react';
|
||||||
|
src/pages/account/AccountSettingsPage.tsx:11:} from 'lucide-react';
|
||||||
|
src/pages/account/PlansAndBillingPage.tsx:14:} from 'lucide-react';
|
||||||
|
src/pages/account/PurchaseCreditsPage.tsx:7:import { AlertCircle, Check, CreditCard, Building2, Wallet, Loader2, Zap } from 'lucide-react';
|
||||||
|
src/pages/account/ContentSettingsPage.tsx:11:} from 'lucide-react';
|
||||||
|
src/pages/account/UsageAnalyticsPage.tsx:9:import { TrendingUp, Activity, BarChart3, Zap, Calendar } from 'lucide-react';
|
||||||
|
src/pages/Sites/Content.tsx:14:import { Search } from 'lucide-react';
|
||||||
|
src/pages/Sites/PostEditor.tsx:8:import { SaveIcon, XIcon, FileTextIcon, TagIcon, CheckCircleIcon, XCircleIcon, AlertCircleIcon } from 'lucide-react';
|
||||||
|
src/pages/Settings/ImportExport.tsx:4:import { Download, Upload, Database, FileArchive, CheckCircle } from 'lucide-react';
|
||||||
|
src/pages/Settings/WordPressIntegrationDebug.tsx:13:} from 'lucide-react';
|
||||||
|
src/pages/settings/ProfileSettingsPage.tsx:7:import { Save, User, Mail, Lock, Loader2 } from 'lucide-react';
|
||||||
|
src/components/sites/WordPressIntegrationModal.tsx:6:import { X, Globe, AlertCircle } from 'lucide-react';
|
||||||
|
src/components/sites/SiteProgressWidget.tsx:10:import { CheckCircleIcon, XCircleIcon, AlertCircleIcon, ArrowRightIcon } from 'lucide-react';
|
||||||
|
src/components/sites/LayoutSelector.tsx:7:import { CheckIcon } from 'lucide-react';
|
||||||
|
src/components/sites/WordPressIntegrationForm.tsx:22:import { Globe, Key, RefreshCw } from 'lucide-react';
|
||||||
|
src/components/sites/LayoutPreview.tsx:7:import { EyeIcon, XIcon, Maximize2Icon } from 'lucide-react';
|
||||||
|
src/components/sites/SiteTypeBadge.tsx:6:import { Wand2, Globe } from 'lucide-react';
|
||||||
|
src/components/sites/WordPressIntegrationCard.tsx:8:import { Globe, CheckCircle, XCircle, Settings, RefreshCw, AlertCircle, ExternalLink } from 'lucide-react';
|
||||||
|
src/components/sites/TemplateLibrary.tsx:7:import { SearchIcon, FilterIcon, CheckIcon } from 'lucide-react';
|
||||||
|
src/components/sites/TemplateCustomizer.tsx:7:import { SettingsIcon, PaletteIcon, TypeIcon, LayoutIcon } from 'lucide-react';
|
||||||
|
src/components/sites/StyleEditor.tsx:7:import { CodeIcon, PaletteIcon, TypeIcon, SaveIcon, RefreshCwIcon } from 'lucide-react';
|
||||||
|
src/components/publishing/PublishingRules.tsx:6:import { PlusIcon, TrashIcon, ArrowUpIcon, ArrowDownIcon } from 'lucide-react';
|
||||||
|
src/components/auth/SignUpFormEnhanced.tsx:12:import { ChevronRight, Check } from 'lucide-react';
|
||||||
|
src/components/auth/SignUpFormSimplified.tsx:10:import { CreditCard, Building2, Wallet, Check, Loader2 } from 'lucide-react';
|
||||||
|
src/components/auth/SignUpFormUnified.tsx:10:import { CreditCard, Building2, Wallet, Check, Loader2, CheckCircle } from 'lucide-react';
|
||||||
|
src/components/billing/PendingPaymentBanner.tsx:8:import { AlertCircle, CreditCard, X } from 'lucide-react';
|
||||||
|
src/components/billing/PaymentMethodSelect.tsx:8:import { CheckCircle, Loader2, AlertCircle } from 'lucide-react';
|
||||||
|
src/components/billing/PaymentHistory.tsx:5:import { CheckCircle, XCircle, Clock, RefreshCw } from 'lucide-react';
|
||||||
|
src/components/billing/PaymentConfirmationModal.tsx:12:import { Loader2, Upload, X, CheckCircle } from 'lucide-react';
|
||||||
|
src/components/billing/UsageLimitsPanel.tsx:8:import { AlertCircle, TrendingUp, Users, Globe, Tag, FileText, Image, Zap } from 'lucide-react';
|
||||||
|
src/components/billing/CreditCostBreakdownPanel.tsx:8:import { DollarSign, TrendingUp, AlertCircle } from 'lucide-react';
|
||||||
|
src/components/integration/SiteIntegrationsSection.tsx:6:import { PlusIcon, TrashIcon, TestTubeIcon, RefreshCw } from 'lucide-react';
|
||||||
|
src/components/integration/IntegrationStatus.tsx:6:import { CheckCircleIcon, XCircleIcon, ClockIcon, RefreshCw } from 'lucide-react';
|
||||||
|
src/components/ui/pricing-table/index.tsx:7:import { Check } from 'lucide-react';
|
||||||
@@ -8,8 +8,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { ChevronLeftIcon, EyeCloseIcon, EyeIcon } from '../../icons';
|
import { ChevronLeftIcon, EyeCloseIcon, EyeIcon, ChevronRightIcon, CheckIcon } from '../../icons';
|
||||||
import { ChevronRight, Check } from 'lucide-react';
|
|
||||||
import Label from '../form/Label';
|
import Label from '../form/Label';
|
||||||
import Input from '../form/input/InputField';
|
import Input from '../form/input/InputField';
|
||||||
import Checkbox from '../form/input/Checkbox';
|
import Checkbox from '../form/input/Checkbox';
|
||||||
@@ -236,7 +235,7 @@ export default function SignUpFormEnhanced({ planDetails: planDetailsProp, planL
|
|||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{step < currentStep ? <Check className="w-5 h-5" /> : step}
|
{step < currentStep ? <CheckIcon className="w-5 h-5" /> : step}
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3">
|
<div className="ml-3">
|
||||||
<div className={`text-sm font-medium ${step === currentStep ? 'text-gray-900 dark:text-white' : 'text-gray-500 dark:text-gray-400'}`}>
|
<div className={`text-sm font-medium ${step === currentStep ? 'text-gray-900 dark:text-white' : 'text-gray-500 dark:text-gray-400'}`}>
|
||||||
@@ -379,7 +378,7 @@ export default function SignUpFormEnhanced({ planDetails: planDetailsProp, planL
|
|||||||
{isPaidPlan ? (
|
{isPaidPlan ? (
|
||||||
<Button type="button" variant="primary" onClick={handleNextStep} className="w-full">
|
<Button type="button" variant="primary" onClick={handleNextStep} className="w-full">
|
||||||
Continue to Billing
|
Continue to Billing
|
||||||
<ChevronRight className="w-4 h-4 ml-2" />
|
<ChevronRightIcon className="w-4 h-4 ml-2" />
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button type="submit" variant="primary" disabled={loading} className="w-full">
|
<Button type="submit" variant="primary" disabled={loading} className="w-full">
|
||||||
@@ -404,7 +403,7 @@ export default function SignUpFormEnhanced({ planDetails: planDetailsProp, planL
|
|||||||
</Button>
|
</Button>
|
||||||
<Button type="button" variant="primary" onClick={handleNextStep} className="flex-1">
|
<Button type="button" variant="primary" onClick={handleNextStep} className="flex-1">
|
||||||
Continue to Payment
|
Continue to Payment
|
||||||
<ChevronRight className="w-4 h-4 ml-2" />
|
<ChevronRightIcon className="w-4 h-4 ml-2" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,8 +6,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { ChevronLeftIcon, EyeCloseIcon, EyeIcon } from '../../icons';
|
import { ChevronLeftIcon, EyeCloseIcon, EyeIcon, CreditCardIcon, Building2Icon, WalletIcon, CheckIcon, Loader2Icon } from '../../icons';
|
||||||
import { CreditCard, Building2, Wallet, Check, Loader2 } from 'lucide-react';
|
|
||||||
import Label from '../form/Label';
|
import Label from '../form/Label';
|
||||||
import Input from '../form/input/InputField';
|
import Input from '../form/input/InputField';
|
||||||
import Checkbox from '../form/input/Checkbox';
|
import Checkbox from '../form/input/Checkbox';
|
||||||
@@ -195,13 +194,13 @@ export default function SignUpFormSimplified({ planDetails: planDetailsProp, pla
|
|||||||
const getPaymentIcon = (method: string) => {
|
const getPaymentIcon = (method: string) => {
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case 'stripe':
|
case 'stripe':
|
||||||
return <CreditCard className="w-5 h-5" />;
|
return <CreditCardIcon className="w-5 h-5" />;
|
||||||
case 'bank_transfer':
|
case 'bank_transfer':
|
||||||
return <Building2 className="w-5 h-5" />;
|
return <Building2Icon className="w-5 h-5" />;
|
||||||
case 'local_wallet':
|
case 'local_wallet':
|
||||||
return <Wallet className="w-5 h-5" />;
|
return <WalletIcon className="w-5 h-5" />;
|
||||||
default:
|
default:
|
||||||
return <CreditCard className="w-5 h-5" />;
|
return <CreditCardIcon className="w-5 h-5" />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -353,7 +352,7 @@ export default function SignUpFormSimplified({ planDetails: planDetailsProp, pla
|
|||||||
|
|
||||||
{paymentMethodsLoading ? (
|
{paymentMethodsLoading ? (
|
||||||
<div className="flex items-center justify-center p-6 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
<div className="flex items-center justify-center p-6 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||||
<Loader2 className="w-5 h-5 animate-spin text-brand-500 mr-2" />
|
<Loader2Icon className="w-5 h-5 animate-spin text-brand-500 mr-2" />
|
||||||
<span className="text-sm text-gray-600 dark:text-gray-400">Loading payment options...</span>
|
<span className="text-sm text-gray-600 dark:text-gray-400">Loading payment options...</span>
|
||||||
</div>
|
</div>
|
||||||
) : paymentMethods.length === 0 ? (
|
) : paymentMethods.length === 0 ? (
|
||||||
@@ -390,7 +389,7 @@ export default function SignUpFormSimplified({ planDetails: planDetailsProp, pla
|
|||||||
{method.display_name}
|
{method.display_name}
|
||||||
</h4>
|
</h4>
|
||||||
{selectedPaymentMethod === method.payment_method && (
|
{selectedPaymentMethod === method.payment_method && (
|
||||||
<Check className="w-5 h-5 text-brand-500" />
|
<CheckIcon className="w-5 h-5 text-brand-500" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{method.instructions && (
|
{method.instructions && (
|
||||||
|
|||||||
@@ -6,8 +6,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { ChevronLeftIcon, EyeCloseIcon, EyeIcon } from '../../icons';
|
import { ChevronLeftIcon, EyeCloseIcon, EyeIcon, CreditCardIcon, Building2Icon, WalletIcon, CheckIcon, Loader2Icon, CheckCircleIcon } from '../../icons';
|
||||||
import { CreditCard, Building2, Wallet, Check, Loader2, CheckCircle } from 'lucide-react';
|
|
||||||
import Label from '../form/Label';
|
import Label from '../form/Label';
|
||||||
import Input from '../form/input/InputField';
|
import Input from '../form/input/InputField';
|
||||||
import Checkbox from '../form/input/Checkbox';
|
import Checkbox from '../form/input/Checkbox';
|
||||||
@@ -234,13 +233,13 @@ export default function SignUpFormUnified({
|
|||||||
const getPaymentIcon = (method: string) => {
|
const getPaymentIcon = (method: string) => {
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case 'stripe':
|
case 'stripe':
|
||||||
return <CreditCard className="w-5 h-5" />;
|
return <CreditCardIcon className="w-5 h-5" />;
|
||||||
case 'bank_transfer':
|
case 'bank_transfer':
|
||||||
return <Building2 className="w-5 h-5" />;
|
return <Building2Icon className="w-5 h-5" />;
|
||||||
case 'local_wallet':
|
case 'local_wallet':
|
||||||
return <Wallet className="w-5 h-5" />;
|
return <WalletIcon className="w-5 h-5" />;
|
||||||
default:
|
default:
|
||||||
return <CreditCard className="w-5 h-5" />;
|
return <CreditCardIcon className="w-5 h-5" />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -359,7 +358,7 @@ export default function SignUpFormUnified({
|
|||||||
<span className={`font-semibold text-sm ${isSelected ? 'text-brand-600 dark:text-brand-400' : 'text-gray-900 dark:text-white'}`}>
|
<span className={`font-semibold text-sm ${isSelected ? 'text-brand-600 dark:text-brand-400' : 'text-gray-900 dark:text-white'}`}>
|
||||||
{plan.name}
|
{plan.name}
|
||||||
</span>
|
</span>
|
||||||
{isSelected && <CheckCircle className="w-4 h-4 text-brand-500" />}
|
{isSelected && <CheckCircleIcon className="w-4 h-4 text-brand-500" />}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-lg font-bold text-gray-900 dark:text-white">
|
<div className="text-lg font-bold text-gray-900 dark:text-white">
|
||||||
{isFree ? 'Free' : `$${displayPrice.toFixed(2)}`}
|
{isFree ? 'Free' : `$${displayPrice.toFixed(2)}`}
|
||||||
@@ -450,7 +449,7 @@ export default function SignUpFormUnified({
|
|||||||
</Label>
|
</Label>
|
||||||
{paymentMethodsLoading ? (
|
{paymentMethodsLoading ? (
|
||||||
<div className="flex items-center justify-center p-4 bg-gray-50 dark:bg-gray-800 rounded-lg h-[52px]">
|
<div className="flex items-center justify-center p-4 bg-gray-50 dark:bg-gray-800 rounded-lg h-[52px]">
|
||||||
<Loader2 className="w-4 h-4 animate-spin text-brand-500" />
|
<Loader2Icon className="w-4 h-4 animate-spin text-brand-500" />
|
||||||
</div>
|
</div>
|
||||||
) : paymentMethods.length === 0 ? (
|
) : paymentMethods.length === 0 ? (
|
||||||
<div className="p-3 bg-warning-50 border border-warning-200 rounded-lg text-warning-800 dark:bg-warning-900/20 dark:border-warning-800 dark:text-warning-200">
|
<div className="p-3 bg-warning-50 border border-warning-200 rounded-lg text-warning-800 dark:bg-warning-900/20 dark:border-warning-800 dark:text-warning-200">
|
||||||
@@ -514,7 +513,7 @@ export default function SignUpFormUnified({
|
|||||||
<Button type="submit" variant="primary" disabled={loading} className="w-full">
|
<Button type="submit" variant="primary" disabled={loading} className="w-full">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<span className="flex items-center justify-center">
|
<span className="flex items-center justify-center">
|
||||||
<Loader2 className="w-4 h-4 animate-spin mr-2" />
|
<Loader2Icon className="w-4 h-4 animate-spin mr-2" />
|
||||||
Creating your account...
|
Creating your account...
|
||||||
</span>
|
</span>
|
||||||
) : isPaidPlan ? (
|
) : isPaidPlan ? (
|
||||||
@@ -613,7 +612,7 @@ export default function SignUpFormUnified({
|
|||||||
{isSelected && (
|
{isSelected && (
|
||||||
<div className="absolute -top-4 -right-4">
|
<div className="absolute -top-4 -right-4">
|
||||||
<div className="bg-brand-500 rounded-full p-2 shadow-lg">
|
<div className="bg-brand-500 rounded-full p-2 shadow-lg">
|
||||||
<CheckCircle className="w-8 h-8 text-white" />
|
<CheckCircleIcon className="w-8 h-8 text-white" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -621,7 +620,7 @@ export default function SignUpFormUnified({
|
|||||||
{/* Header: Plan name, price, and features in horizontal layout */}
|
{/* Header: Plan name, price, and features in horizontal layout */}
|
||||||
<div className="flex items-center gap-6">
|
<div className="flex items-center gap-6">
|
||||||
{/* Plan Name & Price */}
|
{/* Plan Name & Price */}
|
||||||
<div className="flex-shrink-0" style={{ minWidth: '200px' }}>
|
<div className="flex-shrink-0 min-w-[200px]">
|
||||||
<h3 className="text-lg font-bold text-gray-900 dark:text-white mb-1">{plan.name}</h3>
|
<h3 className="text-lg font-bold text-gray-900 dark:text-white mb-1">{plan.name}</h3>
|
||||||
<div className="flex items-baseline gap-1">
|
<div className="flex items-baseline gap-1">
|
||||||
<span className="text-3xl font-bold text-gray-900 dark:text-white">
|
<span className="text-3xl font-bold text-gray-900 dark:text-white">
|
||||||
@@ -644,7 +643,7 @@ export default function SignUpFormUnified({
|
|||||||
<div className="flex-1 grid grid-cols-2 gap-x-6 gap-y-2.5">
|
<div className="flex-1 grid grid-cols-2 gap-x-6 gap-y-2.5">
|
||||||
{features.map((feature, idx) => (
|
{features.map((feature, idx) => (
|
||||||
<div key={idx} className="flex items-center gap-2">
|
<div key={idx} className="flex items-center gap-2">
|
||||||
<CheckCircle className="w-4 h-4 text-success-500 dark:text-success-400 flex-shrink-0" />
|
<CheckCircleIcon className="w-4 h-4 text-success-500 dark:text-success-400 flex-shrink-0" />
|
||||||
<span className="text-sm text-gray-700 dark:text-gray-300 leading-tight">{feature}</span>
|
<span className="text-sm text-gray-700 dark:text-gray-300 leading-tight">{feature}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Card } from '../ui/card';
|
import { Card } from '../ui/card';
|
||||||
import { DollarSign, TrendingUp, AlertCircle } from 'lucide-react';
|
import { DollarSignIcon, TrendingUpIcon, AlertCircleIcon } from '../../icons';
|
||||||
import Badge from '../ui/badge/Badge';
|
import Badge from '../ui/badge/Badge';
|
||||||
import { getCreditUsageSummary } from '../../services/billing.api';
|
import { getCreditUsageSummary } from '../../services/billing.api';
|
||||||
import { useToast } from '../ui/toast/ToastContainer';
|
import { useToast } from '../ui/toast/ToastContainer';
|
||||||
@@ -72,7 +72,7 @@ export default function CreditCostBreakdownPanel() {
|
|||||||
if (error || !summary) {
|
if (error || !summary) {
|
||||||
return (
|
return (
|
||||||
<Card className="p-6 text-center">
|
<Card className="p-6 text-center">
|
||||||
<AlertCircle className="w-12 h-12 text-error-500 mx-auto mb-4" />
|
<AlertCircleIcon className="w-12 h-12 text-error-500 mx-auto mb-4" />
|
||||||
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-2">
|
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-2">
|
||||||
Failed to Load Cost Data
|
Failed to Load Cost Data
|
||||||
</h3>
|
</h3>
|
||||||
@@ -120,7 +120,7 @@ export default function CreditCostBreakdownPanel() {
|
|||||||
<Card className="p-6 border-l-4 border-brand-500 dark:border-brand-400">
|
<Card className="p-6 border-l-4 border-brand-500 dark:border-brand-400">
|
||||||
<div className="flex items-center gap-3 mb-2">
|
<div className="flex items-center gap-3 mb-2">
|
||||||
<div className="p-2 bg-brand-50 dark:bg-brand-900/20 rounded-lg">
|
<div className="p-2 bg-brand-50 dark:bg-brand-900/20 rounded-lg">
|
||||||
<DollarSign className="w-5 h-5 text-brand-500 dark:text-brand-400" />
|
<DollarSignIcon className="w-5 h-5 text-brand-500 dark:text-brand-400" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-400">Total Cost</div>
|
<div className="text-sm text-gray-600 dark:text-gray-400">Total Cost</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -133,7 +133,7 @@ export default function CreditCostBreakdownPanel() {
|
|||||||
<Card className="p-6 border-l-4 border-success-500 dark:border-success-400">
|
<Card className="p-6 border-l-4 border-success-500 dark:border-success-400">
|
||||||
<div className="flex items-center gap-3 mb-2">
|
<div className="flex items-center gap-3 mb-2">
|
||||||
<div className="p-2 bg-success-50 dark:bg-success-900/20 rounded-lg">
|
<div className="p-2 bg-success-50 dark:bg-success-900/20 rounded-lg">
|
||||||
<TrendingUp className="w-5 h-5 text-success-500 dark:text-success-400" />
|
<TrendingUpIcon className="w-5 h-5 text-success-500 dark:text-success-400" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-400">Avg Cost/Day</div>
|
<div className="text-sm text-gray-600 dark:text-gray-400">Avg Cost/Day</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -146,7 +146,7 @@ export default function CreditCostBreakdownPanel() {
|
|||||||
<Card className="p-6 border-l-4 border-purple-500 dark:border-purple-400">
|
<Card className="p-6 border-l-4 border-purple-500 dark:border-purple-400">
|
||||||
<div className="flex items-center gap-3 mb-2">
|
<div className="flex items-center gap-3 mb-2">
|
||||||
<div className="p-2 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
|
<div className="p-2 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
|
||||||
<TrendingUp className="w-5 h-5 text-purple-500 dark:text-purple-400" />
|
<TrendingUpIcon className="w-5 h-5 text-purple-500 dark:text-purple-400" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-400">Total Operations</div>
|
<div className="text-sm text-gray-600 dark:text-gray-400">Total Operations</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -159,7 +159,7 @@ export default function CreditCostBreakdownPanel() {
|
|||||||
<Card className="p-6 border-l-4 border-info-500 dark:border-info-400">
|
<Card className="p-6 border-l-4 border-info-500 dark:border-info-400">
|
||||||
<div className="flex items-center gap-3 mb-2">
|
<div className="flex items-center gap-3 mb-2">
|
||||||
<div className="p-2 bg-info-50 dark:bg-info-900/20 rounded-lg">
|
<div className="p-2 bg-info-50 dark:bg-info-900/20 rounded-lg">
|
||||||
<DollarSign className="w-5 h-5 text-info-500 dark:text-info-400" />
|
<DollarSignIcon className="w-5 h-5 text-info-500 dark:text-info-400" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-400">Total Credits</div>
|
<div className="text-sm text-gray-600 dark:text-gray-400">Total Credits</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -231,7 +231,7 @@ export default function CreditCostBreakdownPanel() {
|
|||||||
|
|
||||||
{operationsList.length === 0 && (
|
{operationsList.length === 0 && (
|
||||||
<div className="col-span-4 text-center py-12">
|
<div className="col-span-4 text-center py-12">
|
||||||
<DollarSign className="w-12 h-12 text-gray-300 dark:text-gray-600 mx-auto mb-3" />
|
<DollarSignIcon className="w-12 h-12 text-gray-300 dark:text-gray-600 mx-auto mb-3" />
|
||||||
<p className="text-gray-500 dark:text-gray-400">
|
<p className="text-gray-500 dark:text-gray-400">
|
||||||
No cost data available for this period
|
No cost data available for this period
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { Modal } from '../ui/modal';
|
|||||||
import Button from '../ui/button/Button';
|
import Button from '../ui/button/Button';
|
||||||
import Label from '../form/Label';
|
import Label from '../form/Label';
|
||||||
import Input from '../form/input/InputField';
|
import Input from '../form/input/InputField';
|
||||||
import { Loader2, Upload, X, CheckCircle } from 'lucide-react';
|
import { Loader2Icon, UploadIcon, XIcon, CheckCircleIcon } from '../../icons';
|
||||||
import { API_BASE_URL } from '../../services/api';
|
import { API_BASE_URL } from '../../services/api';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '../../store/authStore';
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ export default function PaymentConfirmationModal({
|
|||||||
<div className="p-6 sm:p-8">
|
<div className="p-6 sm:p-8">
|
||||||
{success ? (
|
{success ? (
|
||||||
<div className="text-center py-8">
|
<div className="text-center py-8">
|
||||||
<CheckCircle className="w-16 h-16 text-success-500 mx-auto mb-4" />
|
<CheckCircleIcon className="w-16 h-16 text-success-500 mx-auto mb-4" />
|
||||||
<h3 className="text-2xl font-semibold text-gray-900 dark:text-white mb-2">
|
<h3 className="text-2xl font-semibold text-gray-900 dark:text-white mb-2">
|
||||||
Payment Submitted!
|
Payment Submitted!
|
||||||
</h3>
|
</h3>
|
||||||
@@ -267,7 +267,7 @@ export default function PaymentConfirmationModal({
|
|||||||
className="flex flex-col items-center justify-center w-full h-32 border-2 border-dashed border-gray-300 rounded-lg cursor-pointer hover:border-brand-500 hover:bg-brand-50 dark:border-gray-700 dark:hover:border-brand-400 dark:hover:bg-brand-500/10 transition-colors"
|
className="flex flex-col items-center justify-center w-full h-32 border-2 border-dashed border-gray-300 rounded-lg cursor-pointer hover:border-brand-500 hover:bg-brand-50 dark:border-gray-700 dark:hover:border-brand-400 dark:hover:bg-brand-500/10 transition-colors"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col items-center justify-center pt-5 pb-6">
|
<div className="flex flex-col items-center justify-center pt-5 pb-6">
|
||||||
<Upload className="w-10 h-10 mb-3 text-gray-400" />
|
<UploadIcon className="w-10 h-10 mb-3 text-gray-400" />
|
||||||
<p className="mb-2 text-sm text-gray-500 dark:text-gray-400">
|
<p className="mb-2 text-sm text-gray-500 dark:text-gray-400">
|
||||||
<span className="font-semibold">Click to upload</span> or drag and drop
|
<span className="font-semibold">Click to upload</span> or drag and drop
|
||||||
</p>
|
</p>
|
||||||
@@ -288,7 +288,7 @@ export default function PaymentConfirmationModal({
|
|||||||
) : (
|
) : (
|
||||||
<div className="mt-2 flex items-center justify-between p-3 bg-gray-50 border border-gray-200 rounded-lg dark:bg-gray-800 dark:border-gray-700">
|
<div className="mt-2 flex items-center justify-between p-3 bg-gray-50 border border-gray-200 rounded-lg dark:bg-gray-800 dark:border-gray-700">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<CheckCircle className="w-5 h-5 text-success-500" />
|
<CheckCircleIcon className="w-5 h-5 text-success-500" />
|
||||||
<span className="text-sm text-gray-700 dark:text-gray-300">
|
<span className="text-sm text-gray-700 dark:text-gray-300">
|
||||||
{uploadedFileName}
|
{uploadedFileName}
|
||||||
</span>
|
</span>
|
||||||
@@ -299,13 +299,13 @@ export default function PaymentConfirmationModal({
|
|||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="p-1 hover:bg-gray-200 dark:hover:bg-gray-700 rounded transition-colors"
|
className="p-1 hover:bg-gray-200 dark:hover:bg-gray-700 rounded transition-colors"
|
||||||
>
|
>
|
||||||
<X className="w-4 h-4 text-gray-500" />
|
<XIcon className="w-4 h-4 text-gray-500" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{uploading && (
|
{uploading && (
|
||||||
<div className="mt-2 flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400">
|
<div className="mt-2 flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400">
|
||||||
<Loader2 className="w-4 h-4 animate-spin" />
|
<Loader2Icon className="w-4 h-4 animate-spin" />
|
||||||
<span>Uploading...</span>
|
<span>Uploading...</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -330,7 +330,7 @@ export default function PaymentConfirmationModal({
|
|||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
<Loader2Icon className="w-4 h-4 mr-2 animate-spin" />
|
||||||
Submitting...
|
Submitting...
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
|
|||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '../../store/authStore';
|
||||||
import { API_BASE_URL } from '../../services/api';
|
import { API_BASE_URL } from '../../services/api';
|
||||||
import Button from '../ui/button/Button';
|
import Button from '../ui/button/Button';
|
||||||
import { CheckCircle, XCircle, Clock, RefreshCw } from 'lucide-react';
|
import { CheckCircleIcon, XCircleIcon, ClockIcon, RefreshCwIcon } from '../../icons';
|
||||||
|
|
||||||
interface Payment {
|
interface Payment {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -53,13 +53,13 @@ export default function PaymentHistory() {
|
|||||||
const getStatusIcon = (status: string) => {
|
const getStatusIcon = (status: string) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'succeeded':
|
case 'succeeded':
|
||||||
return <CheckCircle className="w-5 h-5 text-success-500" />;
|
return <CheckCircleIcon className="w-5 h-5 text-success-500" />;
|
||||||
case 'failed':
|
case 'failed':
|
||||||
return <XCircle className="w-5 h-5 text-error-500" />;
|
return <XCircleIcon className="w-5 h-5 text-error-500" />;
|
||||||
case 'pending_approval':
|
case 'pending_approval':
|
||||||
return <Clock className="w-5 h-5 text-warning-500" />;
|
return <ClockIcon className="w-5 h-5 text-warning-500" />;
|
||||||
default:
|
default:
|
||||||
return <Clock className="w-5 h-5 text-gray-500" />;
|
return <ClockIcon className="w-5 h-5 text-gray-500" />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ export default function PaymentHistory() {
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center items-center p-12">
|
<div className="flex justify-center items-center p-12">
|
||||||
<RefreshCw className="w-8 h-8 animate-spin text-gray-400" />
|
<RefreshCwIcon className="w-8 h-8 animate-spin text-gray-400" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -98,7 +98,7 @@ export default function PaymentHistory() {
|
|||||||
Payment History
|
Payment History
|
||||||
</h2>
|
</h2>
|
||||||
<Button onClick={loadPayments} variant="outline" size="sm">
|
<Button onClick={loadPayments} variant="outline" size="sm">
|
||||||
<RefreshCw className="w-4 h-4 mr-2" />
|
<RefreshCwIcon className="w-4 h-4 mr-2" />
|
||||||
Refresh
|
Refresh
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { CheckCircle, Loader2, AlertCircle } from 'lucide-react';
|
import { CheckCircleIcon, Loader2Icon, AlertCircleIcon } from '../../icons';
|
||||||
import { API_BASE_URL } from '../../services/api';
|
import { API_BASE_URL } from '../../services/api';
|
||||||
|
|
||||||
export interface PaymentMethodConfig {
|
export interface PaymentMethodConfig {
|
||||||
@@ -76,7 +76,7 @@ export default function PaymentMethodSelect({
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center p-8">
|
<div className="flex items-center justify-center p-8">
|
||||||
<Loader2 className="w-6 h-6 animate-spin text-brand-500" />
|
<Loader2Icon className="w-6 h-6 animate-spin text-brand-500" />
|
||||||
<span className="ml-3 text-gray-600 dark:text-gray-400">Loading payment methods...</span>
|
<span className="ml-3 text-gray-600 dark:text-gray-400">Loading payment methods...</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -86,7 +86,7 @@ export default function PaymentMethodSelect({
|
|||||||
return (
|
return (
|
||||||
<div className="p-4 rounded-lg border border-error-200 bg-error-50 text-error-800 dark:border-error-800 dark:bg-error-900/20 dark:text-error-200">
|
<div className="p-4 rounded-lg border border-error-200 bg-error-50 text-error-800 dark:border-error-800 dark:bg-error-900/20 dark:text-error-200">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<AlertCircle className="w-5 h-5 flex-shrink-0 mt-0.5" />
|
<AlertCircleIcon className="w-5 h-5 flex-shrink-0 mt-0.5" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">Failed to load payment methods</p>
|
<p className="font-medium">Failed to load payment methods</p>
|
||||||
<p className="text-sm mt-1">{fetchError}</p>
|
<p className="text-sm mt-1">{fetchError}</p>
|
||||||
@@ -106,7 +106,7 @@ export default function PaymentMethodSelect({
|
|||||||
return (
|
return (
|
||||||
<div className="p-4 rounded-lg border border-warning-200 bg-warning-50 text-warning-800 dark:border-warning-800 dark:bg-warning-900/20 dark:text-warning-200">
|
<div className="p-4 rounded-lg border border-warning-200 bg-warning-50 text-warning-800 dark:border-warning-800 dark:bg-warning-900/20 dark:text-warning-200">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<AlertCircle className="w-5 h-5 flex-shrink-0 mt-0.5" />
|
<AlertCircleIcon className="w-5 h-5 flex-shrink-0 mt-0.5" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">No payment methods available</p>
|
<p className="font-medium">No payment methods available</p>
|
||||||
<p className="text-sm mt-1">
|
<p className="text-sm mt-1">
|
||||||
@@ -157,7 +157,7 @@ export default function PaymentMethodSelect({
|
|||||||
{/* Radio indicator */}
|
{/* Radio indicator */}
|
||||||
<div className="flex-shrink-0 mt-0.5">
|
<div className="flex-shrink-0 mt-0.5">
|
||||||
{selectedMethod === method.payment_method ? (
|
{selectedMethod === method.payment_method ? (
|
||||||
<CheckCircle className="w-5 h-5 text-brand-500 dark:text-brand-400" />
|
<CheckCircleIcon className="w-5 h-5 text-brand-500 dark:text-brand-400" />
|
||||||
) : (
|
) : (
|
||||||
<div className="w-5 h-5 rounded-full border-2 border-gray-300 dark:border-gray-600" />
|
<div className="w-5 h-5 rounded-full border-2 border-gray-300 dark:border-gray-600" />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { AlertCircle, CreditCard, X } from 'lucide-react';
|
import { AlertCircleIcon, CreditCardIcon, XIcon } from '../../icons';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import Button from '../ui/button/Button';
|
import Button from '../ui/button/Button';
|
||||||
import { useAuthStore } from '../../store/authStore';
|
import { useAuthStore } from '../../store/authStore';
|
||||||
@@ -118,7 +118,7 @@ export default function PendingPaymentBanner({ className = '' }: PendingPaymentB
|
|||||||
<div className={`relative border-l-4 border-warning-500 bg-warning-50 dark:bg-warning-900/20 ${className}`}>
|
<div className={`relative border-l-4 border-warning-500 bg-warning-50 dark:bg-warning-900/20 ${className}`}>
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<AlertCircle className="w-6 h-6 text-warning-600 dark:text-warning-400 flex-shrink-0 mt-0.5" />
|
<AlertCircleIcon className="w-6 h-6 text-warning-600 dark:text-warning-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="font-semibold text-warning-900 dark:text-warning-100">
|
<h3 className="font-semibold text-warning-900 dark:text-warning-100">
|
||||||
Payment Required
|
Payment Required
|
||||||
@@ -143,7 +143,7 @@ export default function PendingPaymentBanner({ className = '' }: PendingPaymentB
|
|||||||
onClick={handleDismiss}
|
onClick={handleDismiss}
|
||||||
className="p-1 hover:bg-warning-100 dark:hover:bg-warning-800/40 rounded transition-colors"
|
className="p-1 hover:bg-warning-100 dark:hover:bg-warning-800/40 rounded transition-colors"
|
||||||
>
|
>
|
||||||
<X className="w-5 h-5 text-warning-600 dark:text-warning-400" />
|
<XIcon className="w-5 h-5 text-warning-600 dark:text-warning-400" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -170,7 +170,7 @@ export default function PendingPaymentBanner({ className = '' }: PendingPaymentB
|
|||||||
<div className={`relative border-l-4 ${isOverdue ? 'border-error-500 bg-error-50 dark:bg-error-900/20' : 'border-warning-500 bg-warning-50 dark:bg-warning-900/20'} ${className}`}>
|
<div className={`relative border-l-4 ${isOverdue ? 'border-error-500 bg-error-50 dark:bg-error-900/20' : 'border-warning-500 bg-warning-50 dark:bg-warning-900/20'} ${className}`}>
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<AlertCircle
|
<AlertCircleIcon
|
||||||
className={`w-6 h-6 flex-shrink-0 mt-0.5 ${isOverdue ? 'text-error-600 dark:text-error-400' : 'text-warning-600 dark:text-warning-400'}`}
|
className={`w-6 h-6 flex-shrink-0 mt-0.5 ${isOverdue ? 'text-error-600 dark:text-error-400' : 'text-warning-600 dark:text-warning-400'}`}
|
||||||
/>
|
/>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
@@ -230,7 +230,7 @@ export default function PendingPaymentBanner({ className = '' }: PendingPaymentB
|
|||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
startIcon={<CreditCard className="w-4 h-4" />}
|
startIcon={<CreditCardIcon className="w-4 h-4" />}
|
||||||
onClick={() => setShowPaymentModal(true)}
|
onClick={() => setShowPaymentModal(true)}
|
||||||
>
|
>
|
||||||
Confirm Payment
|
Confirm Payment
|
||||||
@@ -252,7 +252,7 @@ export default function PendingPaymentBanner({ className = '' }: PendingPaymentB
|
|||||||
: 'hover:bg-warning-100 dark:hover:bg-warning-800/40 text-warning-600 dark:text-warning-400'
|
: 'hover:bg-warning-100 dark:hover:bg-warning-800/40 text-warning-600 dark:text-warning-400'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<X className="w-5 h-5" />
|
<XIcon className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Card } from '../ui/card';
|
import { Card } from '../ui/card';
|
||||||
import { AlertCircle, TrendingUp, Users, Globe, Tag, FileText, Image, Zap } from 'lucide-react';
|
import { AlertCircleIcon, TrendingUpIcon, UsersIcon, GlobeIcon, TagIcon, FileTextIcon, ImageIcon, ZapIcon } from '../../icons';
|
||||||
import Badge from '../ui/badge/Badge';
|
import Badge from '../ui/badge/Badge';
|
||||||
import { getUsageSummary, type UsageSummary, type LimitUsage } from '../../services/billing.api';
|
import { getUsageSummary, type UsageSummary, type LimitUsage } from '../../services/billing.api';
|
||||||
import { useToast } from '../ui/toast/ToastContainer';
|
import { useToast } from '../ui/toast/ToastContainer';
|
||||||
@@ -106,7 +106,7 @@ function LimitCard({ title, icon, usage, type, daysUntilReset, accentColor = 'br
|
|||||||
<div className={`mt-3 flex items-start gap-2 text-xs ${
|
<div className={`mt-3 flex items-start gap-2 text-xs ${
|
||||||
isDanger ? 'text-error-600 dark:text-error-400' : 'text-warning-600 dark:text-warning-400'
|
isDanger ? 'text-error-600 dark:text-error-400' : 'text-warning-600 dark:text-warning-400'
|
||||||
}`}>
|
}`}>
|
||||||
<AlertCircle className="w-4 h-4 mt-0.5 flex-shrink-0" />
|
<AlertCircleIcon className="w-4 h-4 mt-0.5 flex-shrink-0" />
|
||||||
<span>
|
<span>
|
||||||
{isDanger
|
{isDanger
|
||||||
? 'Limit almost reached! Consider upgrading your plan.'
|
? 'Limit almost reached! Consider upgrading your plan.'
|
||||||
@@ -158,7 +158,7 @@ export default function UsageLimitsPanel() {
|
|||||||
if (error || !summary) {
|
if (error || !summary) {
|
||||||
return (
|
return (
|
||||||
<Card className="p-6 text-center">
|
<Card className="p-6 text-center">
|
||||||
<AlertCircle className="w-12 h-12 text-error-500 mx-auto mb-4" />
|
<AlertCircleIcon className="w-12 h-12 text-error-500 mx-auto mb-4" />
|
||||||
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-2">
|
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-2">
|
||||||
Failed to Load Usage Data
|
Failed to Load Usage Data
|
||||||
</h3>
|
</h3>
|
||||||
@@ -174,18 +174,18 @@ export default function UsageLimitsPanel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hardLimitConfig = {
|
const hardLimitConfig = {
|
||||||
sites: { icon: <Globe className="w-5 h-5" />, color: 'success' as const },
|
sites: { icon: <GlobeIcon className="w-5 h-5" />, color: 'success' as const },
|
||||||
users: { icon: <Users className="w-5 h-5" />, color: 'info' as const },
|
users: { icon: <UsersIcon className="w-5 h-5" />, color: 'info' as const },
|
||||||
keywords: { icon: <Tag className="w-5 h-5" />, color: 'purple' as const },
|
keywords: { icon: <TagIcon className="w-5 h-5" />, color: 'purple' as const },
|
||||||
clusters: { icon: <TrendingUp className="w-5 h-5" />, color: 'warning' as const },
|
clusters: { icon: <TrendingUpIcon className="w-5 h-5" />, color: 'warning' as const },
|
||||||
};
|
};
|
||||||
|
|
||||||
const monthlyLimitConfig = {
|
const monthlyLimitConfig = {
|
||||||
content_ideas: { icon: <FileText className="w-5 h-5" />, color: 'brand' as const },
|
content_ideas: { icon: <FileTextIcon className="w-5 h-5" />, color: 'brand' as const },
|
||||||
content_words: { icon: <FileText className="w-5 h-5" />, color: 'indigo' as const },
|
content_words: { icon: <FileTextIcon className="w-5 h-5" />, color: 'indigo' as const },
|
||||||
images_basic: { icon: <Image className="w-5 h-5" />, color: 'teal' as const },
|
images_basic: { icon: <ImageIcon className="w-5 h-5" />, color: 'teal' as const },
|
||||||
images_premium: { icon: <Zap className="w-5 h-5" />, color: 'cyan' as const },
|
images_premium: { icon: <ZapIcon className="w-5 h-5" />, color: 'cyan' as const },
|
||||||
image_prompts: { icon: <Image className="w-5 h-5" />, color: 'pink' as const },
|
image_prompts: { icon: <ImageIcon className="w-5 h-5" />, color: 'pink' as const },
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -256,7 +256,7 @@ export default function UsageLimitsPanel() {
|
|||||||
<Card className="p-6 bg-gradient-to-r from-brand-50 to-brand-100 dark:from-brand-900/20 dark:to-brand-800/10 border-brand-200 dark:border-brand-700">
|
<Card className="p-6 bg-gradient-to-r from-brand-50 to-brand-100 dark:from-brand-900/20 dark:to-brand-800/10 border-brand-200 dark:border-brand-700">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="p-3 bg-brand-500 rounded-lg text-white">
|
<div className="p-3 bg-brand-500 rounded-lg text-white">
|
||||||
<TrendingUp className="w-6 h-6" />
|
<TrendingUpIcon className="w-6 h-6" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
|
||||||
|
|||||||
@@ -109,8 +109,7 @@ export default function ImageResultCard({
|
|||||||
<img
|
<img
|
||||||
src={imageData.url}
|
src={imageData.url}
|
||||||
alt="Generated image"
|
alt="Generated image"
|
||||||
className="w-full object-contain"
|
className="w-full object-contain max-h-[400px]"
|
||||||
style={{ maxHeight: '400px' }}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Phase 6: Site Integration & Multi-Destination Publishing
|
* Phase 6: Site Integration & Multi-Destination Publishing
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { CheckCircleIcon, XCircleIcon, ClockIcon, RefreshCw } from 'lucide-react';
|
import { CheckCircleIcon, XCircleIcon, ClockIcon, RefreshCwIcon } from '../../icons';
|
||||||
|
|
||||||
interface IntegrationStatusProps {
|
interface IntegrationStatusProps {
|
||||||
syncEnabled: boolean;
|
syncEnabled: boolean;
|
||||||
@@ -25,7 +25,7 @@ export default function IntegrationStatus({
|
|||||||
case 'failed':
|
case 'failed':
|
||||||
return <XCircleIcon className="w-5 h-5 text-error-500" />;
|
return <XCircleIcon className="w-5 h-5 text-error-500" />;
|
||||||
case 'syncing':
|
case 'syncing':
|
||||||
return <RefreshCw className="w-5 h-5 text-brand-500 animate-spin" />;
|
return <RefreshCwIcon className="w-5 h-5 text-brand-500 animate-spin" />;
|
||||||
default:
|
default:
|
||||||
return <ClockIcon className="w-5 h-5 text-gray-400" />;
|
return <ClockIcon className="w-5 h-5 text-gray-400" />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Phase 6: Site Integration & Multi-Destination Publishing
|
* Phase 6: Site Integration & Multi-Destination Publishing
|
||||||
*/
|
*/
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { PlusIcon, TrashIcon, TestTubeIcon, RefreshCw } from 'lucide-react';
|
import { PlusIcon, TrashIcon, TestTubeIcon, RefreshCwIcon } from '../../icons';
|
||||||
import Button from '../ui/button/Button';
|
import Button from '../ui/button/Button';
|
||||||
import { Modal } from '../ui/modal';
|
import { Modal } from '../ui/modal';
|
||||||
import FormModal, { FormField } from '../common/FormModal';
|
import FormModal, { FormField } from '../common/FormModal';
|
||||||
@@ -302,7 +302,7 @@ export default function SiteIntegrationsSection({ siteId }: SiteIntegrationsSect
|
|||||||
onClick={() => handleSync(integration)}
|
onClick={() => handleSync(integration)}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
>
|
>
|
||||||
<RefreshCw className="w-4 h-4 mr-1" />
|
<RefreshCwIcon className="w-4 h-4 mr-1" />
|
||||||
Sync
|
Sync
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ export default function OnboardingWizard({ onComplete, onSkip }: OnboardingWizar
|
|||||||
<div
|
<div
|
||||||
className={`size-8 rounded-full flex items-center justify-center text-sm font-medium transition-colors ${
|
className={`size-8 rounded-full flex items-center justify-center text-sm font-medium transition-colors ${
|
||||||
step.id < currentStep
|
step.id < currentStep
|
||||||
? 'bg-green-500 text-white'
|
? 'bg-success-500 text-white'
|
||||||
: step.id === currentStep
|
: step.id === currentStep
|
||||||
? 'bg-brand-500 text-white'
|
? 'bg-brand-500 text-white'
|
||||||
: 'bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400'
|
: 'bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400'
|
||||||
@@ -246,7 +246,7 @@ export default function OnboardingWizard({ onComplete, onSkip }: OnboardingWizar
|
|||||||
<div
|
<div
|
||||||
className={`h-1 flex-1 mx-2 rounded transition-colors ${
|
className={`h-1 flex-1 mx-2 rounded transition-colors ${
|
||||||
step.id < currentStep
|
step.id < currentStep
|
||||||
? 'bg-green-500'
|
? 'bg-success-500'
|
||||||
: 'bg-gray-200 dark:bg-gray-700'
|
: 'bg-gray-200 dark:bg-gray-700'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ export default function Step2AddSite({
|
|||||||
tone={isSelected ? 'success' : 'neutral'}
|
tone={isSelected ? 'success' : 'neutral'}
|
||||||
variant="soft"
|
variant="soft"
|
||||||
className={`cursor-pointer transition-all ${
|
className={`cursor-pointer transition-all ${
|
||||||
isSelected ? 'ring-2 ring-green-500' : 'hover:bg-gray-200 dark:hover:bg-gray-700'
|
isSelected ? 'ring-2 ring-success-500' : 'hover:bg-gray-200 dark:hover:bg-gray-700'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span onClick={() => handleSectorToggle(sector.slug)} className="flex items-center">
|
<span onClick={() => handleSectorToggle(sector.slug)} className="flex items-center">
|
||||||
@@ -254,20 +254,20 @@ export default function Step2AddSite({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Defaults Info */}
|
{/* Defaults Info */}
|
||||||
<Card className="p-4 bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800 mb-6">
|
<Card className="p-4 bg-brand-50 dark:bg-brand-900/20 border-brand-200 dark:border-brand-800 mb-6">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<GridIcon className="w-5 h-5 text-blue-600 dark:text-blue-400 mt-0.5" />
|
<GridIcon className="w-5 h-5 text-brand-600 dark:text-brand-400 mt-0.5" />
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-medium text-blue-900 dark:text-blue-100 text-sm mb-1">
|
<h4 className="font-medium text-brand-900 dark:text-brand-100 text-sm mb-1">
|
||||||
Optimized Defaults Applied
|
Optimized Defaults Applied
|
||||||
</h4>
|
</h4>
|
||||||
<ul className="text-xs text-blue-700 dark:text-blue-300 space-y-0.5">
|
<ul className="text-xs text-brand-700 dark:text-brand-300 space-y-0.5">
|
||||||
<li>• Auto-approval enabled</li>
|
<li>• Auto-approval enabled</li>
|
||||||
<li>• Auto-publish to site enabled</li>
|
<li>• Auto-publish to site enabled</li>
|
||||||
<li>• 3 articles/day limit</li>
|
<li>• 3 articles/day limit</li>
|
||||||
<li>• Publishing Mon-Fri at 9am, 2pm, 6pm</li>
|
<li>• Publishing Mon-Fri at 9am, 2pm, 6pm</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p className="text-xs text-blue-600 dark:text-blue-400 mt-2">
|
<p className="text-xs text-brand-600 dark:text-brand-400 mt-2">
|
||||||
You can customize these in Site Settings anytime.
|
You can customize these in Site Settings anytime.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -216,9 +216,9 @@ export default function Step3ConnectIntegration({
|
|||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className={`size-10 rounded-lg flex items-center justify-center ${
|
<div className={`size-10 rounded-lg flex items-center justify-center ${
|
||||||
testResult === 'success'
|
testResult === 'success'
|
||||||
? 'bg-green-100 dark:bg-green-900/50 text-green-600 dark:text-green-400'
|
? 'bg-success-100 dark:bg-success-900/50 text-success-600 dark:text-success-400'
|
||||||
: testResult === 'failed'
|
: testResult === 'failed'
|
||||||
? 'bg-red-100 dark:bg-red-900/50 text-red-600 dark:text-red-400'
|
? 'bg-error-100 dark:bg-error-900/50 text-error-600 dark:text-error-400'
|
||||||
: 'bg-gray-100 dark:bg-gray-800 text-gray-500'
|
: 'bg-gray-100 dark:bg-gray-800 text-gray-500'
|
||||||
}`}>
|
}`}>
|
||||||
{testResult === 'success' ? (
|
{testResult === 'success' ? (
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ export default function Step4AddKeywords({
|
|||||||
{keywords.length > 0 && (
|
{keywords.length > 0 && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setKeywords([])}
|
onClick={() => setKeywords([])}
|
||||||
className="text-red-500 hover:text-red-600"
|
className="text-error-500 hover:text-error-600"
|
||||||
>
|
>
|
||||||
Clear all
|
Clear all
|
||||||
</button>
|
</button>
|
||||||
@@ -224,15 +224,15 @@ export default function Step4AddKeywords({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Keyword Suggestions */}
|
{/* Keyword Suggestions */}
|
||||||
<Card className="p-4 bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800 mb-6">
|
<Card className="p-4 bg-brand-50 dark:bg-brand-900/20 border-brand-200 dark:border-brand-800 mb-6">
|
||||||
<h4 className="font-medium text-blue-900 dark:text-blue-100 text-sm mb-2">
|
<h4 className="font-medium text-brand-900 dark:text-brand-100 text-sm mb-2">
|
||||||
💡 Keyword Ideas
|
Keyword Ideas
|
||||||
</h4>
|
</h4>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{SUGGESTIONS.map((suggestion, index) => (
|
{SUGGESTIONS.map((suggestion, index) => (
|
||||||
<span
|
<span
|
||||||
key={index}
|
key={index}
|
||||||
className="text-xs text-blue-700 dark:text-blue-300 bg-blue-100 dark:bg-blue-800/50 px-2 py-1 rounded"
|
className="text-xs text-brand-700 dark:text-brand-300 bg-brand-100 dark:bg-brand-800/50 px-2 py-1 rounded"
|
||||||
>
|
>
|
||||||
{suggestion}
|
{suggestion}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export default function Step5Complete({
|
|||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
{/* Success Animation */}
|
{/* Success Animation */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<div className="inline-flex items-center justify-center size-20 rounded-full bg-green-100 dark:bg-green-900/50 text-green-600 dark:text-green-400 mb-4">
|
<div className="inline-flex items-center justify-center size-20 rounded-full bg-success-100 dark:bg-success-900/50 text-success-600 dark:text-success-400 mb-4">
|
||||||
<CheckCircleIcon className="h-10 w-10" />
|
<CheckCircleIcon className="h-10 w-10" />
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white mb-2">
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-white mb-2">
|
||||||
@@ -75,14 +75,14 @@ export default function Step5Complete({
|
|||||||
</h3>
|
</h3>
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
<li className="flex items-center gap-2 text-sm">
|
<li className="flex items-center gap-2 text-sm">
|
||||||
<CheckCircleIcon className="w-4 h-4 text-green-500" />
|
<CheckCircleIcon className="w-4 h-4 text-success-500" />
|
||||||
<span className="text-gray-700 dark:text-gray-300">
|
<span className="text-gray-700 dark:text-gray-300">
|
||||||
Site: <span className="font-medium">{data.siteName || 'Your Site'}</span>
|
Site: <span className="font-medium">{data.siteName || 'Your Site'}</span>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
{data.integrationTested && (
|
{data.integrationTested && (
|
||||||
<li className="flex items-center gap-2 text-sm">
|
<li className="flex items-center gap-2 text-sm">
|
||||||
<CheckCircleIcon className="w-4 h-4 text-green-500" />
|
<CheckCircleIcon className="w-4 h-4 text-success-500" />
|
||||||
<span className="text-gray-700 dark:text-gray-300">
|
<span className="text-gray-700 dark:text-gray-300">
|
||||||
WordPress integration connected
|
WordPress integration connected
|
||||||
</span>
|
</span>
|
||||||
@@ -90,20 +90,20 @@ export default function Step5Complete({
|
|||||||
)}
|
)}
|
||||||
{data.keywordsAdded && (
|
{data.keywordsAdded && (
|
||||||
<li className="flex items-center gap-2 text-sm">
|
<li className="flex items-center gap-2 text-sm">
|
||||||
<CheckCircleIcon className="w-4 h-4 text-green-500" />
|
<CheckCircleIcon className="w-4 h-4 text-success-500" />
|
||||||
<span className="text-gray-700 dark:text-gray-300">
|
<span className="text-gray-700 dark:text-gray-300">
|
||||||
{data.keywordsCount} keyword{data.keywordsCount !== 1 ? 's' : ''} added to pipeline
|
{data.keywordsCount} keyword{data.keywordsCount !== 1 ? 's' : ''} added to pipeline
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
<li className="flex items-center gap-2 text-sm">
|
<li className="flex items-center gap-2 text-sm">
|
||||||
<CheckCircleIcon className="w-4 h-4 text-green-500" />
|
<CheckCircleIcon className="w-4 h-4 text-success-500" />
|
||||||
<span className="text-gray-700 dark:text-gray-300">
|
<span className="text-gray-700 dark:text-gray-300">
|
||||||
Auto-approval & auto-publish enabled
|
Auto-approval & auto-publish enabled
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center gap-2 text-sm">
|
<li className="flex items-center gap-2 text-sm">
|
||||||
<CheckCircleIcon className="w-4 h-4 text-green-500" />
|
<CheckCircleIcon className="w-4 h-4 text-success-500" />
|
||||||
<span className="text-gray-700 dark:text-gray-300">
|
<span className="text-gray-700 dark:text-gray-300">
|
||||||
Daily automation scheduled
|
Daily automation scheduled
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Phase 6: Site Integration & Multi-Destination Publishing
|
* Phase 6: Site Integration & Multi-Destination Publishing
|
||||||
*/
|
*/
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { PlusIcon, TrashIcon, ArrowUpIcon, ArrowDownIcon } from 'lucide-react';
|
import { PlusIcon, TrashIcon, ArrowUpIcon, ArrowDownIcon } from '../../icons';
|
||||||
import Button from '../ui/button/Button';
|
import Button from '../ui/button/Button';
|
||||||
import Checkbox from '../form/input/Checkbox';
|
import Checkbox from '../form/input/Checkbox';
|
||||||
import Label from '../form/Label';
|
import Label from '../form/Label';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* Component for previewing layouts with sample content
|
* Component for previewing layouts with sample content
|
||||||
*/
|
*/
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { EyeIcon, XIcon, Maximize2Icon } from 'lucide-react';
|
import { EyeIcon, XIcon, Maximize2Icon } from '../../icons';
|
||||||
import { Card } from '../../ui/card';
|
import { Card } from '../../ui/card';
|
||||||
import Button from '../../ui/button/Button';
|
import Button from '../../ui/button/Button';
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* Component for selecting page layouts
|
* Component for selecting page layouts
|
||||||
*/
|
*/
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { CheckIcon } from 'lucide-react';
|
import { CheckIcon } from '../../icons';
|
||||||
import { Card } from '../../ui/card';
|
import { Card } from '../../ui/card';
|
||||||
|
|
||||||
export interface LayoutOption {
|
export interface LayoutOption {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
import { Card } from '../ui/card';
|
import { Card } from '../ui/card';
|
||||||
import Badge from '../ui/badge/Badge';
|
import Badge from '../ui/badge/Badge';
|
||||||
// import { fetchSiteProgress, SiteProgress } from '../../services/api';
|
// import { fetchSiteProgress, SiteProgress } from '../../services/api';
|
||||||
import { CheckCircleIcon, XCircleIcon, AlertCircleIcon, ArrowRightIcon } from 'lucide-react';
|
import { CheckCircleIcon, XCircleIcon, AlertCircleIcon, ArrowRightIcon } from '../../icons';
|
||||||
|
|
||||||
interface SiteProgressWidgetProps {
|
interface SiteProgressWidgetProps {
|
||||||
blueprintId: number;
|
blueprintId: number;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Displays site type indicator (Site Builder or WordPress)
|
* Displays site type indicator (Site Builder or WordPress)
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Wand2, Globe } from 'lucide-react';
|
import { Wand2Icon, GlobeIcon } from '../../icons';
|
||||||
import Badge from '../ui/badge/Badge';
|
import Badge from '../ui/badge/Badge';
|
||||||
|
|
||||||
interface SiteTypeBadgeProps {
|
interface SiteTypeBadgeProps {
|
||||||
@@ -18,13 +18,13 @@ export default function SiteTypeBadge({ hostingType, className = '' }: SiteTypeB
|
|||||||
return {
|
return {
|
||||||
label: 'Site Builder',
|
label: 'Site Builder',
|
||||||
color: 'primary' as const,
|
color: 'primary' as const,
|
||||||
icon: <Wand2 className="w-3 h-3" />,
|
icon: <Wand2Icon className="w-3 h-3" />,
|
||||||
};
|
};
|
||||||
case 'wordpress':
|
case 'wordpress':
|
||||||
return {
|
return {
|
||||||
label: 'WordPress',
|
label: 'WordPress',
|
||||||
color: 'info' as const,
|
color: 'info' as const,
|
||||||
icon: <Globe className="w-3 h-3" />,
|
icon: <GlobeIcon className="w-3 h-3" />,
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* Component for editing CSS styles and theme customization
|
* Component for editing CSS styles and theme customization
|
||||||
*/
|
*/
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { CodeIcon, PaletteIcon, TypeIcon, SaveIcon, RefreshCwIcon } from 'lucide-react';
|
import { CodeIcon, PaletteIcon, TypeIcon, SaveIcon, RefreshCwIcon } from '../../icons';
|
||||||
import { Card } from '../../ui/card';
|
import { Card } from '../../ui/card';
|
||||||
import Button from '../../ui/button/Button';
|
import Button from '../../ui/button/Button';
|
||||||
import Label from '../../form/Label';
|
import Label from '../../form/Label';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* Component for customizing template settings and styles
|
* Component for customizing template settings and styles
|
||||||
*/
|
*/
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { SettingsIcon, PaletteIcon, TypeIcon, LayoutIcon } from 'lucide-react';
|
import { SettingsIcon, PaletteIcon, TypeIcon, LayoutIcon } from '../../icons';
|
||||||
import { Card } from '../../ui/card';
|
import { Card } from '../../ui/card';
|
||||||
import Label from '../../form/Label';
|
import Label from '../../form/Label';
|
||||||
import SelectDropdown from '../../form/SelectDropdown';
|
import SelectDropdown from '../../form/SelectDropdown';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* Component for browsing and selecting page templates
|
* Component for browsing and selecting page templates
|
||||||
*/
|
*/
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { SearchIcon, FilterIcon, CheckIcon } from 'lucide-react';
|
import { SearchIcon, FilterIcon, CheckIcon } from '../../icons';
|
||||||
import { Card } from '../../ui/card';
|
import { Card } from '../../ui/card';
|
||||||
import Button from '../../ui/button/Button';
|
import Button from '../../ui/button/Button';
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Globe, CheckCircle, XCircle, Settings, RefreshCw, AlertCircle, ExternalLink } from 'lucide-react';
|
import { GlobeIcon, CheckCircleIcon, XCircleIcon, SettingsIcon, RefreshCwIcon, AlertCircleIcon, ExternalLinkIcon } from '../../icons';
|
||||||
import { Card } from '../ui/card';
|
import { Card } from '../ui/card';
|
||||||
import Button from '../ui/button/Button';
|
import Button from '../ui/button/Button';
|
||||||
import Badge from '../ui/badge/Badge';
|
import Badge from '../ui/badge/Badge';
|
||||||
@@ -49,7 +49,7 @@ export default function WordPressIntegrationCard({
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="p-3 bg-purple-100 dark:bg-purple-900/30 rounded-lg">
|
<div className="p-3 bg-purple-100 dark:bg-purple-900/30 rounded-lg">
|
||||||
<Globe className="w-6 h-6 text-purple-600 dark:text-purple-400" />
|
<GlobeIcon className="w-6 h-6 text-purple-600 dark:text-purple-400" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||||
@@ -74,7 +74,7 @@ export default function WordPressIntegrationCard({
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="p-3 bg-purple-100 dark:bg-purple-900/30 rounded-lg">
|
<div className="p-3 bg-purple-100 dark:bg-purple-900/30 rounded-lg">
|
||||||
<Globe className="w-6 h-6 text-purple-600 dark:text-purple-400" />
|
<GlobeIcon className="w-6 h-6 text-purple-600 dark:text-purple-400" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||||
@@ -101,13 +101,13 @@ export default function WordPressIntegrationCard({
|
|||||||
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">Sync Status</p>
|
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">Sync Status</p>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{integration.sync_status === 'success' || integration.sync_status === 'healthy' ? (
|
{integration.sync_status === 'success' || integration.sync_status === 'healthy' ? (
|
||||||
<CheckCircle className="w-4 h-4 text-success-500" />
|
<CheckCircleIcon className="w-4 h-4 text-success-500" />
|
||||||
) : integration.sync_status === 'failed' || integration.sync_status === 'error' ? (
|
) : integration.sync_status === 'failed' || integration.sync_status === 'error' ? (
|
||||||
<XCircle className="w-4 h-4 text-error-500" />
|
<XCircleIcon className="w-4 h-4 text-error-500" />
|
||||||
) : integration.sync_status === 'warning' ? (
|
) : integration.sync_status === 'warning' ? (
|
||||||
<AlertCircle className="w-4 h-4 text-warning-500" />
|
<AlertCircleIcon className="w-4 h-4 text-warning-500" />
|
||||||
) : (
|
) : (
|
||||||
<RefreshCw className="w-4 h-4 text-warning-500 animate-spin" />
|
<RefreshCwIcon className="w-4 h-4 text-warning-500 animate-spin" />
|
||||||
)}
|
)}
|
||||||
<span className="text-sm font-medium text-gray-900 dark:text-white capitalize">
|
<span className="text-sm font-medium text-gray-900 dark:text-white capitalize">
|
||||||
{integration.sync_status === 'healthy' ? 'Healthy' :
|
{integration.sync_status === 'healthy' ? 'Healthy' :
|
||||||
@@ -132,7 +132,7 @@ export default function WordPressIntegrationCard({
|
|||||||
<div className="pt-2 border-t border-gray-200 dark:border-gray-700">
|
<div className="pt-2 border-t border-gray-200 dark:border-gray-700">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<AlertCircle className="w-4 h-4 text-warning-500" />
|
<AlertCircleIcon className="w-4 h-4 text-warning-500" />
|
||||||
<span className="text-sm text-gray-600 dark:text-gray-400">
|
<span className="text-sm text-gray-600 dark:text-gray-400">
|
||||||
{integration.mismatch_count} sync mismatch{integration.mismatch_count !== 1 ? 'es' : ''} detected
|
{integration.mismatch_count} sync mismatch{integration.mismatch_count !== 1 ? 'es' : ''} detected
|
||||||
</span>
|
</span>
|
||||||
@@ -144,7 +144,7 @@ export default function WordPressIntegrationCard({
|
|||||||
onClick={() => navigate(`/sites/${siteId}/sync`)}
|
onClick={() => navigate(`/sites/${siteId}/sync`)}
|
||||||
>
|
>
|
||||||
View Details
|
View Details
|
||||||
<ExternalLink className="w-3 h-3 ml-1" />
|
<ExternalLinkIcon className="w-3 h-3 ml-1" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -155,7 +155,7 @@ export default function WordPressIntegrationCard({
|
|||||||
<div className="pt-2 border-t border-gray-200 dark:border-gray-700">
|
<div className="pt-2 border-t border-gray-200 dark:border-gray-700">
|
||||||
<div className="p-2 bg-error-50 dark:bg-error-900/20 border border-error-200 dark:border-error-800 rounded-lg">
|
<div className="p-2 bg-error-50 dark:bg-error-900/20 border border-error-200 dark:border-error-800 rounded-lg">
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<XCircle className="w-4 h-4 text-error-500 mt-0.5 flex-shrink-0" />
|
<XCircleIcon className="w-4 h-4 text-error-500 mt-0.5 flex-shrink-0" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<p className="text-xs font-medium text-error-800 dark:text-error-300 mb-1">
|
<p className="text-xs font-medium text-error-800 dark:text-error-300 mb-1">
|
||||||
Sync Error
|
Sync Error
|
||||||
@@ -176,7 +176,7 @@ export default function WordPressIntegrationCard({
|
|||||||
size="sm"
|
size="sm"
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
>
|
>
|
||||||
<Settings className="w-4 h-4 mr-2" />
|
<SettingsIcon className="w-4 h-4 mr-2" />
|
||||||
Manage
|
Manage
|
||||||
</Button>
|
</Button>
|
||||||
{siteId && (
|
{siteId && (
|
||||||
@@ -186,7 +186,7 @@ export default function WordPressIntegrationCard({
|
|||||||
size="sm"
|
size="sm"
|
||||||
title="View Sync Dashboard"
|
title="View Sync Dashboard"
|
||||||
>
|
>
|
||||||
<ExternalLink className="w-4 h-4" />
|
<ExternalLinkIcon className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{onSync && (
|
{onSync && (
|
||||||
@@ -196,7 +196,7 @@ export default function WordPressIntegrationCard({
|
|||||||
size="sm"
|
size="sm"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
<RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />
|
<RefreshCwIcon className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,9 +17,11 @@ import {
|
|||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
CopyIcon,
|
CopyIcon,
|
||||||
TrashBinIcon
|
TrashBinIcon,
|
||||||
|
GlobeIcon,
|
||||||
|
KeyIcon,
|
||||||
|
RefreshCwIcon
|
||||||
} from '../../icons';
|
} from '../../icons';
|
||||||
import { Globe, Key, RefreshCw } from 'lucide-react';
|
|
||||||
|
|
||||||
interface WordPressIntegrationFormProps {
|
interface WordPressIntegrationFormProps {
|
||||||
siteId: number;
|
siteId: number;
|
||||||
@@ -202,7 +204,7 @@ export default function WordPressIntegrationForm({
|
|||||||
<div className="flex items-center justify-between gap-3">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="p-3 bg-purple-100 dark:bg-purple-900/30 rounded-lg">
|
<div className="p-3 bg-purple-100 dark:bg-purple-900/30 rounded-lg">
|
||||||
<Globe className="w-6 h-6 text-purple-600 dark:text-purple-400" />
|
<GlobeIcon className="w-6 h-6 text-purple-600 dark:text-purple-400" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
|
||||||
@@ -257,7 +259,7 @@ export default function WordPressIntegrationForm({
|
|||||||
>
|
>
|
||||||
{generatingKey ? (
|
{generatingKey ? (
|
||||||
<>
|
<>
|
||||||
<RefreshCw className="w-4 h-4 mr-2 animate-spin" />
|
<RefreshCwIcon className="w-4 h-4 mr-2 animate-spin" />
|
||||||
Generating...
|
Generating...
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@@ -313,7 +315,7 @@ export default function WordPressIntegrationForm({
|
|||||||
className="inline-flex h-11 w-11 items-center justify-center rounded-lg border border-gray-300 text-gray-700 dark:border-gray-700 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50"
|
className="inline-flex h-11 w-11 items-center justify-center rounded-lg border border-gray-300 text-gray-700 dark:border-gray-700 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50"
|
||||||
title="Regenerate"
|
title="Regenerate"
|
||||||
>
|
>
|
||||||
<RefreshCw className={`w-5 h-5 ${generatingKey ? 'animate-spin' : ''}`} />
|
<RefreshCwIcon className={`w-5 h-5 ${generatingKey ? 'animate-spin' : ''}`} />
|
||||||
</button>
|
</button>
|
||||||
<div className="invisible absolute bottom-full left-1/2 z-50 mb-2.5 -translate-x-1/2 opacity-0 transition-opacity duration-300 group-hover:visible group-hover:opacity-100">
|
<div className="invisible absolute bottom-full left-1/2 z-50 mb-2.5 -translate-x-1/2 opacity-0 transition-opacity duration-300 group-hover:visible group-hover:opacity-100">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
@@ -358,7 +360,7 @@ export default function WordPressIntegrationForm({
|
|||||||
className="text-gray-500 hover:text-brand-500 dark:text-gray-400 dark:hover:text-brand-400 disabled:opacity-50 transition-colors"
|
className="text-gray-500 hover:text-brand-500 dark:text-gray-400 dark:hover:text-brand-400 disabled:opacity-50 transition-colors"
|
||||||
title="Regenerate API key"
|
title="Regenerate API key"
|
||||||
>
|
>
|
||||||
<RefreshCw className={`w-5 h-5 ${generatingKey ? 'animate-spin' : ''}`} />
|
<RefreshCwIcon className={`w-5 h-5 ${generatingKey ? 'animate-spin' : ''}`} />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleRevokeApiKey}
|
onClick={handleRevokeApiKey}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Form for connecting/managing WordPress integration
|
* Form for connecting/managing WordPress integration
|
||||||
*/
|
*/
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { X, Globe, AlertCircle } from 'lucide-react';
|
import { XIcon, GlobeIcon, AlertCircleIcon } from '../../icons';
|
||||||
import { Modal } from '../ui/modal';
|
import { Modal } from '../ui/modal';
|
||||||
import Button from '../ui/button/Button';
|
import Button from '../ui/button/Button';
|
||||||
import Label from '../form/Label';
|
import Label from '../form/Label';
|
||||||
@@ -70,7 +70,7 @@ export default function WordPressIntegrationModal({
|
|||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="p-2 bg-purple-100 dark:bg-purple-900/30 rounded-lg">
|
<div className="p-2 bg-purple-100 dark:bg-purple-900/30 rounded-lg">
|
||||||
<Globe className="w-5 h-5 text-purple-600 dark:text-purple-400" />
|
<GlobeIcon className="w-5 h-5 text-purple-600 dark:text-purple-400" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
|
||||||
{initialData ? 'Edit WordPress Integration' : 'Connect WordPress Site'}
|
{initialData ? 'Edit WordPress Integration' : 'Connect WordPress Site'}
|
||||||
@@ -80,14 +80,14 @@ export default function WordPressIntegrationModal({
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
||||||
>
|
>
|
||||||
<X className="w-5 h-5" />
|
<XIcon className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
<div className="bg-brand-50 dark:bg-brand-900/20 border border-brand-200 dark:border-brand-800 rounded-lg p-4 mb-4">
|
<div className="bg-brand-50 dark:bg-brand-900/20 border border-brand-200 dark:border-brand-800 rounded-lg p-4 mb-4">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<AlertCircle className="w-5 h-5 text-brand-600 dark:text-brand-400 mt-0.5 flex-shrink-0" />
|
<AlertCircleIcon className="w-5 h-5 text-brand-600 dark:text-brand-400 mt-0.5 flex-shrink-0" />
|
||||||
<div className="text-sm text-brand-800 dark:text-brand-300">
|
<div className="text-sm text-brand-800 dark:text-brand-300">
|
||||||
<p className="font-medium mb-1">WordPress Application Password Required</p>
|
<p className="font-medium mb-1">WordPress Application Password Required</p>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Check } from 'lucide-react';
|
import { CheckIcon } from '../../../icons';
|
||||||
|
|
||||||
export interface PricingPlan {
|
export interface PricingPlan {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -141,7 +141,7 @@ export function PricingTable({ variant = '1', title, plans, showToggle = false,
|
|||||||
<ul className="space-y-3 mb-6 flex-grow">
|
<ul className="space-y-3 mb-6 flex-grow">
|
||||||
{plan.features.map((feature, index) => (
|
{plan.features.map((feature, index) => (
|
||||||
<li key={index} className="flex items-start gap-2">
|
<li key={index} className="flex items-start gap-2">
|
||||||
<Check className="w-5 h-5 text-success-500 flex-shrink-0 mt-0.5" />
|
<CheckIcon className="w-5 h-5 text-success-500 flex-shrink-0 mt-0.5" />
|
||||||
<span className="text-sm text-gray-700 dark:text-gray-300">{feature}</span>
|
<span className="text-sm text-gray-700 dark:text-gray-300">{feature}</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
@@ -152,7 +152,7 @@ export function PricingTable({ variant = '1', title, plans, showToggle = false,
|
|||||||
<div className="text-xs font-semibold text-gray-500 dark:text-gray-400 mb-2">LIMITS</div>
|
<div className="text-xs font-semibold text-gray-500 dark:text-gray-400 mb-2">LIMITS</div>
|
||||||
{plan.max_sites && (
|
{plan.max_sites && (
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<Check className="w-4 h-4 text-brand-500 flex-shrink-0 mt-0.5" />
|
<CheckIcon className="w-4 h-4 text-brand-500 flex-shrink-0 mt-0.5" />
|
||||||
<span className="text-xs text-gray-600 dark:text-gray-400">
|
<span className="text-xs text-gray-600 dark:text-gray-400">
|
||||||
{plan.max_sites === 99999 ? 'Unlimited' : plan.max_sites} Sites
|
{plan.max_sites === 99999 ? 'Unlimited' : plan.max_sites} Sites
|
||||||
</span>
|
</span>
|
||||||
@@ -160,7 +160,7 @@ export function PricingTable({ variant = '1', title, plans, showToggle = false,
|
|||||||
)}
|
)}
|
||||||
{plan.max_users && (
|
{plan.max_users && (
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<Check className="w-4 h-4 text-brand-500 flex-shrink-0 mt-0.5" />
|
<CheckIcon className="w-4 h-4 text-brand-500 flex-shrink-0 mt-0.5" />
|
||||||
<span className="text-xs text-gray-600 dark:text-gray-400">
|
<span className="text-xs text-gray-600 dark:text-gray-400">
|
||||||
{plan.max_users === 99999 ? 'Unlimited' : plan.max_users} Team Members
|
{plan.max_users === 99999 ? 'Unlimited' : plan.max_users} Team Members
|
||||||
</span>
|
</span>
|
||||||
@@ -168,7 +168,7 @@ export function PricingTable({ variant = '1', title, plans, showToggle = false,
|
|||||||
)}
|
)}
|
||||||
{plan.max_content_words && (
|
{plan.max_content_words && (
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<Check className="w-4 h-4 text-brand-500 flex-shrink-0 mt-0.5" />
|
<CheckIcon className="w-4 h-4 text-brand-500 flex-shrink-0 mt-0.5" />
|
||||||
<span className="text-xs text-gray-600 dark:text-gray-400">
|
<span className="text-xs text-gray-600 dark:text-gray-400">
|
||||||
{(plan.max_content_words / 1000).toLocaleString()}K Words/month
|
{(plan.max_content_words / 1000).toLocaleString()}K Words/month
|
||||||
</span>
|
</span>
|
||||||
@@ -176,7 +176,7 @@ export function PricingTable({ variant = '1', title, plans, showToggle = false,
|
|||||||
)}
|
)}
|
||||||
{plan.max_content_ideas && (
|
{plan.max_content_ideas && (
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<Check className="w-4 h-4 text-brand-500 flex-shrink-0 mt-0.5" />
|
<CheckIcon className="w-4 h-4 text-brand-500 flex-shrink-0 mt-0.5" />
|
||||||
<span className="text-xs text-gray-600 dark:text-gray-400">
|
<span className="text-xs text-gray-600 dark:text-gray-400">
|
||||||
{plan.max_content_ideas} Ideas/month
|
{plan.max_content_ideas} Ideas/month
|
||||||
</span>
|
</span>
|
||||||
@@ -184,7 +184,7 @@ export function PricingTable({ variant = '1', title, plans, showToggle = false,
|
|||||||
)}
|
)}
|
||||||
{plan.max_images_basic && (
|
{plan.max_images_basic && (
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<Check className="w-4 h-4 text-brand-500 flex-shrink-0 mt-0.5" />
|
<CheckIcon className="w-4 h-4 text-brand-500 flex-shrink-0 mt-0.5" />
|
||||||
<span className="text-xs text-gray-600 dark:text-gray-400">
|
<span className="text-xs text-gray-600 dark:text-gray-400">
|
||||||
{plan.max_images_basic} Images/month
|
{plan.max_images_basic} Images/month
|
||||||
</span>
|
</span>
|
||||||
@@ -192,7 +192,7 @@ export function PricingTable({ variant = '1', title, plans, showToggle = false,
|
|||||||
)}
|
)}
|
||||||
{plan.included_credits && (
|
{plan.included_credits && (
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<Check className="w-4 h-4 text-brand-500 flex-shrink-0 mt-0.5" />
|
<CheckIcon className="w-4 h-4 text-brand-500 flex-shrink-0 mt-0.5" />
|
||||||
<span className="text-xs text-gray-600 dark:text-gray-400">
|
<span className="text-xs text-gray-600 dark:text-gray-400">
|
||||||
{plan.included_credits.toLocaleString()} Content pieces/month
|
{plan.included_credits.toLocaleString()} Content pieces/month
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -167,7 +167,11 @@ export const createKeywordsPageConfig = (
|
|||||||
...clusterColumn,
|
...clusterColumn,
|
||||||
sortable: false, // Backend doesn't support sorting by cluster_id
|
sortable: false, // Backend doesn't support sorting by cluster_id
|
||||||
sortField: 'cluster_id',
|
sortField: 'cluster_id',
|
||||||
render: (_value: string, row: Keyword) => row.cluster_name || '-',
|
render: (_value: string, row: Keyword) => row.cluster_name ? (
|
||||||
|
<Badge color="info" size="xs" variant="outline">
|
||||||
|
<span className="text-[11px] font-normal">{row.cluster_name}</span>
|
||||||
|
</Badge>
|
||||||
|
) : <span className="text-gray-400">-</span>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...difficultyColumn,
|
...difficultyColumn,
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export const createdColumn = {
|
|||||||
label: 'Created',
|
label: 'Created',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
date: true,
|
date: true,
|
||||||
width: '150px',
|
width: '100px',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updatedColumn = {
|
export const updatedColumn = {
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export {
|
|||||||
CalendarIcon,
|
CalendarIcon,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Aliases for commonly used icon names
|
// Aliases for commonly used icon names (lucide-react replacements)
|
||||||
export { AngleLeftIcon as ArrowLeftIcon };
|
export { AngleLeftIcon as ArrowLeftIcon };
|
||||||
export { FileIcon as FileTextIcon };
|
export { FileIcon as FileTextIcon };
|
||||||
export { FileIcon as ImageIcon }; // Use FileIcon as ImageIcon alias
|
export { FileIcon as ImageIcon }; // Use FileIcon as ImageIcon alias
|
||||||
@@ -124,9 +124,46 @@ export { TimeIcon as ClockIcon };
|
|||||||
export { ErrorIcon as XCircleIcon };
|
export { ErrorIcon as XCircleIcon };
|
||||||
export { BoxIcon as TagIcon };
|
export { BoxIcon as TagIcon };
|
||||||
export { CloseIcon as XMarkIcon };
|
export { CloseIcon as XMarkIcon };
|
||||||
|
export { CloseIcon as XIcon }; // X alias
|
||||||
export { BoltIcon as PlayIcon }; // Use BoltIcon for play (running state)
|
export { BoltIcon as PlayIcon }; // Use BoltIcon for play (running state)
|
||||||
export { TimeIcon as PauseIcon }; // Use TimeIcon for pause state
|
export { TimeIcon as PauseIcon }; // Use TimeIcon for pause state
|
||||||
export { ArrowUpIcon as TrendingUpIcon }; // Trend up indicator
|
export { ArrowUpIcon as TrendingUpIcon }; // Trend up indicator
|
||||||
export { ArrowDownIcon as TrendingDownIcon }; // Trend down indicator
|
export { ArrowDownIcon as TrendingDownIcon }; // Trend down indicator
|
||||||
export { BoxCubeIcon as SettingsIcon }; // Settings/cog alias
|
export { BoxCubeIcon as SettingsIcon }; // Settings/cog alias
|
||||||
export { InfoIcon as HelpCircleIcon }; // Help/question circle
|
export { InfoIcon as HelpCircleIcon }; // Help/question circle
|
||||||
|
export { AlertIcon as AlertCircleIcon }; // Alert/warning circle
|
||||||
|
export { AlertIcon as AlertTriangleIcon }; // Alert triangle alias
|
||||||
|
export { CheckLineIcon as CheckIcon }; // Simple check mark
|
||||||
|
export { TrashBinIcon as TrashIcon }; // Trash alias
|
||||||
|
export { TrashBinIcon as Trash2Icon }; // Trash2 alias
|
||||||
|
export { CheckCircleIcon as CheckCheckIcon }; // Double check alias
|
||||||
|
export { ListIcon as FilterIcon }; // Filter alias
|
||||||
|
export { PageIcon as GlobeIcon }; // Globe alias
|
||||||
|
export { BoltIcon as ZapIcon }; // Zap/lightning alias
|
||||||
|
export { TimeIcon as RefreshCwIcon }; // Refresh alias
|
||||||
|
export { LockIcon as KeyIcon }; // Key alias
|
||||||
|
export { DownloadIcon as UploadIcon }; // Upload alias
|
||||||
|
export { DollarLineIcon as DollarSignIcon }; // Dollar sign alias
|
||||||
|
export { UserIcon as UsersIcon }; // Users alias
|
||||||
|
export { PieChartIcon as BarChart3Icon }; // Bar chart alias
|
||||||
|
export { PieChartIcon as ActivityIcon }; // Activity alias
|
||||||
|
export { TimeIcon as Loader2Icon }; // Loader alias (spinning)
|
||||||
|
export { BoxCubeIcon as DatabaseIcon }; // Database alias
|
||||||
|
export { FileIcon as FileArchiveIcon }; // File archive alias
|
||||||
|
export { PageIcon as ExternalLinkIcon }; // External link alias
|
||||||
|
export { PencilIcon as Wand2Icon }; // Wand alias
|
||||||
|
export { BoxCubeIcon as LayoutIcon }; // Layout alias
|
||||||
|
export { PencilIcon as SaveIcon }; // Save alias
|
||||||
|
export { BoxCubeIcon as CodeIcon }; // Code alias
|
||||||
|
export { GridIcon as SearchIcon }; // Search alias
|
||||||
|
export { BoxCubeIcon as PaletteIcon }; // Palette alias
|
||||||
|
export { FileIcon as TypeIcon }; // Type alias
|
||||||
|
export { EyeIcon as Maximize2Icon }; // Maximize alias
|
||||||
|
export { AngleRightIcon as ChevronRightIcon }; // Chevron right alias
|
||||||
|
export { BoxCubeIcon as Building2Icon }; // Building alias
|
||||||
|
export { BoxIcon as CreditCardIcon }; // Credit card alias
|
||||||
|
export { BoxIcon as WalletIcon }; // Wallet alias
|
||||||
|
export { AlertIcon as BellIcon }; // Bell notification alias
|
||||||
|
export { PlugInIcon as TestTubeIcon }; // Test tube alias
|
||||||
|
export { MailIcon as Mail }; // Mail without Icon suffix
|
||||||
|
export { BoxIcon as PackageIcon }; // Package alias
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import PageMeta from "../../components/common/PageMeta";
|
import PageMeta from "../../components/common/PageMeta";
|
||||||
import ComponentCard from "../../components/common/ComponentCard";
|
import ComponentCard from "../../components/common/ComponentCard";
|
||||||
import { Card } from "../../components/ui/card";
|
import { Card } from "../../components/ui/card";
|
||||||
import { Download, Upload, Database, FileArchive, CheckCircle } from 'lucide-react';
|
import { DownloadIcon, UploadIcon, DatabaseIcon, FileArchiveIcon, CheckCircleIcon } from '../../icons';
|
||||||
|
|
||||||
export default function ImportExport() {
|
export default function ImportExport() {
|
||||||
return (
|
return (
|
||||||
@@ -12,7 +12,7 @@ export default function ImportExport() {
|
|||||||
<div className="text-center py-8 max-w-3xl mx-auto">
|
<div className="text-center py-8 max-w-3xl mx-auto">
|
||||||
<div className="mb-6 flex justify-center">
|
<div className="mb-6 flex justify-center">
|
||||||
<div className="p-4 bg-brand-100 dark:bg-brand-900/30 rounded-full">
|
<div className="p-4 bg-brand-100 dark:bg-brand-900/30 rounded-full">
|
||||||
<Database className="w-12 h-12 text-brand-600 dark:text-brand-400" />
|
<DatabaseIcon className="w-12 h-12 text-brand-600 dark:text-brand-400" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -30,31 +30,31 @@ export default function ImportExport() {
|
|||||||
</h2>
|
</h2>
|
||||||
<div className="space-y-3 text-left">
|
<div className="space-y-3 text-left">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<CheckCircle className="w-5 h-5 text-success-600 dark:text-success-400 flex-shrink-0 mt-0.5" />
|
<CheckCircleIcon className="w-5 h-5 text-success-600 dark:text-success-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="text-gray-700 dark:text-gray-300">
|
<div className="text-gray-700 dark:text-gray-300">
|
||||||
<strong>Export your keywords as a file</strong> (backup or share)
|
<strong>Export your keywords as a file</strong> (backup or share)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<CheckCircle className="w-5 h-5 text-success-600 dark:text-success-400 flex-shrink-0 mt-0.5" />
|
<CheckCircleIcon className="w-5 h-5 text-success-600 dark:text-success-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="text-gray-700 dark:text-gray-300">
|
<div className="text-gray-700 dark:text-gray-300">
|
||||||
<strong>Export all your articles</strong> in different formats
|
<strong>Export all your articles</strong> in different formats
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<CheckCircle className="w-5 h-5 text-success-600 dark:text-success-400 flex-shrink-0 mt-0.5" />
|
<CheckCircleIcon className="w-5 h-5 text-success-600 dark:text-success-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="text-gray-700 dark:text-gray-300">
|
<div className="text-gray-700 dark:text-gray-300">
|
||||||
<strong>Import keywords from other sources</strong>
|
<strong>Import keywords from other sources</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<CheckCircle className="w-5 h-5 text-success-600 dark:text-success-400 flex-shrink-0 mt-0.5" />
|
<CheckCircleIcon className="w-5 h-5 text-success-600 dark:text-success-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="text-gray-700 dark:text-gray-300">
|
<div className="text-gray-700 dark:text-gray-300">
|
||||||
<strong>Backup and restore</strong> your entire account
|
<strong>Backup and restore</strong> your entire account
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<CheckCircle className="w-5 h-5 text-success-600 dark:text-success-400 flex-shrink-0 mt-0.5" />
|
<CheckCircleIcon className="w-5 h-5 text-success-600 dark:text-success-400 flex-shrink-0 mt-0.5" />
|
||||||
<div className="text-gray-700 dark:text-gray-300">
|
<div className="text-gray-700 dark:text-gray-300">
|
||||||
<strong>Download your settings</strong> and configurations
|
<strong>Download your settings</strong> and configurations
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ import Badge from '../../components/ui/badge/Badge';
|
|||||||
|
|
||||||
// Site Icon SVG - Globe
|
// Site Icon SVG - Globe
|
||||||
const SiteIcon = () => (
|
const SiteIcon = () => (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" className="text-brand-500">
|
||||||
<circle cx="12" cy="12" r="10" stroke="#3B82F6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
<circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
<path d="M2 12h20" stroke="#3B82F6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
<path d="M2 12h20" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" stroke="#3B82F6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
CheckCircle,
|
CheckCircleIcon,
|
||||||
XCircle,
|
XCircleIcon,
|
||||||
AlertTriangle,
|
AlertTriangleIcon,
|
||||||
Loader2,
|
Loader2Icon,
|
||||||
Activity,
|
ActivityIcon,
|
||||||
Clock,
|
ClockIcon,
|
||||||
Globe,
|
GlobeIcon,
|
||||||
RefreshCw,
|
RefreshCwIcon,
|
||||||
TestTube,
|
TestTubeIcon,
|
||||||
Wrench
|
SettingsIcon as WrenchIcon
|
||||||
} from 'lucide-react';
|
} from '../../icons';
|
||||||
import { useSiteStore } from '../../store/siteStore';
|
import { useSiteStore } from '../../store/siteStore';
|
||||||
import { useToast } from '../../components/ui/toast/ToastContainer';
|
import { useToast } from '../../components/ui/toast/ToastContainer';
|
||||||
import { API_BASE_URL, fetchAPI } from '../../services/api';
|
import { API_BASE_URL, fetchAPI } from '../../services/api';
|
||||||
@@ -343,7 +343,7 @@ export default function WordPressIntegrationDebug() {
|
|||||||
if (initializing) {
|
if (initializing) {
|
||||||
return (
|
return (
|
||||||
<div className="p-8 text-center">
|
<div className="p-8 text-center">
|
||||||
<Loader2 className="h-8 w-8 animate-spin mx-auto text-brand-600 dark:text-brand-400" />
|
<Loader2Icon className="h-8 w-8 animate-spin mx-auto text-brand-600 dark:text-brand-400" />
|
||||||
<p className="text-sm text-gray-500 dark:text-gray-400 mt-2">Loading WordPress integration...</p>
|
<p className="text-sm text-gray-500 dark:text-gray-400 mt-2">Loading WordPress integration...</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -354,7 +354,7 @@ export default function WordPressIntegrationDebug() {
|
|||||||
return (
|
return (
|
||||||
<div className="p-8">
|
<div className="p-8">
|
||||||
<div className="bg-warning-50 dark:bg-warning-900/20 border border-warning-200 dark:border-warning-900/50 rounded-lg p-6 text-center">
|
<div className="bg-warning-50 dark:bg-warning-900/20 border border-warning-200 dark:border-warning-900/50 rounded-lg p-6 text-center">
|
||||||
<AlertTriangle className="h-12 w-12 text-warning-600 dark:text-warning-400 mx-auto mb-4" />
|
<AlertTriangleIcon className="h-12 w-12 text-warning-600 dark:text-warning-400 mx-auto mb-4" />
|
||||||
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-2">
|
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-2">
|
||||||
No WordPress Integration Found
|
No WordPress Integration Found
|
||||||
</h3>
|
</h3>
|
||||||
@@ -423,13 +423,13 @@ export default function WordPressIntegrationDebug() {
|
|||||||
{/* No WordPress Integration Found */}
|
{/* No WordPress Integration Found */}
|
||||||
{initializing ? (
|
{initializing ? (
|
||||||
<div className="bg-white dark:bg-gray-800 shadow rounded-lg p-6 text-center">
|
<div className="bg-white dark:bg-gray-800 shadow rounded-lg p-6 text-center">
|
||||||
<Loader2 className="h-8 w-8 animate-spin mx-auto text-gray-400 mb-2" />
|
<Loader2Icon className="h-8 w-8 animate-spin mx-auto text-gray-400 mb-2" />
|
||||||
<p className="text-gray-600 dark:text-gray-400">Checking for WordPress integration...</p>
|
<p className="text-gray-600 dark:text-gray-400">Checking for WordPress integration...</p>
|
||||||
</div>
|
</div>
|
||||||
) : !integrationId && activeSite ? (
|
) : !integrationId && activeSite ? (
|
||||||
<div className="bg-warning-50 dark:bg-warning-900/20 border border-warning-200 dark:border-warning-900/50 rounded-lg p-6">
|
<div className="bg-warning-50 dark:bg-warning-900/20 border border-warning-200 dark:border-warning-900/50 rounded-lg p-6">
|
||||||
<div className="flex items-start space-x-3">
|
<div className="flex items-start space-x-3">
|
||||||
<AlertTriangle className="h-8 w-8 text-warning-500 mt-0.5" />
|
<AlertTriangleIcon className="h-8 w-8 text-warning-500 mt-0.5" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-lg font-semibold text-warning-800 dark:text-warning-200">No WordPress Integration Found</p>
|
<p className="text-lg font-semibold text-warning-800 dark:text-warning-200">No WordPress Integration Found</p>
|
||||||
<p className="text-sm text-warning-600 dark:text-warning-300 mt-2">
|
<p className="text-sm text-warning-600 dark:text-warning-300 mt-2">
|
||||||
@@ -444,7 +444,7 @@ export default function WordPressIntegrationDebug() {
|
|||||||
) : !activeSite ? (
|
) : !activeSite ? (
|
||||||
<div className="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
<div className="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<Globe className="h-12 w-12 mx-auto text-gray-400 mb-4" />
|
<GlobeIcon className="h-12 w-12 mx-auto text-gray-400 mb-4" />
|
||||||
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-2">No Site Selected</h3>
|
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-2">No Site Selected</h3>
|
||||||
<p className="text-gray-500 dark:text-gray-400">
|
<p className="text-gray-500 dark:text-gray-400">
|
||||||
Please select a site to view WordPress integration debug data.
|
Please select a site to view WordPress integration debug data.
|
||||||
@@ -463,7 +463,7 @@ export default function WordPressIntegrationDebug() {
|
|||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="inline-flex items-center px-3 py-1 text-xs bg-brand-100 hover:bg-brand-200 text-brand-700 rounded-md disabled:opacity-50"
|
className="inline-flex items-center px-3 py-1 text-xs bg-brand-100 hover:bg-brand-200 text-brand-700 rounded-md disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<TestTube className="h-3 w-3 mr-1" />
|
<TestTubeIcon className="h-3 w-3 mr-1" />
|
||||||
Test Connection
|
Test Connection
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -471,7 +471,7 @@ export default function WordPressIntegrationDebug() {
|
|||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="inline-flex items-center px-3 py-1 text-xs bg-warning-100 hover:bg-warning-200 text-warning-700 rounded-md disabled:opacity-50"
|
className="inline-flex items-center px-3 py-1 text-xs bg-warning-100 hover:bg-warning-200 text-warning-700 rounded-md disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<RefreshCw className="h-3 w-3 mr-1" />
|
<RefreshCwIcon className="h-3 w-3 mr-1" />
|
||||||
Re-sync Metadata
|
Re-sync Metadata
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -479,7 +479,7 @@ export default function WordPressIntegrationDebug() {
|
|||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="inline-flex items-center px-3 py-1 text-xs bg-purple-100 hover:bg-purple-200 text-purple-700 rounded-md disabled:opacity-50"
|
className="inline-flex items-center px-3 py-1 text-xs bg-purple-100 hover:bg-purple-200 text-purple-700 rounded-md disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<Wrench className="h-3 w-3 mr-1" />
|
<SettingsIcon className="h-3 w-3 mr-1" />
|
||||||
Validate Content
|
Validate Content
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -491,9 +491,9 @@ export default function WordPressIntegrationDebug() {
|
|||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<span className="text-sm font-medium">API Connection</span>
|
<span className="text-sm font-medium">API Connection</span>
|
||||||
{integrationHealth.api_status === 'healthy' ? (
|
{integrationHealth.api_status === 'healthy' ? (
|
||||||
<CheckCircle className="h-4 w-4 text-success-500" />
|
<CheckCircleIcon className="h-4 w-4 text-success-500" />
|
||||||
) : (
|
) : (
|
||||||
<XCircle className="h-4 w-4 text-error-500" />
|
<XCircleIcon className="h-4 w-4 text-error-500" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-600 dark:text-gray-400">{integrationHealth.api_message}</p>
|
<p className="text-xs text-gray-600 dark:text-gray-400">{integrationHealth.api_message}</p>
|
||||||
@@ -506,9 +506,9 @@ export default function WordPressIntegrationDebug() {
|
|||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<span className="text-sm font-medium">Plugin Status</span>
|
<span className="text-sm font-medium">Plugin Status</span>
|
||||||
{integrationHealth.plugin_active ? (
|
{integrationHealth.plugin_active ? (
|
||||||
<CheckCircle className="h-4 w-4 text-success-500" />
|
<CheckCircleIcon className="h-4 w-4 text-success-500" />
|
||||||
) : (
|
) : (
|
||||||
<XCircle className="h-4 w-4 text-error-500" />
|
<XCircleIcon className="h-4 w-4 text-error-500" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-600 dark:text-gray-400">
|
<p className="text-xs text-gray-600 dark:text-gray-400">
|
||||||
@@ -523,9 +523,9 @@ export default function WordPressIntegrationDebug() {
|
|||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<span className="text-sm font-medium">Sync Status</span>
|
<span className="text-sm font-medium">Sync Status</span>
|
||||||
{integrationHealth.sync_healthy ? (
|
{integrationHealth.sync_healthy ? (
|
||||||
<CheckCircle className="h-4 w-4 text-success-500" />
|
<CheckCircleIcon className="h-4 w-4 text-success-500" />
|
||||||
) : (
|
) : (
|
||||||
<AlertTriangle className="h-4 w-4 text-warning-500" />
|
<AlertTriangleIcon className="h-4 w-4 text-warning-500" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-600 dark:text-gray-400">
|
<p className="text-xs text-gray-600 dark:text-gray-400">
|
||||||
@@ -539,7 +539,7 @@ export default function WordPressIntegrationDebug() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-4 text-gray-500">
|
<div className="text-center py-4 text-gray-500">
|
||||||
<Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" />
|
<Loader2Icon className="h-5 w-5 animate-spin mx-auto mb-2" />
|
||||||
Loading WordPress integration health...
|
Loading WordPress integration health...
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -587,7 +587,7 @@ export default function WordPressIntegrationDebug() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-8 text-gray-500">
|
<div className="text-center py-8 text-gray-500">
|
||||||
<Clock className="h-8 w-8 mx-auto mb-2 opacity-50" />
|
<ClockIcon className="h-8 w-8 mx-auto mb-2 opacity-50" />
|
||||||
<p>No WordPress sync events yet. Actions will appear here in real-time.</p>
|
<p>No WordPress sync events yet. Actions will appear here in real-time.</p>
|
||||||
<p className="text-xs mt-2">Content publishing, metadata sync, and webhook calls will be logged here.</p>
|
<p className="text-xs mt-2">Content publishing, metadata sync, and webhook calls will be logged here.</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -631,10 +631,10 @@ export default function WordPressIntegrationDebug() {
|
|||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 whitespace-nowrap">
|
<td className="px-6 py-4 whitespace-nowrap">
|
||||||
{validation.matches ? (
|
{validation.matches ? (
|
||||||
<CheckCircle className="h-4 w-4 text-success-500" />
|
<CheckCircleIcon className="h-4 w-4 text-success-500" />
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center space-x-1">
|
<div className="flex items-center space-x-1">
|
||||||
<XCircle className="h-4 w-4 text-error-500" />
|
<XCircleIcon className="h-4 w-4 text-error-500" />
|
||||||
{validation.error && (
|
{validation.error && (
|
||||||
<span className="text-xs text-error-600">{validation.error}</span>
|
<span className="text-xs text-error-600">{validation.error}</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { Card } from '../../components/ui/card';
|
|||||||
import Button from '../../components/ui/button/Button';
|
import Button from '../../components/ui/button/Button';
|
||||||
import { useToast } from '../../components/ui/toast/ToastContainer';
|
import { useToast } from '../../components/ui/toast/ToastContainer';
|
||||||
import { fetchAPI } from '../../services/api';
|
import { fetchAPI } from '../../services/api';
|
||||||
import { Search } from 'lucide-react';
|
import { SearchIcon } from '../../icons';
|
||||||
import {
|
import {
|
||||||
PencilIcon,
|
PencilIcon,
|
||||||
EyeIcon,
|
EyeIcon,
|
||||||
@@ -139,7 +139,7 @@ export default function SiteContentManager() {
|
|||||||
<Card className="p-4 mb-6">
|
<Card className="p-4 mb-6">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
|
<SearchIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search content..."
|
placeholder="Search content..."
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ export default function SiteDashboard() {
|
|||||||
<h4 className="font-semibold text-gray-900 mb-1">Publishing Queue</h4>
|
<h4 className="font-semibold text-gray-900 mb-1">Publishing Queue</h4>
|
||||||
<p className="text-sm text-gray-600">View scheduled content</p>
|
<p className="text-sm text-gray-600">View scheduled content</p>
|
||||||
</div>
|
</div>
|
||||||
<ArrowRightIcon className="h-5 w-5 text-gray-400 group-hover:text-amber-500 transition" />
|
<ArrowRightIcon className="h-5 w-5 text-gray-400 group-hover:text-warning-500 transition" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</ComponentCard>
|
</ComponentCard>
|
||||||
|
|||||||
@@ -601,8 +601,7 @@ export default function SiteList() {
|
|||||||
{/* Standard Filters Bar for Grid View - Matches Table View */}
|
{/* Standard Filters Bar for Grid View - Matches Table View */}
|
||||||
<div className="flex justify-center mb-4">
|
<div className="flex justify-center mb-4">
|
||||||
<div
|
<div
|
||||||
className="w-[75%] igny8-filter-bar p-3 rounded-lg bg-transparent"
|
className="w-[75%] igny8-filter-bar p-3 rounded-lg bg-transparent shadow-theme-md"
|
||||||
style={{ boxShadow: '0 2px 6px 3px rgba(0, 0, 0, 0.08)' }}
|
|
||||||
>
|
>
|
||||||
<div className="flex flex-nowrap gap-3 items-center justify-between w-full">
|
<div className="flex flex-nowrap gap-3 items-center justify-between w-full">
|
||||||
<div className="flex flex-nowrap gap-3 items-center flex-1 min-w-0 w-full">
|
<div className="flex flex-nowrap gap-3 items-center flex-1 min-w-0 w-full">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { SaveIcon, XIcon, FileTextIcon, TagIcon, CheckCircleIcon, XCircleIcon, AlertCircleIcon } from 'lucide-react';
|
import { SaveIcon, XIcon, FileTextIcon, TagIcon, CheckCircleIcon, XCircleIcon, AlertCircleIcon } from '../../icons';
|
||||||
import PageMeta from '../../components/common/PageMeta';
|
import PageMeta from '../../components/common/PageMeta';
|
||||||
import { Card } from '../../components/ui/card';
|
import { Card } from '../../components/ui/card';
|
||||||
import Button from '../../components/ui/button/Button';
|
import Button from '../../components/ui/button/Button';
|
||||||
|
|||||||
@@ -166,14 +166,14 @@ export default function PublishingQueue() {
|
|||||||
}
|
}
|
||||||
if (item.site_status === 'publishing') {
|
if (item.site_status === 'publishing') {
|
||||||
return (
|
return (
|
||||||
<span className="inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300">
|
<span className="inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium bg-brand-100 text-brand-700 dark:bg-brand-900 dark:text-brand-300">
|
||||||
<ArrowRightIcon className="w-3 h-3 animate-pulse" />
|
<ArrowRightIcon className="w-3 h-3 animate-pulse" />
|
||||||
Publishing...
|
Publishing...
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<span className="inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300">
|
<span className="inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium bg-warning-100 text-warning-700 dark:bg-warning-900 dark:text-warning-300">
|
||||||
<ClockIcon className="w-3 h-3" />
|
<ClockIcon className="w-3 h-3" />
|
||||||
Scheduled
|
Scheduled
|
||||||
</span>
|
</span>
|
||||||
@@ -228,8 +228,8 @@ export default function PublishingQueue() {
|
|||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
||||||
<Card className="p-4">
|
<Card className="p-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="size-10 rounded-lg bg-amber-100 dark:bg-amber-900/30 flex items-center justify-center">
|
<div className="size-10 rounded-lg bg-warning-100 dark:bg-warning-900/30 flex items-center justify-center">
|
||||||
<ClockIcon className="w-5 h-5 text-amber-600" />
|
<ClockIcon className="w-5 h-5 text-warning-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-2xl font-bold text-gray-900 dark:text-white">{stats.scheduled}</p>
|
<p className="text-2xl font-bold text-gray-900 dark:text-white">{stats.scheduled}</p>
|
||||||
@@ -239,8 +239,8 @@ export default function PublishingQueue() {
|
|||||||
</Card>
|
</Card>
|
||||||
<Card className="p-4">
|
<Card className="p-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="size-10 rounded-lg bg-blue-100 dark:bg-blue-900/30 flex items-center justify-center">
|
<div className="size-10 rounded-lg bg-brand-100 dark:bg-brand-900/30 flex items-center justify-center">
|
||||||
<ArrowRightIcon className="w-5 h-5 text-blue-600" />
|
<ArrowRightIcon className="w-5 h-5 text-brand-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-2xl font-bold text-gray-900 dark:text-white">{stats.publishing}</p>
|
<p className="text-2xl font-bold text-gray-900 dark:text-white">{stats.publishing}</p>
|
||||||
@@ -250,8 +250,8 @@ export default function PublishingQueue() {
|
|||||||
</Card>
|
</Card>
|
||||||
<Card className="p-4">
|
<Card className="p-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="size-10 rounded-lg bg-green-100 dark:bg-green-900/30 flex items-center justify-center">
|
<div className="size-10 rounded-lg bg-success-100 dark:bg-success-900/30 flex items-center justify-center">
|
||||||
<CheckCircleIcon className="w-5 h-5 text-green-600" />
|
<CheckCircleIcon className="w-5 h-5 text-success-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-2xl font-bold text-gray-900 dark:text-white">{stats.published}</p>
|
<p className="text-2xl font-bold text-gray-900 dark:text-white">{stats.published}</p>
|
||||||
@@ -261,8 +261,8 @@ export default function PublishingQueue() {
|
|||||||
</Card>
|
</Card>
|
||||||
<Card className="p-4">
|
<Card className="p-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="size-10 rounded-lg bg-red-100 dark:bg-red-900/30 flex items-center justify-center">
|
<div className="size-10 rounded-lg bg-error-100 dark:bg-error-900/30 flex items-center justify-center">
|
||||||
<TrashBinIcon className="w-5 h-5 text-red-600" />
|
<TrashBinIcon className="w-5 h-5 text-error-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-2xl font-bold text-gray-900 dark:text-white">{stats.failed}</p>
|
<p className="text-2xl font-bold text-gray-900 dark:text-white">{stats.failed}</p>
|
||||||
@@ -377,7 +377,7 @@ export default function PublishingQueue() {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleRemoveFromQueue(item)}
|
onClick={() => handleRemoveFromQueue(item)}
|
||||||
className="p-2 text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20"
|
className="p-2 text-error-500 hover:text-error-700 dark:text-error-400 dark:hover:text-error-300 rounded-lg hover:bg-error-50 dark:hover:bg-error-900/20"
|
||||||
title="Remove from queue"
|
title="Remove from queue"
|
||||||
>
|
>
|
||||||
<TrashBinIcon className="w-4 h-4" />
|
<TrashBinIcon className="w-4 h-4" />
|
||||||
@@ -422,7 +422,7 @@ export default function PublishingQueue() {
|
|||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
onClick={() => handleViewContent(item)}
|
onClick={() => handleViewContent(item)}
|
||||||
className="text-xs p-1 bg-amber-100 dark:bg-amber-900/30 text-amber-800 dark:text-amber-200 rounded truncate cursor-pointer hover:bg-amber-200 dark:hover:bg-amber-900/50"
|
className="text-xs p-1 bg-warning-100 dark:bg-warning-900/30 text-warning-800 dark:text-warning-200 rounded truncate cursor-pointer hover:bg-warning-200 dark:hover:bg-warning-900/50"
|
||||||
title={item.title}
|
title={item.title}
|
||||||
>
|
>
|
||||||
{item.title}
|
{item.title}
|
||||||
|
|||||||
@@ -861,7 +861,7 @@ export default function SiteSettings() {
|
|||||||
setPublishingSettings({ ...publishingSettings, publish_time_slots: newSlots });
|
setPublishingSettings({ ...publishingSettings, publish_time_slots: newSlots });
|
||||||
savePublishingSettings({ publish_time_slots: newSlots });
|
savePublishingSettings({ publish_time_slots: newSlots });
|
||||||
}}
|
}}
|
||||||
className="p-2 text-gray-400 hover:text-red-500 transition-colors"
|
className="p-2 text-gray-400 hover:text-error-500 transition-colors"
|
||||||
>
|
>
|
||||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||||
@@ -888,14 +888,14 @@ export default function SiteSettings() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Info Box */}
|
{/* Info Box */}
|
||||||
<div className="mt-6 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
|
<div className="mt-6 p-4 bg-brand-50 dark:bg-brand-900/20 rounded-lg">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<svg className="w-5 h-5 text-blue-600 dark:text-blue-400 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-5 h-5 text-brand-600 dark:text-brand-400 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
<div className="text-sm text-blue-800 dark:text-blue-200">
|
<div className="text-sm text-brand-800 dark:text-brand-200">
|
||||||
<p className="font-medium mb-1">How Publishing Works</p>
|
<p className="font-medium mb-1">How Publishing Works</p>
|
||||||
<ul className="list-disc list-inside space-y-1 text-blue-700 dark:text-blue-300">
|
<ul className="list-disc list-inside space-y-1 text-brand-700 dark:text-brand-300">
|
||||||
<li>Content moves from <span className="font-medium">Draft</span> → <span className="font-medium">Review</span> → <span className="font-medium">Approved</span> → <span className="font-medium">Published</span></li>
|
<li>Content moves from <span className="font-medium">Draft</span> → <span className="font-medium">Review</span> → <span className="font-medium">Approved</span> → <span className="font-medium">Published</span></li>
|
||||||
<li>Auto-approval moves content from Review to Approved automatically</li>
|
<li>Auto-approval moves content from Review to Approved automatically</li>
|
||||||
<li>Auto-publish sends Approved content to your WordPress site</li>
|
<li>Auto-publish sends Approved content to your WordPress site</li>
|
||||||
|
|||||||
@@ -657,8 +657,7 @@ export default function Images() {
|
|||||||
<img
|
<img
|
||||||
src={modalImageUrl}
|
src={modalImageUrl}
|
||||||
alt="Content image"
|
alt="Content image"
|
||||||
className="w-full h-auto object-contain rounded-lg"
|
className="w-full h-auto object-contain rounded-lg max-h-[90vh]"
|
||||||
style={{ maxHeight: '90vh' }}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
Save, Loader2, Settings, User, Users, UserPlus, Shield, Lock, X
|
SaveIcon, Loader2Icon, SettingsIcon, UserIcon, UsersIcon, UserIcon as UserPlusIcon, LockIcon, LockIcon as ShieldIcon, XIcon
|
||||||
} from 'lucide-react';
|
} from '../../icons';
|
||||||
import { Card } from '../../components/ui/card';
|
import { Card } from '../../components/ui/card';
|
||||||
import Button from '../../components/ui/button/Button';
|
import Button from '../../components/ui/button/Button';
|
||||||
import Badge from '../../components/ui/badge/Badge';
|
import Badge from '../../components/ui/badge/Badge';
|
||||||
@@ -259,9 +259,9 @@ export default function AccountSettingsPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ id: 'account' as TabType, label: 'Account', icon: <Settings className="w-4 h-4" /> },
|
{ id: 'account' as TabType, label: 'Account', icon: <SettingsIcon className="w-4 h-4" /> },
|
||||||
{ id: 'profile' as TabType, label: 'Profile', icon: <User className="w-4 h-4" /> },
|
{ id: 'profile' as TabType, label: 'Profile', icon: <UserIcon className="w-4 h-4" /> },
|
||||||
{ id: 'team' as TabType, label: 'Team', icon: <Users className="w-4 h-4" /> },
|
{ id: 'team' as TabType, label: 'Team', icon: <UsersIcon className="w-4 h-4" /> },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@@ -270,12 +270,12 @@ export default function AccountSettingsPage() {
|
|||||||
<PageMeta title="Account Settings" description="Manage your account, profile, and team" />
|
<PageMeta title="Account Settings" description="Manage your account, profile, and team" />
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Account Settings"
|
title="Account Settings"
|
||||||
badge={{ icon: <Settings className="w-4 h-4" />, color: 'blue' }}
|
badge={{ icon: <SettingsIcon className="w-4 h-4" />, color: 'blue' }}
|
||||||
/>
|
/>
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="flex items-center justify-center h-64">
|
<div className="flex items-center justify-center h-64">
|
||||||
<div className="flex flex-col items-center gap-3">
|
<div className="flex flex-col items-center gap-3">
|
||||||
<Loader2 className="w-8 h-8 animate-spin text-[var(--color-brand-500)]" />
|
<Loader2Icon className="w-8 h-8 animate-spin text-[var(--color-brand-500)]" />
|
||||||
<div className="text-gray-500 dark:text-gray-400">Loading settings...</div>
|
<div className="text-gray-500 dark:text-gray-400">Loading settings...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -297,7 +297,7 @@ export default function AccountSettingsPage() {
|
|||||||
<PageHeader
|
<PageHeader
|
||||||
title={pageTitles[activeTab].title}
|
title={pageTitles[activeTab].title}
|
||||||
description={pageTitles[activeTab].description}
|
description={pageTitles[activeTab].description}
|
||||||
badge={{ icon: <Settings className="w-4 h-4" />, color: 'blue' }}
|
badge={{ icon: <SettingsIcon className="w-4 h-4" />, color: 'blue' }}
|
||||||
parent="Account Settings"
|
parent="Account Settings"
|
||||||
/>
|
/>
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
@@ -468,7 +468,7 @@ export default function AccountSettingsPage() {
|
|||||||
variant="primary"
|
variant="primary"
|
||||||
tone="brand"
|
tone="brand"
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
startIcon={saving ? <Loader2 className="w-4 h-4 animate-spin" /> : <Save className="w-4 h-4" />}
|
startIcon={saving ? <Loader2Icon className="w-4 h-4 animate-spin" /> : <SaveIcon className="w-4 h-4" />}
|
||||||
>
|
>
|
||||||
{saving ? 'Saving...' : 'Save Changes'}
|
{saving ? 'Saving...' : 'Save Changes'}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -608,7 +608,7 @@ export default function AccountSettingsPage() {
|
|||||||
|
|
||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<h2 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white flex items-center gap-2">
|
<h2 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white flex items-center gap-2">
|
||||||
<Lock className="w-5 h-5" />
|
<LockIcon className="w-5 h-5" />
|
||||||
Security
|
Security
|
||||||
</h2>
|
</h2>
|
||||||
<Button
|
<Button
|
||||||
@@ -627,7 +627,7 @@ export default function AccountSettingsPage() {
|
|||||||
variant="primary"
|
variant="primary"
|
||||||
tone="brand"
|
tone="brand"
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
startIcon={saving ? <Loader2 className="w-4 h-4 animate-spin" /> : <Save className="w-4 h-4" />}
|
startIcon={saving ? <Loader2Icon className="w-4 h-4 animate-spin" /> : <SaveIcon className="w-4 h-4" />}
|
||||||
>
|
>
|
||||||
{saving ? 'Saving...' : 'Save Profile'}
|
{saving ? 'Saving...' : 'Save Profile'}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -658,7 +658,7 @@ export default function AccountSettingsPage() {
|
|||||||
|
|
||||||
{teamLoading ? (
|
{teamLoading ? (
|
||||||
<div className="flex items-center justify-center h-32">
|
<div className="flex items-center justify-center h-32">
|
||||||
<Loader2 className="w-6 h-6 animate-spin text-[var(--color-brand-500)]" />
|
<Loader2Icon className="w-6 h-6 animate-spin text-[var(--color-brand-500)]" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Card className="overflow-hidden">
|
<Card className="overflow-hidden">
|
||||||
@@ -714,7 +714,7 @@ export default function AccountSettingsPage() {
|
|||||||
{members.length === 0 && (
|
{members.length === 0 && (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={6} className="py-12 text-center text-gray-500 dark:text-gray-400">
|
<td colSpan={6} className="py-12 text-center text-gray-500 dark:text-gray-400">
|
||||||
<Users className="w-12 h-12 mx-auto mb-3 text-gray-300 dark:text-gray-600" />
|
<UsersIcon className="w-12 h-12 mx-auto mb-3 text-gray-300 dark:text-gray-600" />
|
||||||
No team members yet. Invite your first team member!
|
No team members yet. Invite your first team member!
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -728,7 +728,7 @@ export default function AccountSettingsPage() {
|
|||||||
{/* Role Permissions Info */}
|
{/* Role Permissions Info */}
|
||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<h3 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white flex items-center gap-2">
|
<h3 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white flex items-center gap-2">
|
||||||
<Shield className="w-5 h-5" />
|
<ShieldIcon className="w-5 h-5" />
|
||||||
Role Permissions
|
Role Permissions
|
||||||
</h3>
|
</h3>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
@@ -841,14 +841,14 @@ export default function AccountSettingsPage() {
|
|||||||
<Card className="relative z-10 w-full max-w-md p-6 m-4">
|
<Card className="relative z-10 w-full max-w-md p-6 m-4">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
<h2 className="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
||||||
<Lock className="w-5 h-5" />
|
<LockIcon className="w-5 h-5" />
|
||||||
Change Password
|
Change Password
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowPasswordModal(false)}
|
onClick={() => setShowPasswordModal(false)}
|
||||||
className="p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
className="p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
||||||
>
|
>
|
||||||
<X className="w-5 h-5" />
|
<XIcon className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
Save, Loader2, Image as ImageIcon, FileText, Send, Settings
|
SaveIcon, Loader2Icon, ImageIcon, FileTextIcon, PaperPlaneIcon as SendIcon, SettingsIcon
|
||||||
} from 'lucide-react';
|
} from '../../icons';
|
||||||
import { Card } from '../../components/ui/card';
|
import { Card } from '../../components/ui/card';
|
||||||
import Button from '../../components/ui/button/Button';
|
import Button from '../../components/ui/button/Button';
|
||||||
import { fetchAPI } from '../../services/api';
|
import { fetchAPI } from '../../services/api';
|
||||||
@@ -316,7 +316,7 @@ export default function ContentSettingsPage() {
|
|||||||
<PageMeta title="Content Settings" description="Configure your content generation settings" />
|
<PageMeta title="Content Settings" description="Configure your content generation settings" />
|
||||||
<div className="flex items-center justify-center h-64">
|
<div className="flex items-center justify-center h-64">
|
||||||
<div className="flex flex-col items-center gap-3">
|
<div className="flex flex-col items-center gap-3">
|
||||||
<Loader2 className="w-8 h-8 animate-spin text-[var(--color-brand-500)]" />
|
<Loader2Icon className="w-8 h-8 animate-spin text-[var(--color-brand-500)]" />
|
||||||
<div className="text-gray-500 dark:text-gray-400">Loading settings...</div>
|
<div className="text-gray-500 dark:text-gray-400">Loading settings...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -346,7 +346,7 @@ export default function ContentSettingsPage() {
|
|||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<div className="flex items-center gap-3 mb-6">
|
<div className="flex items-center gap-3 mb-6">
|
||||||
<div className="p-2 bg-brand-100 dark:bg-brand-900/30 rounded-lg">
|
<div className="p-2 bg-brand-100 dark:bg-brand-900/30 rounded-lg">
|
||||||
<FileText className="w-5 h-5 text-brand-600 dark:text-brand-400" />
|
<FileTextIcon className="w-5 h-5 text-brand-600 dark:text-brand-400" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">Content Generation</h2>
|
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">Content Generation</h2>
|
||||||
@@ -410,7 +410,7 @@ export default function ContentSettingsPage() {
|
|||||||
tone="brand"
|
tone="brand"
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
startIcon={saving ? <Loader2 className="w-4 h-4 animate-spin" /> : <Save className="w-4 h-4" />}
|
startIcon={saving ? <Loader2Icon className="w-4 h-4 animate-spin" /> : <SaveIcon className="w-4 h-4" />}
|
||||||
>
|
>
|
||||||
{saving ? 'Saving...' : 'Save Settings'}
|
{saving ? 'Saving...' : 'Save Settings'}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -424,7 +424,7 @@ export default function ContentSettingsPage() {
|
|||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<div className="flex items-center gap-3 mb-6">
|
<div className="flex items-center gap-3 mb-6">
|
||||||
<div className="p-2 bg-success-100 dark:bg-success-900/30 rounded-lg">
|
<div className="p-2 bg-success-100 dark:bg-success-900/30 rounded-lg">
|
||||||
<Send className="w-5 h-5 text-success-600 dark:text-success-400" />
|
<PaperPlaneIcon className="w-5 h-5 text-success-600 dark:text-success-400" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">WordPress Publishing</h2>
|
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">WordPress Publishing</h2>
|
||||||
@@ -486,7 +486,7 @@ export default function ContentSettingsPage() {
|
|||||||
tone="brand"
|
tone="brand"
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
startIcon={saving ? <Loader2 className="w-4 h-4 animate-spin" /> : <Save className="w-4 h-4" />}
|
startIcon={saving ? <Loader2Icon className="w-4 h-4 animate-spin" /> : <SaveIcon className="w-4 h-4" />}
|
||||||
>
|
>
|
||||||
{saving ? 'Saving...' : 'Save Settings'}
|
{saving ? 'Saving...' : 'Save Settings'}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -643,7 +643,7 @@ export default function ContentSettingsPage() {
|
|||||||
tone="brand"
|
tone="brand"
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
startIcon={saving ? <Loader2 className="w-4 h-4 animate-spin" /> : <Save className="w-4 h-4" />}
|
startIcon={saving ? <Loader2Icon className="w-4 h-4 animate-spin" /> : <SaveIcon className="w-4 h-4" />}
|
||||||
>
|
>
|
||||||
{saving ? 'Saving...' : 'Save Settings'}
|
{saving ? 'Saving...' : 'Save Settings'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ import { useState, useEffect } from 'react';
|
|||||||
import { Helmet } from 'react-helmet-async';
|
import { Helmet } from 'react-helmet-async';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
Bell,
|
BellIcon,
|
||||||
CheckCircle,
|
CheckCircleIcon,
|
||||||
AlertTriangle,
|
AlertTriangleIcon,
|
||||||
XCircle,
|
XCircleIcon,
|
||||||
Info,
|
InfoIcon,
|
||||||
Trash2,
|
Trash2Icon,
|
||||||
CheckCheck,
|
CheckCheckIcon,
|
||||||
Filter,
|
FilterIcon,
|
||||||
Calendar,
|
CalendarIcon,
|
||||||
Globe,
|
GlobeIcon,
|
||||||
} from 'lucide-react';
|
} from '../../icons';
|
||||||
import { Card } from '../../components/ui/card';
|
import { Card } from '../../components/ui/card';
|
||||||
import Button from '../../components/ui/button/Button';
|
import Button from '../../components/ui/button/Button';
|
||||||
import PageMeta from '../../components/common/PageMeta';
|
import PageMeta from '../../components/common/PageMeta';
|
||||||
@@ -70,13 +70,13 @@ export default function NotificationsPage() {
|
|||||||
const getSeverityIcon = (severity: string) => {
|
const getSeverityIcon = (severity: string) => {
|
||||||
switch (severity) {
|
switch (severity) {
|
||||||
case 'success':
|
case 'success':
|
||||||
return <CheckCircle className="w-5 h-5 text-success-500" />;
|
return <CheckCircleIcon className="w-5 h-5 text-success-500" />;
|
||||||
case 'warning':
|
case 'warning':
|
||||||
return <AlertTriangle className="w-5 h-5 text-warning-500" />;
|
return <AlertTriangleIcon className="w-5 h-5 text-warning-500" />;
|
||||||
case 'error':
|
case 'error':
|
||||||
return <XCircle className="w-5 h-5 text-error-500" />;
|
return <XCircleIcon className="w-5 h-5 text-error-500" />;
|
||||||
default:
|
default:
|
||||||
return <Info className="w-5 h-5 text-brand-500" />;
|
return <InfoIcon className="w-5 h-5 text-brand-500" />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -183,7 +183,7 @@ export default function NotificationsPage() {
|
|||||||
<PageHeader
|
<PageHeader
|
||||||
title="Notifications"
|
title="Notifications"
|
||||||
description="View and manage your notifications"
|
description="View and manage your notifications"
|
||||||
badge={{ icon: <Bell className="w-4 h-4" />, color: 'blue' }}
|
badge={{ icon: <BellIcon className="w-4 h-4" />, color: 'blue' }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="p-6 space-y-6">
|
<div className="p-6 space-y-6">
|
||||||
@@ -210,7 +210,7 @@ export default function NotificationsPage() {
|
|||||||
onClick={() => setShowFilters(!showFilters)}
|
onClick={() => setShowFilters(!showFilters)}
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<Filter className="w-4 h-4" />
|
<FilterIcon className="w-4 h-4" />
|
||||||
Filters
|
Filters
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
@@ -221,7 +221,7 @@ export default function NotificationsPage() {
|
|||||||
onClick={handleMarkAllRead}
|
onClick={handleMarkAllRead}
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<CheckCheck className="w-4 h-4" />
|
<CheckCheckIcon className="w-4 h-4" />
|
||||||
Mark All Read
|
Mark All Read
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
@@ -321,7 +321,7 @@ export default function NotificationsPage() {
|
|||||||
</div>
|
</div>
|
||||||
) : filteredNotifications.length === 0 ? (
|
) : filteredNotifications.length === 0 ? (
|
||||||
<div className="text-center p-12">
|
<div className="text-center p-12">
|
||||||
<Bell className="w-12 h-12 text-gray-400 mx-auto mb-3" />
|
<BellIcon className="w-12 h-12 text-gray-400 mx-auto mb-3" />
|
||||||
<p className="text-gray-600 dark:text-gray-400">
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
{apiNotifications.length === 0
|
{apiNotifications.length === 0
|
||||||
? 'No notifications yet'
|
? 'No notifications yet'
|
||||||
@@ -363,7 +363,7 @@ export default function NotificationsPage() {
|
|||||||
{/* Metadata */}
|
{/* Metadata */}
|
||||||
<div className="flex flex-wrap items-center gap-3 mt-2 text-xs text-gray-500 dark:text-gray-500">
|
<div className="flex flex-wrap items-center gap-3 mt-2 text-xs text-gray-500 dark:text-gray-500">
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
<Calendar className="w-3 h-3" />
|
<CalendarIcon className="w-3 h-3" />
|
||||||
{formatTimestamp(notification.created_at)}
|
{formatTimestamp(notification.created_at)}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -383,7 +383,7 @@ export default function NotificationsPage() {
|
|||||||
handleNotificationClick(notification.id, false)
|
handleNotificationClick(notification.id, false)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<CheckCircle className="w-4 h-4" />
|
<CheckCircleIcon className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -393,7 +393,7 @@ export default function NotificationsPage() {
|
|||||||
onClick={() => handleDelete(notification.id)}
|
onClick={() => handleDelete(notification.id)}
|
||||||
className="text-error-600 hover:text-error-700 hover:bg-error-50 dark:hover:bg-error-900/20"
|
className="text-error-600 hover:text-error-700 hover:bg-error-50 dark:hover:bg-error-900/20"
|
||||||
>
|
>
|
||||||
<Trash2 className="w-4 h-4" />
|
<Trash2Icon className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
|
|
||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
import { Link, useLocation } from 'react-router-dom';
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
CreditCard, Package, TrendingUp, FileText, Wallet, ArrowUpCircle,
|
CreditCardIcon, BoxIcon as PackageIcon, TrendingUpIcon, FileTextIcon, WalletIcon, ArrowUpIcon as ArrowUpCircleIcon,
|
||||||
Loader2, AlertCircle, CheckCircle, Download, Zap, Globe, Users, X
|
Loader2Icon, AlertCircleIcon, CheckCircleIcon, DownloadIcon, ZapIcon, GlobeIcon, UsersIcon, XIcon
|
||||||
} from 'lucide-react';
|
} from '../../icons';
|
||||||
import { Card } from '../../components/ui/card';
|
import { Card } from '../../components/ui/card';
|
||||||
import Badge from '../../components/ui/badge/Badge';
|
import Badge from '../../components/ui/badge/Badge';
|
||||||
import Button from '../../components/ui/button/Button';
|
import Button from '../../components/ui/button/Button';
|
||||||
@@ -346,11 +346,11 @@ export default function PlansAndBillingPage() {
|
|||||||
<PageMeta title="Plans & Billing" description="Manage your subscription and billing" />
|
<PageMeta title="Plans & Billing" description="Manage your subscription and billing" />
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Plans & Billing"
|
title="Plans & Billing"
|
||||||
badge={{ icon: <CreditCard className="w-4 h-4" />, color: 'blue' }}
|
badge={{ icon: <CreditCardIcon className="w-4 h-4" />, color: 'blue' }}
|
||||||
/>
|
/>
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="flex items-center justify-center h-64">
|
<div className="flex items-center justify-center h-64">
|
||||||
<Loader2 className="w-8 h-8 animate-spin text-[var(--color-brand-500)]" />
|
<Loader2Icon className="w-8 h-8 animate-spin text-[var(--color-brand-500)]" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -381,7 +381,7 @@ export default function PlansAndBillingPage() {
|
|||||||
<PageHeader
|
<PageHeader
|
||||||
title={pageTitles[activeTab].title}
|
title={pageTitles[activeTab].title}
|
||||||
description={pageTitles[activeTab].description}
|
description={pageTitles[activeTab].description}
|
||||||
badge={{ icon: <CreditCard className="w-4 h-4" />, color: 'blue' }}
|
badge={{ icon: <CreditCardIcon className="w-4 h-4" />, color: 'blue' }}
|
||||||
parent="Plans & Billing"
|
parent="Plans & Billing"
|
||||||
/>
|
/>
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
@@ -399,7 +399,7 @@ export default function PlansAndBillingPage() {
|
|||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="mb-6 p-4 bg-error-50 dark:bg-error-900/20 border border-error-200 dark:border-error-800 rounded-lg flex items-center gap-3">
|
<div className="mb-6 p-4 bg-error-50 dark:bg-error-900/20 border border-error-200 dark:border-error-800 rounded-lg flex items-center gap-3">
|
||||||
<AlertCircle className="w-5 h-5 text-error-600" />
|
<AlertCircleIcon className="w-5 h-5 text-error-600" />
|
||||||
<p className="text-error-800 dark:text-error-200">{error}</p>
|
<p className="text-error-800 dark:text-error-200">{error}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -416,7 +416,7 @@ export default function PlansAndBillingPage() {
|
|||||||
<h2 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">Your Current Plan</h2>
|
<h2 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">Your Current Plan</h2>
|
||||||
{!hasActivePlan && (
|
{!hasActivePlan && (
|
||||||
<div className="p-4 mb-4 rounded-lg border border-warning-200 bg-warning-50 text-warning-800 dark:border-warning-800 dark:bg-warning-900/20 dark:text-warning-200 flex items-start gap-3">
|
<div className="p-4 mb-4 rounded-lg border border-warning-200 bg-warning-50 text-warning-800 dark:border-warning-800 dark:bg-warning-900/20 dark:text-warning-200 flex items-start gap-3">
|
||||||
<AlertCircle className="w-5 h-5 mt-0.5 flex-shrink-0" />
|
<AlertCircleIcon className="w-5 h-5 mt-0.5 flex-shrink-0" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">No Active Plan</p>
|
<p className="font-medium">No Active Plan</p>
|
||||||
<p className="text-sm mt-1">Choose a plan below to activate your account and unlock all features.</p>
|
<p className="text-sm mt-1">Choose a plan below to activate your account and unlock all features.</p>
|
||||||
@@ -442,7 +442,7 @@ export default function PlansAndBillingPage() {
|
|||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
|
||||||
<div className="p-4 bg-gradient-to-br from-brand-50 to-brand-100 dark:from-brand-900/20 dark:to-brand-800/10 rounded-lg border border-brand-200 dark:border-brand-700">
|
<div className="p-4 bg-gradient-to-br from-brand-50 to-brand-100 dark:from-brand-900/20 dark:to-brand-800/10 rounded-lg border border-brand-200 dark:border-brand-700">
|
||||||
<div className="flex items-center gap-2 text-sm text-brand-700 dark:text-brand-300 mb-1">
|
<div className="flex items-center gap-2 text-sm text-brand-700 dark:text-brand-300 mb-1">
|
||||||
<Zap className="w-4 h-4" />
|
<ZapIcon className="w-4 h-4" />
|
||||||
Monthly Credits
|
Monthly Credits
|
||||||
</div>
|
</div>
|
||||||
<div className="text-2xl font-bold text-brand-600 dark:text-brand-400">
|
<div className="text-2xl font-bold text-brand-600 dark:text-brand-400">
|
||||||
@@ -451,7 +451,7 @@ export default function PlansAndBillingPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="p-4 bg-gradient-to-br from-success-50 to-success-100 dark:from-success-900/20 dark:to-success-800/10 rounded-lg border border-success-200 dark:border-success-700">
|
<div className="p-4 bg-gradient-to-br from-success-50 to-success-100 dark:from-success-900/20 dark:to-success-800/10 rounded-lg border border-success-200 dark:border-success-700">
|
||||||
<div className="flex items-center gap-2 text-sm text-success-700 dark:text-success-300 mb-1">
|
<div className="flex items-center gap-2 text-sm text-success-700 dark:text-success-300 mb-1">
|
||||||
<Wallet className="w-4 h-4" />
|
<WalletIcon className="w-4 h-4" />
|
||||||
Current Balance
|
Current Balance
|
||||||
</div>
|
</div>
|
||||||
<div className="text-2xl font-bold text-success-600 dark:text-success-400">
|
<div className="text-2xl font-bold text-success-600 dark:text-success-400">
|
||||||
@@ -460,7 +460,7 @@ export default function PlansAndBillingPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="p-4 bg-gradient-to-br from-purple-50 to-purple-100 dark:from-purple-900/20 dark:to-purple-800/10 rounded-lg border border-purple-200 dark:border-purple-700">
|
<div className="p-4 bg-gradient-to-br from-purple-50 to-purple-100 dark:from-purple-900/20 dark:to-purple-800/10 rounded-lg border border-purple-200 dark:border-purple-700">
|
||||||
<div className="flex items-center gap-2 text-sm text-purple-700 dark:text-purple-300 mb-1">
|
<div className="flex items-center gap-2 text-sm text-purple-700 dark:text-purple-300 mb-1">
|
||||||
<Package className="w-4 h-4" />
|
<PackageIcon className="w-4 h-4" />
|
||||||
Renewal Date
|
Renewal Date
|
||||||
</div>
|
</div>
|
||||||
<div className="text-lg font-bold text-purple-600 dark:text-purple-400">
|
<div className="text-lg font-bold text-purple-600 dark:text-purple-400">
|
||||||
@@ -478,7 +478,7 @@ export default function PlansAndBillingPage() {
|
|||||||
tone="brand"
|
tone="brand"
|
||||||
as={Link}
|
as={Link}
|
||||||
to="/account/plans/upgrade"
|
to="/account/plans/upgrade"
|
||||||
startIcon={<ArrowUpCircle className="w-4 h-4" />}
|
startIcon={<ArrowUpIcon className="w-4 h-4" />}
|
||||||
>
|
>
|
||||||
Upgrade Plan
|
Upgrade Plan
|
||||||
</Button>
|
</Button>
|
||||||
@@ -513,7 +513,7 @@ export default function PlansAndBillingPage() {
|
|||||||
: ['AI Content Writer', 'Image Generation', 'Auto Publishing', 'Custom Prompts', 'Email Support', 'API Access'])
|
: ['AI Content Writer', 'Image Generation', 'Auto Publishing', 'Custom Prompts', 'Email Support', 'API Access'])
|
||||||
.map((feature: string, index: number) => (
|
.map((feature: string, index: number) => (
|
||||||
<div key={index} className="flex items-start gap-2 text-sm">
|
<div key={index} className="flex items-start gap-2 text-sm">
|
||||||
<CheckCircle className="w-4 h-4 text-success-600 dark:text-success-400 mt-0.5 flex-shrink-0" />
|
<CheckCircleIcon className="w-4 h-4 text-success-600 dark:text-success-400 mt-0.5 flex-shrink-0" />
|
||||||
<span className="text-gray-700 dark:text-gray-300">{feature}</span>
|
<span className="text-gray-700 dark:text-gray-300">{feature}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -544,7 +544,7 @@ export default function PlansAndBillingPage() {
|
|||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||||
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||||
<div className="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400 mb-2">
|
<div className="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400 mb-2">
|
||||||
<Globe className="w-4 h-4" />
|
<GlobeIcon className="w-4 h-4" />
|
||||||
Sites
|
Sites
|
||||||
</div>
|
</div>
|
||||||
<div className="text-2xl font-bold text-gray-900 dark:text-white">
|
<div className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
@@ -553,7 +553,7 @@ export default function PlansAndBillingPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||||
<div className="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400 mb-2">
|
<div className="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400 mb-2">
|
||||||
<Users className="w-4 h-4" />
|
<UsersIcon className="w-4 h-4" />
|
||||||
Team Members
|
Team Members
|
||||||
</div>
|
</div>
|
||||||
<div className="text-2xl font-bold text-gray-900 dark:text-white">
|
<div className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
@@ -562,7 +562,7 @@ export default function PlansAndBillingPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||||
<div className="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400 mb-2">
|
<div className="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400 mb-2">
|
||||||
<FileText className="w-4 h-4" />
|
<FileTextIcon className="w-4 h-4" />
|
||||||
Content Words/mo
|
Content Words/mo
|
||||||
</div>
|
</div>
|
||||||
<div className="text-2xl font-bold text-gray-900 dark:text-white">
|
<div className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
@@ -575,7 +575,7 @@ export default function PlansAndBillingPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||||
<div className="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400 mb-2">
|
<div className="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400 mb-2">
|
||||||
<Zap className="w-4 h-4" />
|
<ZapIcon className="w-4 h-4" />
|
||||||
Monthly Credits
|
Monthly Credits
|
||||||
</div>
|
</div>
|
||||||
<div className="text-2xl font-bold text-gray-900 dark:text-white">
|
<div className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
@@ -593,7 +593,7 @@ export default function PlansAndBillingPage() {
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Upgrade Plans Section */}
|
{/* Upgrade Plans Section */}
|
||||||
<div>
|
<div>
|
||||||
<div className="mx-auto" style={{ maxWidth: '1560px' }}>
|
<div className="mx-auto max-w-[1560px]">
|
||||||
<PricingTable1
|
<PricingTable1
|
||||||
title=""
|
title=""
|
||||||
plans={plans
|
plans={plans
|
||||||
@@ -616,24 +616,24 @@ export default function PlansAndBillingPage() {
|
|||||||
{/* Plan Change Policy */}
|
{/* Plan Change Policy */}
|
||||||
<Card className="p-6 bg-brand-50 dark:bg-brand-900/20 border-brand-200 dark:border-brand-800 mt-6">
|
<Card className="p-6 bg-brand-50 dark:bg-brand-900/20 border-brand-200 dark:border-brand-800 mt-6">
|
||||||
<h3 className="font-semibold text-brand-900 dark:text-brand-100 mb-2 flex items-center gap-2">
|
<h3 className="font-semibold text-brand-900 dark:text-brand-100 mb-2 flex items-center gap-2">
|
||||||
<AlertCircle className="w-5 h-5" />
|
<AlertCircleIcon className="w-5 h-5" />
|
||||||
Plan Change Policy
|
Plan Change Policy
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="space-y-2 text-sm text-brand-800 dark:text-brand-200">
|
<ul className="space-y-2 text-sm text-brand-800 dark:text-brand-200">
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<CheckCircle className="w-4 h-4 mt-0.5 flex-shrink-0" />
|
<CheckCircleIcon className="w-4 h-4 mt-0.5 flex-shrink-0" />
|
||||||
Upgrades take effect immediately with prorated billing
|
Upgrades take effect immediately with prorated billing
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<CheckCircle className="w-4 h-4 mt-0.5 flex-shrink-0" />
|
<CheckCircleIcon className="w-4 h-4 mt-0.5 flex-shrink-0" />
|
||||||
Downgrades take effect at the end of your current billing period
|
Downgrades take effect at the end of your current billing period
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<CheckCircle className="w-4 h-4 mt-0.5 flex-shrink-0" />
|
<CheckCircleIcon className="w-4 h-4 mt-0.5 flex-shrink-0" />
|
||||||
Unused credits carry over when changing plans
|
Unused credits carry over when changing plans
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<CheckCircle className="w-4 h-4 mt-0.5 flex-shrink-0" />
|
<CheckCircleIcon className="w-4 h-4 mt-0.5 flex-shrink-0" />
|
||||||
Cancel anytime - no long-term commitments
|
Cancel anytime - no long-term commitments
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -677,7 +677,7 @@ export default function PlansAndBillingPage() {
|
|||||||
{invoices.length === 0 ? (
|
{invoices.length === 0 ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={5} className="px-6 py-8 text-center text-gray-500">
|
<td colSpan={5} className="px-6 py-8 text-center text-gray-500">
|
||||||
<FileText className="w-12 h-12 mx-auto mb-2 text-gray-400" />
|
<FileTextIcon className="w-12 h-12 mx-auto mb-2 text-gray-400" />
|
||||||
No invoices yet
|
No invoices yet
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -702,7 +702,7 @@ export default function PlansAndBillingPage() {
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
tone="brand"
|
tone="brand"
|
||||||
size="sm"
|
size="sm"
|
||||||
startIcon={<Download className="w-4 h-4" />}
|
startIcon={<DownloadIcon className="w-4 h-4" />}
|
||||||
className="ml-auto"
|
className="ml-auto"
|
||||||
onClick={() => handleDownloadInvoice(invoice.id)}
|
onClick={() => handleDownloadInvoice(invoice.id)}
|
||||||
>
|
>
|
||||||
@@ -790,7 +790,7 @@ export default function PlansAndBillingPage() {
|
|||||||
{paymentMethods.map((method) => (
|
{paymentMethods.map((method) => (
|
||||||
<div key={method.id} className="border border-gray-200 dark:border-gray-700 rounded-lg p-4 flex items-center justify-between">
|
<div key={method.id} className="border border-gray-200 dark:border-gray-700 rounded-lg p-4 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<CreditCard className="w-8 h-8 text-gray-400" />
|
<CreditCardIcon className="w-8 h-8 text-gray-400" />
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium text-gray-900 dark:text-white">{method.display_name}</div>
|
<div className="font-medium text-gray-900 dark:text-white">{method.display_name}</div>
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-400">{method.type}</div>
|
<div className="text-sm text-gray-600 dark:text-gray-400">{method.type}</div>
|
||||||
@@ -837,12 +837,12 @@ export default function PlansAndBillingPage() {
|
|||||||
onClick={() => setShowCancelConfirm(false)}
|
onClick={() => setShowCancelConfirm(false)}
|
||||||
className="p-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
className="p-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
||||||
>
|
>
|
||||||
<X className="w-5 h-5 text-gray-500" />
|
<XIcon className="w-5 h-5 text-gray-500" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-6 space-y-4">
|
<div className="p-6 space-y-4">
|
||||||
<div className="flex items-start gap-3 p-4 bg-warning-50 dark:bg-warning-900/20 border border-warning-200 dark:border-warning-800 rounded-lg">
|
<div className="flex items-start gap-3 p-4 bg-warning-50 dark:bg-warning-900/20 border border-warning-200 dark:border-warning-800 rounded-lg">
|
||||||
<AlertCircle className="w-5 h-5 text-warning-600 dark:text-warning-400 mt-0.5 flex-shrink-0" />
|
<AlertCircleIcon className="w-5 h-5 text-warning-600 dark:text-warning-400 mt-0.5 flex-shrink-0" />
|
||||||
<div className="text-sm text-warning-800 dark:text-warning-200">
|
<div className="text-sm text-warning-800 dark:text-warning-200">
|
||||||
<p className="font-medium mb-1">Are you sure you want to cancel?</p>
|
<p className="font-medium mb-1">Are you sure you want to cancel?</p>
|
||||||
<p>Your subscription will remain active until the end of your current billing period. After that:</p>
|
<p>Your subscription will remain active until the end of your current billing period. After that:</p>
|
||||||
@@ -882,7 +882,7 @@ export default function PlansAndBillingPage() {
|
|||||||
>
|
>
|
||||||
{planLoadingId === currentSubscription?.id ? (
|
{planLoadingId === currentSubscription?.id ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
<Loader2Icon className="w-4 h-4 mr-2 animate-spin" />
|
||||||
Cancelling...
|
Cancelling...
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -1,980 +0,0 @@
|
|||||||
/**
|
|
||||||
* Plans & Billing Page - Consolidated
|
|
||||||
* Tabs: Current Plan, Upgrade/Downgrade, Credits Overview, Purchase Credits, Billing History, Payment Methods
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { useState, useEffect, useRef } from 'react';
|
|
||||||
import {
|
|
||||||
CreditCard, Package, TrendingUp, FileText, Wallet, ArrowUpCircle,
|
|
||||||
Loader2, AlertCircle, CheckCircle, Download
|
|
||||||
} from 'lucide-react';
|
|
||||||
import { Card } from '../../components/ui/card';
|
|
||||||
import Badge from '../../components/ui/badge/Badge';
|
|
||||||
import Button from '../../components/ui/button/Button';
|
|
||||||
import { useToast } from '../../components/ui/toast/ToastContainer';
|
|
||||||
import {
|
|
||||||
getCreditBalance,
|
|
||||||
getCreditPackages,
|
|
||||||
getInvoices,
|
|
||||||
getAvailablePaymentMethods,
|
|
||||||
purchaseCreditPackage,
|
|
||||||
downloadInvoicePDF,
|
|
||||||
getPayments,
|
|
||||||
submitManualPayment,
|
|
||||||
createPaymentMethod,
|
|
||||||
deletePaymentMethod,
|
|
||||||
setDefaultPaymentMethod,
|
|
||||||
type CreditBalance,
|
|
||||||
type CreditPackage,
|
|
||||||
type Invoice,
|
|
||||||
type PaymentMethod,
|
|
||||||
type Payment,
|
|
||||||
getPlans,
|
|
||||||
getSubscriptions,
|
|
||||||
createSubscription,
|
|
||||||
cancelSubscription,
|
|
||||||
type Plan,
|
|
||||||
type Subscription,
|
|
||||||
} from '../../services/billing.api';
|
|
||||||
import { useAuthStore } from '../../store/authStore';
|
|
||||||
|
|
||||||
type TabType = 'plan' | 'credits' | 'billing-history';
|
|
||||||
|
|
||||||
export default function PlansAndBillingPage() {
|
|
||||||
const [activeTab, setActiveTab] = useState<TabType>('plan');
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [error, setError] = useState<string>('');
|
|
||||||
const [planLoadingId, setPlanLoadingId] = useState<number | null>(null);
|
|
||||||
const [purchaseLoadingId, setPurchaseLoadingId] = useState<number | null>(null);
|
|
||||||
|
|
||||||
// Data states
|
|
||||||
const [creditBalance, setCreditBalance] = useState<CreditBalance | null>(null);
|
|
||||||
const [packages, setPackages] = useState<CreditPackage[]>([]);
|
|
||||||
const [invoices, setInvoices] = useState<Invoice[]>([]);
|
|
||||||
const [payments, setPayments] = useState<Payment[]>([]);
|
|
||||||
const [paymentMethods, setPaymentMethods] = useState<PaymentMethod[]>([]);
|
|
||||||
const [plans, setPlans] = useState<Plan[]>([]);
|
|
||||||
const [subscriptions, setSubscriptions] = useState<Subscription[]>([]);
|
|
||||||
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState<string | undefined>(undefined);
|
|
||||||
const [manualPayment, setManualPayment] = useState({
|
|
||||||
invoice_id: '',
|
|
||||||
amount: '',
|
|
||||||
payment_method: '',
|
|
||||||
reference: '',
|
|
||||||
notes: '',
|
|
||||||
});
|
|
||||||
const [newPaymentMethod, setNewPaymentMethod] = useState({
|
|
||||||
type: 'bank_transfer',
|
|
||||||
display_name: '',
|
|
||||||
instructions: '',
|
|
||||||
});
|
|
||||||
const { user } = useAuthStore.getState();
|
|
||||||
const hasLoaded = useRef(false);
|
|
||||||
const isAwsAdmin = user?.account?.slug === 'aws-admin';
|
|
||||||
const handleBillingError = (err: any, fallback: string) => {
|
|
||||||
const message = err?.message || fallback;
|
|
||||||
setError(message);
|
|
||||||
toast?.error?.(message);
|
|
||||||
};
|
|
||||||
|
|
||||||
const toast = useToast();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (hasLoaded.current) return;
|
|
||||||
hasLoaded.current = true;
|
|
||||||
loadData();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const loadData = async (allowRetry = true) => {
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
// Fetch in controlled sequence to avoid burst 429s on auth/system scopes
|
|
||||||
const balanceData = await getCreditBalance();
|
|
||||||
|
|
||||||
// Small gap between auth endpoints to satisfy tight throttles
|
|
||||||
const wait = (ms: number) => new Promise((res) => setTimeout(res, ms));
|
|
||||||
|
|
||||||
const packagesPromise = getCreditPackages();
|
|
||||||
const invoicesPromise = getInvoices({});
|
|
||||||
const paymentsPromise = getPayments({});
|
|
||||||
const methodsPromise = getAvailablePaymentMethods();
|
|
||||||
|
|
||||||
const plansData = await getPlans();
|
|
||||||
await wait(400);
|
|
||||||
|
|
||||||
// Subscriptions: retry once on 429 after short backoff; do not hard-fail page
|
|
||||||
let subsData: { results: Subscription[] } = { results: [] };
|
|
||||||
try {
|
|
||||||
subsData = await getSubscriptions();
|
|
||||||
} catch (subErr: any) {
|
|
||||||
if (subErr?.status === 429 && allowRetry) {
|
|
||||||
await wait(2500);
|
|
||||||
try {
|
|
||||||
subsData = await getSubscriptions();
|
|
||||||
} catch {
|
|
||||||
subsData = { results: [] };
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
subsData = { results: [] };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [packagesData, invoicesData, paymentsData, methodsData] = await Promise.all([
|
|
||||||
packagesPromise,
|
|
||||||
invoicesPromise,
|
|
||||||
paymentsPromise,
|
|
||||||
methodsPromise,
|
|
||||||
]);
|
|
||||||
|
|
||||||
setCreditBalance(balanceData);
|
|
||||||
setPackages(packagesData.results || []);
|
|
||||||
setInvoices(invoicesData.results || []);
|
|
||||||
setPayments(paymentsData.results || []);
|
|
||||||
|
|
||||||
// Prefer manual payment method id 14 as default (tenant-facing)
|
|
||||||
const methods = (methodsData.results || []).filter((m) => m.is_enabled !== false);
|
|
||||||
setPaymentMethods(methods);
|
|
||||||
if (methods.length > 0) {
|
|
||||||
// Preferred ordering: bank_transfer (default), then manual
|
|
||||||
const bank = methods.find((m) => m.type === 'bank_transfer');
|
|
||||||
const manual = methods.find((m) => m.type === 'manual');
|
|
||||||
const selected =
|
|
||||||
bank ||
|
|
||||||
manual ||
|
|
||||||
methods.find((m) => m.is_default) ||
|
|
||||||
methods[0];
|
|
||||||
setSelectedPaymentMethod((prev) => prev || selected.type || selected.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Surface all active plans (avoid hiding plans and showing empty state)
|
|
||||||
const activePlans = (plansData.results || []).filter((p) => p.is_active !== false);
|
|
||||||
// Exclude Enterprise plan for non aws-admin accounts
|
|
||||||
const filteredPlans = activePlans.filter((p) => {
|
|
||||||
const name = (p.name || '').toLowerCase();
|
|
||||||
const slug = (p.slug || '').toLowerCase();
|
|
||||||
const isEnterprise = name.includes('enterprise') || slug === 'enterprise';
|
|
||||||
return isAwsAdmin ? true : !isEnterprise;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ensure the user's assigned plan is included even if subscriptions list is empty
|
|
||||||
const accountPlan = user?.account?.plan;
|
|
||||||
const isAccountEnterprise = (() => {
|
|
||||||
if (!accountPlan) return false;
|
|
||||||
const name = (accountPlan.name || '').toLowerCase();
|
|
||||||
const slug = (accountPlan.slug || '').toLowerCase();
|
|
||||||
return name.includes('enterprise') || slug === 'enterprise';
|
|
||||||
})();
|
|
||||||
|
|
||||||
const shouldIncludeAccountPlan = accountPlan && (!isAccountEnterprise || isAwsAdmin);
|
|
||||||
if (shouldIncludeAccountPlan && !filteredPlans.find((p) => p.id === accountPlan.id)) {
|
|
||||||
filteredPlans.push(accountPlan as any);
|
|
||||||
}
|
|
||||||
setPlans(filteredPlans);
|
|
||||||
const subs = subsData.results || [];
|
|
||||||
if (subs.length === 0 && shouldIncludeAccountPlan && accountPlan) {
|
|
||||||
subs.push({
|
|
||||||
id: accountPlan.id || 0,
|
|
||||||
plan: accountPlan,
|
|
||||||
status: 'active',
|
|
||||||
} as any);
|
|
||||||
}
|
|
||||||
setSubscriptions(subs);
|
|
||||||
} catch (err: any) {
|
|
||||||
// Handle throttling gracefully: don't block the page on subscriptions throttle
|
|
||||||
if (err?.status === 429 && allowRetry) {
|
|
||||||
setError('Request was throttled. Retrying...');
|
|
||||||
setTimeout(() => loadData(false), 2500);
|
|
||||||
} else if (err?.status === 429) {
|
|
||||||
setError(''); // suppress lingering banner
|
|
||||||
} else {
|
|
||||||
setError(err.message || 'Failed to load billing data');
|
|
||||||
console.error('Billing load error:', err);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSelectPlan = async (planId: number) => {
|
|
||||||
try {
|
|
||||||
if (!selectedPaymentMethod && paymentMethods.length > 0) {
|
|
||||||
setError('Select a payment method to continue');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setPlanLoadingId(planId);
|
|
||||||
await createSubscription({ plan_id: planId, payment_method: selectedPaymentMethod });
|
|
||||||
toast?.success?.('Subscription updated');
|
|
||||||
await loadData();
|
|
||||||
} catch (err: any) {
|
|
||||||
handleBillingError(err, 'Failed to update subscription');
|
|
||||||
} finally {
|
|
||||||
setPlanLoadingId(null);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCancelSubscription = async () => {
|
|
||||||
if (!currentSubscription?.id) {
|
|
||||||
setError('No active subscription to cancel');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
setPlanLoadingId(currentSubscription.id);
|
|
||||||
await cancelSubscription(currentSubscription.id);
|
|
||||||
toast?.success?.('Subscription cancellation requested');
|
|
||||||
await loadData();
|
|
||||||
} catch (err: any) {
|
|
||||||
handleBillingError(err, 'Failed to cancel subscription');
|
|
||||||
} finally {
|
|
||||||
setPlanLoadingId(null);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePurchase = async (packageId: number) => {
|
|
||||||
try {
|
|
||||||
if (!selectedPaymentMethod && paymentMethods.length > 0) {
|
|
||||||
setError('Select a payment method to continue');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setPurchaseLoadingId(packageId);
|
|
||||||
await purchaseCreditPackage({
|
|
||||||
package_id: packageId,
|
|
||||||
payment_method: (selectedPaymentMethod as any) || 'stripe',
|
|
||||||
});
|
|
||||||
await loadData();
|
|
||||||
} catch (err: any) {
|
|
||||||
handleBillingError(err, 'Failed to purchase credits');
|
|
||||||
} finally {
|
|
||||||
setPurchaseLoadingId(null);
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDownloadInvoice = async (invoiceId: number) => {
|
|
||||||
try {
|
|
||||||
const blob = await downloadInvoicePDF(invoiceId);
|
|
||||||
const url = window.URL.createObjectURL(blob);
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = url;
|
|
||||||
link.download = `invoice-${invoiceId}.pdf`;
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
link.remove();
|
|
||||||
window.URL.revokeObjectURL(url);
|
|
||||||
} catch (err: any) {
|
|
||||||
handleBillingError(err, 'Failed to download invoice');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmitManualPayment = async () => {
|
|
||||||
try {
|
|
||||||
const payload = {
|
|
||||||
invoice_id: manualPayment.invoice_id ? Number(manualPayment.invoice_id) : undefined,
|
|
||||||
amount: manualPayment.amount,
|
|
||||||
payment_method: manualPayment.payment_method || (selectedPaymentMethod as any) || 'manual',
|
|
||||||
reference: manualPayment.reference,
|
|
||||||
notes: manualPayment.notes,
|
|
||||||
};
|
|
||||||
await submitManualPayment(payload as any);
|
|
||||||
toast?.success?.('Manual payment submitted');
|
|
||||||
setManualPayment({ invoice_id: '', amount: '', payment_method: '', reference: '', notes: '' });
|
|
||||||
await loadData();
|
|
||||||
} catch (err: any) {
|
|
||||||
handleBillingError(err, 'Failed to submit payment');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddPaymentMethod = async () => {
|
|
||||||
if (!newPaymentMethod.display_name.trim()) {
|
|
||||||
setError('Payment method name is required');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await createPaymentMethod(newPaymentMethod as any);
|
|
||||||
toast?.success?.('Payment method added');
|
|
||||||
setNewPaymentMethod({ type: 'bank_transfer', display_name: '', instructions: '' });
|
|
||||||
await loadData();
|
|
||||||
} catch (err: any) {
|
|
||||||
handleBillingError(err, 'Failed to add payment method');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemovePaymentMethod = async (id: string) => {
|
|
||||||
try {
|
|
||||||
await deletePaymentMethod(id);
|
|
||||||
toast?.success?.('Payment method removed');
|
|
||||||
await loadData();
|
|
||||||
} catch (err: any) {
|
|
||||||
handleBillingError(err, 'Failed to remove payment method');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSetDefaultPaymentMethod = async (id: string) => {
|
|
||||||
try {
|
|
||||||
await setDefaultPaymentMethod(id);
|
|
||||||
toast?.success?.('Default payment method updated');
|
|
||||||
await loadData();
|
|
||||||
} catch (err: any) {
|
|
||||||
handleBillingError(err, 'Failed to set default');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<div className="flex items-center justify-center min-h-screen">
|
|
||||||
<Loader2 className="w-8 h-8 animate-spin text-blue-600" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentSubscription = subscriptions.find((sub) => sub.status === 'active') || subscriptions[0];
|
|
||||||
const currentPlanId = typeof currentSubscription?.plan === 'object' ? currentSubscription.plan.id : currentSubscription?.plan;
|
|
||||||
// Fallback to account plan if subscription is missing
|
|
||||||
const accountPlanId = user?.account?.plan?.id;
|
|
||||||
const effectivePlanId = currentPlanId || accountPlanId;
|
|
||||||
const currentPlan = plans.find((p) => p.id === effectivePlanId) || user?.account?.plan;
|
|
||||||
const hasActivePlan = Boolean(effectivePlanId);
|
|
||||||
const hasPaymentMethods = paymentMethods.length > 0;
|
|
||||||
const subscriptionStatus = currentSubscription?.status || (hasActivePlan ? 'active' : 'none');
|
|
||||||
const hasPendingManualPayment = payments.some((p) => p.status === 'pending_approval');
|
|
||||||
|
|
||||||
const tabs = [
|
|
||||||
{ id: 'plan' as TabType, label: 'Current Plan', icon: <Package className="w-4 h-4" /> },
|
|
||||||
{ id: 'credits' as TabType, label: 'Credits Overview', icon: <TrendingUp className="w-4 h-4" /> },
|
|
||||||
{ id: 'billing-history' as TabType, label: 'Billing History', icon: <FileText className="w-4 h-4" /> },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="p-6">
|
|
||||||
<div className="mb-6">
|
|
||||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Plans & Billing</h1>
|
|
||||||
<p className="text-gray-600 dark:text-gray-400 mt-1">
|
|
||||||
Manage your subscription, credits, and billing information
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Activation / pending payment notice */}
|
|
||||||
{!hasActivePlan && (
|
|
||||||
<div className="mb-4 p-4 rounded-lg border border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-800 dark:bg-amber-900/20 dark:text-amber-200">
|
|
||||||
No active plan. Choose a plan below to activate your account.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{hasPendingManualPayment && (
|
|
||||||
<div className="mb-4 p-4 rounded-lg border border-blue-200 bg-blue-50 text-blue-800 dark:border-blue-800 dark:bg-blue-900/20 dark:text-blue-100">
|
|
||||||
We received your manual payment. It’s pending admin approval; activation will complete once approved.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{error && (
|
|
||||||
<div className="mb-6 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg flex items-center gap-3">
|
|
||||||
<AlertCircle className="w-5 h-5 text-red-600" />
|
|
||||||
<p className="text-red-800 dark:text-red-200">{error}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Tabs */}
|
|
||||||
<div className="mb-6 border-b border-gray-200 dark:border-gray-700">
|
|
||||||
<nav className="-mb-px flex space-x-8 overflow-x-auto">
|
|
||||||
{tabs.map((tab) => (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
key={tab.id}
|
|
||||||
onClick={() => setActiveTab(tab.id)}
|
|
||||||
className={`
|
|
||||||
flex items-center gap-2 py-4 px-1 border-b-2 font-medium text-sm whitespace-nowrap
|
|
||||||
${activeTab === tab.id
|
|
||||||
? 'border-blue-500 text-blue-600 dark:text-blue-400'
|
|
||||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300'
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
{tab.icon}
|
|
||||||
{tab.label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Tab Content */}
|
|
||||||
<div className="mt-6">
|
|
||||||
{/* Current Plan Tab */}
|
|
||||||
{activeTab === 'plan' && (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<Card className="p-6">
|
|
||||||
<h2 className="text-lg font-semibold mb-4">Your Current Plan</h2>
|
|
||||||
{!hasActivePlan && (
|
|
||||||
<div className="p-4 mb-4 rounded-lg border border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-800 dark:bg-amber-900/20 dark:text-amber-200">
|
|
||||||
No active plan found. Please choose a plan to activate your account.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<div className="text-2xl font-bold text-gray-900 dark:text-white">
|
|
||||||
{currentPlan?.name || 'No Plan Selected'}
|
|
||||||
</div>
|
|
||||||
<div className="text-gray-600 dark:text-gray-400">
|
|
||||||
{currentPlan?.description || 'Select a plan to unlock full access.'}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Badge variant="light" color={hasActivePlan ? 'success' : 'warning'}>
|
|
||||||
{hasActivePlan ? subscriptionStatus : 'plan required'}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-6">
|
|
||||||
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-400">Monthly Credits</div>
|
|
||||||
<div className="text-2xl font-bold text-gray-900 dark:text-white">
|
|
||||||
{creditBalance?.plan_credits_per_month?.toLocaleString?.() || 0}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-400">Current Balance</div>
|
|
||||||
<div className="text-2xl font-bold text-gray-900 dark:text-white">
|
|
||||||
{creditBalance?.credits?.toLocaleString?.() || 0}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-400">Period Ends</div>
|
|
||||||
<div className="text-2xl font-bold text-gray-900 dark:text-white text-base">
|
|
||||||
{currentSubscription?.current_period_end
|
|
||||||
? new Date(currentSubscription.current_period_end).toLocaleDateString()
|
|
||||||
: '—'}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-6 flex gap-3">
|
|
||||||
<Button variant="primary" tone="brand" onClick={() => setActiveTab('upgrade')}>
|
|
||||||
{hasActivePlan ? 'Change Plan' : 'Choose a Plan'}
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" tone="neutral" onClick={() => setActiveTab('purchase')}>
|
|
||||||
Purchase Credits
|
|
||||||
</Button>
|
|
||||||
{hasActivePlan && (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
tone="neutral"
|
|
||||||
disabled={planLoadingId === currentSubscription?.id}
|
|
||||||
onClick={handleCancelSubscription}
|
|
||||||
>
|
|
||||||
{planLoadingId === currentSubscription?.id ? 'Cancelling...' : 'Cancel Subscription'}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="p-6">
|
|
||||||
<h2 className="text-lg font-semibold mb-4">Plan Features</h2>
|
|
||||||
<ul className="space-y-3">
|
|
||||||
{(currentPlan?.features && currentPlan.features.length > 0
|
|
||||||
? currentPlan.features
|
|
||||||
: ['Credits included each month', 'Module access per plan limits', 'Email support'])
|
|
||||||
.map((feature) => (
|
|
||||||
<li key={feature} className="flex items-center gap-2 text-gray-700 dark:text-gray-300">
|
|
||||||
<CheckCircle className="w-5 h-5 text-green-600" />
|
|
||||||
{feature}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Upgrade/Downgrade Tab */}
|
|
||||||
{activeTab === 'upgrade' && (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="mb-4">
|
|
||||||
<h2 className="text-xl font-semibold mb-2">Available Plans</h2>
|
|
||||||
<p className="text-gray-600 dark:text-gray-400">Choose the plan that best fits your needs</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{hasPaymentMethods ? (
|
|
||||||
<div className="p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
|
|
||||||
<div className="text-sm font-medium text-gray-700 dark:text-gray-200 mb-2">Select payment method</div>
|
|
||||||
<div className="flex flex-wrap gap-3">
|
|
||||||
{paymentMethods.map((method) => (
|
|
||||||
<label
|
|
||||||
key={method.id}
|
|
||||||
className={`px-3 py-2 rounded-lg border cursor-pointer text-sm ${
|
|
||||||
selectedPaymentMethod === (method.type || method.id)
|
|
||||||
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/30'
|
|
||||||
: 'border-gray-200 dark:border-gray-700'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
className="sr-only"
|
|
||||||
checked={selectedPaymentMethod === (method.type || method.id)}
|
|
||||||
onChange={() => setSelectedPaymentMethod(method.type || method.id)}
|
|
||||||
/>
|
|
||||||
<div className="font-semibold text-gray-900 dark:text-white">{method.display_name}</div>
|
|
||||||
<div className="text-xs text-gray-500 dark:text-gray-400">{method.type}</div>
|
|
||||||
</label>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="p-4 rounded-lg border border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-800 dark:bg-amber-900/20 dark:text-amber-200">
|
|
||||||
No payment methods available. Please contact support or add one from the Payment Methods tab.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
||||||
{plans.map((plan) => {
|
|
||||||
const isCurrent = plan.id === currentPlanId;
|
|
||||||
const price = plan.price ? `$${plan.price}/${plan.interval || 'month'}` : 'Custom';
|
|
||||||
return (
|
|
||||||
<Card key={plan.id} className="p-6 relative border border-gray-200 dark:border-gray-700">
|
|
||||||
<div className="mb-4">
|
|
||||||
<h3 className="text-lg font-semibold">{plan.name}</h3>
|
|
||||||
<div className="text-3xl font-bold text-gray-900 dark:text-white mt-2">{price}</div>
|
|
||||||
<div className="text-sm text-gray-500">{plan.description || 'Standard plan'}</div>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-3 mb-6">
|
|
||||||
{(plan.features && plan.features.length > 0 ? plan.features : ['Monthly credits included', 'Module access per plan', 'Email support']).map((feature) => (
|
|
||||||
<div key={feature} className="flex items-center gap-2 text-sm">
|
|
||||||
<CheckCircle className="w-4 h-4 text-green-600" />
|
|
||||||
<span>{feature}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant={isCurrent ? 'outline' : 'primary'}
|
|
||||||
tone="brand"
|
|
||||||
fullWidth
|
|
||||||
disabled={isCurrent || planLoadingId === plan.id}
|
|
||||||
onClick={() => handleSelectPlan(plan.id)}
|
|
||||||
>
|
|
||||||
{planLoadingId === plan.id
|
|
||||||
? 'Updating...'
|
|
||||||
: isCurrent
|
|
||||||
? 'Current Plan'
|
|
||||||
: 'Select Plan'}
|
|
||||||
</Button>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{plans.length === 0 && (
|
|
||||||
<div className="col-span-3 text-center py-12 text-gray-500">
|
|
||||||
No plans available. Please contact support.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Card className="p-6 bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800">
|
|
||||||
<h3 className="font-semibold text-blue-900 dark:text-blue-100 mb-2">Plan Change Policy</h3>
|
|
||||||
<ul className="space-y-2 text-sm text-blue-800 dark:text-blue-200">
|
|
||||||
<li>• Upgrades take effect immediately and you'll be charged a prorated amount</li>
|
|
||||||
<li>• Downgrades take effect at the end of your current billing period</li>
|
|
||||||
<li>• Unused credits from your current plan will carry over</li>
|
|
||||||
<li>• You can cancel your subscription at any time</li>
|
|
||||||
</ul>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Credits Overview Tab */}
|
|
||||||
{activeTab === 'credits' && (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
||||||
<Card className="p-6">
|
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-400 mb-1">Current Balance</div>
|
|
||||||
<div className="text-3xl font-bold text-blue-600 dark:text-blue-400">
|
|
||||||
{creditBalance?.credits.toLocaleString() || 0}
|
|
||||||
</div>
|
|
||||||
<div className="text-sm text-gray-500 mt-2">credits available</div>
|
|
||||||
</Card>
|
|
||||||
<Card className="p-6">
|
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-400 mb-1">Used This Month</div>
|
|
||||||
<div className="text-3xl font-bold text-red-600 dark:text-red-400">
|
|
||||||
{creditBalance?.credits_used_this_month.toLocaleString() || 0}
|
|
||||||
</div>
|
|
||||||
<div className="text-sm text-gray-500 mt-2">credits consumed</div>
|
|
||||||
</Card>
|
|
||||||
<Card className="p-6">
|
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-400 mb-1">Monthly Included</div>
|
|
||||||
<div className="text-3xl font-bold text-green-600 dark:text-green-400">
|
|
||||||
{creditBalance?.plan_credits_per_month.toLocaleString() || 0}
|
|
||||||
</div>
|
|
||||||
<div className="text-sm text-gray-500 mt-2">from your plan</div>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Card className="p-6">
|
|
||||||
<h2 className="text-lg font-semibold mb-4">Credit Usage Summary</h2>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="text-gray-700 dark:text-gray-300">Remaining Credits</span>
|
|
||||||
<span className="font-semibold">{creditBalance?.credits_remaining.toLocaleString() || 0}</span>
|
|
||||||
</div>
|
|
||||||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
|
||||||
<div
|
|
||||||
className="bg-blue-600 h-2 rounded-full"
|
|
||||||
style={{
|
|
||||||
width: creditBalance?.credits
|
|
||||||
? `${Math.min((creditBalance.credits / (creditBalance.plan_credits_per_month || 1)) * 100, 100)}%`
|
|
||||||
: '0%'
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Purchase Credits Tab */}
|
|
||||||
{activeTab === 'purchase' && (
|
|
||||||
<div className="space-y-6">
|
|
||||||
{hasPaymentMethods ? (
|
|
||||||
<div className="p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
|
|
||||||
<div className="text-sm font-medium text-gray-700 dark:text-gray-200 mb-2">Select payment method</div>
|
|
||||||
<div className="flex flex-wrap gap-3">
|
|
||||||
{paymentMethods.map((method) => (
|
|
||||||
<label
|
|
||||||
key={method.id}
|
|
||||||
className={`px-3 py-2 rounded-lg border cursor-pointer text-sm ${
|
|
||||||
selectedPaymentMethod === (method.type || method.id)
|
|
||||||
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/30'
|
|
||||||
: 'border-gray-200 dark:border-gray-700'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
className="sr-only"
|
|
||||||
checked={selectedPaymentMethod === (method.type || method.id)}
|
|
||||||
onChange={() => setSelectedPaymentMethod(method.type || method.id)}
|
|
||||||
/>
|
|
||||||
<div className="font-semibold text-gray-900 dark:text-white">{method.display_name}</div>
|
|
||||||
<div className="text-xs text-gray-500 dark:text-gray-400">{method.type}</div>
|
|
||||||
</label>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="p-4 rounded-lg border border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-800 dark:bg-amber-900/20 dark:text-amber-200">
|
|
||||||
No payment methods available. Please contact support or add one from the Payment Methods tab.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Card className="p-6">
|
|
||||||
<h2 className="text-lg font-semibold mb-4">Credit Packages</h2>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
||||||
{packages.map((pkg) => (
|
|
||||||
<div key={pkg.id} className="border border-gray-200 dark:border-gray-700 rounded-lg p-6 hover:border-blue-500 transition-colors">
|
|
||||||
<div className="text-lg font-semibold text-gray-900 dark:text-white">{pkg.name}</div>
|
|
||||||
<div className="text-3xl font-bold text-blue-600 dark:text-blue-400 mt-2">
|
|
||||||
{pkg.credits.toLocaleString()} <span className="text-sm text-gray-500">credits</span>
|
|
||||||
</div>
|
|
||||||
<div className="text-2xl font-semibold text-gray-900 dark:text-white mt-4">
|
|
||||||
${pkg.price}
|
|
||||||
</div>
|
|
||||||
{pkg.description && (
|
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-400 mt-2">{pkg.description}</div>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
variant="primary"
|
|
||||||
tone="brand"
|
|
||||||
onClick={() => handlePurchase(pkg.id)}
|
|
||||||
fullWidth
|
|
||||||
className="mt-6"
|
|
||||||
disabled={purchaseLoadingId === pkg.id || (!hasPaymentMethods && paymentMethods.length > 0)}
|
|
||||||
>
|
|
||||||
{purchaseLoadingId === pkg.id ? 'Processing...' : 'Purchase'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{packages.length === 0 && (
|
|
||||||
<div className="col-span-3 text-center py-12 text-gray-500">
|
|
||||||
No credit packages available at this time
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Billing History Tab */}
|
|
||||||
{activeTab === 'invoices' && (
|
|
||||||
<Card className="overflow-hidden">
|
|
||||||
<div className="overflow-x-auto">
|
|
||||||
<table className="w-full">
|
|
||||||
<thead className="bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
|
||||||
<tr>
|
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
||||||
Invoice
|
|
||||||
</th>
|
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
||||||
Date
|
|
||||||
</th>
|
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
||||||
Amount
|
|
||||||
</th>
|
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
||||||
Status
|
|
||||||
</th>
|
|
||||||
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
||||||
Actions
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="divide-y divide-gray-200 dark:divide-gray-700">
|
|
||||||
{invoices.length === 0 ? (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={5} className="px-6 py-8 text-center text-gray-500">
|
|
||||||
<FileText className="w-12 h-12 mx-auto mb-2 text-gray-400" />
|
|
||||||
No invoices yet
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
) : (
|
|
||||||
invoices.map((invoice) => (
|
|
||||||
<tr key={invoice.id} className="hover:bg-gray-50 dark:hover:bg-gray-800">
|
|
||||||
<td className="px-6 py-4 font-medium">{invoice.invoice_number}</td>
|
|
||||||
<td className="px-6 py-4 text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
{new Date(invoice.created_at).toLocaleDateString()}
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-4 font-semibold">${invoice.total_amount}</td>
|
|
||||||
<td className="px-6 py-4">
|
|
||||||
<Badge
|
|
||||||
variant="light"
|
|
||||||
color={invoice.status === 'paid' ? 'success' : 'warning'}
|
|
||||||
>
|
|
||||||
{invoice.status}
|
|
||||||
</Badge>
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-4 text-right">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
tone="brand"
|
|
||||||
size="sm"
|
|
||||||
startIcon={<Download className="w-4 h-4" />}
|
|
||||||
className="ml-auto"
|
|
||||||
onClick={() => handleDownloadInvoice(invoice.id)}
|
|
||||||
>
|
|
||||||
Download
|
|
||||||
</Button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Payments Tab */}
|
|
||||||
{activeTab === 'payments' && (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<Card className="p-6">
|
|
||||||
<div className="flex items-center justify-between mb-4">
|
|
||||||
<div>
|
|
||||||
<h2 className="text-lg font-semibold">Payments</h2>
|
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-400">Recent payments and manual submissions</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="overflow-x-auto">
|
|
||||||
<table className="w-full">
|
|
||||||
<thead className="bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
|
||||||
<tr>
|
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Invoice</th>
|
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Amount</th>
|
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Method</th>
|
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
|
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="divide-y divide-gray-200 dark:divide-gray-700">
|
|
||||||
{payments.length === 0 ? (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={5} className="px-6 py-8 text-center text-gray-500">
|
|
||||||
No payments yet
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
) : (
|
|
||||||
payments.map((payment) => (
|
|
||||||
<tr key={payment.id} className="hover:bg-gray-50 dark:hover:bg-gray-800">
|
|
||||||
<td className="px-6 py-4 text-sm font-medium text-gray-900 dark:text-white">
|
|
||||||
{payment.invoice_number || payment.invoice_id || '-'}
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-4 text-sm text-gray-900 dark:text-white">
|
|
||||||
${payment.amount}
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-4 text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
{payment.payment_method}
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-4">
|
|
||||||
<Badge
|
|
||||||
variant="light"
|
|
||||||
color={
|
|
||||||
payment.status === 'succeeded' || payment.status === 'completed'
|
|
||||||
? 'success'
|
|
||||||
: payment.status === 'pending' || payment.status === 'processing'
|
|
||||||
? 'warning'
|
|
||||||
: 'error'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{payment.status}
|
|
||||||
</Badge>
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-4 text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
{new Date(payment.created_at).toLocaleDateString()}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="p-6">
|
|
||||||
<h3 className="text-lg font-semibold mb-4">Submit Manual Payment</h3>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Invoice ID (optional)</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
value={manualPayment.invoice_id}
|
|
||||||
onChange={(e) => setManualPayment((p) => ({ ...p, invoice_id: e.target.value }))}
|
|
||||||
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white"
|
|
||||||
placeholder="Invoice ID"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Amount</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={manualPayment.amount}
|
|
||||||
onChange={(e) => setManualPayment((p) => ({ ...p, amount: e.target.value }))}
|
|
||||||
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white"
|
|
||||||
placeholder="e.g., 99.00"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Payment Method</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={manualPayment.payment_method}
|
|
||||||
onChange={(e) => setManualPayment((p) => ({ ...p, payment_method: e.target.value }))}
|
|
||||||
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white"
|
|
||||||
placeholder="bank_transfer / local_wallet / manual"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Reference</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={manualPayment.reference}
|
|
||||||
onChange={(e) => setManualPayment((p) => ({ ...p, reference: e.target.value }))}
|
|
||||||
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white"
|
|
||||||
placeholder="Reference or transaction id"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="md:col-span-2">
|
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Notes</label>
|
|
||||||
<textarea
|
|
||||||
value={manualPayment.notes}
|
|
||||||
onChange={(e) => setManualPayment((p) => ({ ...p, notes: e.target.value }))}
|
|
||||||
className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white"
|
|
||||||
placeholder="Optional notes"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-4 flex justify-end">
|
|
||||||
<Button variant="primary" tone="brand" onClick={handleSubmitManualPayment}>
|
|
||||||
Submit Manual Payment
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Payment Methods Tab */}
|
|
||||||
{activeTab === 'payment-methods' && (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<Card className="p-6">
|
|
||||||
<div className="flex items-center justify-between mb-4">
|
|
||||||
<h2 className="text-lg font-semibold">Payment Methods</h2>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Type</label>
|
|
||||||
<select
|
|
||||||
value={newPaymentMethod.type}
|
|
||||||
onChange={(e) => setNewPaymentMethod((p) => ({ ...p, type: e.target.value }))}
|
|
||||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white"
|
|
||||||
>
|
|
||||||
<option value="bank_transfer">Bank Transfer</option>
|
|
||||||
<option value="local_wallet">Local Wallet</option>
|
|
||||||
<option value="manual">Manual</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Display Name</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={newPaymentMethod.display_name}
|
|
||||||
onChange={(e) => setNewPaymentMethod((p) => ({ ...p, display_name: e.target.value }))}
|
|
||||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white"
|
|
||||||
placeholder="e.g., Bank Transfer (USD)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Instructions (optional)</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={newPaymentMethod.instructions}
|
|
||||||
onChange={(e) => setNewPaymentMethod((p) => ({ ...p, instructions: e.target.value }))}
|
|
||||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white"
|
|
||||||
placeholder="Where to send payment"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mb-4">
|
|
||||||
<Button variant="primary" tone="brand" onClick={handleAddPaymentMethod}>
|
|
||||||
Add Payment Method
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-4">
|
|
||||||
{paymentMethods.map((method) => (
|
|
||||||
<div key={method.id} className="border border-gray-200 dark:border-gray-700 rounded-lg p-4 flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<CreditCard className="w-8 h-8 text-gray-400" />
|
|
||||||
<div>
|
|
||||||
<div className="font-medium text-gray-900 dark:text-white">{method.display_name}</div>
|
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-400">{method.type}</div>
|
|
||||||
{method.instructions && (
|
|
||||||
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1">{method.instructions}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{method.is_enabled && (
|
|
||||||
<Badge variant="light" color="success">Active</Badge>
|
|
||||||
)}
|
|
||||||
{method.is_default ? (
|
|
||||||
<Badge variant="light" color="info">Default</Badge>
|
|
||||||
) : (
|
|
||||||
<Button variant="outline" size="sm" onClick={() => handleSetDefaultPaymentMethod(method.id)}>
|
|
||||||
Make Default
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button variant="outline" size="sm" tone="neutral" onClick={() => handleRemovePaymentMethod(method.id)}>
|
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{paymentMethods.length === 0 && (
|
|
||||||
<div className="text-center py-12 text-gray-500">
|
|
||||||
No payment methods configured
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { AlertCircle, Check, CreditCard, Building2, Wallet, Loader2, Zap } from 'lucide-react';
|
import { AlertCircleIcon, CheckIcon, CreditCardIcon, Building2Icon, WalletIcon, Loader2Icon, ZapIcon } from '../../icons';
|
||||||
import Button from '../../components/ui/button/Button';
|
import Button from '../../components/ui/button/Button';
|
||||||
import PageMeta from '../../components/common/PageMeta';
|
import PageMeta from '../../components/common/PageMeta';
|
||||||
import PageHeader from '../../components/common/PageHeader';
|
import PageHeader from '../../components/common/PageHeader';
|
||||||
@@ -132,13 +132,13 @@ export default function PurchaseCreditsPage() {
|
|||||||
const getPaymentMethodIcon = (type: string) => {
|
const getPaymentMethodIcon = (type: string) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'stripe':
|
case 'stripe':
|
||||||
return <CreditCard className="w-5 h-5" />;
|
return <CreditCardIcon className="w-5 h-5" />;
|
||||||
case 'bank_transfer':
|
case 'bank_transfer':
|
||||||
return <Building2 className="w-5 h-5" />;
|
return <Building2Icon className="w-5 h-5" />;
|
||||||
case 'local_wallet':
|
case 'local_wallet':
|
||||||
return <Wallet className="w-5 h-5" />;
|
return <WalletIcon className="w-5 h-5" />;
|
||||||
default:
|
default:
|
||||||
return <CreditCard className="w-5 h-5" />;
|
return <CreditCardIcon className="w-5 h-5" />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -148,11 +148,11 @@ export default function PurchaseCreditsPage() {
|
|||||||
<PageMeta title="Purchase Credits" description="Top up your account with credit packages" />
|
<PageMeta title="Purchase Credits" description="Top up your account with credit packages" />
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Purchase Credits"
|
title="Purchase Credits"
|
||||||
badge={{ icon: <Zap className="w-4 h-4" />, color: 'blue' }}
|
badge={{ icon: <ZapIcon className="w-4 h-4" />, color: 'blue' }}
|
||||||
/>
|
/>
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="flex items-center justify-center h-64">
|
<div className="flex items-center justify-center h-64">
|
||||||
<Loader2 className="w-8 h-8 animate-spin text-[var(--color-brand-500)]" />
|
<Loader2Icon className="w-8 h-8 animate-spin text-[var(--color-brand-500)]" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -167,7 +167,7 @@ export default function PurchaseCreditsPage() {
|
|||||||
<PageMeta title="Complete Payment" description="Complete your credit purchase" />
|
<PageMeta title="Complete Payment" description="Complete your credit purchase" />
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Complete Payment"
|
title="Complete Payment"
|
||||||
badge={{ icon: <Zap className="w-4 h-4" />, color: 'blue' }}
|
badge={{ icon: <ZapIcon className="w-4 h-4" />, color: 'blue' }}
|
||||||
parent="Purchase Credits"
|
parent="Purchase Credits"
|
||||||
/>
|
/>
|
||||||
<div className="p-6 max-w-2xl">
|
<div className="p-6 max-w-2xl">
|
||||||
@@ -238,7 +238,7 @@ export default function PurchaseCreditsPage() {
|
|||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="bg-error-50 border border-error-200 rounded-lg p-4 mb-4 flex items-start gap-2">
|
<div className="bg-error-50 border border-error-200 rounded-lg p-4 mb-4 flex items-start gap-2">
|
||||||
<AlertCircle className="w-5 h-5 text-error-600 flex-shrink-0 mt-0.5" />
|
<AlertCircleIcon className="w-5 h-5 text-error-600 flex-shrink-0 mt-0.5" />
|
||||||
<p className="text-error-800 text-sm">{error}</p>
|
<p className="text-error-800 text-sm">{error}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -293,7 +293,7 @@ export default function PurchaseCreditsPage() {
|
|||||||
tone="brand"
|
tone="brand"
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={purchasing}
|
disabled={purchasing}
|
||||||
startIcon={purchasing ? <Loader2 className="w-4 h-4 animate-spin" /> : undefined}
|
startIcon={purchasing ? <Loader2Icon className="w-4 h-4 animate-spin" /> : undefined}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
>
|
>
|
||||||
{purchasing ? 'Submitting...' : 'Submit Payment'}
|
{purchasing ? 'Submitting...' : 'Submit Payment'}
|
||||||
@@ -312,13 +312,13 @@ export default function PurchaseCreditsPage() {
|
|||||||
<PageHeader
|
<PageHeader
|
||||||
title="Purchase Credits"
|
title="Purchase Credits"
|
||||||
description="Choose a credit package and payment method to top up your account balance."
|
description="Choose a credit package and payment method to top up your account balance."
|
||||||
badge={{ icon: <Zap className="w-4 h-4" />, color: 'blue' }}
|
badge={{ icon: <ZapIcon className="w-4 h-4" />, color: 'blue' }}
|
||||||
/>
|
/>
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="max-w-6xl mx-auto">
|
<div className="max-w-6xl mx-auto">
|
||||||
{error && (
|
{error && (
|
||||||
<div className="bg-error-50 border border-error-200 rounded-lg p-4 mb-6 flex items-start gap-2">
|
<div className="bg-error-50 border border-error-200 rounded-lg p-4 mb-6 flex items-start gap-2">
|
||||||
<AlertCircle className="w-5 h-5 text-error-600 flex-shrink-0 mt-0.5" />
|
<AlertCircleIcon className="w-5 h-5 text-error-600 flex-shrink-0 mt-0.5" />
|
||||||
<p className="text-error-800">{error}</p>
|
<p className="text-error-800">{error}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -363,7 +363,7 @@ export default function PurchaseCreditsPage() {
|
|||||||
{selectedPackage?.id === pkg.id && (
|
{selectedPackage?.id === pkg.id && (
|
||||||
<div className="absolute top-3 right-3">
|
<div className="absolute top-3 right-3">
|
||||||
<div className="w-6 h-6 bg-[var(--color-brand-500)] rounded-full flex items-center justify-center">
|
<div className="w-6 h-6 bg-[var(--color-brand-500)] rounded-full flex items-center justify-center">
|
||||||
<Check className="w-4 h-4 text-white" />
|
<CheckIcon className="w-4 h-4 text-white" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -402,7 +402,7 @@ export default function PurchaseCreditsPage() {
|
|||||||
<p className="text-sm text-gray-600">{method.instructions}</p>
|
<p className="text-sm text-gray-600">{method.instructions}</p>
|
||||||
</div>
|
</div>
|
||||||
{selectedPaymentMethod === method.type && (
|
{selectedPaymentMethod === method.type && (
|
||||||
<Check className="w-5 h-5 text-[var(--color-brand-500)] flex-shrink-0" />
|
<CheckIcon className="w-5 h-5 text-[var(--color-brand-500)] flex-shrink-0" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -436,7 +436,7 @@ export default function PurchaseCreditsPage() {
|
|||||||
size="lg"
|
size="lg"
|
||||||
onClick={handlePurchase}
|
onClick={handlePurchase}
|
||||||
disabled={purchasing}
|
disabled={purchasing}
|
||||||
startIcon={purchasing ? <Loader2 className="w-5 h-5 animate-spin" /> : undefined}
|
startIcon={purchasing ? <Loader2Icon className="w-5 h-5 animate-spin" /> : undefined}
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
{purchasing ? 'Processing...' : 'Proceed to Payment'}
|
{purchasing ? 'Processing...' : 'Proceed to Payment'}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { TrendingUp, Activity, BarChart3, Zap, Calendar } from 'lucide-react';
|
import { TrendingUpIcon, ActivityIcon, BarChart3Icon, ZapIcon, CalendarIcon } from '../../icons';
|
||||||
import PageMeta from '../../components/common/PageMeta';
|
import PageMeta from '../../components/common/PageMeta';
|
||||||
import PageHeader from '../../components/common/PageHeader';
|
import PageHeader from '../../components/common/PageHeader';
|
||||||
import { useToast } from '../../components/ui/toast/ToastContainer';
|
import { useToast } from '../../components/ui/toast/ToastContainer';
|
||||||
@@ -62,7 +62,7 @@ export default function UsageAnalyticsPage() {
|
|||||||
<PageMeta title="Usage & Analytics" description="Monitor your plan limits and usage" />
|
<PageMeta title="Usage & Analytics" description="Monitor your plan limits and usage" />
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Usage & Analytics"
|
title="Usage & Analytics"
|
||||||
badge={{ icon: <TrendingUp className="w-4 h-4" />, color: 'blue' }}
|
badge={{ icon: <TrendingUpIcon className="w-4 h-4" />, color: 'blue' }}
|
||||||
/>
|
/>
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="flex items-center justify-center h-64">
|
<div className="flex items-center justify-center h-64">
|
||||||
@@ -94,7 +94,7 @@ export default function UsageAnalyticsPage() {
|
|||||||
<PageHeader
|
<PageHeader
|
||||||
title={tabTitles[activeTab]}
|
title={tabTitles[activeTab]}
|
||||||
description={tabDescriptions[activeTab]}
|
description={tabDescriptions[activeTab]}
|
||||||
badge={{ icon: <TrendingUp className="w-4 h-4" />, color: 'blue' }}
|
badge={{ icon: <TrendingUpIcon className="w-4 h-4" />, color: 'blue' }}
|
||||||
parent="Usage & Analytics"
|
parent="Usage & Analytics"
|
||||||
/>
|
/>
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
@@ -104,7 +104,7 @@ export default function UsageAnalyticsPage() {
|
|||||||
<Card className="p-4 bg-gradient-to-br from-brand-50 to-brand-100 dark:from-brand-900/20 dark:to-brand-800/10 border-brand-200 dark:border-brand-700">
|
<Card className="p-4 bg-gradient-to-br from-brand-50 to-brand-100 dark:from-brand-900/20 dark:to-brand-800/10 border-brand-200 dark:border-brand-700">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="p-2 bg-brand-500 rounded-lg">
|
<div className="p-2 bg-brand-500 rounded-lg">
|
||||||
<Zap className="w-5 h-5 text-white" />
|
<ZapIcon className="w-5 h-5 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-xs text-brand-700 dark:text-brand-300">Credits Left</div>
|
<div className="text-xs text-brand-700 dark:text-brand-300">Credits Left</div>
|
||||||
@@ -119,7 +119,7 @@ export default function UsageAnalyticsPage() {
|
|||||||
<Card className="p-4 bg-gradient-to-br from-purple-50 to-purple-100 dark:from-purple-900/20 dark:to-purple-800/10 border-purple-200 dark:border-purple-700">
|
<Card className="p-4 bg-gradient-to-br from-purple-50 to-purple-100 dark:from-purple-900/20 dark:to-purple-800/10 border-purple-200 dark:border-purple-700">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="p-2 bg-purple-500 rounded-lg">
|
<div className="p-2 bg-purple-500 rounded-lg">
|
||||||
<TrendingUp className="w-5 h-5 text-white" />
|
<TrendingUpIcon className="w-5 h-5 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-xs text-purple-700 dark:text-purple-300">Credits Used This Month</div>
|
<div className="text-xs text-purple-700 dark:text-purple-300">Credits Used This Month</div>
|
||||||
@@ -134,7 +134,7 @@ export default function UsageAnalyticsPage() {
|
|||||||
<Card className="p-4 bg-gradient-to-br from-success-50 to-success-100 dark:from-success-900/20 dark:to-success-800/10 border-success-200 dark:border-success-700">
|
<Card className="p-4 bg-gradient-to-br from-success-50 to-success-100 dark:from-success-900/20 dark:to-success-800/10 border-success-200 dark:border-success-700">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="p-2 bg-success-500 rounded-lg">
|
<div className="p-2 bg-success-500 rounded-lg">
|
||||||
<BarChart3 className="w-5 h-5 text-white" />
|
<BarChart3Icon className="w-5 h-5 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-xs text-success-700 dark:text-success-300">Your Monthly Limit</div>
|
<div className="text-xs text-success-700 dark:text-success-300">Your Monthly Limit</div>
|
||||||
@@ -149,7 +149,7 @@ export default function UsageAnalyticsPage() {
|
|||||||
<Card className="p-4 bg-gradient-to-br from-purple-50 to-purple-100 dark:from-purple-900/20 dark:to-purple-800/10 border-purple-200 dark:border-purple-700">
|
<Card className="p-4 bg-gradient-to-br from-purple-50 to-purple-100 dark:from-purple-900/20 dark:to-purple-800/10 border-purple-200 dark:border-purple-700">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="p-2 bg-purple-500 rounded-lg">
|
<div className="p-2 bg-purple-500 rounded-lg">
|
||||||
<Calendar className="w-5 h-5 text-white" />
|
<CalendarIcon className="w-5 h-5 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-xs text-purple-700 dark:text-purple-300">Usage %</div>
|
<div className="text-xs text-purple-700 dark:text-purple-300">Usage %</div>
|
||||||
@@ -210,7 +210,7 @@ export default function UsageAnalyticsPage() {
|
|||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<div className="flex items-center gap-3 mb-3">
|
<div className="flex items-center gap-3 mb-3">
|
||||||
<div className="p-2 bg-brand-100 dark:bg-brand-900/30 rounded-lg">
|
<div className="p-2 bg-brand-100 dark:bg-brand-900/30 rounded-lg">
|
||||||
<Activity className="w-5 h-5 text-brand-600 dark:text-brand-400" />
|
<ActivityIcon className="w-5 h-5 text-brand-600 dark:text-brand-400" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm font-medium text-gray-600 dark:text-gray-400">Total Operations</div>
|
<div className="text-sm font-medium text-gray-600 dark:text-gray-400">Total Operations</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -223,7 +223,7 @@ export default function UsageAnalyticsPage() {
|
|||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<div className="flex items-center gap-3 mb-3">
|
<div className="flex items-center gap-3 mb-3">
|
||||||
<div className="p-2 bg-purple-100 dark:bg-purple-900/30 rounded-lg">
|
<div className="p-2 bg-purple-100 dark:bg-purple-900/30 rounded-lg">
|
||||||
<BarChart3 className="w-5 h-5 text-purple-600 dark:text-purple-400" />
|
<BarChart3Icon className="w-5 h-5 text-purple-600 dark:text-purple-400" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm font-medium text-gray-600 dark:text-gray-400">Avg Operations/Day</div>
|
<div className="text-sm font-medium text-gray-600 dark:text-gray-400">Avg Operations/Day</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -236,7 +236,7 @@ export default function UsageAnalyticsPage() {
|
|||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<div className="flex items-center gap-3 mb-3">
|
<div className="flex items-center gap-3 mb-3">
|
||||||
<div className="p-2 bg-success-100 dark:bg-success-900/30 rounded-lg">
|
<div className="p-2 bg-success-100 dark:bg-success-900/30 rounded-lg">
|
||||||
<TrendingUp className="w-5 h-5 text-success-600 dark:text-success-400" />
|
<TrendingUpIcon className="w-5 h-5 text-success-600 dark:text-success-400" />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm font-medium text-gray-600 dark:text-gray-400">Credits Used</div>
|
<div className="text-sm font-medium text-gray-600 dark:text-gray-400">Credits Used</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -273,7 +273,7 @@ export default function UsageAnalyticsPage() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
|
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||||
<Activity className="w-12 h-12 mx-auto mb-3 opacity-30" />
|
<ActivityIcon className="w-12 h-12 mx-auto mb-3 opacity-30" />
|
||||||
<p>No operations recorded in the selected period</p>
|
<p>No operations recorded in the selected period</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Save, User, Mail, Lock, Loader2 } from 'lucide-react';
|
import { SaveIcon, UserIcon, MailIcon, LockIcon, Loader2Icon } from '../../icons';
|
||||||
import { Card } from '../../components/ui/card';
|
import { Card } from '../../components/ui/card';
|
||||||
|
|
||||||
export default function ProfileSettingsPage() {
|
export default function ProfileSettingsPage() {
|
||||||
@@ -31,7 +31,7 @@ export default function ProfileSettingsPage() {
|
|||||||
<div className="mb-6 flex items-center justify-between">
|
<div className="mb-6 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white flex items-center gap-2">
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-white flex items-center gap-2">
|
||||||
<User className="w-6 h-6" />
|
<UserIcon className="w-6 h-6" />
|
||||||
Your Profile
|
Your Profile
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-gray-600 dark:text-gray-400 mt-1">
|
<p className="text-gray-600 dark:text-gray-400 mt-1">
|
||||||
@@ -43,7 +43,7 @@ export default function ProfileSettingsPage() {
|
|||||||
disabled={saving}
|
disabled={saving}
|
||||||
className="flex items-center gap-2 px-4 py-2 bg-brand-600 text-white rounded-lg hover:bg-brand-700 disabled:opacity-50"
|
className="flex items-center gap-2 px-4 py-2 bg-brand-600 text-white rounded-lg hover:bg-brand-700 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{saving ? <Loader2 className="w-4 h-4 animate-spin" /> : <Save className="w-4 h-4" />}
|
{saving ? <Loader2Icon className="w-4 h-4 animate-spin" /> : <SaveIcon className="w-4 h-4" />}
|
||||||
{saving ? 'Saving...' : '✓ Save My Settings'}
|
{saving ? 'Saving...' : '✓ Save My Settings'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -174,7 +174,7 @@ export default function ProfileSettingsPage() {
|
|||||||
|
|
||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<h2 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
<h2 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
||||||
<Lock className="w-5 h-5" />
|
<LockIcon className="w-5 h-5" />
|
||||||
Security
|
Security
|
||||||
</h2>
|
</h2>
|
||||||
<button className="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800">
|
<button className="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800">
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
🔒 STYLE LOCKED - All colors, spacing, and components must use
|
🔒 STYLE LOCKED - All colors, spacing, and components must use
|
||||||
these tokens. See DESIGN_SYSTEM.md for usage guidelines.
|
these tokens. See DESIGN_SYSTEM.md for usage guidelines.
|
||||||
|
|
||||||
|
⚠️ ONLY 6 HEX VALUES IN ENTIRE SYSTEM - Everything else derived!
|
||||||
|
|
||||||
Last Updated: 2026-01-01
|
Last Updated: 2026-01-01
|
||||||
=================================================================== */
|
=================================================================== */
|
||||||
|
|
||||||
@@ -16,135 +18,70 @@
|
|||||||
|
|
||||||
|
|
||||||
/* ===================================================================
|
/* ===================================================================
|
||||||
SECTION 1: DESIGN TOKENS (CSS VARIABLES)
|
THE 6 PRIMARY COLOR TOKENS - ONLY HEX VALUES ALLOWED HERE
|
||||||
|
===================================================================
|
||||||
|
All other colors in the system MUST derive from these 6 using color-mix().
|
||||||
|
This enables dynamic theming - change these 6 = entire app updates.
|
||||||
=================================================================== */
|
=================================================================== */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
/* -----------------------------------------------------------------
|
/* ===== THE ONLY 6 HEX VALUES IN THE ENTIRE SYSTEM ===== */
|
||||||
1.1 PRIMARY BRAND COLORS
|
--color-primary: #2C7AA1; /* Brand Blue - main CTA, links, primary actions */
|
||||||
----------------------------------------------------------------- */
|
--color-success: #2CA18E; /* Success Green - confirmations, positive states */
|
||||||
|
--color-warning: #D9A12C; /* Warning Amber - alerts, cautions */
|
||||||
/* Primary Blue - Electric & Confident */
|
--color-danger: #A12C40; /* Danger Red - errors, destructive actions */
|
||||||
--color-primary: #0077B6;
|
--color-purple: #2C40A1; /* Purple - premium features, special emphasis */
|
||||||
--color-primary-dark: #005A8C;
|
--color-gray-base: #667085; /* Gray Base - neutral text, borders, backgrounds */
|
||||||
--color-primary-light: #00A8E8;
|
|
||||||
--color-primary-subtle: #E0F4FF;
|
|
||||||
|
|
||||||
/* Success - Fresh Mint Teal */
|
|
||||||
--color-success: #00B894;
|
|
||||||
--color-success-dark: #009676;
|
|
||||||
--color-success-light: #55EFC4;
|
|
||||||
--color-success-subtle: #E0FFF7;
|
|
||||||
|
|
||||||
/* Warning - Bold Amber */
|
|
||||||
--color-warning: #F59E0B;
|
|
||||||
--color-warning-dark: #D97706;
|
|
||||||
--color-warning-light: #FCD34D;
|
|
||||||
--color-warning-subtle: #FFF8E6;
|
|
||||||
|
|
||||||
/* Danger/Error - Vivid Red */
|
|
||||||
--color-danger: #DC2626;
|
|
||||||
--color-danger-dark: #B91C1C;
|
|
||||||
--color-danger-light: #F87171;
|
|
||||||
--color-danger-subtle: #FEE8E8;
|
|
||||||
|
|
||||||
/* Purple - Electric Violet (Premium/Special) */
|
|
||||||
--color-purple: #7C3AED;
|
|
||||||
--color-purple-dark: #5B21B6;
|
|
||||||
--color-purple-light: #A78BFA;
|
|
||||||
--color-purple-subtle: #F3EEFF;
|
|
||||||
|
|
||||||
/* -----------------------------------------------------------------
|
/* ===================================================================
|
||||||
1.2 ACCENT COLORS (Charts, Tags, Highlights)
|
DERIVED COLORS - All computed from the 6 primaries above
|
||||||
----------------------------------------------------------------- */
|
=================================================================== */
|
||||||
|
|
||||||
/* Coral */
|
/* Primary Variants (derived from --color-primary) */
|
||||||
--color-coral: #FF6B6B;
|
--color-primary-dark: color-mix(in srgb, var(--color-primary) 75%, black);
|
||||||
--color-coral-dark: #EE5A5A;
|
--color-primary-light: color-mix(in srgb, var(--color-primary) 60%, white);
|
||||||
--color-coral-subtle: #FFF0F0;
|
--color-primary-subtle: color-mix(in srgb, var(--color-primary) 10%, white);
|
||||||
|
|
||||||
/* Cyan */
|
/* Success Variants (derived from --color-success) */
|
||||||
--color-cyan: #06B6D4;
|
--color-success-dark: color-mix(in srgb, var(--color-success) 75%, black);
|
||||||
--color-cyan-dark: #0891B2;
|
--color-success-light: color-mix(in srgb, var(--color-success) 60%, white);
|
||||||
--color-cyan-subtle: #E0FCFF;
|
--color-success-subtle: color-mix(in srgb, var(--color-success) 10%, white);
|
||||||
|
|
||||||
/* Indigo */
|
/* Warning Variants (derived from --color-warning) */
|
||||||
--color-indigo: #4F46E5;
|
--color-warning-dark: color-mix(in srgb, var(--color-warning) 75%, black);
|
||||||
--color-indigo-dark: #4338CA;
|
--color-warning-light: color-mix(in srgb, var(--color-warning) 60%, white);
|
||||||
--color-indigo-subtle: #EEF2FF;
|
--color-warning-subtle: color-mix(in srgb, var(--color-warning) 10%, white);
|
||||||
|
|
||||||
/* Rose */
|
/* Danger Variants (derived from --color-danger) */
|
||||||
--color-rose: #F43F5E;
|
--color-danger-dark: color-mix(in srgb, var(--color-danger) 75%, black);
|
||||||
--color-rose-dark: #E11D48;
|
--color-danger-light: color-mix(in srgb, var(--color-danger) 60%, white);
|
||||||
--color-rose-subtle: #FFF1F3;
|
--color-danger-subtle: color-mix(in srgb, var(--color-danger) 10%, white);
|
||||||
|
|
||||||
/* Emerald */
|
/* Purple Variants (derived from --color-purple) */
|
||||||
--color-emerald: #10B981;
|
--color-purple-dark: color-mix(in srgb, var(--color-purple) 75%, black);
|
||||||
--color-emerald-dark: #059669;
|
--color-purple-light: color-mix(in srgb, var(--color-purple) 60%, white);
|
||||||
--color-emerald-subtle: #ECFDF5;
|
--color-purple-subtle: color-mix(in srgb, var(--color-purple) 10%, white);
|
||||||
|
|
||||||
/* Orange */
|
|
||||||
--color-orange: #F97316;
|
|
||||||
--color-orange-dark: #EA580C;
|
|
||||||
--color-orange-subtle: #FFF7ED;
|
|
||||||
|
|
||||||
/* -----------------------------------------------------------------
|
/* ===================================================================
|
||||||
1.3 SURFACE & BACKGROUND COLORS
|
BACKGROUND COLORS (derived from primaries)
|
||||||
----------------------------------------------------------------- */
|
=================================================================== */
|
||||||
|
--color-navy: color-mix(in srgb, var(--color-gray-base) 25%, black);
|
||||||
/* Sidebar */
|
--color-navy-light: color-mix(in srgb, var(--color-gray-base) 35%, black);
|
||||||
--color-navy: #0F172A;
|
--color-surface: color-mix(in srgb, var(--color-gray-base) 3%, white);
|
||||||
--color-navy-light: #1E293B;
|
|
||||||
--color-navy-lighter: #334155;
|
|
||||||
|
|
||||||
/* Page Backgrounds */
|
|
||||||
--color-surface: #F8FAFC;
|
|
||||||
--color-surface-alt: #F1F5F9;
|
|
||||||
|
|
||||||
/* Cards & Panels */
|
|
||||||
--color-panel: #FFFFFF;
|
--color-panel: #FFFFFF;
|
||||||
--color-panel-alt: #F8FAFC;
|
--color-panel-alt: color-mix(in srgb, var(--color-gray-base) 8%, white);
|
||||||
--color-panel-hover: #F1F5F9;
|
|
||||||
|
|
||||||
/* Elevated Surfaces */
|
/* ===================================================================
|
||||||
--color-elevated: #FFFFFF;
|
TEXT COLORS (derived from gray-base)
|
||||||
--color-overlay: rgba(15, 23, 42, 0.5);
|
=================================================================== */
|
||||||
|
--color-text: color-mix(in srgb, var(--color-gray-base) 30%, black);
|
||||||
|
--color-text-dim: var(--color-gray-base);
|
||||||
|
--color-text-light: color-mix(in srgb, var(--color-gray-base) 15%, white);
|
||||||
|
--color-stroke: color-mix(in srgb, var(--color-gray-base) 25%, white);
|
||||||
|
|
||||||
/* -----------------------------------------------------------------
|
/* ===================================================================
|
||||||
1.4 TEXT COLORS
|
BORDER RADIUS
|
||||||
----------------------------------------------------------------- */
|
=================================================================== */
|
||||||
|
|
||||||
--color-text: #0F172A;
|
|
||||||
--color-text-secondary: #475569;
|
|
||||||
--color-text-tertiary: #94A3B8;
|
|
||||||
--color-text-inverse: #FFFFFF;
|
|
||||||
--color-text-light: #E2E8F0;
|
|
||||||
--color-text-link: #0077B6;
|
|
||||||
--color-text-link-hover: #005A8C;
|
|
||||||
|
|
||||||
/* -----------------------------------------------------------------
|
|
||||||
1.5 BORDER & STROKE COLORS
|
|
||||||
----------------------------------------------------------------- */
|
|
||||||
|
|
||||||
--color-stroke: #E2E8F0;
|
|
||||||
--color-stroke-light: #F1F5F9;
|
|
||||||
--color-stroke-dark: #CBD5E1;
|
|
||||||
--color-stroke-focus: #0077B6;
|
|
||||||
|
|
||||||
/* -----------------------------------------------------------------
|
|
||||||
1.6 INTERACTIVE STATES
|
|
||||||
----------------------------------------------------------------- */
|
|
||||||
|
|
||||||
--color-hover: rgba(0, 119, 182, 0.08);
|
|
||||||
--color-active: rgba(0, 119, 182, 0.12);
|
|
||||||
--color-selected: rgba(0, 119, 182, 0.16);
|
|
||||||
--color-disabled: #94A3B8;
|
|
||||||
--color-disabled-bg: #F1F5F9;
|
|
||||||
|
|
||||||
/* -----------------------------------------------------------------
|
|
||||||
1.7 BORDER RADIUS
|
|
||||||
----------------------------------------------------------------- */
|
|
||||||
|
|
||||||
--radius-xs: 4px;
|
--radius-xs: 4px;
|
||||||
--radius-sm: 6px;
|
--radius-sm: 6px;
|
||||||
--radius-base: 8px;
|
--radius-base: 8px;
|
||||||
@@ -153,209 +90,51 @@
|
|||||||
--radius-xl: 16px;
|
--radius-xl: 16px;
|
||||||
--radius-2xl: 20px;
|
--radius-2xl: 20px;
|
||||||
--radius-full: 9999px;
|
--radius-full: 9999px;
|
||||||
|
|
||||||
|
/* ===================================================================
|
||||||
|
GRADIENTS (using CSS variables, not hex)
|
||||||
|
=================================================================== */
|
||||||
|
--gradient-primary: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
|
||||||
|
--gradient-success: linear-gradient(135deg, var(--color-success) 0%, var(--color-success-dark) 100%);
|
||||||
|
--gradient-warning: linear-gradient(135deg, var(--color-warning) 0%, var(--color-warning-dark) 100%);
|
||||||
|
--gradient-danger: linear-gradient(135deg, var(--color-danger) 0%, var(--color-danger-dark) 100%);
|
||||||
|
--gradient-purple: linear-gradient(135deg, var(--color-purple) 0%, var(--color-purple-dark) 100%);
|
||||||
|
--gradient-panel: linear-gradient(180deg, var(--color-panel) 0%, var(--color-panel-alt) 100%);
|
||||||
|
|
||||||
/* -----------------------------------------------------------------
|
/* ===================================================================
|
||||||
1.8 SHADOWS
|
SHADOWS (using rgba with consistent opacity)
|
||||||
----------------------------------------------------------------- */
|
=================================================================== */
|
||||||
|
--shadow-xs: 0px 1px 2px 0px rgba(0, 0, 0, 0.05);
|
||||||
--shadow-xs: 0 1px 2px rgba(15, 23, 42, 0.05);
|
--shadow-sm: 0px 1px 3px 0px rgba(0, 0, 0, 0.1), 0px 1px 2px 0px rgba(0, 0, 0, 0.06);
|
||||||
--shadow-sm: 0 1px 3px rgba(15, 23, 42, 0.08), 0 1px 2px rgba(15, 23, 42, 0.04);
|
--shadow-md: 0px 4px 8px -2px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.06);
|
||||||
--shadow-md: 0 4px 6px -1px rgba(15, 23, 42, 0.08), 0 2px 4px -1px rgba(15, 23, 42, 0.04);
|
--shadow-lg: 0px 12px 16px -4px rgba(0, 0, 0, 0.08), 0px 4px 6px -2px rgba(0, 0, 0, 0.03);
|
||||||
--shadow-lg: 0 10px 15px -3px rgba(15, 23, 42, 0.08), 0 4px 6px -2px rgba(15, 23, 42, 0.04);
|
--shadow-xl: 0px 20px 24px -4px rgba(0, 0, 0, 0.08), 0px 8px 8px -4px rgba(0, 0, 0, 0.03);
|
||||||
--shadow-xl: 0 20px 25px -5px rgba(15, 23, 42, 0.10), 0 10px 10px -5px rgba(15, 23, 42, 0.04);
|
|
||||||
--shadow-glow-primary: 0 0 20px rgba(0, 119, 182, 0.3);
|
|
||||||
--shadow-glow-success: 0 0 20px rgba(0, 184, 148, 0.3);
|
|
||||||
--shadow-glow-purple: 0 0 20px rgba(124, 58, 237, 0.3);
|
|
||||||
|
|
||||||
/* -----------------------------------------------------------------
|
|
||||||
1.9 GRADIENTS
|
|
||||||
----------------------------------------------------------------- */
|
|
||||||
|
|
||||||
--gradient-primary: linear-gradient(135deg, #00A8E8 0%, #0077B6 50%, #005A8C 100%);
|
|
||||||
--gradient-primary-soft: linear-gradient(135deg, #E0F4FF 0%, #B8E4FF 100%);
|
|
||||||
--gradient-success: linear-gradient(135deg, #55EFC4 0%, #00B894 50%, #009676 100%);
|
|
||||||
--gradient-success-soft: linear-gradient(135deg, #E0FFF7 0%, #B8F5E8 100%);
|
|
||||||
--gradient-warning: linear-gradient(135deg, #FCD34D 0%, #F59E0B 50%, #D97706 100%);
|
|
||||||
--gradient-warning-soft: linear-gradient(135deg, #FFF8E6 0%, #FFEDB8 100%);
|
|
||||||
--gradient-danger: linear-gradient(135deg, #F87171 0%, #DC2626 50%, #B91C1C 100%);
|
|
||||||
--gradient-danger-soft: linear-gradient(135deg, #FEE8E8 0%, #FECACA 100%);
|
|
||||||
--gradient-purple: linear-gradient(135deg, #A78BFA 0%, #7C3AED 50%, #5B21B6 100%);
|
|
||||||
--gradient-purple-soft: linear-gradient(135deg, #F3EEFF 0%, #E4D8FF 100%);
|
|
||||||
--gradient-hero: linear-gradient(135deg, #0077B6 0%, #7C3AED 100%);
|
|
||||||
--gradient-premium: linear-gradient(135deg, #F59E0B 0%, #F97316 50%, #DC2626 100%);
|
|
||||||
--gradient-ocean: linear-gradient(135deg, #06B6D4 0%, #0077B6 50%, #4F46E5 100%);
|
|
||||||
--gradient-forest: linear-gradient(135deg, #10B981 0%, #00B894 50%, #06B6D4 100%);
|
|
||||||
--gradient-panel: linear-gradient(180deg, #FFFFFF 0%, #F8FAFC 100%);
|
|
||||||
--gradient-sidebar: linear-gradient(180deg, #1E293B 0%, #0F172A 100%);
|
|
||||||
|
|
||||||
/* -----------------------------------------------------------------
|
|
||||||
1.10 CHART COLORS (Data Visualization)
|
|
||||||
----------------------------------------------------------------- */
|
|
||||||
|
|
||||||
--chart-1: #0077B6;
|
|
||||||
--chart-2: #00B894;
|
|
||||||
--chart-3: #7C3AED;
|
|
||||||
--chart-4: #F59E0B;
|
|
||||||
--chart-5: #F43F5E;
|
|
||||||
--chart-6: #06B6D4;
|
|
||||||
--chart-7: #10B981;
|
|
||||||
--chart-8: #F97316;
|
|
||||||
|
|
||||||
/* -----------------------------------------------------------------
|
|
||||||
1.11 BADGE PRESETS
|
|
||||||
----------------------------------------------------------------- */
|
|
||||||
|
|
||||||
--badge-info-bg: var(--color-primary-subtle);
|
|
||||||
--badge-info-text: var(--color-primary-dark);
|
|
||||||
--badge-info-border: var(--color-primary-light);
|
|
||||||
|
|
||||||
--badge-success-bg: var(--color-success-subtle);
|
|
||||||
--badge-success-text: var(--color-success-dark);
|
|
||||||
--badge-success-border: var(--color-success-light);
|
|
||||||
|
|
||||||
--badge-warning-bg: var(--color-warning-subtle);
|
|
||||||
--badge-warning-text: var(--color-warning-dark);
|
|
||||||
--badge-warning-border: var(--color-warning-light);
|
|
||||||
|
|
||||||
--badge-danger-bg: var(--color-danger-subtle);
|
|
||||||
--badge-danger-text: var(--color-danger-dark);
|
|
||||||
--badge-danger-border: var(--color-danger-light);
|
|
||||||
|
|
||||||
--badge-purple-bg: var(--color-purple-subtle);
|
|
||||||
--badge-purple-text: var(--color-purple-dark);
|
|
||||||
--badge-purple-border: var(--color-purple-light);
|
|
||||||
|
|
||||||
--badge-neutral-bg: #F1F5F9;
|
|
||||||
--badge-neutral-text: #475569;
|
|
||||||
--badge-neutral-border: #CBD5E1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ===================================================================
|
/* ===================================================================
|
||||||
SECTION 2: DARK MODE OVERRIDES
|
DARK MODE OVERRIDES (all derived from primaries)
|
||||||
=================================================================== */
|
=================================================================== */
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
/* Backgrounds */
|
/* Backgrounds - derived from gray-base */
|
||||||
--color-surface: #0F172A;
|
--color-surface: color-mix(in srgb, var(--color-gray-base) 20%, black);
|
||||||
--color-surface-alt: #1E293B;
|
--color-panel: color-mix(in srgb, var(--color-gray-base) 30%, black);
|
||||||
--color-panel: #1E293B;
|
--color-panel-alt: color-mix(in srgb, var(--color-gray-base) 15%, black);
|
||||||
--color-panel-alt: #334155;
|
|
||||||
--color-panel-hover: #334155;
|
|
||||||
--color-elevated: #334155;
|
|
||||||
--color-overlay: rgba(0, 0, 0, 0.7);
|
|
||||||
|
|
||||||
/* Text */
|
/* Text - derived from gray-base */
|
||||||
--color-text: #F1F5F9;
|
--color-text: color-mix(in srgb, var(--color-gray-base) 10%, white);
|
||||||
--color-text-secondary: #CBD5E1;
|
--color-text-dim: color-mix(in srgb, var(--color-gray-base) 40%, white);
|
||||||
--color-text-tertiary: #64748B;
|
--color-stroke: color-mix(in srgb, var(--color-gray-base) 50%, black);
|
||||||
--color-text-link: #00A8E8;
|
|
||||||
--color-text-link-hover: #38BDF8;
|
|
||||||
|
|
||||||
/* Borders */
|
/* Shadows for dark mode */
|
||||||
--color-stroke: #334155;
|
--shadow-xs: 0px 1px 2px 0px rgba(0, 0, 0, 0.3);
|
||||||
--color-stroke-light: #1E293B;
|
--shadow-sm: 0px 1px 3px 0px rgba(0, 0, 0, 0.4), 0px 1px 2px 0px rgba(0, 0, 0, 0.2);
|
||||||
--color-stroke-dark: #475569;
|
--shadow-md: 0px 4px 8px -2px rgba(0, 0, 0, 0.4), 0px 2px 4px -2px rgba(0, 0, 0, 0.2);
|
||||||
--color-stroke-focus: #00A8E8;
|
--shadow-lg: 0px 12px 16px -4px rgba(0, 0, 0, 0.4), 0px 4px 6px -2px rgba(0, 0, 0, 0.2);
|
||||||
|
--shadow-xl: 0px 20px 24px -4px rgba(0, 0, 0, 0.5), 0px 8px 8px -4px rgba(0, 0, 0, 0.2);
|
||||||
/* Interactive States */
|
|
||||||
--color-hover: rgba(0, 168, 232, 0.12);
|
|
||||||
--color-active: rgba(0, 168, 232, 0.18);
|
|
||||||
--color-selected: rgba(0, 168, 232, 0.24);
|
|
||||||
--color-disabled: #64748B;
|
|
||||||
--color-disabled-bg: #1E293B;
|
|
||||||
|
|
||||||
/* Primary Colors (brighter for dark mode) */
|
|
||||||
--color-primary: #00A8E8;
|
|
||||||
--color-primary-dark: #0077B6;
|
|
||||||
--color-primary-light: #38BDF8;
|
|
||||||
--color-primary-subtle: rgba(0, 168, 232, 0.15);
|
|
||||||
|
|
||||||
--color-success: #34D399;
|
|
||||||
--color-success-dark: #10B981;
|
|
||||||
--color-success-light: #6EE7B7;
|
|
||||||
--color-success-subtle: rgba(52, 211, 153, 0.15);
|
|
||||||
|
|
||||||
--color-warning: #FBBF24;
|
|
||||||
--color-warning-dark: #F59E0B;
|
|
||||||
--color-warning-light: #FDE68A;
|
|
||||||
--color-warning-subtle: rgba(251, 191, 36, 0.15);
|
|
||||||
|
|
||||||
--color-danger: #F87171;
|
|
||||||
--color-danger-dark: #EF4444;
|
|
||||||
--color-danger-light: #FCA5A5;
|
|
||||||
--color-danger-subtle: rgba(248, 113, 113, 0.15);
|
|
||||||
|
|
||||||
--color-purple: #A78BFA;
|
|
||||||
--color-purple-dark: #8B5CF6;
|
|
||||||
--color-purple-light: #C4B5FD;
|
|
||||||
--color-purple-subtle: rgba(167, 139, 250, 0.15);
|
|
||||||
|
|
||||||
/* Accents (brighter) */
|
|
||||||
--color-coral: #FCA5A5;
|
|
||||||
--color-coral-dark: #F87171;
|
|
||||||
--color-coral-subtle: rgba(252, 165, 165, 0.15);
|
|
||||||
|
|
||||||
--color-cyan: #22D3EE;
|
|
||||||
--color-cyan-dark: #06B6D4;
|
|
||||||
--color-cyan-subtle: rgba(34, 211, 238, 0.15);
|
|
||||||
|
|
||||||
--color-indigo: #818CF8;
|
|
||||||
--color-indigo-dark: #6366F1;
|
|
||||||
--color-indigo-subtle: rgba(129, 140, 248, 0.15);
|
|
||||||
|
|
||||||
/* Gradients */
|
|
||||||
--gradient-panel: linear-gradient(180deg, #1E293B 0%, #0F172A 100%);
|
|
||||||
--gradient-sidebar: linear-gradient(180deg, #0F172A 0%, #020617 100%);
|
|
||||||
--gradient-primary-soft: linear-gradient(135deg, rgba(0, 168, 232, 0.2) 0%, rgba(0, 119, 182, 0.1) 100%);
|
|
||||||
|
|
||||||
/* Shadows */
|
|
||||||
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.3);
|
|
||||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.2);
|
|
||||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
|
|
||||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
|
|
||||||
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.2);
|
|
||||||
--shadow-glow-primary: 0 0 30px rgba(0, 168, 232, 0.4);
|
|
||||||
--shadow-glow-success: 0 0 30px rgba(52, 211, 153, 0.4);
|
|
||||||
--shadow-glow-purple: 0 0 30px rgba(167, 139, 250, 0.4);
|
|
||||||
|
|
||||||
/* Chart Colors */
|
|
||||||
--chart-1: #38BDF8;
|
|
||||||
--chart-2: #34D399;
|
|
||||||
--chart-3: #A78BFA;
|
|
||||||
--chart-4: #FBBF24;
|
|
||||||
--chart-5: #FB7185;
|
|
||||||
--chart-6: #22D3EE;
|
|
||||||
--chart-7: #6EE7B7;
|
|
||||||
--chart-8: #FB923C;
|
|
||||||
|
|
||||||
/* Badges */
|
|
||||||
--badge-info-bg: rgba(0, 168, 232, 0.15);
|
|
||||||
--badge-info-text: #38BDF8;
|
|
||||||
--badge-info-border: rgba(0, 168, 232, 0.3);
|
|
||||||
|
|
||||||
--badge-success-bg: rgba(52, 211, 153, 0.15);
|
|
||||||
--badge-success-text: #34D399;
|
|
||||||
--badge-success-border: rgba(52, 211, 153, 0.3);
|
|
||||||
|
|
||||||
--badge-warning-bg: rgba(251, 191, 36, 0.15);
|
|
||||||
--badge-warning-text: #FBBF24;
|
|
||||||
--badge-warning-border: rgba(251, 191, 36, 0.3);
|
|
||||||
|
|
||||||
--badge-danger-bg: rgba(248, 113, 113, 0.15);
|
|
||||||
--badge-danger-text: #F87171;
|
|
||||||
--badge-danger-border: rgba(248, 113, 113, 0.3);
|
|
||||||
|
|
||||||
--badge-purple-bg: rgba(167, 139, 250, 0.15);
|
|
||||||
--badge-purple-text: #A78BFA;
|
|
||||||
--badge-purple-border: rgba(167, 139, 250, 0.3);
|
|
||||||
|
|
||||||
--badge-neutral-bg: #334155;
|
|
||||||
--badge-neutral-text: #CBD5E1;
|
|
||||||
--badge-neutral-border: #475569;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* ===================================================================
|
/* ===================================================================
|
||||||
SECTION 3: TAILWIND CONFIGURATION
|
SECTION 3: TAILWIND CONFIGURATION
|
||||||
=================================================================== */
|
=================================================================== */
|
||||||
@@ -421,7 +200,9 @@
|
|||||||
--breakpoint-2xl: 1536px;
|
--breakpoint-2xl: 1536px;
|
||||||
--breakpoint-3xl: 2000px;
|
--breakpoint-3xl: 2000px;
|
||||||
|
|
||||||
/* Typography */
|
/* -----------------------------------------------------------------
|
||||||
|
TYPOGRAPHY
|
||||||
|
----------------------------------------------------------------- */
|
||||||
--text-title-2xl: 72px;
|
--text-title-2xl: 72px;
|
||||||
--text-title-2xl--line-height: 90px;
|
--text-title-2xl--line-height: 90px;
|
||||||
--text-title-xl: 60px;
|
--text-title-xl: 60px;
|
||||||
@@ -439,13 +220,17 @@
|
|||||||
--text-theme-xs: 12px;
|
--text-theme-xs: 12px;
|
||||||
--text-theme-xs--line-height: 18px;
|
--text-theme-xs--line-height: 18px;
|
||||||
|
|
||||||
/* Base Colors */
|
/* -----------------------------------------------------------------
|
||||||
|
BASE COLORS
|
||||||
|
----------------------------------------------------------------- */
|
||||||
--color-current: currentColor;
|
--color-current: currentColor;
|
||||||
--color-transparent: transparent;
|
--color-transparent: transparent;
|
||||||
--color-white: #ffffff;
|
--color-white: #ffffff;
|
||||||
--color-black: #101828;
|
--color-black: color-mix(in srgb, var(--color-gray-base) 15%, black);
|
||||||
|
|
||||||
/* Brand Color Scale (Tailwind integration) */
|
/* -----------------------------------------------------------------
|
||||||
|
BRAND COLOR SCALE (ALL derived from --color-primary)
|
||||||
|
----------------------------------------------------------------- */
|
||||||
--color-brand-25: color-mix(in srgb, var(--color-primary) 3%, white);
|
--color-brand-25: color-mix(in srgb, var(--color-primary) 3%, white);
|
||||||
--color-brand-50: color-mix(in srgb, var(--color-primary) 8%, white);
|
--color-brand-50: color-mix(in srgb, var(--color-primary) 8%, white);
|
||||||
--color-brand-100: color-mix(in srgb, var(--color-primary) 15%, white);
|
--color-brand-100: color-mix(in srgb, var(--color-primary) 15%, white);
|
||||||
@@ -453,28 +238,32 @@
|
|||||||
--color-brand-300: color-mix(in srgb, var(--color-primary) 40%, white);
|
--color-brand-300: color-mix(in srgb, var(--color-primary) 40%, white);
|
||||||
--color-brand-400: color-mix(in srgb, var(--color-primary) 60%, white);
|
--color-brand-400: color-mix(in srgb, var(--color-primary) 60%, white);
|
||||||
--color-brand-500: var(--color-primary);
|
--color-brand-500: var(--color-primary);
|
||||||
--color-brand-600: var(--color-primary-dark);
|
--color-brand-600: color-mix(in srgb, var(--color-primary) 85%, black);
|
||||||
--color-brand-700: color-mix(in srgb, var(--color-primary-dark) 85%, black);
|
--color-brand-700: color-mix(in srgb, var(--color-primary) 70%, black);
|
||||||
--color-brand-800: color-mix(in srgb, var(--color-primary-dark) 70%, black);
|
--color-brand-800: color-mix(in srgb, var(--color-primary) 55%, black);
|
||||||
--color-brand-900: color-mix(in srgb, var(--color-primary-dark) 55%, black);
|
--color-brand-900: color-mix(in srgb, var(--color-primary) 40%, black);
|
||||||
--color-brand-950: color-mix(in srgb, var(--color-primary-dark) 35%, black);
|
--color-brand-950: color-mix(in srgb, var(--color-primary) 25%, black);
|
||||||
|
|
||||||
/* Gray Scale */
|
/* -----------------------------------------------------------------
|
||||||
--color-gray-25: #fcfcfd;
|
GRAY SCALE (ALL derived from --color-gray-base)
|
||||||
--color-gray-50: #f9fafb;
|
----------------------------------------------------------------- */
|
||||||
--color-gray-100: #f2f4f7;
|
--color-gray-25: color-mix(in srgb, var(--color-gray-base) 2%, white);
|
||||||
--color-gray-200: #e4e7ec;
|
--color-gray-50: color-mix(in srgb, var(--color-gray-base) 4%, white);
|
||||||
--color-gray-300: #d0d5dd;
|
--color-gray-100: color-mix(in srgb, var(--color-gray-base) 8%, white);
|
||||||
--color-gray-400: #98a2b3;
|
--color-gray-200: color-mix(in srgb, var(--color-gray-base) 15%, white);
|
||||||
--color-gray-500: #667085;
|
--color-gray-300: color-mix(in srgb, var(--color-gray-base) 25%, white);
|
||||||
--color-gray-600: #475467;
|
--color-gray-400: color-mix(in srgb, var(--color-gray-base) 50%, white);
|
||||||
--color-gray-700: #344054;
|
--color-gray-500: var(--color-gray-base);
|
||||||
--color-gray-800: #1d2939;
|
--color-gray-600: color-mix(in srgb, var(--color-gray-base) 85%, black);
|
||||||
--color-gray-900: #101828;
|
--color-gray-700: color-mix(in srgb, var(--color-gray-base) 70%, black);
|
||||||
--color-gray-950: #0c111d;
|
--color-gray-800: color-mix(in srgb, var(--color-gray-base) 55%, black);
|
||||||
--color-gray-dark: #1a2231;
|
--color-gray-900: color-mix(in srgb, var(--color-gray-base) 40%, black);
|
||||||
|
--color-gray-950: color-mix(in srgb, var(--color-gray-base) 25%, black);
|
||||||
|
--color-gray-dark: color-mix(in srgb, var(--color-gray-base) 30%, black);
|
||||||
|
|
||||||
/* Success Scale */
|
/* -----------------------------------------------------------------
|
||||||
|
SUCCESS SCALE (ALL derived from --color-success)
|
||||||
|
----------------------------------------------------------------- */
|
||||||
--color-success-25: color-mix(in srgb, var(--color-success) 2%, white);
|
--color-success-25: color-mix(in srgb, var(--color-success) 2%, white);
|
||||||
--color-success-50: color-mix(in srgb, var(--color-success) 6%, white);
|
--color-success-50: color-mix(in srgb, var(--color-success) 6%, white);
|
||||||
--color-success-100: color-mix(in srgb, var(--color-success) 15%, white);
|
--color-success-100: color-mix(in srgb, var(--color-success) 15%, white);
|
||||||
@@ -482,13 +271,15 @@
|
|||||||
--color-success-300: color-mix(in srgb, var(--color-success) 50%, white);
|
--color-success-300: color-mix(in srgb, var(--color-success) 50%, white);
|
||||||
--color-success-400: color-mix(in srgb, var(--color-success) 75%, white);
|
--color-success-400: color-mix(in srgb, var(--color-success) 75%, white);
|
||||||
--color-success-500: var(--color-success);
|
--color-success-500: var(--color-success);
|
||||||
--color-success-600: var(--color-success-dark);
|
--color-success-600: color-mix(in srgb, var(--color-success) 85%, black);
|
||||||
--color-success-700: color-mix(in srgb, var(--color-success-dark) 85%, black);
|
--color-success-700: color-mix(in srgb, var(--color-success) 70%, black);
|
||||||
--color-success-800: color-mix(in srgb, var(--color-success-dark) 65%, black);
|
--color-success-800: color-mix(in srgb, var(--color-success) 55%, black);
|
||||||
--color-success-900: color-mix(in srgb, var(--color-success-dark) 50%, black);
|
--color-success-900: color-mix(in srgb, var(--color-success) 40%, black);
|
||||||
--color-success-950: color-mix(in srgb, var(--color-success-dark) 35%, black);
|
--color-success-950: color-mix(in srgb, var(--color-success) 25%, black);
|
||||||
|
|
||||||
/* Error Scale */
|
/* -----------------------------------------------------------------
|
||||||
|
ERROR SCALE (ALL derived from --color-danger)
|
||||||
|
----------------------------------------------------------------- */
|
||||||
--color-error-25: color-mix(in srgb, var(--color-danger) 2%, white);
|
--color-error-25: color-mix(in srgb, var(--color-danger) 2%, white);
|
||||||
--color-error-50: color-mix(in srgb, var(--color-danger) 5%, white);
|
--color-error-50: color-mix(in srgb, var(--color-danger) 5%, white);
|
||||||
--color-error-100: color-mix(in srgb, var(--color-danger) 10%, white);
|
--color-error-100: color-mix(in srgb, var(--color-danger) 10%, white);
|
||||||
@@ -496,13 +287,15 @@
|
|||||||
--color-error-300: color-mix(in srgb, var(--color-danger) 40%, white);
|
--color-error-300: color-mix(in srgb, var(--color-danger) 40%, white);
|
||||||
--color-error-400: color-mix(in srgb, var(--color-danger) 65%, white);
|
--color-error-400: color-mix(in srgb, var(--color-danger) 65%, white);
|
||||||
--color-error-500: var(--color-danger);
|
--color-error-500: var(--color-danger);
|
||||||
--color-error-600: var(--color-danger-dark);
|
--color-error-600: color-mix(in srgb, var(--color-danger) 85%, black);
|
||||||
--color-error-700: color-mix(in srgb, var(--color-danger-dark) 80%, black);
|
--color-error-700: color-mix(in srgb, var(--color-danger) 70%, black);
|
||||||
--color-error-800: color-mix(in srgb, var(--color-danger-dark) 65%, black);
|
--color-error-800: color-mix(in srgb, var(--color-danger) 55%, black);
|
||||||
--color-error-900: color-mix(in srgb, var(--color-danger-dark) 50%, black);
|
--color-error-900: color-mix(in srgb, var(--color-danger) 40%, black);
|
||||||
--color-error-950: color-mix(in srgb, var(--color-danger-dark) 30%, black);
|
--color-error-950: color-mix(in srgb, var(--color-danger) 25%, black);
|
||||||
|
|
||||||
/* Warning Scale */
|
/* -----------------------------------------------------------------
|
||||||
|
WARNING SCALE (ALL derived from --color-warning)
|
||||||
|
----------------------------------------------------------------- */
|
||||||
--color-warning-25: color-mix(in srgb, var(--color-warning) 2%, white);
|
--color-warning-25: color-mix(in srgb, var(--color-warning) 2%, white);
|
||||||
--color-warning-50: color-mix(in srgb, var(--color-warning) 4%, white);
|
--color-warning-50: color-mix(in srgb, var(--color-warning) 4%, white);
|
||||||
--color-warning-100: color-mix(in srgb, var(--color-warning) 10%, white);
|
--color-warning-100: color-mix(in srgb, var(--color-warning) 10%, white);
|
||||||
@@ -510,13 +303,15 @@
|
|||||||
--color-warning-300: color-mix(in srgb, var(--color-warning) 40%, white);
|
--color-warning-300: color-mix(in srgb, var(--color-warning) 40%, white);
|
||||||
--color-warning-400: color-mix(in srgb, var(--color-warning) 65%, white);
|
--color-warning-400: color-mix(in srgb, var(--color-warning) 65%, white);
|
||||||
--color-warning-500: var(--color-warning);
|
--color-warning-500: var(--color-warning);
|
||||||
--color-warning-600: var(--color-warning-dark);
|
--color-warning-600: color-mix(in srgb, var(--color-warning) 85%, black);
|
||||||
--color-warning-700: color-mix(in srgb, var(--color-warning-dark) 80%, black);
|
--color-warning-700: color-mix(in srgb, var(--color-warning) 70%, black);
|
||||||
--color-warning-800: color-mix(in srgb, var(--color-warning-dark) 65%, black);
|
--color-warning-800: color-mix(in srgb, var(--color-warning) 55%, black);
|
||||||
--color-warning-900: color-mix(in srgb, var(--color-warning-dark) 50%, black);
|
--color-warning-900: color-mix(in srgb, var(--color-warning) 40%, black);
|
||||||
--color-warning-950: color-mix(in srgb, var(--color-warning-dark) 30%, black);
|
--color-warning-950: color-mix(in srgb, var(--color-warning) 25%, black);
|
||||||
|
|
||||||
/* Purple Scale */
|
/* -----------------------------------------------------------------
|
||||||
|
PURPLE SCALE (ALL derived from --color-purple)
|
||||||
|
----------------------------------------------------------------- */
|
||||||
--color-purple-25: color-mix(in srgb, var(--color-purple) 2%, white);
|
--color-purple-25: color-mix(in srgb, var(--color-purple) 2%, white);
|
||||||
--color-purple-50: color-mix(in srgb, var(--color-purple) 8%, white);
|
--color-purple-50: color-mix(in srgb, var(--color-purple) 8%, white);
|
||||||
--color-purple-100: color-mix(in srgb, var(--color-purple) 15%, white);
|
--color-purple-100: color-mix(in srgb, var(--color-purple) 15%, white);
|
||||||
@@ -524,32 +319,38 @@
|
|||||||
--color-purple-300: color-mix(in srgb, var(--color-purple) 40%, white);
|
--color-purple-300: color-mix(in srgb, var(--color-purple) 40%, white);
|
||||||
--color-purple-400: color-mix(in srgb, var(--color-purple) 60%, white);
|
--color-purple-400: color-mix(in srgb, var(--color-purple) 60%, white);
|
||||||
--color-purple-500: var(--color-purple);
|
--color-purple-500: var(--color-purple);
|
||||||
--color-purple-600: var(--color-purple-dark);
|
--color-purple-600: color-mix(in srgb, var(--color-purple) 85%, black);
|
||||||
--color-purple-700: color-mix(in srgb, var(--color-purple-dark) 85%, black);
|
--color-purple-700: color-mix(in srgb, var(--color-purple) 70%, black);
|
||||||
--color-purple-800: color-mix(in srgb, var(--color-purple-dark) 70%, black);
|
--color-purple-800: color-mix(in srgb, var(--color-purple) 55%, black);
|
||||||
--color-purple-900: color-mix(in srgb, var(--color-purple-dark) 55%, black);
|
--color-purple-900: color-mix(in srgb, var(--color-purple) 40%, black);
|
||||||
--color-purple-950: color-mix(in srgb, var(--color-purple-dark) 35%, black);
|
--color-purple-950: color-mix(in srgb, var(--color-purple) 25%, black);
|
||||||
|
|
||||||
/* Blue-Light Scale (Info) */
|
/* -----------------------------------------------------------------
|
||||||
--color-blue-light-25: #f0faff;
|
INFO/BLUE-LIGHT SCALE (derived from --color-primary for consistency)
|
||||||
--color-blue-light-50: #e0f4ff;
|
----------------------------------------------------------------- */
|
||||||
--color-blue-light-100: #bae6fd;
|
--color-blue-light-25: color-mix(in srgb, var(--color-primary) 2%, white);
|
||||||
--color-blue-light-200: #7dd3fc;
|
--color-blue-light-50: color-mix(in srgb, var(--color-primary) 5%, white);
|
||||||
--color-blue-light-300: #38bdf8;
|
--color-blue-light-100: color-mix(in srgb, var(--color-primary) 12%, white);
|
||||||
--color-blue-light-400: #0ea5e9;
|
--color-blue-light-200: color-mix(in srgb, var(--color-primary) 25%, white);
|
||||||
--color-blue-light-500: #0284c7;
|
--color-blue-light-300: color-mix(in srgb, var(--color-primary) 45%, white);
|
||||||
--color-blue-light-600: #0369a1;
|
--color-blue-light-400: color-mix(in srgb, var(--color-primary) 70%, white);
|
||||||
--color-blue-light-700: #075985;
|
--color-blue-light-500: color-mix(in srgb, var(--color-primary) 90%, white);
|
||||||
|
--color-blue-light-600: var(--color-primary);
|
||||||
|
--color-blue-light-700: color-mix(in srgb, var(--color-primary) 85%, black);
|
||||||
|
|
||||||
/* Shadows */
|
/* -----------------------------------------------------------------
|
||||||
--shadow-theme-xs: 0px 1px 2px 0px rgba(16, 24, 40, 0.05);
|
SHADOWS (Tailwind integration)
|
||||||
--shadow-theme-sm: 0px 1px 3px 0px rgba(16, 24, 40, 0.1), 0px 1px 2px 0px rgba(16, 24, 40, 0.06);
|
----------------------------------------------------------------- */
|
||||||
--shadow-theme-md: 0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06);
|
--shadow-theme-xs: var(--shadow-xs);
|
||||||
--shadow-theme-lg: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
|
--shadow-theme-sm: var(--shadow-sm);
|
||||||
--shadow-theme-xl: 0px 20px 24px -4px rgba(16, 24, 40, 0.08), 0px 8px 8px -4px rgba(16, 24, 40, 0.03);
|
--shadow-theme-md: var(--shadow-md);
|
||||||
|
--shadow-theme-lg: var(--shadow-lg);
|
||||||
|
--shadow-theme-xl: var(--shadow-xl);
|
||||||
--drop-shadow-4xl: 0 35px 35px rgba(0, 0, 0, 0.25), 0 45px 65px rgba(0, 0, 0, 0.15);
|
--drop-shadow-4xl: 0 35px 35px rgba(0, 0, 0, 0.25), 0 45px 65px rgba(0, 0, 0, 0.15);
|
||||||
|
|
||||||
/* Z-Index */
|
/* -----------------------------------------------------------------
|
||||||
|
Z-INDEX
|
||||||
|
----------------------------------------------------------------- */
|
||||||
--z-index-1: 1;
|
--z-index-1: 1;
|
||||||
--z-index-9: 9;
|
--z-index-9: 9;
|
||||||
--z-index-99: 99;
|
--z-index-99: 99;
|
||||||
@@ -682,31 +483,33 @@
|
|||||||
|
|
||||||
/* Compact Table Headers */
|
/* Compact Table Headers */
|
||||||
.igny8-table-compact th {
|
.igny8-table-compact th {
|
||||||
padding: 12px 16px !important;
|
padding: 8px 12px !important;
|
||||||
font-size: 14px !important;
|
font-size: 13px !important;
|
||||||
font-weight: 600 !important;
|
font-weight: 600 !important;
|
||||||
color: var(--color-gray-600) !important;
|
color: var(--color-text-dim) !important;
|
||||||
text-align: left !important;
|
text-align: left !important;
|
||||||
background-color: var(--color-gray-50) !important;
|
background-color: var(--color-surface) !important;
|
||||||
border-bottom: 2px solid var(--color-gray-200) !important;
|
border-bottom: 2px solid var(--color-stroke) !important;
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
letter-spacing: 0.3px;
|
letter-spacing: 0.3px;
|
||||||
|
line-height: 1.3 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .igny8-table-compact th {
|
.dark .igny8-table-compact th {
|
||||||
color: var(--color-gray-200) !important;
|
color: var(--color-text-dim) !important;
|
||||||
background-color: rgba(15, 23, 42, 0.5) !important;
|
background-color: var(--color-panel-alt) !important;
|
||||||
border-bottom-color: rgba(255, 255, 255, 0.1) !important;
|
border-bottom-color: var(--color-stroke) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.igny8-table-compact td {
|
.igny8-table-compact td {
|
||||||
padding: 8px 12px !important;
|
padding: 6px 10px !important;
|
||||||
font-size: 14px !important;
|
font-size: 13px !important;
|
||||||
border-bottom: 1px solid var(--color-gray-200) !important;
|
line-height: 1.3 !important;
|
||||||
|
border-bottom: 1px solid var(--color-stroke) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .igny8-table-compact td {
|
.dark .igny8-table-compact td {
|
||||||
border-bottom-color: rgba(255, 255, 255, 0.05) !important;
|
border-bottom-color: var(--color-stroke) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.igny8-table-compact th.text-center,
|
.igny8-table-compact th.text-center,
|
||||||
@@ -786,13 +589,13 @@
|
|||||||
gap: 6px;
|
gap: 6px;
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border-radius: 6px;
|
border-radius: var(--radius-sm);
|
||||||
box-shadow: 0 2px 6px 3px rgba(0, 0, 0, 0.08);
|
box-shadow: var(--shadow-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .igny8-header-metrics {
|
.dark .igny8-header-metrics {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
box-shadow: 0 2px 6px 3px rgba(0, 0, 0, 0.08);
|
box-shadow: var(--shadow-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.igny8-header-metric {
|
.igny8-header-metric {
|
||||||
@@ -804,12 +607,12 @@
|
|||||||
.igny8-header-metric-separator {
|
.igny8-header-metric-separator {
|
||||||
width: 1px;
|
width: 1px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
background: rgb(203 213 225);
|
background: var(--color-stroke);
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .igny8-header-metric-separator {
|
.dark .igny8-header-metric-separator {
|
||||||
background: rgb(148 163 184);
|
background: var(--color-stroke);
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -818,17 +621,17 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
letter-spacing: 0.3px;
|
letter-spacing: 0.3px;
|
||||||
color: rgb(100 116 139);
|
color: var(--color-text-dim);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .igny8-header-metric-label {
|
.dark .igny8-header-metric-label {
|
||||||
color: rgb(148 163 184);
|
color: var(--color-text-dim);
|
||||||
}
|
}
|
||||||
|
|
||||||
.igny8-header-metric-value {
|
.igny8-header-metric-value {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: rgb(30 41 59);
|
color: var(--color-text);
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -837,7 +640,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .igny8-header-metric-value {
|
.dark .igny8-header-metric-value {
|
||||||
color: white;
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.igny8-header-metric-accent {
|
.igny8-header-metric-accent {
|
||||||
|
|||||||
@@ -799,7 +799,7 @@ export default function TablePageTemplate({
|
|||||||
{selection && (
|
{selection && (
|
||||||
<TableCell
|
<TableCell
|
||||||
isHeader
|
isHeader
|
||||||
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400 w-12"
|
className="font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400 w-12"
|
||||||
>
|
>
|
||||||
{showContent && (
|
{showContent && (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@@ -821,7 +821,7 @@ export default function TablePageTemplate({
|
|||||||
<TableCell
|
<TableCell
|
||||||
key={column.key}
|
key={column.key}
|
||||||
isHeader
|
isHeader
|
||||||
className={`px-5 py-3 font-medium text-gray-500 text-${column.align || 'start'} text-[11px] dark:text-gray-400 ${column.sortable ? 'cursor-pointer hover:text-gray-700 dark:hover:text-gray-300' : ''} ${isLastColumn && rowActions.length > 0 ? 'pr-16' : ''}`}
|
className={`font-medium text-gray-500 text-${column.align || 'start'} text-[11px] dark:text-gray-400 ${column.sortable ? 'cursor-pointer hover:text-gray-700 dark:hover:text-gray-300' : ''} ${isLastColumn && rowActions.length > 0 ? 'pr-16' : ''}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<div className="flex items-center flex-1">
|
<div className="flex items-center flex-1">
|
||||||
@@ -915,7 +915,7 @@ export default function TablePageTemplate({
|
|||||||
className={`igny8-data-row ${isRowAdded ? 'bg-brand-50 dark:bg-brand-500/10' : ''} ${customRowClass}`}
|
className={`igny8-data-row ${isRowAdded ? 'bg-brand-50 dark:bg-brand-500/10' : ''} ${customRowClass}`}
|
||||||
>
|
>
|
||||||
{selection && (
|
{selection && (
|
||||||
<TableCell className="px-5 py-4 text-start">
|
<TableCell className="text-start">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={selectedIds.includes(row.id?.toString() || '')}
|
checked={selectedIds.includes(row.id?.toString() || '')}
|
||||||
onChange={(checked) => handleSelectRow(row.id?.toString() || '', checked)}
|
onChange={(checked) => handleSelectRow(row.id?.toString() || '', checked)}
|
||||||
@@ -939,10 +939,10 @@ export default function TablePageTemplate({
|
|||||||
return (
|
return (
|
||||||
<TableCell
|
<TableCell
|
||||||
key={column.key}
|
key={column.key}
|
||||||
className={`px-5 py-4 text-${column.align || 'start'} text-gray-800 dark:text-white/90 ${isLastColumn && rowActions.length > 0 ? 'relative pr-16' : ''}`}
|
className={`text-${column.align || 'start'} text-gray-800 dark:text-white/90 ${isLastColumn && rowActions.length > 0 ? 'relative pr-16' : ''}`}
|
||||||
>
|
>
|
||||||
<div className={`flex items-center ${column.toggleable && hasToggleContent ? 'justify-between w-full' : ''} gap-2`}>
|
<div className={`flex items-center ${column.align === 'center' ? 'justify-center' : column.align === 'end' ? 'justify-end' : ''} ${column.toggleable && hasToggleContent ? 'justify-between w-full' : ''} gap-2`}>
|
||||||
<div className="flex-1">
|
<div className={column.toggleable && hasToggleContent ? 'flex-1' : ''}>
|
||||||
{column.render ? (
|
{column.render ? (
|
||||||
column.render(row[column.key], row)
|
column.render(row[column.key], row)
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
Reference in New Issue
Block a user