fixed bugs

This commit is contained in:
belviskhoremk
2026-02-22 23:25:10 +00:00
parent 53279e8fe1
commit f5d1bfb49d
10 changed files with 1073 additions and 834 deletions

View File

@@ -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">&times;</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>
)
}
}