feat(web): move from react-router to @tanstack/router (#1338)

* fix(auth): invalid cookie handling and wrongful basic auth invalidation

* fix(auth): fix test to reflect new HTTP status code

* fix(auth/web): do not throw on error

* fix(http): replace http codes in middleware to prevent basic auth invalidation
fix typo in comment

* fix test

* fix(web): api client handle 403

* refactor(http): auth_test use testify.assert

* refactor(http): set session opts after valid login

* refactor(http): send more client headers

* fix(http): test

* refactor(web): move router to tanstack/router

* refactor(web): use route loaders and suspense

* refactor(web): useSuspense for settings

* refactor(web): invalidate cookie in middleware

* fix: loclfile

* fix: load filter/id

* fix(web): login, onboard, types, imports

* fix(web): filter load

* fix(web): build errors

* fix(web): ts-expect-error

* fix(tests): filter_test.go

* fix(filters): tests

* refactor: remove duplicate spinner components
refactor: ReleaseTable.tsx loading animation
refactor: remove dedicated `pendingComponent` for `settingsRoute`

* fix: refactor missed SectionLoader to RingResizeSpinner

* fix: substitute divides with borders to account for unloaded elements

* fix(api): action status URL param

* revert: action status URL param
add comment

* fix(routing): notfound handling and split files

* fix(filters): notfound get params

* fix(queries): colon

* fix(queries): comments ts-ignore

* fix(queries): extract queryKeys

* fix(queries): remove err

* fix(routes): move zob schema inline

* fix(auth): middleware and redirect to login

* fix(auth): failing test

* fix(logs): invalidate correct key

* fix(logs): invalidate correct key

* fix(logs): invalidate correct key

* fix: JSX element stealing focus from searchbar

* reimplement empty release table state text

* fix(context): use deep-copy

* fix(releases): empty state and filter input warnings

* fix(releases): empty states

* fix(auth): onboarding

* fix(cache): invalidate queries

---------

Co-authored-by: ze0s <43699394+zze0s@users.noreply.github.com>
This commit is contained in:
martylukyy 2024-02-12 13:07:00 +01:00 committed by GitHub
parent cc9656cd41
commit 1a23b69bcf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
64 changed files with 2543 additions and 2091 deletions

View file

@ -5,11 +5,11 @@
import toast from "react-hot-toast";
import { useMutation, useQuery } from "@tanstack/react-query";
import { useRouter } from "@tanstack/react-router";
import { Disclosure } from "@headlessui/react";
import { Bars3Icon, XMarkIcon, MegaphoneIcon } from "@heroicons/react/24/outline";
import { APIClient } from "@api/APIClient";
import { AuthContext } from "@utils/Context";
import Toast from "@components/notifications/Toast";
import { LeftNav } from "./LeftNav";
@ -17,37 +17,35 @@ import { RightNav } from "./RightNav";
import { MobileNav } from "./MobileNav";
import { ExternalLink } from "@components/ExternalLink";
export const Header = () => {
const { isError:isConfigError, error: configError, data: config } = useQuery({
queryKey: ["config"],
queryFn: () => APIClient.config.get(),
retry: false,
refetchOnWindowFocus: false
});
import { AuthIndexRoute } from "@app/routes";
import { ConfigQueryOptions, UpdatesQueryOptions } from "@api/queries";
export const Header = () => {
const router = useRouter()
const { auth } = AuthIndexRoute.useRouteContext()
const { isError:isConfigError, error: configError, data: config } = useQuery(ConfigQueryOptions(true));
if (isConfigError) {
console.log(configError);
}
const { isError, error, data } = useQuery({
queryKey: ["updates"],
queryFn: () => APIClient.updates.getLatestRelease(),
retry: false,
refetchOnWindowFocus: false,
enabled: config?.check_for_updates === true
});
if (isError) {
console.log(error);
const { isError: isUpdateError, error, data } = useQuery(UpdatesQueryOptions(config?.check_for_updates === true));
if (isUpdateError) {
console.log("update error", error);
}
const logoutMutation = useMutation({
mutationFn: APIClient.auth.logout,
onSuccess: () => {
AuthContext.reset();
toast.custom((t) => (
<Toast type="success" body="You have been logged out. Goodbye!" t={t} />
));
auth.logout()
router.history.push("/")
},
onError: (err) => {
console.error("logout error", err)
}
});
@ -62,7 +60,7 @@ export const Header = () => {
<div className="border-b border-gray-300 dark:border-gray-775">
<div className="flex items-center justify-between h-16 px-4 sm:px-0">
<LeftNav />
<RightNav logoutMutation={logoutMutation.mutate} />
<RightNav logoutMutation={logoutMutation.mutate} auth={auth} />
<div className="-mr-2 flex sm:hidden">
{/* Mobile menu button */}
<Disclosure.Button className="bg-gray-200 dark:bg-gray-800 inline-flex items-center justify-center p-2 rounded-md text-gray-600 dark:text-gray-400 hover:text-white hover:bg-gray-700">
@ -94,7 +92,7 @@ export const Header = () => {
)}
</div>
<MobileNav logoutMutation={logoutMutation.mutate} />
<MobileNav logoutMutation={logoutMutation.mutate} auth={auth} />
</>
)}
</Disclosure>

View file

@ -3,7 +3,10 @@
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import { Link, NavLink } from "react-router-dom";
// import { Link, NavLink } from "react-router-dom";
import { Link } from '@tanstack/react-router'
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/solid";
import { classNames } from "@utils";
@ -23,22 +26,27 @@ export const LeftNav = () => (
<div className="sm:ml-3 hidden sm:block">
<div className="flex items-baseline space-x-4">
{NAV_ROUTES.map((item, itemIdx) => (
<NavLink
<Link
key={item.name + itemIdx}
to={item.path}
className={({ isActive }) =>
classNames(
"hover:bg-gray-200 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white px-3 py-2 rounded-2xl text-sm font-medium",
"transition-colors duration-200",
isActive
? "text-black dark:text-gray-50 font-bold"
: "text-gray-600 dark:text-gray-500"
)
}
end={item.path === "/"}
params={{}}
>
{item.name}
</NavLink>
{({ isActive }) => {
return (
<>
<span className={
classNames(
"hover:bg-gray-200 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white px-3 py-2 rounded-2xl text-sm font-medium",
"transition-colors duration-200",
isActive
? "text-black dark:text-gray-50 font-bold"
: "text-gray-600 dark:text-gray-500"
)
}>{item.name}</span>
</>
)
}}
</Link>
))}
<ExternalLink
href="https://autobrr.com"

View file

@ -3,7 +3,7 @@
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import { NavLink } from "react-router-dom";
import {Link} from "@tanstack/react-router";
import { Disclosure } from "@headlessui/react";
import { classNames } from "@utils";
@ -15,21 +15,28 @@ export const MobileNav = (props: RightNavProps) => (
<Disclosure.Panel className="border-b border-gray-300 dark:border-gray-700 md:hidden">
<div className="px-2 py-3 space-y-1 sm:px-3">
{NAV_ROUTES.map((item) => (
<NavLink
<Link
key={item.path}
activeOptions={{ exact: item.exact }}
to={item.path}
className={({ isActive }) =>
classNames(
"shadow-sm border bg-gray-100 border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-white block px-3 py-2 rounded-md text-base",
isActive
search={{}}
params={{}}
>
{({ isActive }) => {
return (
<span className={
classNames(
"shadow-sm border bg-gray-100 border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-white block px-3 py-2 rounded-md text-base",
isActive
? "underline underline-offset-2 decoration-2 decoration-sky-500 font-bold text-black"
: "font-medium"
)
}
end={item.path === "/"}
>
{item.name}
</NavLink>
)
}>
{item.name}
</span>
)
}}
</Link>
))}
<button
onClick={(e) => {

View file

@ -4,18 +4,16 @@
*/
import { Fragment } from "react";
import { Link } from "react-router-dom";
import { UserIcon } from "@heroicons/react/24/solid";
import { Menu, Transition } from "@headlessui/react";
import { classNames } from "@utils";
import { AuthContext } from "@utils/Context";
import { RightNavProps } from "./_shared";
import { Cog6ToothIcon, ArrowLeftOnRectangleIcon } from "@heroicons/react/24/outline";
import {Link} from "@tanstack/react-router";
export const RightNav = (props: RightNavProps) => {
const authContext = AuthContext.useValue();
return (
<div className="hidden sm:block">
<div className="ml-4 flex items-center sm:ml-6">
@ -34,7 +32,7 @@ export const RightNav = (props: RightNavProps) => {
<span className="sr-only">
Open user menu for{" "}
</span>
{authContext.username}
{props.auth.username}
</span>
<UserIcon
className="inline ml-1 h-5 w-5"

View file

@ -3,17 +3,21 @@
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import { AuthCtx } from "@utils/Context";
interface NavItem {
name: string;
path: string;
exact?: boolean;
}
export interface RightNavProps {
logoutMutation: () => void;
auth: AuthCtx
}
export const NAV_ROUTES: Array<NavItem> = [
{ name: "Dashboard", path: "/" },
{ name: "Dashboard", path: "/", exact: true },
{ name: "Filters", path: "/filters" },
{ name: "Releases", path: "/releases" },
{ name: "Settings", path: "/settings" },