"use client"; import { useState, useEffect } from "react"; import { Card, Button, Badge, Toggle, Input } from "@/shared/components"; import { useTheme } from "@/shared/hooks/useTheme"; import { cn } from "@/shared/utils/cn"; import { APP_CONFIG } from "@/shared/constants/config"; export default function ProfilePage() { const { theme, setTheme, isDark } = useTheme(); const [settings, setSettings] = useState({ fallbackStrategy: "fill-first" }); const [loading, setLoading] = useState(true); const [passwords, setPasswords] = useState({ current: "", new: "", confirm: "" }); const [passStatus, setPassStatus] = useState({ type: "", message: "" }); const [passLoading, setPassLoading] = useState(false); useEffect(() => { fetch("/api/settings") .then((res) => res.json()) .then((data) => { setSettings(data); setLoading(false); }) .catch((err) => { console.error("Failed to fetch settings:", err); setLoading(false); }); }, []); const handlePasswordChange = async (e) => { e.preventDefault(); if (passwords.new !== passwords.confirm) { setPassStatus({ type: "error", message: "Passwords do not match" }); return; } setPassLoading(true); setPassStatus({ type: "", message: "" }); try { const res = await fetch("/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ currentPassword: passwords.current, newPassword: passwords.new, }), }); const data = await res.json(); if (res.ok) { setPassStatus({ type: "success", message: "Password updated successfully" }); setPasswords({ current: "", new: "", confirm: "" }); } else { setPassStatus({ type: "error", message: data.error || "Failed to update password" }); } } catch (err) { setPassStatus({ type: "error", message: "An error occurred" }); } finally { setPassLoading(false); } }; const updateFallbackStrategy = async (strategy) => { try { const res = await fetch("/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ fallbackStrategy: strategy }), }); if (res.ok) { setSettings(prev => ({ ...prev, fallbackStrategy: strategy })); } } catch (err) { console.error("Failed to update settings:", err); } }; const updateStickyLimit = async (limit) => { const numLimit = parseInt(limit); if (isNaN(numLimit) || numLimit < 1) return; try { const res = await fetch("/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ stickyRoundRobinLimit: numLimit }), }); if (res.ok) { setSettings(prev => ({ ...prev, stickyRoundRobinLimit: numLimit })); } } catch (err) { console.error("Failed to update sticky limit:", err); } }; const updateRequireLogin = async (requireLogin) => { try { const res = await fetch("/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ requireLogin }), }); if (res.ok) { setSettings(prev => ({ ...prev, requireLogin })); } } catch (err) { console.error("Failed to update require login:", err); } }; return (
Running on your machine
All data is stored locally in the ~/.9router/db.json file.
Require login
When ON, dashboard requires password. When OFF, access without login.
Round Robin
Cycle through accounts to distribute load
Sticky Limit
Calls per account before switching
{settings.fallbackStrategy === "round-robin" ? `Currently distributing requests across all available accounts with ${settings.stickyRoundRobinLimit || 3} calls per account.` : "Currently using accounts in priority order (Fill First)."}
Dark Mode
Switch between light and dark themes
Database Location
~/.9router/db.json
{APP_CONFIG.name} v{APP_CONFIG.version}
Local Mode - All data stored on your machine