On load, a signed-out visitor first asks the backend to sign them in as the user an authenticating proxy vouches for, before guest mode or a login redirect takes over; an explicit logout skips this for the rest of the tab. Settings gains a password card for signed-in users, where a proxy-signed-in user can set a password without the current one to log in without the proxy.
137 lines
5.1 KiB
TypeScript
137 lines
5.1 KiB
TypeScript
import { useState } from 'react';
|
|
import { Settings } from 'lucide-react';
|
|
import { AnimatePresence } from 'framer-motion';
|
|
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
import { AccountPasswordCard } from '@/components/settings/AccountPasswordCard';
|
|
import { useAuthStore } from '@/store/auth';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import { ruffleApi } from '@/lib/api';
|
|
import { usePublicSettings } from '@/hooks/usePublicSettings';
|
|
import { APP_VERSION } from '@/lib/version';
|
|
import { FormattedDate } from '@/components/common/FormattedDate';
|
|
|
|
import { GeneralSettingsTab } from '@/components/settings/GeneralSettingsTab';
|
|
import { AppSettingsTab } from '@/components/settings/AppSettingsTab';
|
|
import { UpdateSettingsTab } from '@/components/settings/UpdateSettingsTab';
|
|
import { FeaturesSettingsTab } from '@/components/settings/FeaturesSettingsTab';
|
|
|
|
const tabContentVariants = {
|
|
initial: {
|
|
opacity: 0,
|
|
y: 10,
|
|
},
|
|
animate: {
|
|
opacity: 1,
|
|
y: 0,
|
|
transition: {
|
|
duration: 0.25,
|
|
ease: 'easeOut' as const,
|
|
},
|
|
},
|
|
exit: {
|
|
opacity: 0,
|
|
y: -10,
|
|
transition: {
|
|
duration: 0.2,
|
|
ease: 'easeIn' as const,
|
|
},
|
|
},
|
|
};
|
|
|
|
export function SettingsView() {
|
|
const [activeTab, setActiveTab] = useState('general');
|
|
const { user } = useAuthStore();
|
|
const isAdmin = user?.permissions.includes('settings.update') ?? false;
|
|
|
|
const { data: ruffleVersion, isLoading: isLoadingRuffleVersion } = useQuery({
|
|
queryKey: ['ruffleVersion'],
|
|
queryFn: () => ruffleApi.getVersion(),
|
|
staleTime: 5 * 60 * 1000,
|
|
enabled: !isAdmin, // Only fetch for non-admin users (admin users get it from GeneralSettingsTab)
|
|
});
|
|
|
|
const { data: publicSettings } = usePublicSettings();
|
|
|
|
const flashpointVersion = publicSettings?.metadata?.flashpointVersion ?? 'Unknown';
|
|
const packagedAt = publicSettings?.metadata?.flashpointPackagedAt;
|
|
const lastUpdate = publicSettings?.metadata?.flashpointLastUpdate;
|
|
|
|
return (
|
|
<div className="max-w-5xl mx-auto space-y-6">
|
|
<div className="flex items-center gap-3">
|
|
<Settings size={32} className="text-primary" />
|
|
<h1 className="text-3xl font-bold">Settings</h1>
|
|
</div>
|
|
|
|
<AccountPasswordCard />
|
|
|
|
{isAdmin ? (
|
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
|
<TabsList className="grid w-full grid-cols-4 border-2 border-primary/60 h-full">
|
|
<TabsTrigger value="general">General</TabsTrigger>
|
|
<TabsTrigger value="app">App</TabsTrigger>
|
|
<TabsTrigger value="update">Update</TabsTrigger>
|
|
<TabsTrigger value="features">Features</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<div className="mt-6">
|
|
<AnimatePresence mode="wait">
|
|
{activeTab === 'general' ? (
|
|
<GeneralSettingsTab tabContentVariants={tabContentVariants} />
|
|
) : null}
|
|
|
|
{activeTab === 'app' ? (
|
|
<AppSettingsTab tabContentVariants={tabContentVariants} />
|
|
) : null}
|
|
|
|
{activeTab === 'update' ? (
|
|
<UpdateSettingsTab tabContentVariants={tabContentVariants} />
|
|
) : null}
|
|
|
|
{activeTab === 'features' ? (
|
|
<FeaturesSettingsTab tabContentVariants={tabContentVariants} />
|
|
) : null}
|
|
</AnimatePresence>
|
|
</div>
|
|
</Tabs>
|
|
) : (
|
|
<div className="space-y-6">
|
|
<div className="bg-card rounded-lg p-6 border border-border shadow-md">
|
|
<h2 className="text-xl font-semibold mb-4">Version Information</h2>
|
|
<div className="space-y-2 text-sm">
|
|
<div className="flex justify-between">
|
|
<span className="text-muted-foreground">Flashpoint Version:</span>
|
|
<span className="font-medium">{flashpointVersion}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-muted-foreground">Package Date:</span>
|
|
<span className="font-medium">
|
|
{packagedAt ? <FormattedDate date={packagedAt} type="date" /> : 'Unknown'}
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-muted-foreground">Last Update:</span>
|
|
<span className="font-medium">
|
|
{lastUpdate ? <FormattedDate date={lastUpdate} type="datetime" /> : 'Never'}
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-muted-foreground">Web App Version:</span>
|
|
<span className="font-medium">{APP_VERSION}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-muted-foreground">Ruffle Emulator:</span>
|
|
<span className="font-medium">
|
|
{isLoadingRuffleVersion
|
|
? 'Loading...'
|
|
: ruffleVersion?.currentVersion || 'Not installed'}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|