mirror of
http://88.130.71.182:3000/BlitTech/contexta_fe.git
synced 2026-06-12 23:23:22 +00:00
fixed bugs
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useQuery, useMutation } from '@tanstack/react-query'
|
||||
import { useNavigate, Link } from 'react-router-dom'
|
||||
import React, { useState, useEffect, useCallback } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useNavigate, useLocation, Link } from 'react-router-dom'
|
||||
import { billingAPI } from '@/services/api'
|
||||
import { useAuthStore } from '@/store/authStore'
|
||||
import { Button, Card, Input, Badge } from '@/components/ui'
|
||||
@@ -8,48 +8,74 @@ import { getPlanColor, formatDate } from '@/lib/utils'
|
||||
import { CreditCard, User, Shield, Download, ExternalLink } from 'lucide-react'
|
||||
|
||||
export const SettingsPage: React.FC = () => {
|
||||
const { user, updateUser } = useAuthStore()
|
||||
// BUG-04 FIX: Removed unused 'updateUser' from destructuring
|
||||
const { user } = useAuthStore()
|
||||
const navigate = useNavigate()
|
||||
const [tab, setTab] = useState<'profile' | 'billing' | 'export'>('profile')
|
||||
const location = useLocation()
|
||||
|
||||
// BUG-06 FIX: Sync tab with URL path on mount and when path changes
|
||||
const getTabFromPath = (pathname: string): 'profile' | 'billing' => {
|
||||
if (pathname === '/settings/billing') return 'billing'
|
||||
return 'profile'
|
||||
}
|
||||
|
||||
const [tab, setTab] = useState<'profile' | 'billing'>(getTabFromPath(location.pathname))
|
||||
const [toast, setToast] = useState('')
|
||||
|
||||
// Keep tab in sync if URL changes externally
|
||||
useEffect(() => {
|
||||
setTab(getTabFromPath(location.pathname))
|
||||
}, [location.pathname])
|
||||
|
||||
// Update URL when tab changes
|
||||
const handleTabChange = useCallback((newTab: 'profile' | 'billing') => {
|
||||
setTab(newTab)
|
||||
const newPath = newTab === 'billing' ? '/settings/billing' : '/settings'
|
||||
if (location.pathname !== newPath) {
|
||||
navigate(newPath, { replace: true })
|
||||
}
|
||||
}, [navigate, location.pathname])
|
||||
|
||||
const showToast = (msg: string) => {
|
||||
setToast(msg)
|
||||
setTimeout(() => setToast(''), 3000)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 max-w-3xl mx-auto">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-6">Settings</h1>
|
||||
<div className="p-6 max-w-3xl mx-auto">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-6">Settings</h1>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="flex gap-1 mb-6 bg-gray-100 p-1 rounded-xl w-fit">
|
||||
{[
|
||||
{ id: 'profile', label: 'Profile', icon: User },
|
||||
{ id: 'billing', label: 'Billing', icon: CreditCard },
|
||||
].map(({ id, label, icon: Icon }) => (
|
||||
<button
|
||||
key={id}
|
||||
onClick={() => setTab(id as any)}
|
||||
className={`flex items-center gap-1.5 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
||||
tab === id ? 'bg-white shadow-sm text-gray-900' : 'text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
<Icon className="w-3.5 h-3.5" />
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{tab === 'profile' && <ProfileSettings onToast={showToast} />}
|
||||
{tab === 'billing' && <BillingSettings onToast={showToast} />}
|
||||
|
||||
{toast && (
|
||||
<div className="fixed bottom-4 right-4 bg-gray-900 text-white px-4 py-2 rounded-lg text-sm shadow-lg z-50">
|
||||
{toast}
|
||||
{/* Tabs */}
|
||||
<div className="flex gap-1 mb-6 bg-gray-100 p-1 rounded-xl w-fit">
|
||||
{[
|
||||
{ id: 'profile' as const, label: 'Profile', icon: User },
|
||||
{ id: 'billing' as const, label: 'Billing', icon: CreditCard },
|
||||
].map(({ id, label, icon: Icon }) => (
|
||||
<button
|
||||
key={id}
|
||||
onClick={() => handleTabChange(id)}
|
||||
className={`flex items-center gap-1.5 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
||||
tab === id
|
||||
? 'bg-white shadow-sm text-gray-900'
|
||||
: 'text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
<Icon className="w-3.5 h-3.5" />
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{tab === 'profile' && <ProfileSettings onToast={showToast} />}
|
||||
{tab === 'billing' && <BillingSettings onToast={showToast} />}
|
||||
|
||||
{toast && (
|
||||
<div className="fixed bottom-4 right-4 bg-gray-900 text-white px-4 py-2 rounded-lg text-sm shadow-lg z-50 animate-fade-in-up">
|
||||
{toast}
|
||||
<button onClick={() => setToast('')} className="ml-3 opacity-60 hover:opacity-100">×</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -57,22 +83,22 @@ const ProfileSettings: React.FC<{ onToast: (msg: string) => void }> = ({ onToast
|
||||
const { user } = useAuthStore()
|
||||
|
||||
return (
|
||||
<Card className="p-6 space-y-4">
|
||||
<h2 className="font-semibold text-gray-900">Profile Information</h2>
|
||||
<Input label="Email" value={user?.email || ''} disabled />
|
||||
<Input label="Company Name" value={user?.company_name || ''} disabled hint="Contact support to change company name" />
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1.5">Plan</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Card className="p-6 space-y-4">
|
||||
<h2 className="font-semibold text-gray-900">Profile Information</h2>
|
||||
<Input label="Email" value={user?.email || ''} disabled />
|
||||
<Input label="Company Name" value={user?.company_name || ''} disabled hint="Contact support to change company name" />
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1.5">Plan</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`px-3 py-1 text-sm font-medium rounded-full capitalize ${getPlanColor(user?.plan || 'free')}`}>
|
||||
{user?.plan || 'free'}
|
||||
</span>
|
||||
<Link to="/pricing" className="text-sm text-primary-600 hover:underline">
|
||||
Manage plan
|
||||
</Link>
|
||||
<Link to="/pricing" className="text-sm text-primary-600 hover:underline">
|
||||
Manage plan
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -101,59 +127,59 @@ const BillingSettings: React.FC<{ onToast: (msg: string) => void }> = ({ onToast
|
||||
const isPaid = subscription?.plan && subscription.plan !== 'free'
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Card className="p-6">
|
||||
<h2 className="font-semibold text-gray-900 mb-4">Current Plan</h2>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<div className="space-y-4">
|
||||
<Card className="p-6">
|
||||
<h2 className="font-semibold text-gray-900 mb-4">Current Plan</h2>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<span className={`px-3 py-1 text-sm font-semibold rounded-full capitalize ${getPlanColor(subscription?.plan || 'free')}`}>
|
||||
{subscription?.plan || 'free'}
|
||||
</span>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
Status: <span className={subscription?.status === 'active' ? 'text-green-600' : 'text-red-600'}>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
Status: <span className={subscription?.status === 'active' ? 'text-green-600' : 'text-red-600'}>
|
||||
{subscription?.status || 'active'}
|
||||
</span>
|
||||
</p>
|
||||
</p>
|
||||
</div>
|
||||
{isPaid && subscription?.current_period_end && (
|
||||
<div className="text-right">
|
||||
<p className="text-xs text-gray-500">Renews on</p>
|
||||
<p className="text-sm font-medium">{formatDate(subscription.current_period_end)}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isPaid && subscription?.current_period_end && (
|
||||
<div className="text-right">
|
||||
<p className="text-xs text-gray-500">Renews on</p>
|
||||
<p className="text-sm font-medium">{formatDate(subscription.current_period_end)}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
{!isPaid ? (
|
||||
<Button onClick={() => navigate('/pricing')} className="flex-1">
|
||||
Upgrade Plan
|
||||
</Button>
|
||||
) : (
|
||||
<Button variant="outline" onClick={handlePortal} loading={loading} className="flex-1">
|
||||
<ExternalLink className="w-3.5 h-3.5" />
|
||||
Manage Billing
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
<div className="flex gap-3">
|
||||
{!isPaid ? (
|
||||
<Button onClick={() => navigate('/pricing')} className="flex-1">
|
||||
Upgrade Plan
|
||||
</Button>
|
||||
) : (
|
||||
<Button variant="outline" onClick={handlePortal} loading={loading} className="flex-1">
|
||||
<ExternalLink className="w-3.5 h-3.5" />
|
||||
Manage Billing
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Plan features */}
|
||||
<Card className="p-6">
|
||||
<h3 className="font-semibold text-gray-900 mb-4">Plan Features</h3>
|
||||
<div className="space-y-3">
|
||||
{[
|
||||
{ label: 'Chatbots created', value: 'Unlimited', always: true },
|
||||
{ label: 'Chatbots published', value: subscription?.plan === 'pro' ? '3' : subscription?.plan === 'starter' ? '1' : '0 (upgrade to publish)', always: true },
|
||||
{ label: 'Conversations/month', value: subscription?.plan === 'pro' ? '20,000' : subscription?.plan === 'starter' ? '5,000' : 'Unlimited (preview)', always: true },
|
||||
{ label: 'Code export', value: subscription?.plan === 'pro' || subscription?.plan === 'enterprise' ? '✅ Included' : '❌ Pro+ only', always: true },
|
||||
].map(({ label, value }) => (
|
||||
<div key={label} className="flex justify-between py-2 border-b border-gray-100 last:border-0">
|
||||
<span className="text-sm text-gray-600">{label}</span>
|
||||
<span className="text-sm font-medium text-gray-900">{value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
{/* Plan features */}
|
||||
<Card className="p-6">
|
||||
<h3 className="font-semibold text-gray-900 mb-4">Plan Features</h3>
|
||||
<div className="space-y-3">
|
||||
{[
|
||||
{ label: 'Chatbots created', value: 'Unlimited' },
|
||||
{ label: 'Chatbots published', value: subscription?.plan === 'pro' ? '3' : subscription?.plan === 'starter' ? '1' : '0 (upgrade to publish)' },
|
||||
{ label: 'Conversations/month', value: subscription?.plan === 'pro' ? '20,000' : subscription?.plan === 'starter' ? '5,000' : 'Unlimited (preview)' },
|
||||
{ label: 'Code export', value: subscription?.plan === 'pro' || subscription?.plan === 'enterprise' ? '✅ Included' : '❌ Pro+ only' },
|
||||
].map(({ label, value }) => (
|
||||
<div key={label} className="flex justify-between py-2 border-b border-gray-100 last:border-0">
|
||||
<span className="text-sm text-gray-600">{label}</span>
|
||||
<span className="text-sm font-medium text-gray-900">{value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user