diff --git a/src/app/router.tsx b/src/app/router.tsx
index a3d9839..3bd66d5 100644
--- a/src/app/router.tsx
+++ b/src/app/router.tsx
@@ -3,7 +3,7 @@ import { createRouter as createTanStackRouter } from '@tanstack/react-router'
import { routerWithQueryClient } from '@tanstack/react-router-with-query'
import { routeTree } from './routeTree.gen'
import { DefaultCatchBoundary } from '../components/DefaultCatchBoundary'
-import { defaultHeaderConfig } from '@/features/core/hooks/use-header-config'
+import { defaultHeaderConfig } from '@/features/core/hooks/use-router-config'
export function createRouter() {
const queryClient = new QueryClient({
@@ -21,7 +21,7 @@ export function createRouter() {
return routerWithQueryClient(
createTanStackRouter({
routeTree,
- context: { queryClient, auth: undefined!, header: defaultHeaderConfig, refresh: { toRefresh: [] } },
+ context: { queryClient, auth: undefined!, header: defaultHeaderConfig, refresh: [], withPadding: true },
defaultPreload: 'intent',
defaultErrorComponent: DefaultCatchBoundary,
scrollRestoration: true,
diff --git a/src/app/routes/__root.tsx b/src/app/routes/__root.tsx
index dac2d58..9c232ad 100644
--- a/src/app/routes/__root.tsx
+++ b/src/app/routes/__root.tsx
@@ -21,7 +21,8 @@ export const Route = createRootRouteWithContext<{
queryClient: QueryClient,
auth: AuthContextType,
header: HeaderConfig,
- refresh: { toRefresh: string[] }
+ refresh: string[]
+ withPadding: boolean
}>()({
head: () => ({
meta: [
diff --git a/src/app/routes/_authed/admin/tournaments/index.tsx b/src/app/routes/_authed/admin/tournaments/index.tsx
index 92a928a..8b9ab86 100644
--- a/src/app/routes/_authed/admin/tournaments/index.tsx
+++ b/src/app/routes/_authed/admin/tournaments/index.tsx
@@ -13,15 +13,12 @@ export const Route = createFileRoute("/_authed/admin/tournaments/")({
withBackButton: true,
title: "Manage Tournaments",
},
- refresh: {
- toRefresh: tournamentQueries.list().queryKey,
- },
+ refresh: tournamentQueries.list().queryKey,
+ withPadding: false
}),
component: RouteComponent,
});
function RouteComponent() {
- return
-
-
+ return
}
diff --git a/src/app/routes/_authed/index.tsx b/src/app/routes/_authed/index.tsx
index 8c62a38..7c0f17a 100644
--- a/src/app/routes/_authed/index.tsx
+++ b/src/app/routes/_authed/index.tsx
@@ -10,11 +10,14 @@ export const Route = createFileRoute("/_authed/")({
beforeLoad: async ({ context }) => {
await context.queryClient.ensureQueryData(tournamentQueries.list());
},
+ loader: () => ({
+ withPadding: false
+ })
});
function Home() {
return (
-
+ <>
Some Content Here
@@ -24,6 +27,6 @@ function Home() {
-
+ >
);
}
diff --git a/src/app/routes/_authed/profile.$playerId.tsx b/src/app/routes/_authed/profile.$playerId.tsx
index af422d2..5ebfd2e 100644
--- a/src/app/routes/_authed/profile.$playerId.tsx
+++ b/src/app/routes/_authed/profile.$playerId.tsx
@@ -24,12 +24,10 @@ export const Route = createFileRoute("/_authed/profile/$playerId")({
withBackButton: true,
settingsLink: context?.auth.user.id === params.playerId ? 'settings' : undefined
},
- refresh: {
- toRefresh: [playerQueries.details(params.playerId).queryKey],
- }
+ refresh: [playerQueries.details(params.playerId).queryKey]
}),
component: () => {
const { player } = Route.useRouteContext();
- return
+ return
},
})
diff --git a/src/app/routes/_authed/settings.tsx b/src/app/routes/_authed/settings.tsx
index 052699f..71e09fc 100644
--- a/src/app/routes/_authed/settings.tsx
+++ b/src/app/routes/_authed/settings.tsx
@@ -4,7 +4,6 @@ import { ColorSchemePicker } from "@/features/settings/components/color-scheme-p
import AccentColorPicker from "@/features/settings/components/accent-color-picker"
import { SignOutIcon } from "@phosphor-icons/react"
import ListLink from "@/components/list-link"
-import Page from "@/components/page"
export const Route = createFileRoute("/_authed/settings")({
loader: () => ({
@@ -12,12 +11,13 @@ export const Route = createFileRoute("/_authed/settings")({
title: 'Settings',
withBackButton: true,
},
+ withPadding: false
}),
component: RouteComponent,
})
function RouteComponent() {
- return
+ return <>
Appearance
@@ -30,5 +30,5 @@ function RouteComponent() {
to='/logout'
Icon={SignOutIcon}
/>
-
+ >
}
diff --git a/src/app/routes/_authed/teams.$teamId.tsx b/src/app/routes/_authed/teams.$teamId.tsx
index 0ab150b..7095cd9 100644
--- a/src/app/routes/_authed/teams.$teamId.tsx
+++ b/src/app/routes/_authed/teams.$teamId.tsx
@@ -21,12 +21,10 @@ export const Route = createFileRoute("/_authed/teams/$teamId")({
collapsed: true,
withBackButton: true
},
- refresh: {
- toRefresh: [teamQueries.details(params.teamId).queryKey],
- }
+ refresh: [teamQueries.details(params.teamId).queryKey]
}),
component: () => {
const { team } = Route.useRouteContext();
- return
+ return
},
})
diff --git a/src/app/routes/_authed/tournaments/$tournamentId.tsx b/src/app/routes/_authed/tournaments/$tournamentId.tsx
index c36f592..16e30bc 100644
--- a/src/app/routes/_authed/tournaments/$tournamentId.tsx
+++ b/src/app/routes/_authed/tournaments/$tournamentId.tsx
@@ -28,16 +28,13 @@ export const Route = createFileRoute('/_authed/tournaments/$tournamentId')({
withBackButton: true,
settingsLink: context.auth.roles.includes("Admin") ? `/admin/tournaments/${params.tournamentId}` : undefined
},
- refresh: {
- toRefresh: tournamentQueries.details(params.tournamentId).queryKey,
- }
+ refresh: tournamentQueries.details(params.tournamentId).queryKey,
+ withPadding: false
}),
component: RouteComponent,
})
function RouteComponent() {
const { data: tournament } = useQuery(tournamentQueries.details(Route.useParams().tournamentId));
- return
-
-
+ return
}
diff --git a/src/app/routes/_authed/tournaments/index.tsx b/src/app/routes/_authed/tournaments/index.tsx
index b5df4ed..42379d3 100644
--- a/src/app/routes/_authed/tournaments/index.tsx
+++ b/src/app/routes/_authed/tournaments/index.tsx
@@ -21,9 +21,7 @@ export const Route = createFileRoute('/_authed/tournaments/')({
withBackButton: true,
title: 'Tournaments',
},
- refresh: {
- toRefresh: tournamentQueries.list().queryKey,
- }
+ refresh: tournamentQueries.list().queryKey
}),
component: RouteComponent,
})
diff --git a/src/components/page.tsx b/src/components/page.tsx
index cdf1f8a..6d4382f 100644
--- a/src/components/page.tsx
+++ b/src/components/page.tsx
@@ -1,13 +1,13 @@
import { Container, ContainerProps } from "@mantine/core";
-import useHeaderConfig from "@/features/core/hooks/use-header-config";
+import useRouterConfig from "@/features/core/hooks/use-router-config";
interface PageProps extends ContainerProps, React.PropsWithChildren {
noPadding?: boolean;
}
const Page = ({ children, noPadding, ...props }: PageProps) => {
- const headerConfig = useHeaderConfig();
- return
+ const { header } = useRouterConfig();
+ return
{children}
}
diff --git a/src/features/core/components/layout.tsx b/src/features/core/components/layout.tsx
index b523e35..0a6640d 100644
--- a/src/features/core/components/layout.tsx
+++ b/src/features/core/components/layout.tsx
@@ -2,20 +2,22 @@ import { AppShell } from '@mantine/core';
import { PropsWithChildren, useState } from 'react';
import Header from './header';
import Navbar from './navbar';
-import useHeaderConfig from '../hooks/use-header-config';
import Pullable from './pullable';
import useVisualViewportSize from '../hooks/use-visual-viewport-size';
+import useRouterConfig from '../hooks/use-router-config';
+import Page from '@/components/page';
const Layout: React.FC = ({ children }) => {
- const headerConfig = useHeaderConfig();
+ const { header } = useRouterConfig();
const viewport = useVisualViewportSize();
const [scrollPosition, setScrollPosition] = useState({ x: 0, y: 0 });
+ const { withPadding } = useRouterConfig();
return (
= ({ children }) => {
mah='100dvh'
style={{ top: viewport.top }} //, transition: 'top 0.1s ease-in-out' }}
>
-
+
= ({ children }) => {
style={{ transition: 'none' }}
>
- {children}
+
+ {children}
+
diff --git a/src/features/core/components/pullable.tsx b/src/features/core/components/pullable.tsx
index d3e3599..b38e951 100644
--- a/src/features/core/components/pullable.tsx
+++ b/src/features/core/components/pullable.tsx
@@ -1,9 +1,9 @@
import { ActionIcon, Box, Button, Flex, ScrollArea } from "@mantine/core";
import { PropsWithChildren, useCallback, useEffect, useMemo, useRef, useState } from "react";
import useAppShellHeight from "@/hooks/use-appshell-height";
-import useRefreshConfig from "@/features/core/hooks/use-refresh-config";
import { ArrowClockwiseIcon, SpinnerIcon } from "@phosphor-icons/react";
import { useQueryClient } from "@tanstack/react-query";
+import useRouterConfig from "../hooks/use-router-config";
const THRESHOLD = 80;
@@ -20,20 +20,20 @@ const Pullable: React.FC = ({ children, scrollPosition, onScrollP
const height = useAppShellHeight();
const [isRefreshing, setIsRefreshing] = useState(false);
const [scrolling, setScrolling] = useState(false);
- const { toRefresh } = useRefreshConfig();
+ const { refresh } = useRouterConfig();
const queryClient = useQueryClient();
const scrollY = useMemo(() => scrollPosition.y < 0 && scrolling ? Math.abs(scrollPosition.y) : 0, [scrollPosition.y, scrolling]);
const onTrigger = useCallback(async () => {
setIsRefreshing(true);
- if (toRefresh.length > 0) {
+ if (refresh.length > 0) {
// TODO: Remove this after testing - or does the delay help ux?
await new Promise(resolve => setTimeout(resolve, 1000));
- await queryClient.refetchQueries({ queryKey: toRefresh, exact: true});
+ await queryClient.refetchQueries({ queryKey: refresh, exact: true});
}
setIsRefreshing(false);
- }, [toRefresh]);
+ }, [refresh]);
useEffect(() => {
if (!isRefreshing && scrollY > THRESHOLD) {
@@ -43,7 +43,7 @@ const Pullable: React.FC = ({ children, scrollPosition, onScrollP
const iconOpacity = useMemo(() => {
if (isRefreshing) return 1;
- if (toRefresh.length === 0) return 0;
+ if (refresh.length === 0) return 0;
const clampedValue = Math.max(5, Math.min(THRESHOLD, scrollY));
const min = 5;
@@ -111,7 +111,7 @@ const Pullable: React.FC = ({ children, scrollPosition, onScrollP
>
{ /* TODO: Remove this debug button */}
-
+
{children}
diff --git a/src/features/core/hooks/use-header-config.ts b/src/features/core/hooks/use-header-config.ts
deleted file mode 100644
index 06c7856..0000000
--- a/src/features/core/hooks/use-header-config.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { isMatch, useMatches } from "@tanstack/react-router";
-import { HeaderConfig } from "../types/header-config";
-
-export const defaultHeaderConfig: HeaderConfig = {
- title: 'FLXN',
- withBackButton: false,
- collapsed: false,
-}
-
-const useHeaderConfig = () => {
- const matches = useMatches();
-
- const matchesWithHeader = matches.filter((match) =>
- isMatch(match, 'loaderData.header'),
- )
-
- const config = matchesWithHeader.reduce((acc, match) => {
- return {
- ...acc,
- ...match?.loaderData?.header,
- }
- }, defaultHeaderConfig) as HeaderConfig;
-
- return config;
-}
-
-export default useHeaderConfig;
diff --git a/src/features/core/hooks/use-refresh-config.ts b/src/features/core/hooks/use-refresh-config.ts
deleted file mode 100644
index eaed1e4..0000000
--- a/src/features/core/hooks/use-refresh-config.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { isMatch, useMatches } from "@tanstack/react-router";
-
-export const defaultRefreshConfig: { toRefresh: string[] } = {
- toRefresh: [],
-}
-
-const useRefreshConfig = () => {
- const matches = useMatches();
-
- const matchesWithRefresh = matches.filter((match) =>
- isMatch(match, 'loaderData.refresh'),
- )
-
- const config = matchesWithRefresh.reduce((acc, match) => {
- return {
- ...acc,
- ...match?.loaderData?.refresh,
- }
- }, defaultRefreshConfig) as { toRefresh: string[] };
-
- return config;
-}
-
-export default useRefreshConfig;
diff --git a/src/features/core/hooks/use-router-config.ts b/src/features/core/hooks/use-router-config.ts
new file mode 100644
index 0000000..e05cc79
--- /dev/null
+++ b/src/features/core/hooks/use-router-config.ts
@@ -0,0 +1,40 @@
+import { useMatches } from "@tanstack/react-router";
+import { HeaderConfig } from "../types/header-config";
+
+export const defaultHeaderConfig: HeaderConfig = {
+ title: 'FLXN',
+ withBackButton: false,
+ collapsed: false,
+}
+
+const useRouterConfig = () => {
+ const matches = useMatches();
+
+ const matchesWithHeader = matches.filter((match) =>
+ match?.loaderData && 'header' in match.loaderData
+ );
+
+ const headerConfig = matchesWithHeader.reduce((acc, match) => {
+ const loaderData = match?.loaderData;
+ if (loaderData && typeof loaderData === 'object' && 'header' in loaderData) {
+ const header = loaderData.header;
+ if (header && typeof header === 'object') {
+ return {
+ ...acc,
+ ...header,
+ }
+ }
+ }
+ return acc;
+ }, defaultHeaderConfig);
+
+ const current = matches[matches.length - 1]?.loaderData;
+
+ return {
+ header: headerConfig,
+ refresh: current && typeof current === 'object' && 'refresh' in current ? current.refresh : [],
+ withPadding: current && typeof current === 'object' && 'withPadding' in current ? current.withPadding : true
+ };
+}
+
+export default useRouterConfig;
diff --git a/src/hooks/use-appshell-height.ts b/src/hooks/use-appshell-height.ts
index ed80146..6d809da 100644
--- a/src/hooks/use-appshell-height.ts
+++ b/src/hooks/use-appshell-height.ts
@@ -1,18 +1,19 @@
import { useMemo } from "react";
import { useIsMobile } from "./use-is-mobile";
-import useHeaderConfig from "@/features/core/hooks/use-header-config";
+import useRouterConfig from "@/features/core/hooks/use-router-config";
+
const useAppShellHeight = () => {
const isMobile = useIsMobile();
- const headerConfig = useHeaderConfig();
+ const { header } = useRouterConfig();
const height = useMemo(() => {
const appShellBottomPadding = isMobile ? '70px' : '0px';
const pageBottomPadding = '20px';
- const mobileNavbar = isMobile && !headerConfig.collapsed ? '4rem' : '0px';
+ const mobileNavbar = isMobile && !header.collapsed ? '4rem' : '0px';
const pullablePadding = '1.285rem';
return `calc(100dvh - var(--app-shell-header-height, 0px) - ${mobileNavbar} - ${pullablePadding} - ${appShellBottomPadding} - ${pageBottomPadding})`;
- }, [isMobile, headerConfig.collapsed]);
+ }, [isMobile, header.collapsed]);
return height;
}