style upgrades
This commit is contained in:
+1
-1
@@ -31,7 +31,7 @@ export function getRouter() {
|
||||
defaultPreload: "intent",
|
||||
defaultErrorComponent: DefaultCatchBoundary,
|
||||
scrollRestoration: true,
|
||||
defaultViewTransition: false,
|
||||
defaultViewTransition: true,
|
||||
});
|
||||
|
||||
setupRouterSsrQueryIntegration({
|
||||
|
||||
@@ -19,6 +19,7 @@ import { HeaderConfig } from "@/features/core/types/header-config";
|
||||
import { playerQueries } from "@/features/players/queries";
|
||||
import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
|
||||
import FullScreenLoader from "@/components/full-screen-loader";
|
||||
import { CHROME_COLORS } from "@/lib/mantine/theme-colors";
|
||||
import mantineCssUrl from '@mantine/core/styles.css?url'
|
||||
import mantineDatesCssUrl from '@mantine/dates/styles.css?url'
|
||||
import mantineCarouselCssUrl from '@mantine/carousel/styles.css?url'
|
||||
@@ -41,11 +42,10 @@ export const Route = createRootRouteWithContext<{
|
||||
{
|
||||
name: "viewport",
|
||||
content:
|
||||
"width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, interactive-widget=resizes-content",
|
||||
"width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, interactive-widget=resizes-content, viewport-fit=cover",
|
||||
},
|
||||
{ name: 'description', content: 'Amicus meus madidus' },
|
||||
{ name: 'keywords', content: 'FLXN, beer pong, tournament, sports, statistics, pong' },
|
||||
{ name: 'theme-color', content: '#1e293b' },
|
||||
{ property: 'og:title', content: 'FLXN' },
|
||||
{ property: 'og:description', content: 'Amicus meus madidus' },
|
||||
{ property: 'og:url', content: 'https://flexxon.app' },
|
||||
@@ -179,6 +179,16 @@ function RootDocument({ children }: { children: React.ReactNode }) {
|
||||
>
|
||||
<head>
|
||||
<HeadContent />
|
||||
<meta
|
||||
name="theme-color"
|
||||
media="(prefers-color-scheme: light)"
|
||||
content={CHROME_COLORS.light.base}
|
||||
/>
|
||||
<meta
|
||||
name="theme-color"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
content={CHROME_COLORS.dark.base}
|
||||
/>
|
||||
<ColorSchemeScript />
|
||||
<link rel="stylesheet" href="/styles.css" />
|
||||
</head>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { redirect, createFileRoute, Outlet } from "@tanstack/react-router";
|
||||
import Layout from "@/features/core/components/layout";
|
||||
import { useServerEvents } from "@/hooks/use-server-events";
|
||||
import { Flex, Loader } from "@mantine/core";
|
||||
import { Group, Skeleton, Stack } from "@mantine/core";
|
||||
|
||||
export const Route = createFileRoute("/_authed")({
|
||||
beforeLoad: ({ context }) => {
|
||||
@@ -26,9 +26,21 @@ export const Route = createFileRoute("/_authed")({
|
||||
},
|
||||
pendingComponent: () => (
|
||||
<Layout>
|
||||
<Flex w='100%' h="40dvh" justify="center" align="flex-end">
|
||||
<Loader size='xl' />
|
||||
</Flex>
|
||||
<Stack gap="md" p="md" w="100%">
|
||||
<Group gap="sm">
|
||||
<Skeleton height={40} width={40} radius="sm" />
|
||||
<Skeleton height={24} width="45%" radius="sm" />
|
||||
</Group>
|
||||
{Array.from({ length: 4 }).map((_, index) => (
|
||||
<Skeleton
|
||||
key={`authed-pending-${index}`}
|
||||
height={96}
|
||||
w="100%"
|
||||
radius="md"
|
||||
style={{ opacity: Math.max(1 - index * 0.18, 0.3) }}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</Layout>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -2,8 +2,8 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||
import { prefetchServerQuery } from "@/lib/tanstack-query/utils/prefetch";
|
||||
import { ActivitiesTable, activityQueries } from "@/features/activities";
|
||||
import { PlayersActivityTable, playerQueries } from "@/features/players";
|
||||
import { Tabs } from "@mantine/core";
|
||||
import { useState } from "react";
|
||||
import { Box, Divider, Group, Skeleton, Stack, Tabs } from "@mantine/core";
|
||||
import { Suspense, useState } from "react";
|
||||
|
||||
export const Route = createFileRoute("/_authed/admin/activities")({
|
||||
component: Stats,
|
||||
@@ -23,6 +23,30 @@ export const Route = createFileRoute("/_authed/admin/activities")({
|
||||
}),
|
||||
});
|
||||
|
||||
function ActivityRowsSkeleton({ withSearch = false }: { withSearch?: boolean }) {
|
||||
return (
|
||||
<Stack gap={0}>
|
||||
{withSearch && (
|
||||
<Box px="md" pb="sm">
|
||||
<Skeleton height={42} radius="sm" />
|
||||
</Box>
|
||||
)}
|
||||
{Array.from({ length: 8 }).map((_, index) => (
|
||||
<div key={`activity-skeleton-${index}`} style={{ opacity: Math.max(1 - index * 0.1, 0.35) }}>
|
||||
<Group p="md" wrap="nowrap" w="100%" justify="space-between">
|
||||
<Stack gap={6}>
|
||||
<Skeleton height={14} width={160} radius="sm" />
|
||||
<Skeleton height={10} width={100} radius="sm" />
|
||||
</Stack>
|
||||
<Skeleton height={10} width={60} radius="sm" />
|
||||
</Group>
|
||||
<Divider />
|
||||
</div>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function Stats() {
|
||||
const [activeTab, setActiveTab] = useState<string | null>("server-functions");
|
||||
|
||||
@@ -34,11 +58,15 @@ function Stats() {
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Panel value="server-functions">
|
||||
<ActivitiesTable />
|
||||
<Suspense fallback={<ActivityRowsSkeleton withSearch />}>
|
||||
<ActivitiesTable />
|
||||
</Suspense>
|
||||
</Tabs.Panel>
|
||||
|
||||
<Tabs.Panel value="player-activity">
|
||||
<PlayersActivityTable />
|
||||
<Suspense fallback={<ActivityRowsSkeleton />}>
|
||||
<PlayersActivityTable />
|
||||
</Suspense>
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createFileRoute, redirect, useNavigate } from "@tanstack/react-router";
|
||||
import { tournamentQueries, useFreeAgents, useTournament } from "@/features/tournaments/queries";
|
||||
import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
|
||||
import { Stack, Text, Button, Alert, LoadingOverlay, Group } from "@mantine/core";
|
||||
import { Stack, Text, Button, Alert, LoadingOverlay, Group, Skeleton } from "@mantine/core";
|
||||
import { useState } from "react";
|
||||
import useGenerateRandomTeams from "@/features/tournaments/hooks/use-generate-random-teams";
|
||||
import useConfirmTeamAssignments from "@/features/tournaments/hooks/use-confirm-team-assignments";
|
||||
@@ -27,8 +27,23 @@ export const Route = createFileRoute("/_authed/admin/tournaments/$id/assign-part
|
||||
},
|
||||
}),
|
||||
component: RouteComponent,
|
||||
pendingComponent: AssignPartnersPending,
|
||||
});
|
||||
|
||||
function AssignPartnersPending() {
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
<Stack gap="xs">
|
||||
<Group gap="xs" align="baseline">
|
||||
<Skeleton height={28} width={36} radius="sm" />
|
||||
<Skeleton height={14} width={110} radius="sm" />
|
||||
</Group>
|
||||
<Skeleton height={36} w="100%" radius="sm" style={{ opacity: 0.7 }} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
interface TeamAssignment {
|
||||
player1: PlayerInfo;
|
||||
player2: PlayerInfo;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||
import { tournamentQueries } from "@/features/tournaments/queries";
|
||||
import ManageTournament from "@/features/tournaments/components/manage-tournament";
|
||||
import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
|
||||
import { Divider, Group, Skeleton, Stack } from "@mantine/core";
|
||||
|
||||
export const Route = createFileRoute("/_authed/admin/tournaments/$id/")({
|
||||
beforeLoad: async ({ context, params }) => {
|
||||
@@ -23,8 +24,26 @@ export const Route = createFileRoute("/_authed/admin/tournaments/$id/")({
|
||||
withPadding: false,
|
||||
}),
|
||||
component: RouteComponent,
|
||||
pendingComponent: ManageTournamentPending,
|
||||
});
|
||||
|
||||
function ManageTournamentPending() {
|
||||
return (
|
||||
<Stack gap={0}>
|
||||
{Array.from({ length: 5 }).map((_, index) => (
|
||||
<div key={`manage-tournament-skeleton-${index}`} style={{ opacity: Math.max(1 - index * 0.14, 0.4) }}>
|
||||
<Group p="md" wrap="nowrap" w="100%">
|
||||
<Skeleton height={20} width={20} radius="sm" />
|
||||
<Skeleton height={16} width="45%" radius="sm" />
|
||||
<Skeleton ml="auto" height={20} width={20} radius="sm" />
|
||||
</Group>
|
||||
<Divider />
|
||||
</div>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function RouteComponent() {
|
||||
const { id } = Route.useParams();
|
||||
return <ManageTournament tournamentId={id} />;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||
import { tournamentQueries } from "@/features/tournaments/queries";
|
||||
import ManageTeams from "@/features/teams/components/manage-teams";
|
||||
import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
|
||||
import { Box, Divider, Group, Skeleton, Stack } from "@mantine/core";
|
||||
|
||||
export const Route = createFileRoute("/_authed/admin/tournaments/$id/teams")({
|
||||
beforeLoad: async ({ context, params }) => {
|
||||
@@ -23,8 +24,37 @@ export const Route = createFileRoute("/_authed/admin/tournaments/$id/teams")({
|
||||
withPadding: false,
|
||||
}),
|
||||
component: RouteComponent,
|
||||
pendingComponent: ManageTeamsPending,
|
||||
});
|
||||
|
||||
function ManageTeamsPending() {
|
||||
return (
|
||||
<Stack gap="xs">
|
||||
<Box px="md">
|
||||
<Skeleton height={42} radius="sm" />
|
||||
</Box>
|
||||
<Box px="md">
|
||||
<Skeleton height={12} width={90} radius="sm" />
|
||||
</Box>
|
||||
<Stack gap={0}>
|
||||
{Array.from({ length: 8 }).map((_, index) => (
|
||||
<div key={`manage-teams-skeleton-${index}`} style={{ opacity: Math.max(1 - index * 0.1, 0.35) }}>
|
||||
<Group p="xs" wrap="nowrap" w="100%">
|
||||
<Skeleton height={40} width={40} radius="sm" />
|
||||
<Skeleton height={16} width="45%" radius="sm" />
|
||||
<Stack ml="auto" gap={6} align="flex-end">
|
||||
<Skeleton height={10} width={90} radius="sm" />
|
||||
<Skeleton height={10} width={70} radius="sm" />
|
||||
</Stack>
|
||||
</Group>
|
||||
<Divider />
|
||||
</div>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function RouteComponent() {
|
||||
const { id } = Route.useParams();
|
||||
const { tournament } = Route.useRouteContext();
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import ManageTournaments from "@/features/admin/components/manage-tournaments";
|
||||
import { tournamentQueries } from "@/features/tournaments/queries";
|
||||
import { prefetchServerQuery } from "@/lib/tanstack-query/utils/prefetch";
|
||||
import { Divider, Group, Skeleton, Stack } from "@mantine/core";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { Suspense } from "react";
|
||||
|
||||
export const Route = createFileRoute("/_authed/admin/tournaments/")({
|
||||
beforeLoad: async ({ context }) => {
|
||||
beforeLoad: ({ context }) => {
|
||||
const { queryClient } = context;
|
||||
await prefetchServerQuery(queryClient, tournamentQueries.list());
|
||||
prefetchServerQuery(queryClient, tournamentQueries.list());
|
||||
},
|
||||
loader: () => ({
|
||||
header: {
|
||||
@@ -19,6 +21,26 @@ export const Route = createFileRoute("/_authed/admin/tournaments/")({
|
||||
component: RouteComponent,
|
||||
});
|
||||
|
||||
function TournamentListSkeleton() {
|
||||
return (
|
||||
<Stack gap={0}>
|
||||
{Array.from({ length: 6 }).map((_, index) => (
|
||||
<div key={`manage-tournaments-skeleton-${index}`} style={{ opacity: Math.max(1 - index * 0.12, 0.4) }}>
|
||||
<Group p="md" wrap="nowrap" w="100%">
|
||||
<Skeleton height={16} width="55%" radius="sm" />
|
||||
<Skeleton ml="auto" height={20} width={20} radius="sm" />
|
||||
</Group>
|
||||
<Divider />
|
||||
</div>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function RouteComponent() {
|
||||
return <ManageTournaments />;
|
||||
return (
|
||||
<Suspense fallback={<TournamentListSkeleton />}>
|
||||
<ManageTournaments />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
|
||||
import SeedTournament from "@/features/tournaments/components/seed-tournament";
|
||||
import SetupGroupStage from "@/features/tournaments/components/setup-group-stage";
|
||||
import GroupStageView from "@/features/tournaments/components/group-stage-view";
|
||||
import { Container, Stack, Divider, Title } from "@mantine/core";
|
||||
import { Container, Stack, Divider, Title, Box, Card, Group, Skeleton, SimpleGrid } from "@mantine/core";
|
||||
import { useMemo } from "react";
|
||||
import { BracketData } from "@/features/bracket/types";
|
||||
import { Match } from "@/features/matches/types";
|
||||
@@ -37,8 +37,46 @@ export const Route = createFileRoute("/_authed/admin/tournaments/run/$id")({
|
||||
},
|
||||
}),
|
||||
component: RouteComponent,
|
||||
pendingComponent: RunTournamentPending,
|
||||
});
|
||||
|
||||
function RunTournamentPending() {
|
||||
return (
|
||||
<Container size="md" px={0}>
|
||||
<Box p="md">
|
||||
<Stack gap="md">
|
||||
<Group gap={0} grow mb="md">
|
||||
{Array.from({ length: 4 }).map((_, index) => (
|
||||
<Skeleton
|
||||
key={`run-pending-tab-${index}`}
|
||||
height={44}
|
||||
radius="sm"
|
||||
style={{ opacity: 0.85 - index * 0.1 }}
|
||||
/>
|
||||
))}
|
||||
</Group>
|
||||
<Card withBorder radius="md" p={0}>
|
||||
<Group justify="space-between" p="sm">
|
||||
<Skeleton height={16} width={120} radius="sm" />
|
||||
<Skeleton height={22} width={22} radius="xl" />
|
||||
</Group>
|
||||
</Card>
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="md">
|
||||
{Array.from({ length: 6 }).map((_, index) => (
|
||||
<Skeleton
|
||||
key={`run-pending-match-${index}`}
|
||||
height={100}
|
||||
radius="md"
|
||||
style={{ opacity: Math.max(1 - index * 0.12, 0.4) }}
|
||||
/>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
function RouteComponent() {
|
||||
const { id } = Route.useParams();
|
||||
const { data: tournament } = useTournament(id);
|
||||
|
||||
@@ -64,7 +64,7 @@ function Stats() {
|
||||
</Button>
|
||||
</Group>
|
||||
<Box style={{ opacity: isStale ? 0.6 : 1, transition: 'opacity 150ms' }}>
|
||||
<Suspense key={deferredViewType} fallback={<PlayerStatsTableSkeleton hideFilters />}>
|
||||
<Suspense fallback={<PlayerStatsTableSkeleton hideFilters />}>
|
||||
<PlayerStatsTable viewType={deferredViewType} />
|
||||
</Suspense>
|
||||
</Box>
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
useTournament,
|
||||
} from "@/features/tournaments/queries";
|
||||
import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
|
||||
import { Container } from "@mantine/core";
|
||||
import { Box, Container, Flex, Skeleton, Stack } from "@mantine/core";
|
||||
import { useMemo } from "react";
|
||||
import { BracketData } from "@/features/bracket/types";
|
||||
import { Match } from "@/features/matches/types";
|
||||
@@ -31,8 +31,49 @@ export const Route = createFileRoute("/_authed/tournaments/$id/bracket")({
|
||||
},
|
||||
}),
|
||||
component: RouteComponent,
|
||||
pendingComponent: BracketPending,
|
||||
});
|
||||
|
||||
function BracketPending() {
|
||||
const columns = [4, 2, 1];
|
||||
|
||||
return (
|
||||
<Container size="md" px={0}>
|
||||
<Box
|
||||
p={0}
|
||||
style={{
|
||||
overflow: "hidden",
|
||||
backgroundImage: `radial-gradient(circle, var(--mantine-color-default-border) 1px, transparent 1px)`,
|
||||
backgroundSize: "16px 16px",
|
||||
backgroundPosition: "0 0, 8px 8px",
|
||||
minHeight: "70dvh",
|
||||
}}
|
||||
>
|
||||
<Skeleton height={18} width={140} radius="sm" m={16} />
|
||||
<Flex gap="xl" px={16} align="stretch">
|
||||
{columns.map((count, columnIndex) => (
|
||||
<Stack
|
||||
key={`bracket-pending-round-${columnIndex}`}
|
||||
gap="xl"
|
||||
justify="space-around"
|
||||
style={{ opacity: 1 - columnIndex * 0.25 }}
|
||||
>
|
||||
{Array.from({ length: count }).map((_, matchIndex) => (
|
||||
<Skeleton
|
||||
key={`bracket-pending-match-${columnIndex}-${matchIndex}`}
|
||||
height={84}
|
||||
width={220}
|
||||
radius="md"
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
))}
|
||||
</Flex>
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
function RouteComponent() {
|
||||
const { id } = Route.useParams();
|
||||
const { data: tournament } = useTournament(id);
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
} from "@/features/tournaments/queries";
|
||||
import { ensureServerQueryData } from "@/lib/tanstack-query/utils/ensure";
|
||||
import GroupStageView from "@/features/tournaments/components/group-stage-view";
|
||||
import { Container } from "@mantine/core";
|
||||
import { Box, Card, Container, Group, Skeleton, SimpleGrid, Stack } from "@mantine/core";
|
||||
|
||||
export const Route = createFileRoute("/_authed/tournaments/$id/groups")({
|
||||
beforeLoad: async ({ context, params }) => {
|
||||
@@ -28,8 +28,46 @@ export const Route = createFileRoute("/_authed/tournaments/$id/groups")({
|
||||
},
|
||||
}),
|
||||
component: RouteComponent,
|
||||
pendingComponent: GroupsPending,
|
||||
});
|
||||
|
||||
function GroupsPending() {
|
||||
return (
|
||||
<Container size="md" px={0}>
|
||||
<Box p="md">
|
||||
<Stack gap="md">
|
||||
<Group gap={0} grow mb="md">
|
||||
{Array.from({ length: 4 }).map((_, index) => (
|
||||
<Skeleton
|
||||
key={`groups-pending-tab-${index}`}
|
||||
height={44}
|
||||
radius="sm"
|
||||
style={{ opacity: 0.85 - index * 0.1 }}
|
||||
/>
|
||||
))}
|
||||
</Group>
|
||||
<Card withBorder radius="md" p={0}>
|
||||
<Group justify="space-between" p="sm">
|
||||
<Skeleton height={16} width={120} radius="sm" />
|
||||
<Skeleton height={22} width={22} radius="xl" />
|
||||
</Group>
|
||||
</Card>
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="md">
|
||||
{Array.from({ length: 6 }).map((_, index) => (
|
||||
<Skeleton
|
||||
key={`groups-pending-match-${index}`}
|
||||
height={100}
|
||||
radius="md"
|
||||
style={{ opacity: Math.max(1 - index * 0.12, 0.4) }}
|
||||
/>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
function RouteComponent() {
|
||||
const { id } = Route.useParams();
|
||||
const { data: tournament } = useTournament(id);
|
||||
|
||||
@@ -23,7 +23,13 @@ export const Route = createFileRoute('/_authed/tournaments/')({
|
||||
function RouteComponent() {
|
||||
return <Suspense fallback={<Stack gap="md">
|
||||
{Array(10).fill(null).map((_, index) => (
|
||||
<Skeleton height="120px" w="100%" />
|
||||
<Skeleton
|
||||
key={`tournament-card-skeleton-${index}`}
|
||||
height="120px"
|
||||
w="100%"
|
||||
radius="md"
|
||||
style={{ opacity: Math.max(1 - index * 0.08, 0.35) }}
|
||||
/>
|
||||
))}
|
||||
</Stack>}>
|
||||
<TournamentCardList />
|
||||
|
||||
Reference in New Issue
Block a user