import { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; import Layout from '../components/Layout'; import { settingsApi, profileApi, adminApi, NotificationSettings, UserProfile, SystemSettings, } from '../api/client'; type SettingsSection = 'profile' | 'notifications' | 'admin'; export default function Settings() { const [activeSection, setActiveSection] = useState('profile'); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(''); const [success, setSuccess] = useState(''); // Profile state const [profile, setProfile] = useState(null); const [profileName, setProfileName] = useState(''); const [currentPassword, setCurrentPassword] = useState(''); const [newPassword, setNewPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [isSavingProfile, setIsSavingProfile] = useState(false); const [isChangingPassword, setIsChangingPassword] = useState(false); // Notification state const [notificationSettings, setNotificationSettings] = useState(null); const [telegramBotToken, setTelegramBotToken] = useState(''); const [telegramChatId, setTelegramChatId] = useState(''); const [discordWebhookUrl, setDiscordWebhookUrl] = useState(''); const [isSavingNotifications, setIsSavingNotifications] = useState(false); const [isTesting, setIsTesting] = useState<'telegram' | 'discord' | null>(null); // Admin state const [users, setUsers] = useState([]); const [systemSettings, setSystemSettings] = useState(null); const [isLoadingAdmin, setIsLoadingAdmin] = useState(false); const [isSavingAdmin, setIsSavingAdmin] = useState(false); const [showAddUser, setShowAddUser] = useState(false); const [newUserEmail, setNewUserEmail] = useState(''); const [newUserPassword, setNewUserPassword] = useState(''); const [newUserRole, setNewUserRole] = useState<'user' | 'admin'>('user'); const [isCreatingUser, setIsCreatingUser] = useState(false); useEffect(() => { fetchInitialData(); }, []); const fetchInitialData = async () => { try { const [profileRes, notificationsRes] = await Promise.all([ profileApi.get(), settingsApi.getNotifications(), ]); setProfile(profileRes.data); setProfileName(profileRes.data.name || ''); setNotificationSettings(notificationsRes.data); if (notificationsRes.data.telegram_chat_id) { setTelegramChatId(notificationsRes.data.telegram_chat_id); } } catch { setError('Failed to load settings'); } finally { setIsLoading(false); } }; const fetchAdminData = async () => { if (!profile?.is_admin) return; setIsLoadingAdmin(true); try { const [usersRes, settingsRes] = await Promise.all([ adminApi.getUsers(), adminApi.getSettings(), ]); setUsers(usersRes.data); setSystemSettings(settingsRes.data); } catch { setError('Failed to load admin data'); } finally { setIsLoadingAdmin(false); } }; useEffect(() => { if (activeSection === 'admin' && profile?.is_admin && users.length === 0) { fetchAdminData(); } }, [activeSection, profile]); const clearMessages = () => { setError(''); setSuccess(''); }; // Profile handlers const handleSaveProfile = async () => { clearMessages(); setIsSavingProfile(true); try { const response = await profileApi.update({ name: profileName }); setProfile(response.data); setSuccess('Profile updated successfully'); } catch { setError('Failed to update profile'); } finally { setIsSavingProfile(false); } }; const handleChangePassword = async () => { clearMessages(); if (newPassword !== confirmPassword) { setError('New passwords do not match'); return; } if (newPassword.length < 6) { setError('Password must be at least 6 characters'); return; } setIsChangingPassword(true); try { await profileApi.changePassword(currentPassword, newPassword); setCurrentPassword(''); setNewPassword(''); setConfirmPassword(''); setSuccess('Password changed successfully'); } catch (err: unknown) { const error = err as { response?: { data?: { error?: string } } }; setError(error.response?.data?.error || 'Failed to change password'); } finally { setIsChangingPassword(false); } }; // Notification handlers const handleSaveTelegram = async () => { clearMessages(); setIsSavingNotifications(true); try { const response = await settingsApi.updateNotifications({ telegram_bot_token: telegramBotToken || null, telegram_chat_id: telegramChatId || null, }); setNotificationSettings(response.data); setTelegramBotToken(''); setSuccess('Telegram settings saved successfully'); } catch { setError('Failed to save Telegram settings'); } finally { setIsSavingNotifications(false); } }; const handleSaveDiscord = async () => { clearMessages(); setIsSavingNotifications(true); try { const response = await settingsApi.updateNotifications({ discord_webhook_url: discordWebhookUrl || null, }); setNotificationSettings(response.data); setDiscordWebhookUrl(''); setSuccess('Discord settings saved successfully'); } catch { setError('Failed to save Discord settings'); } finally { setIsSavingNotifications(false); } }; const handleTestTelegram = async () => { clearMessages(); setIsTesting('telegram'); try { await settingsApi.testTelegram(); setSuccess('Test notification sent to Telegram!'); } catch { setError('Failed to send test notification'); } finally { setIsTesting(null); } }; const handleTestDiscord = async () => { clearMessages(); setIsTesting('discord'); try { await settingsApi.testDiscord(); setSuccess('Test notification sent to Discord!'); } catch { setError('Failed to send test notification'); } finally { setIsTesting(null); } }; // Admin handlers const handleToggleRegistration = async () => { clearMessages(); setIsSavingAdmin(true); try { const newValue = systemSettings?.registration_enabled !== 'true'; const response = await adminApi.updateSettings({ registration_enabled: newValue }); setSystemSettings(response.data); setSuccess(`Registration ${newValue ? 'enabled' : 'disabled'}`); } catch { setError('Failed to update settings'); } finally { setIsSavingAdmin(false); } }; const handleCreateUser = async () => { clearMessages(); if (!newUserEmail || !newUserPassword) { setError('Email and password are required'); return; } if (newUserPassword.length < 8) { setError('Password must be at least 8 characters'); return; } setIsCreatingUser(true); try { await adminApi.createUser(newUserEmail, newUserPassword, newUserRole === 'admin'); // Refresh users list const usersRes = await adminApi.getUsers(); setUsers(usersRes.data); setNewUserEmail(''); setNewUserPassword(''); setNewUserRole('user'); setShowAddUser(false); setSuccess('User created successfully'); } catch (err: unknown) { const error = err as { response?: { data?: { error?: string } } }; setError(error.response?.data?.error || 'Failed to create user'); } finally { setIsCreatingUser(false); } }; const handleDeleteUser = async (userId: number) => { if (!confirm('Are you sure you want to delete this user? All their data will be lost.')) { return; } clearMessages(); try { await adminApi.deleteUser(userId); setUsers(users.filter(u => u.id !== userId)); setSuccess('User deleted successfully'); } catch { setError('Failed to delete user'); } }; const handleRoleChange = async (userId: number, newRole: 'user' | 'admin') => { clearMessages(); const isAdmin = newRole === 'admin'; try { await adminApi.setUserAdmin(userId, isAdmin); setUsers(users.map(u => u.id === userId ? { ...u, is_admin: isAdmin } : u)); setSuccess(`User role updated to ${newRole}`); } catch { setError('Failed to update user role'); } }; if (isLoading) { return (
); } return (
← Back to Dashboard

Settings

{error &&
{error}
} {success &&
{success}
}
{activeSection === 'profile' && ( <>
👤

Profile Information

Update your display name and email preferences.

Email cannot be changed

setProfileName(e.target.value)} placeholder="Enter your name" />
🔒

Change Password

Update your password to keep your account secure.

setCurrentPassword(e.target.value)} placeholder="Enter current password" />
setNewPassword(e.target.value)} placeholder="Enter new password" />
setConfirmPassword(e.target.value)} placeholder="Confirm new password" />
)} {activeSection === 'notifications' && ( <>
📱

Telegram Notifications

{notificationSettings?.telegram_configured ? 'Configured' : 'Not configured'}

Receive price drop and back-in-stock alerts via Telegram. You'll need to create a Telegram bot and get your chat ID.

setTelegramBotToken(e.target.value)} placeholder={notificationSettings?.telegram_configured ? '••••••••••••••••' : 'Enter your bot token'} />

Create a bot via @BotFather on Telegram to get a token

setTelegramChatId(e.target.value)} placeholder="Enter your chat ID" />

