Please consider reporting this error to our
@@ -60,14 +67,14 @@ export const ErrorPage = ({ error, resetErrorBoundary }: FallbackProps) => {
>
-
{error.toString()}
+
{errorLine}
{summary ? (
@@ -83,7 +90,7 @@ export const ErrorPage = ({ error, resetErrorBoundary }: FallbackProps) => {
className="text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-3 py-1.5 mr-2 text-center inline-flex items-center dark:bg-red-800 dark:hover:bg-red-900"
onClick={(event) => {
event.preventDefault();
- resetErrorBoundary();
+ reset();
}}
>
diff --git a/web/src/components/header/Header.tsx b/web/src/components/header/Header.tsx
index 34b9d6a..7036fcb 100644
--- a/web/src/components/header/Header.tsx
+++ b/web/src/components/header/Header.tsx
@@ -16,13 +16,11 @@ import { LeftNav } from "./LeftNav";
import { RightNav } from "./RightNav";
import { MobileNav } from "./MobileNav";
import { ExternalLink } from "@components/ExternalLink";
-
-import { AuthIndexRoute } from "@app/routes";
import { ConfigQueryOptions, UpdatesQueryOptions } from "@api/queries";
+import { AuthContext } from "@utils/Context";
export const Header = () => {
const router = useRouter()
- const { auth } = AuthIndexRoute.useRouteContext()
const { isError:isConfigError, error: configError, data: config } = useQuery(ConfigQueryOptions(true));
if (isConfigError) {
@@ -40,9 +38,8 @@ export const Header = () => {
toast.custom((t) => (
));
- auth.logout()
-
- router.history.push("/")
+ AuthContext.reset();
+ router.history.push("/");
},
onError: (err) => {
console.error("logout error", err)
@@ -60,7 +57,7 @@ export const Header = () => {
-
+
{/* Mobile menu button */}
@@ -92,7 +89,7 @@ export const Header = () => {
)}
-
+
>
)}
diff --git a/web/src/components/header/RightNav.tsx b/web/src/components/header/RightNav.tsx
index 32a4fdb..0f3bccb 100644
--- a/web/src/components/header/RightNav.tsx
+++ b/web/src/components/header/RightNav.tsx
@@ -13,7 +13,7 @@ import { RightNavProps } from "./_shared";
import { Cog6ToothIcon, ArrowLeftOnRectangleIcon, MoonIcon, SunIcon } from "@heroicons/react/24/outline";
import { Link } from "@tanstack/react-router";
-import { SettingsContext } from "@utils/Context";
+import { AuthContext, SettingsContext } from "@utils/Context";
export const RightNav = (props: RightNavProps) => {
const [settings, setSettings] = SettingsContext.use();
@@ -56,7 +56,7 @@ export const RightNav = (props: RightNavProps) => {
Open user menu for{" "}
- {props.auth.username}
+ {AuthContext.get().username}
void;
- auth: AuthCtx
}
export const NAV_ROUTES: Array = [
diff --git a/web/src/routes.tsx b/web/src/routes.tsx
index 8c10ed9..4e141fa 100644
--- a/web/src/routes.tsx
+++ b/web/src/routes.tsx
@@ -7,11 +7,11 @@ import {
createRootRouteWithContext,
createRoute,
createRouter,
- ErrorComponent,
+ Navigate,
notFound,
Outlet,
redirect,
-} from "@tanstack/react-router";
+ } from "@tanstack/react-router";
import { z } from "zod";
import { QueryClient } from "@tanstack/react-query";
@@ -46,11 +46,13 @@ import DownloadClientSettings from "@screens/settings/DownloadClient";
import FeedSettings from "@screens/settings/Feed";
import { Dashboard } from "@screens/Dashboard";
import AccountSettings from "@screens/settings/Account";
-import { AuthContext, AuthCtx, localStorageUserKey, SettingsContext } from "@utils/Context";
+import { AuthContext, SettingsContext } from "@utils/Context";
import { TanStackRouterDevtools } from "@tanstack/router-devtools";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { queryClient } from "@api/QueryClient";
+import { ErrorPage } from "@components/alerts";
+
const DashboardRoute = createRoute({
getParentRoute: () => AuthIndexRoute,
path: '/',
@@ -133,16 +135,9 @@ export const FilterActionsRoute = createRoute({
component: Actions
});
-const ReleasesRoute = createRoute({
+export const ReleasesRoute = createRoute({
getParentRoute: () => AuthIndexRoute,
- path: 'releases'
-});
-
-// type ReleasesSearch = z.infer
-
-export const ReleasesIndexRoute = createRoute({
- getParentRoute: () => ReleasesRoute,
- path: '/',
+ path: 'releases',
component: Releases,
validateSearch: (search) => z.object({
offset: z.number().optional(),
@@ -260,7 +255,7 @@ export const LoginRoute = createRoute({
validateSearch: z.object({
redirect: z.string().optional(),
}),
- beforeLoad: ({ navigate}) => {
+ beforeLoad: ({ navigate }) => {
// handle canOnboard
APIClient.auth.canOnboard().then(() => {
console.info("onboarding available, redirecting")
@@ -277,44 +272,36 @@ export const AuthRoute = createRoute({
id: 'auth',
// Before loading, authenticate the user via our auth context
// This will also happen during prefetching (e.g. hovering over links, etc.)
- beforeLoad: ({context, location}) => {
+ beforeLoad: ({ context, location }) => {
// If the user is not logged in, check for item in localStorage
- if (!context.auth.isLoggedIn) {
- const storage = localStorage.getItem(localStorageUserKey);
- if (storage) {
- try {
- const json = JSON.parse(storage);
- if (json === null) {
- console.warn(`JSON localStorage value for '${localStorageUserKey}' context state is null`);
- } else {
- context.auth.isLoggedIn = json.isLoggedIn
- context.auth.username = json.username
- }
- } catch (e) {
- console.error(`auth Failed to merge ${localStorageUserKey} context state: ${e}`);
- }
- } else {
- // If the user is logged out, redirect them to the login page
- throw redirect({
- to: LoginRoute.to,
- search: {
- // Use the current location to power a redirect after login
- // (Do not use `router.state.resolvedLocation` as it can
- // potentially lag behind the actual current location)
- redirect: location.href,
- },
- })
- }
+ if (!AuthContext.get().isLoggedIn) {
+ throw redirect({
+ to: LoginRoute.to,
+ search: {
+ // Use the current location to power a redirect after login
+ // (Do not use `router.state.resolvedLocation` as it can
+ // potentially lag behind the actual current location)
+ redirect: location.href,
+ },
+ });
}
// Otherwise, return the user in context
- return {
- username: AuthContext.username,
- }
+ return context;
},
})
function AuthenticatedLayout() {
+ const isLoggedIn = AuthContext.useSelector((s) => s.isLoggedIn);
+ if (!isLoggedIn) {
+ const redirect = (
+ location.pathname.length > 1
+ ? { redirect: location.pathname }
+ : undefined
+ );
+ return ;
+ }
+
return (
@@ -345,7 +332,6 @@ export const RootComponent = () => {
}
export const RootRoute = createRootRouteWithContext<{
- auth: AuthCtx,
queryClient: QueryClient
}>()({
component: RootComponent,
@@ -354,7 +340,7 @@ export const RootRoute = createRootRouteWithContext<{
const filterRouteTree = FiltersRoute.addChildren([FilterIndexRoute, FilterGetByIdRoute.addChildren([FilterGeneralRoute, FilterMoviesTvRoute, FilterMusicRoute, FilterAdvancedRoute, FilterExternalRoute, FilterActionsRoute])])
const settingsRouteTree = SettingsRoute.addChildren([SettingsIndexRoute, SettingsLogRoute, SettingsIndexersRoute, SettingsIrcRoute, SettingsFeedsRoute, SettingsClientsRoute, SettingsNotificationsRoute, SettingsApiRoute, SettingsReleasesRoute, SettingsAccountRoute])
-const authenticatedTree = AuthRoute.addChildren([AuthIndexRoute.addChildren([DashboardRoute, filterRouteTree, ReleasesRoute.addChildren([ReleasesIndexRoute]), settingsRouteTree, LogsRoute])])
+const authenticatedTree = AuthRoute.addChildren([AuthIndexRoute.addChildren([DashboardRoute, filterRouteTree, ReleasesRoute, settingsRouteTree, LogsRoute])])
const routeTree = RootRoute.addChildren([
authenticatedTree,
LoginRoute,
@@ -368,9 +354,10 @@ export const Router = createRouter({
),
- defaultErrorComponent: ({error}) => ,
+ defaultErrorComponent: (ctx) => (
+
+ ),
context: {
- auth: undefined!, // We'll inject this when we render
queryClient
},
});
diff --git a/web/src/screens/Settings.tsx b/web/src/screens/Settings.tsx
index 3542130..4252c7c 100644
--- a/web/src/screens/Settings.tsx
+++ b/web/src/screens/Settings.tsx
@@ -26,16 +26,16 @@ interface NavTabType {
}
const subNavigation: NavTabType[] = [
- { name: "Application", href: ".", icon: CogIcon, exact: true },
- { name: "Logs", href: "logs", icon: Square3Stack3DIcon },
- { name: "Indexers", href: "indexers", icon: KeyIcon },
- { name: "IRC", href: "irc", icon: ChatBubbleLeftRightIcon },
- { name: "Feeds", href: "feeds", icon: RssIcon },
- { name: "Clients", href: "clients", icon: FolderArrowDownIcon },
- { name: "Notifications", href: "notifications", icon: BellIcon },
- { name: "API keys", href: "api", icon: KeyIcon },
- { name: "Releases", href: "releases", icon: RectangleStackIcon },
- { name: "Account", href: "account", icon: UserCircleIcon }
+ { name: "Application", href: "/settings", icon: CogIcon, exact: true },
+ { name: "Logs", href: "/settings/logs", icon: Square3Stack3DIcon },
+ { name: "Indexers", href: "/settings/indexers", icon: KeyIcon },
+ { name: "IRC", href: "/settings/irc", icon: ChatBubbleLeftRightIcon },
+ { name: "Feeds", href: "/settings/feeds", icon: RssIcon },
+ { name: "Clients", href: "/settings/clients", icon: FolderArrowDownIcon },
+ { name: "Notifications", href: "/settings/notifications", icon: BellIcon },
+ { name: "API keys", href: "/settings/api", icon: KeyIcon },
+ { name: "Releases", href: "/settings/releases", icon: RectangleStackIcon },
+ { name: "Account", href: "/settings/account", icon: UserCircleIcon }
// {name: 'Regex Playground', href: 'regex-playground', icon: CogIcon, current: false}
// {name: 'Rules', href: 'rules', icon: ClipboardCheckIcon, current: false},
];
diff --git a/web/src/screens/auth/Login.tsx b/web/src/screens/auth/Login.tsx
index cc85893..e0cc5de 100644
--- a/web/src/screens/auth/Login.tsx
+++ b/web/src/screens/auth/Login.tsx
@@ -5,7 +5,7 @@
import React, { useEffect } from "react";
import { useForm } from "react-hook-form";
-import { useMutation } from "@tanstack/react-query";
+import { useMutation, useQueryErrorResetBoundary } from "@tanstack/react-query";
import { useRouter, useSearch } from "@tanstack/react-router";
import toast from "react-hot-toast";
@@ -18,15 +18,20 @@ import { PasswordInput, TextInput } from "@components/inputs/text";
import { LoginRoute } from "@app/routes";
import Logo from "@app/logo.svg?react";
+import { AuthContext } from "@utils/Context";
+// import { WarningAlert } from "@components/alerts";
type LoginFormFields = {
username: string;
password: string;
};
-export const Login = () => {
+export const Login = () => {
+ const [auth, setAuth] = AuthContext.use();
+
+ const queryErrorResetBoundary = useQueryErrorResetBoundary()
+
const router = useRouter()
- const { auth } = LoginRoute.useRouteContext()
const search = useSearch({ from: LoginRoute.id })
const { handleSubmit, register, formState } = useForm({
@@ -35,14 +40,19 @@ export const Login = () => {
});
useEffect(() => {
+ queryErrorResetBoundary.reset()
// remove user session when visiting login page
- auth.logout()
- }, []);
+ AuthContext.reset();
+ }, [queryErrorResetBoundary]);
const loginMutation = useMutation({
mutationFn: (data: LoginFormFields) => APIClient.auth.login(data.username, data.password),
onSuccess: (_, variables: LoginFormFields) => {
- auth.login(variables.username)
+ queryErrorResetBoundary.reset()
+ setAuth({
+ isLoggedIn: true,
+ username: variables.username
+ });
router.invalidate()
},
onError: (error) => {
@@ -60,7 +70,7 @@ export const Login = () => {
} else if (auth.isLoggedIn) {
router.history.push("/")
}
- }, [auth.isLoggedIn, search.redirect])
+ }, [auth.isLoggedIn, search.redirect]) // eslint-disable-line react-hooks/exhaustive-deps
return (
diff --git a/web/src/screens/auth/Onboarding.tsx b/web/src/screens/auth/Onboarding.tsx
index 0978174..49f63bd 100644
--- a/web/src/screens/auth/Onboarding.tsx
+++ b/web/src/screens/auth/Onboarding.tsx
@@ -43,7 +43,7 @@ export const Onboarding = () => {
const mutation = useMutation({
mutationFn: (data: InputValues) => APIClient.auth.onboard(data.username, data.password1),
- onSuccess: () => navigate({ to: "/" })
+ onSuccess: () => navigate({ to: "/login" })
});
return (
diff --git a/web/src/screens/filters/Details.tsx b/web/src/screens/filters/Details.tsx
index d71aa4a..16baf9a 100644
--- a/web/src/screens/filters/Details.tsx
+++ b/web/src/screens/filters/Details.tsx
@@ -33,12 +33,12 @@ interface tabType {
}
const tabs: tabType[] = [
- { name: "General", href: ".", exact: true },
- { name: "Movies and TV", href: "movies-tv" },
- { name: "Music", href: "music" },
- { name: "Advanced", href: "advanced" },
- { name: "External", href: "external" },
- { name: "Actions", href: "actions" }
+ { name: "General", href: "/filters/$filterId", exact: true },
+ { name: "Movies and TV", href: "/filters/$filterId/movies-tv" },
+ { name: "Music", href: "/filters/$filterId/music" },
+ { name: "Advanced", href: "/filters/$filterId/advanced" },
+ { name: "External", href: "/filters/$filterId/external" },
+ { name: "Actions", href: "/filters/$filterId/actions" }
];
export interface NavLinkProps {
diff --git a/web/src/screens/releases/ReleaseTable.tsx b/web/src/screens/releases/ReleaseTable.tsx
index db482d3..9febb46 100644
--- a/web/src/screens/releases/ReleaseTable.tsx
+++ b/web/src/screens/releases/ReleaseTable.tsx
@@ -16,7 +16,7 @@ import {
EyeSlashIcon
} from "@heroicons/react/24/solid";
-import { ReleasesIndexRoute } from "@app/routes";
+import { ReleasesRoute } from "@app/routes";
import { ReleasesListQueryOptions } from "@api/queries";
import { RandomLinuxIsos } from "@utils";
@@ -94,7 +94,7 @@ const EmptyReleaseList = () => (
);
export const ReleaseTable = () => {
- const search = ReleasesIndexRoute.useSearch()
+ const search = ReleasesRoute.useSearch()
const columns = React.useMemo(() => [
{
diff --git a/web/src/screens/settings/Account.tsx b/web/src/screens/settings/Account.tsx
index 8f5f10b..1e72414 100644
--- a/web/src/screens/settings/Account.tsx
+++ b/web/src/screens/settings/Account.tsx
@@ -8,12 +8,11 @@ import { Form, Formik } from "formik";
import toast from "react-hot-toast";
import { UserIcon } from "@heroicons/react/24/solid";
-import { SettingsAccountRoute } from "@app/routes";
-import { AuthContext } from "@utils/Context";
import { APIClient } from "@api/APIClient";
import { Section } from "./_components";
import { PasswordField, TextField } from "@components/inputs";
import Toast from "@components/notifications/Toast";
+import { AuthContext } from "@utils/Context";
const AccountSettings = () => (
s.username);
const validate = (values: InputValues) => {
const errors: Record = {};
@@ -52,7 +51,7 @@ function Credentials() {
const logoutMutation = useMutation({
mutationFn: APIClient.auth.logout,
onSuccess: () => {
- AuthContext.logout();
+ AuthContext.reset();
toast.custom((t) => (
@@ -78,7 +77,7 @@ function Credentials() {
(
ctxState.set(values);
}
+const AuthKey = "autobrr_user_auth";
const SettingsKey = "autobrr_settings";
const FilterListKey = "autobrr_filter_list";
export const InitializeGlobalContext = () => {
- // ContextMerger(localStorageUserKey, AuthContextDefaults, AuthContextt);
+ ContextMerger(AuthKey, AuthContextDefaults, AuthContext);
ContextMerger(
SettingsKey,
SettingsContextDefaults,
@@ -101,9 +102,12 @@ function DefaultSetter(name: string, newState: T, prevState: T) {
}
}
-// export const AuthContextt = newRidgeState(AuthContextDefaults, {
-// onSet: (newState, prevState) => DefaultSetter(localStorageUserKey, newState, prevState)
-// });
+export const AuthContext = newRidgeState(
+ AuthContextDefaults,
+ {
+ onSet: (newState, prevState) => DefaultSetter(AuthKey, newState, prevState)
+ }
+);
export const SettingsContext = newRidgeState(
SettingsContextDefaults,
@@ -121,29 +125,3 @@ export const FilterListContext = newRidgeState(
onSet: (newState, prevState) => DefaultSetter(FilterListKey, newState, prevState)
}
);
-
-export type AuthCtx = {
- isLoggedIn: boolean
- username?: string
- login: (username: string) => void
- logout: () => void
-}
-
-export const localStorageUserKey = "autobrr_user_auth"
-
-export const AuthContext: AuthCtx = {
- isLoggedIn: false,
- username: undefined,
- login: (username: string) => {
- AuthContext.isLoggedIn = true
- AuthContext.username = username
-
- localStorage.setItem(localStorageUserKey, JSON.stringify(AuthContext));
- },
- logout: () => {
- AuthContext.isLoggedIn = false
- AuthContext.username = undefined
-
- localStorage.removeItem(localStorageUserKey);
- },
-}
\ No newline at end of file