import { FocusContext, setFocus, useFocusable, } from "@noriginmedia/norigin-spatial-navigation"; import { useIsMutating, useMutation, useQuery } from "@tanstack/react-query"; import { createFileRoute } from "@tanstack/react-router"; import classNames from "classnames"; import { Key, Link, Lock, LogOut, Save, ScanQrCode, Trash, User, X } from "lucide-react"; import { useEffect, useRef, } from "react"; import { RPC_URL } from "@shared/constants"; import { getCurrentUserApiUsersMeGetOptions, statsApiStatsGetOptions, } from "@clients/romm/@tanstack/react-query.gen"; import toast from "react-hot-toast"; import z from "zod"; import { OptionSpace } from "../../components/options/OptionSpace"; import { useSettingsForm, useSettingsFormContext } from "../../components/options/SettingsAppForm"; import { rommApi, settingsApi } from "../../scripts/clientApi"; import { Button } from "../../components/options/Button"; import { ContextDialog } from "@/mainview/components/ContextDialog"; import QRCode from "react-qr-code"; import { useJobStatus } from "@/mainview/scripts/utils"; import { useInterval } from "usehooks-ts"; import { TwitchIcon } from "@/mainview/scripts/brandIcons"; export const Route = createFileRoute("/settings/accounts")({ component: RouteComponent, }); function LoginQR (data: { id: string, isOpen: boolean, cancel: () => void, url: string; endsAt: Date; startedAt: Date; code?: string; }) { const progressRef = useRef(null); useInterval(() => { if (progressRef.current) { const time = data.endsAt.getTime() - data.startedAt.getTime(); progressRef.current.value = ((data.endsAt.getTime() - new Date().getTime()) / time) * 100; } }, 1000); return data.cancel()} className="flex flex-col justify-center items-center gap-2"> {!!data.code &&

Code: {data.code}

}
; } function TwitchLogin (data: {}) { const loginStatus = useQuery({ queryKey: ['twitch', 'login', 'status'], retry (failureCount, error) { if (error.status === 404) { return false; } return failureCount < 3; }, queryFn: async () => { const { data, error, status } = await rommApi.api.romm.login.twitch.get(); if (error) throw { ...error, status }; return data; } }); const loginMutation = useMutation({ mutationKey: ['twitch', 'login'], mutationFn: (openInBrowser: boolean) => { return rommApi.api.romm.login.twitch.post({ openInBrowser }); }, onSuccess: () => loginStatus.refetch() }); const logoutMutation = useMutation({ mutationKey: ['twitch', 'logout'], mutationFn: () => { return rommApi.api.romm.logout.twitch.post(); }, onSuccess: () => loginStatus.refetch() }); const { data: loginData, wsRef } = useJobStatus('twitch-login-job', { onEnded: () => loginStatus.refetch() }); return
{loginStatus.isSuccess ?
{loginStatus.data.login}
:
{loginStatus.isError || loginStatus.isRefetchError ? : }
} {loginStatus.isSuccess && } {!!loginData && wsRef.current?.send({ type: 'cancel' })} id='twitch-login-qr' isOpen={true} endsAt={loginData.expires_at} startedAt={loginData.started_at} />}
; } function LoginControls (data: { hasPassword: boolean; }) { const user = useQuery({ ...getCurrentUserApiUsersMeGetOptions(), queryKey: ['romm', 'auth', "login"], refetchOnWindowFocus: false, retry: 0 }); const loginMutation = useMutation({ mutationKey: ['login', 'qr', 'cancel'], mutationFn: () => rommApi.api.romm.login.romm.post() }); const { data: statusValue, error: loginError, wsRef } = useJobStatus('login-job'); const context = useSettingsFormContext({}); const isMutatingRomm = useIsMutating({ mutationKey: ["romm", "auth"] }) > 0; const logoutMutation = useMutation({ mutationKey: ["romm", "auth", "logout"], mutationFn: () => rommApi.api.romm.logout.post(), onSuccess: async (d, v, r, c) => { user.refetch(); c.client.invalidateQueries({ queryKey: ["romm", "auth"] }); } }); return
{user.isSuccess ?

Logged In As:

{user.data?.username}
:
{user.isError ? : }
} {data.hasPassword && } {!!statusValue && { setFocus(`qr-login`); wsRef.current?.send({ type: 'cancel' }); }} url={statusValue?.url ?? ''} />}
; } const dataSchema = z.object({ hostname: z.url(), username: z.string(), password: z.string() }); function RouteComponent () { const { focus } = Route.useSearch(); const { ref, focusKey, focusSelf } = useFocusable({ focusKey: "accounts", preferredChildFocusKey: focus }); const { data: hasPassword } = useQuery({ queryKey: ['romm', 'auth', 'passLength'], queryFn: () => rommApi.api.romm.login.get().then(d => d.data?.hasPassword as boolean) }); const { data: hostname } = useQuery({ queryKey: ['romm', 'auth', 'hostname'], queryFn: () => settingsApi.api.settings({ id: 'rommAddress' }).get().then(d => d.data?.value as string) }); const { data: username } = useQuery({ queryKey: ['romm', 'auth', 'username'], queryFn: () => settingsApi.api.settings({ id: 'rommUser' }).get().then(d => d.data?.value as string) }); const loginForm = useSettingsForm({ defaultValues: { hostname: hostname ?? '', username: username ?? '', password: '' }, onSubmit: async ({ value }) => { await toast.promise(loginMutation.mutateAsync(value), { loading: "Logging In", success: "Logged In", error: e => e?.detail ?? "Error Logging In", }); loginForm.reset(); }, validators: { onChange: dataSchema } }); const rommOnline = useQuery({ ...statsApiStatsGetOptions(), refetchInterval: 30000, retry: false, }); useEffect(() => { if (focus) { focusSelf(); } }, [focus]); const loginMutation = useMutation({ mutationKey: ["romm", "login"], mutationFn: async (data: z.infer) => { const { error } = await rommApi.api.romm.login.post({ username: data.username, password: data.password, host: data.hostname }); if (error) throw error; }, onSuccess: (d, v, r, c) => { c.client.invalidateQueries({ queryKey: ['romm', 'auth'] }); }, onError: (e) => { console.error(e); }, }); let indicator = ""; if (rommOnline.isError) { indicator = "status-error"; } else if (rommOnline.isSuccess) { indicator = "status-success"; } return (

    Romm

    { e.preventDefault(); e.stopPropagation(); loginForm.handleSubmit(); }} onReset={e => { e.preventDefault(); e.stopPropagation(); loginForm.reset(); }} > } type='url' />} /> } type="text" />} /> } type="password" placeholder={hasPassword ? '*****' : "Password"} />} /> } />
    {TwitchIcon}

    Twitch

Twitch Login for IGDB Metadata } id="twitch-login-space" className="justify-end border-0">
); }