Send /start to @userinfobot to get your chat ID

{notificationSettings?.telegram_configured && ( )}
💬

Discord Notifications

{notificationSettings?.discord_configured ? 'Configured' : 'Not configured'}

Receive price drop and back-in-stock alerts in a Discord channel. Create a webhook in your Discord server settings.

setDiscordWebhookUrl(e.target.value)} placeholder={notificationSettings?.discord_configured ? '••••••••••••••••' : 'https://discord.com/api/webhooks/...'} />

Server Settings → Integrations → Webhooks → New Webhook

{notificationSettings?.discord_configured && ( )}
)} {activeSection === 'admin' && profile?.is_admin && ( <>
⚙️

System Settings

Configure system-wide settings for PriceGhost.

User Registration Allow new users to register accounts
👥

User Management

Manage user accounts and permissions.

{!showAddUser ? ( ) : (

Add New User

setNewUserEmail(e.target.value)} placeholder="user@example.com" />
setNewUserPassword(e.target.value)} placeholder="Minimum 8 characters" />
)} {isLoadingAdmin ? (
) : ( {users.map((user) => ( ))}
Email Name Role Joined Actions
{user.email} {user.name || '-'} {user.id === profile?.id ? ( Admin (You) ) : ( )} {new Date(user.created_at).toLocaleDateString()} {user.id !== profile?.id && ( )}
)}
)}
); }