Section 6 COmpleted
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
/**
|
||||
* Account Settings Page - Consolidated Settings
|
||||
* Tabs: Account, Profile, Team
|
||||
* Consistent with system page structure (like Plans & Usage pages)
|
||||
* Tab selection driven by URL path for sidebar navigation
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import {
|
||||
Save, Loader2, Settings, User, Users, UserPlus, Shield, Lock, X
|
||||
} from 'lucide-react';
|
||||
@@ -28,10 +29,19 @@ import {
|
||||
|
||||
type TabType = 'account' | 'profile' | 'team';
|
||||
|
||||
// Map URL paths to tab types
|
||||
function getTabFromPath(pathname: string): TabType {
|
||||
if (pathname.includes('/profile')) return 'profile';
|
||||
if (pathname.includes('/team')) return 'team';
|
||||
return 'account';
|
||||
}
|
||||
|
||||
export default function AccountSettingsPage() {
|
||||
const toast = useToast();
|
||||
const location = useLocation();
|
||||
const { user, refreshUser } = useAuthStore();
|
||||
const [activeTab, setActiveTab] = useState<TabType>('account');
|
||||
// Derive active tab from URL path
|
||||
const activeTab = getTabFromPath(location.pathname);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState<string>('');
|
||||
@@ -271,37 +281,29 @@ export default function AccountSettingsPage() {
|
||||
<div className="p-6">
|
||||
<PageMeta title="Account Settings" description="Manage your account, profile, and team" />
|
||||
|
||||
{/* Page Header */}
|
||||
{/* Page Header with Breadcrumb */}
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Account Settings</h1>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 mb-2">
|
||||
<span>Account Settings</span>
|
||||
<span>›</span>
|
||||
<span className="text-gray-900 dark:text-white font-medium">
|
||||
{activeTab === 'account' && 'Account'}
|
||||
{activeTab === 'profile' && 'Profile'}
|
||||
{activeTab === 'team' && 'Team'}
|
||||
</span>
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{activeTab === 'account' && 'Account Information'}
|
||||
{activeTab === 'profile' && 'Profile Settings'}
|
||||
{activeTab === 'team' && 'Team Management'}
|
||||
</h1>
|
||||
<p className="text-gray-600 dark:text-gray-400 mt-1">
|
||||
Manage your account information, profile settings, and team members
|
||||
{activeTab === 'account' && 'Manage your organization and billing information'}
|
||||
{activeTab === 'profile' && 'Update your personal information and preferences'}
|
||||
{activeTab === 'team' && 'Invite and manage team members'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="mb-6 border-b border-gray-200 dark:border-gray-700">
|
||||
<nav className="-mb-px flex space-x-8">
|
||||
{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
|
||||
${activeTab === tab.id
|
||||
? 'border-[var(--color-brand-500)] text-[var(--color-brand-600)] dark:text-[var(--color-brand-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">
|
||||
{/* Account Tab */}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import {
|
||||
Save, Loader2, Image as ImageIcon, FileText, Send, Settings
|
||||
} from 'lucide-react';
|
||||
@@ -83,9 +84,17 @@ const getImageSizes = (provider: string, model: string) => {
|
||||
return [{ value: '1024x1024', label: '1024×1024 pixels' }];
|
||||
};
|
||||
|
||||
// Get tab from URL path
|
||||
function getTabFromPath(pathname: string): TabType {
|
||||
if (pathname.includes('/publishing')) return 'publishing';
|
||||
if (pathname.includes('/images')) return 'images';
|
||||
return 'content';
|
||||
}
|
||||
|
||||
export default function ContentSettingsPage() {
|
||||
const toast = useToast();
|
||||
const [activeTab, setActiveTab] = useState<TabType>('content');
|
||||
const location = useLocation();
|
||||
const activeTab = getTabFromPath(location.pathname);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
@@ -293,11 +302,11 @@ export default function ContentSettingsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const tabs = [
|
||||
{ id: 'content' as TabType, label: 'Content Generation', icon: <FileText className="w-4 h-4" /> },
|
||||
{ id: 'publishing' as TabType, label: 'Publishing', icon: <Send className="w-4 h-4" /> },
|
||||
{ id: 'images' as TabType, label: 'Image Settings', icon: <ImageIcon className="w-4 h-4" /> },
|
||||
];
|
||||
const tabTitles: Record<TabType, string> = {
|
||||
content: 'Content Generation',
|
||||
publishing: 'Publishing',
|
||||
images: 'Image Settings',
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -319,35 +328,17 @@ export default function ContentSettingsPage() {
|
||||
|
||||
{/* Page Header */}
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Content Settings</h1>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 mb-1">
|
||||
Content Settings / {tabTitles[activeTab]}
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">{tabTitles[activeTab]}</h1>
|
||||
<p className="text-gray-600 dark:text-gray-400 mt-1">
|
||||
Configure how your content and images are generated
|
||||
{activeTab === 'content' && 'Customize how your articles are written'}
|
||||
{activeTab === 'publishing' && 'Configure automatic publishing settings'}
|
||||
{activeTab === 'images' && 'Set up AI image generation preferences'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="mb-6 border-b border-gray-200 dark:border-gray-700">
|
||||
<nav className="-mb-px flex space-x-8">
|
||||
{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
|
||||
${activeTab === tab.id
|
||||
? 'border-[var(--color-brand-500)] text-[var(--color-brand-600)] dark:text-[var(--color-brand-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">
|
||||
{/* Content Generation Tab */}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
/**
|
||||
* Plans & Billing Page - Subscription & Payment Management
|
||||
* Tabs: Current Plan, Upgrade Plan, Billing History
|
||||
* Tab selection driven by URL path for sidebar navigation
|
||||
*
|
||||
* Note: Usage tracking is consolidated in UsageAnalyticsPage (/account/usage)
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import {
|
||||
CreditCard, Package, TrendingUp, FileText, Wallet, ArrowUpCircle,
|
||||
Loader2, AlertCircle, CheckCircle, Download, Zap, Globe, Users, X
|
||||
@@ -49,8 +50,17 @@ import { useAuthStore } from '../../store/authStore';
|
||||
|
||||
type TabType = 'plan' | 'upgrade' | 'invoices';
|
||||
|
||||
// Map URL paths to tab types
|
||||
function getTabFromPath(pathname: string): TabType {
|
||||
if (pathname.includes('/upgrade')) return 'upgrade';
|
||||
if (pathname.includes('/history')) return 'invoices';
|
||||
return 'plan';
|
||||
}
|
||||
|
||||
export default function PlansAndBillingPage() {
|
||||
const [activeTab, setActiveTab] = useState<TabType>('plan');
|
||||
const location = useLocation();
|
||||
// Derive active tab from URL path
|
||||
const activeTab = getTabFromPath(location.pathname);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string>('');
|
||||
const [planLoadingId, setPlanLoadingId] = useState<number | null>(null);
|
||||
@@ -347,18 +357,25 @@ export default function PlansAndBillingPage() {
|
||||
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: 'upgrade' as TabType, label: 'Upgrade Plan', icon: <Wallet className="w-4 h-4" /> },
|
||||
{ id: 'invoices' as TabType, label: 'History', icon: <FileText className="w-4 h-4" /> },
|
||||
];
|
||||
// Page titles based on active tab
|
||||
const pageTitles = {
|
||||
plan: { title: 'Current Plan', description: 'View your subscription details and features' },
|
||||
upgrade: { title: 'Upgrade Plan', description: 'Compare plans and upgrade your subscription' },
|
||||
invoices: { title: 'Billing History', description: 'View invoices and manage payment methods' },
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
{/* Page Header with Breadcrumb */}
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Your Subscription</h1>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 mb-2">
|
||||
<span>Plans & Billing</span>
|
||||
<span>›</span>
|
||||
<span className="text-gray-900 dark:text-white font-medium">{pageTitles[activeTab].title}</span>
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">{pageTitles[activeTab].title}</h1>
|
||||
<p className="text-gray-600 dark:text-gray-400 mt-1">
|
||||
Manage your plan and view usage
|
||||
{pageTitles[activeTab].description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -367,7 +384,7 @@ export default function PlansAndBillingPage() {
|
||||
<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-[var(--color-info-200)] bg-[var(--color-info-50)] text-[var(--color-info-800)] dark:border-[var(--color-info-800)] dark:bg-[var(--color-info-900)]/20 dark:text-[var(--color-info-100)]">
|
||||
We received your manual payment. It’s pending admin approval; activation will complete once approved.
|
||||
@@ -381,29 +398,6 @@ export default function PlansAndBillingPage() {
|
||||
</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-[var(--color-brand-500)] text-[var(--color-brand-500)]'
|
||||
: '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 */}
|
||||
@@ -476,7 +470,8 @@ export default function PlansAndBillingPage() {
|
||||
<Button
|
||||
variant="primary"
|
||||
tone="brand"
|
||||
onClick={() => setActiveTab('upgrade')}
|
||||
as={Link}
|
||||
to="/account/plans/upgrade"
|
||||
startIcon={<ArrowUpCircle className="w-4 h-4" />}
|
||||
>
|
||||
Upgrade Plan
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
/**
|
||||
* Usage & Analytics Page - Refactored
|
||||
* Organized tabs: Plan Limits & Usage, Credit Activity, API Usage
|
||||
* Tab selection driven by URL path for sidebar navigation
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { TrendingUp, Activity, BarChart3, Zap, Calendar } from 'lucide-react';
|
||||
import PageMeta from '../../components/common/PageMeta';
|
||||
import { useToast } from '../../components/ui/toast/ToastContainer';
|
||||
@@ -16,9 +18,18 @@ import Button from '../../components/ui/button/Button';
|
||||
|
||||
type TabType = 'limits' | 'activity' | 'api';
|
||||
|
||||
// Map URL paths to tab types
|
||||
function getTabFromPath(pathname: string): TabType {
|
||||
if (pathname.includes('/credits')) return 'activity';
|
||||
if (pathname.includes('/activity')) return 'api';
|
||||
return 'limits';
|
||||
}
|
||||
|
||||
export default function UsageAnalyticsPage() {
|
||||
const toast = useToast();
|
||||
const [activeTab, setActiveTab] = useState<TabType>('limits');
|
||||
const location = useLocation();
|
||||
// Derive active tab from URL path
|
||||
const activeTab = getTabFromPath(location.pathname);
|
||||
const [analytics, setAnalytics] = useState<UsageAnalytics | null>(null);
|
||||
const [creditBalance, setCreditBalance] = useState<CreditBalance | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -58,11 +69,11 @@ export default function UsageAnalyticsPage() {
|
||||
);
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
{ id: 'limits' as TabType, label: 'Your Limits & Usage (What you\'re using)', icon: <BarChart3 className="w-4 h-4" /> },
|
||||
{ id: 'activity' as TabType, label: 'Credit History (Where credits go)', icon: <TrendingUp className="w-4 h-4" /> },
|
||||
{ id: 'api' as TabType, label: 'API Activity (Technical requests)', icon: <Activity className="w-4 h-4" /> },
|
||||
];
|
||||
const tabTitles: Record<TabType, string> = {
|
||||
limits: 'Limits & Usage',
|
||||
activity: 'Credit History',
|
||||
api: 'Activity Log',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
@@ -70,9 +81,14 @@ export default function UsageAnalyticsPage() {
|
||||
|
||||
{/* Page Header */}
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Your Usage</h1>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 mb-1">
|
||||
Usage & Analytics / {tabTitles[activeTab]}
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">{tabTitles[activeTab]}</h1>
|
||||
<p className="text-gray-600 dark:text-gray-400 mt-1">
|
||||
See how much you're using - Track your credits, content limits, and API activity
|
||||
{activeTab === 'limits' && 'See how much you\'re using - Track your credits and content limits'}
|
||||
{activeTab === 'activity' && 'See where your credits go - Track credit usage history'}
|
||||
{activeTab === 'api' && 'Technical requests - Monitor API activity and usage'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -142,32 +158,9 @@ export default function UsageAnalyticsPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tabs and Period Selector */}
|
||||
<div className="mb-6 flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
|
||||
{/* Tabs */}
|
||||
<div className="border-b border-gray-200 dark:border-gray-700 w-full sm:w-auto">
|
||||
<nav className="-mb-px flex space-x-8 overflow-x-auto">
|
||||
{tabs.map((tab) => (
|
||||
<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-brand-500 text-brand-600 dark:text-brand-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>
|
||||
|
||||
{/* Period Selector (only show on activity and api tabs) */}
|
||||
{(activeTab === 'activity' || activeTab === 'api') && (
|
||||
{/* Period Selector (only show on activity and api tabs) */}
|
||||
{(activeTab === 'activity' || activeTab === 'api') && (
|
||||
<div className="mb-6 flex justify-end">
|
||||
<div className="flex gap-2">
|
||||
{[7, 30, 90].map((value) => {
|
||||
const isActive = period === value;
|
||||
@@ -184,8 +177,8 @@ export default function UsageAnalyticsPage() {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tab Content */}
|
||||
<div className="mt-6">
|
||||
|
||||
Reference in New Issue
Block a user