perf upgrade
This commit is contained in:
@@ -16,7 +16,7 @@ const activitySearchParamsSchema = z.object({
|
||||
});
|
||||
|
||||
export const searchActivities = createServerFn()
|
||||
.inputValidator(activitySearchParamsSchema)
|
||||
.validator(activitySearchParamsSchema)
|
||||
.middleware([superTokensAdminFunctionMiddleware])
|
||||
.handler(async ({ data }) =>
|
||||
toServerResult<ActivityListResult>(async () => {
|
||||
|
||||
@@ -47,6 +47,7 @@ export const BadgeIcon = ({ badge, filled, size = 48 }: BadgeIconProps & { size?
|
||||
alt={badge.name}
|
||||
width={size}
|
||||
height={size}
|
||||
loading="lazy"
|
||||
onError={() => setImageError(true)}
|
||||
style={{
|
||||
objectFit: 'contain',
|
||||
|
||||
@@ -5,7 +5,7 @@ import { pbAdmin } from "@/lib/pocketbase/client";
|
||||
import { z } from "zod";
|
||||
|
||||
export const getPlayerBadges = createServerFn()
|
||||
.inputValidator(z.string())
|
||||
.validator(z.string())
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ data: playerId }) =>
|
||||
toServerResult(() => pbAdmin.getPlayerBadgeProgress(playerId))
|
||||
@@ -28,7 +28,7 @@ export const getAllEarnedBadges = createServerFn()
|
||||
.handler(async () => toServerResult(() => pbAdmin.listEarnedBadges()));
|
||||
|
||||
export const awardManualBadge = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
.validator(z.object({
|
||||
playerId: z.string(),
|
||||
badgeId: z.string(),
|
||||
}))
|
||||
|
||||
@@ -9,7 +9,7 @@ import { toServerResult } from "@/lib/tanstack-query/utils/to-server-result";
|
||||
const logger = new Logger("Bracket Generation");
|
||||
|
||||
export const previewBracket = createServerFn()
|
||||
.inputValidator(z.number())
|
||||
.validator(z.number())
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ data: teams }) =>
|
||||
toServerResult(async () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AppShell } from '@mantine/core';
|
||||
import { PropsWithChildren, useState } from 'react';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import Header from './header';
|
||||
import Navbar from './navbar';
|
||||
import Pullable from './pullable';
|
||||
@@ -8,10 +8,8 @@ import useRouterConfig from '../hooks/use-router-config';
|
||||
import Page from '@/components/page';
|
||||
|
||||
const Layout: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
const { header } = useRouterConfig();
|
||||
const { header, withPadding, fullWidth } = useRouterConfig();
|
||||
const viewport = useVisualViewportSize();
|
||||
const [scrollPosition, setScrollPosition] = useState({ x: 0, y: 0 });
|
||||
const { withPadding, fullWidth } = useRouterConfig();
|
||||
|
||||
return (
|
||||
<AppShell
|
||||
@@ -47,7 +45,7 @@ const Layout: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
maw='100dvw'
|
||||
style={{ transition: 'none', overflow: 'hidden' }}
|
||||
>
|
||||
<Pullable scrollPosition={scrollPosition} onScrollPositionChange={setScrollPosition}>
|
||||
<Pullable>
|
||||
<Page noPadding={!withPadding} fullWidth={fullWidth}>
|
||||
{children}
|
||||
</Page>
|
||||
|
||||
@@ -19,14 +19,13 @@ export const NavLink = ({
|
||||
include,
|
||||
exclude,
|
||||
}: NavLinkProps) => {
|
||||
const router = useRouterState();
|
||||
const pathname = useRouterState({ select: (s) => s.location.pathname });
|
||||
const isActive = useMemo(
|
||||
() =>
|
||||
(!exclude?.some((e) => router.location.pathname.includes(e)) &&
|
||||
(router.location.pathname === href ||
|
||||
(router.location.pathname.includes(href) && href !== "/"))) ||
|
||||
include?.includes(router.location.pathname),
|
||||
[router.location.pathname, href]
|
||||
(!exclude?.some((e) => pathname.includes(e)) &&
|
||||
(pathname === href || (pathname.includes(href) && href !== "/"))) ||
|
||||
include?.includes(pathname),
|
||||
[pathname, href]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -8,15 +8,10 @@ import { useLocation } from "@tanstack/react-router";
|
||||
|
||||
const THRESHOLD = 80;
|
||||
|
||||
interface PullableProps extends PropsWithChildren {
|
||||
scrollPosition: { x: number, y: number };
|
||||
onScrollPositionChange: (position: { x: number, y: number }) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pullable is a component that allows the user to pull down to refresh the page
|
||||
*/
|
||||
const Pullable: React.FC<PullableProps> = ({ children, scrollPosition, onScrollPositionChange }) => {
|
||||
const Pullable: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
const height = useAppShellHeight();
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const [scrolling, setScrolling] = useState(false);
|
||||
@@ -25,7 +20,14 @@ const Pullable: React.FC<PullableProps> = ({ children, scrollPosition, onScrollP
|
||||
const location = useLocation();
|
||||
const scrollAreaRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const scrollY = useMemo(() => scrollPosition.y < 0 && scrolling ? Math.abs(scrollPosition.y) : 0, [scrollPosition.y, scrolling]);
|
||||
const [pullDistance, setPullDistance] = useState(0);
|
||||
|
||||
const handleScrollPositionChange = useCallback((position: { x: number, y: number }) => {
|
||||
const next = position.y < 0 ? Math.abs(position.y) : 0;
|
||||
setPullDistance((prev) => (prev === next ? prev : next));
|
||||
}, []);
|
||||
|
||||
const scrollY = scrolling ? pullDistance : 0;
|
||||
|
||||
const onTrigger = useCallback(async () => {
|
||||
setIsRefreshing(true);
|
||||
@@ -143,11 +145,11 @@ const Pullable: React.FC<PullableProps> = ({ children, scrollPosition, onScrollP
|
||||
viewport.scrollLeft = 0;
|
||||
}
|
||||
}
|
||||
onScrollPositionChange({ x: 0, y: 0 });
|
||||
setPullDistance(0);
|
||||
}, 10);
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [location.pathname, onScrollPositionChange]);
|
||||
}, [location.pathname]);
|
||||
|
||||
|
||||
return (
|
||||
@@ -175,7 +177,7 @@ const Pullable: React.FC<PullableProps> = ({ children, scrollPosition, onScrollP
|
||||
<ScrollArea
|
||||
ref={scrollAreaRef}
|
||||
id='scroll-wrapper'
|
||||
onScrollPositionChange={onScrollPositionChange}
|
||||
onScrollPositionChange={handleScrollPositionChange}
|
||||
type='never' mah='100%' h='100%'
|
||||
pt={(scrolling || scrollY > 40) || !isRefreshing ? 0 : 40 - scrollY}
|
||||
styles={{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useMatches } from "@tanstack/react-router";
|
||||
import { useMemo } from "react";
|
||||
import { HeaderConfig } from "../types/header-config";
|
||||
|
||||
export const defaultHeaderConfig: HeaderConfig = {
|
||||
@@ -10,32 +11,34 @@ export const defaultHeaderConfig: HeaderConfig = {
|
||||
const useRouterConfig = () => {
|
||||
const matches = useMatches();
|
||||
|
||||
const matchesWithHeader = matches.filter((match) =>
|
||||
match?.loaderData && 'header' in match.loaderData
|
||||
);
|
||||
return useMemo(() => {
|
||||
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,
|
||||
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);
|
||||
return acc;
|
||||
}, defaultHeaderConfig);
|
||||
|
||||
const current = matches[matches.length - 1]?.loaderData;
|
||||
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,
|
||||
fullWidth: current && typeof current === 'object' && 'fullWidth' in current ? current.fullWidth : false,
|
||||
};
|
||||
return {
|
||||
header: headerConfig,
|
||||
refresh: current && typeof current === 'object' && 'refresh' in current ? current.refresh : [],
|
||||
withPadding: current && typeof current === 'object' && 'withPadding' in current ? current.withPadding : true,
|
||||
fullWidth: current && typeof current === 'object' && 'fullWidth' in current ? current.fullWidth : false,
|
||||
};
|
||||
}, [matches]);
|
||||
}
|
||||
|
||||
export default useRouterConfig;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
const eventListerOptions = {
|
||||
passive: true,
|
||||
@@ -11,25 +11,49 @@ const useVisualViewportSize = () => {
|
||||
height: windowExists ? window.visualViewport?.height || 0 : 0,
|
||||
top: windowExists ? window.visualViewport?.offsetTop || 0 : 0,
|
||||
});
|
||||
|
||||
const setSize = useCallback(() => {
|
||||
if (!windowExists) return;
|
||||
setWindowSize({ width: window.visualViewport?.width || 0, height: window.visualViewport?.height || 0, top: window.visualViewport?.offsetTop || 0 });
|
||||
}, []);
|
||||
const rafRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!windowExists) return;
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const setSize = () => {
|
||||
setWindowSize((prev) => {
|
||||
const next = {
|
||||
width: window.visualViewport?.width || 0,
|
||||
height: window.visualViewport?.height || 0,
|
||||
top: window.visualViewport?.offsetTop || 0,
|
||||
};
|
||||
if (
|
||||
prev.width === next.width &&
|
||||
prev.height === next.height &&
|
||||
prev.top === next.top
|
||||
) {
|
||||
return prev;
|
||||
}
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const scheduleSetSize = () => {
|
||||
if (rafRef.current !== null) return;
|
||||
rafRef.current = window.requestAnimationFrame(() => {
|
||||
rafRef.current = null;
|
||||
setSize();
|
||||
});
|
||||
};
|
||||
|
||||
setSize();
|
||||
|
||||
window.visualViewport?.addEventListener('resize', setSize, eventListerOptions);
|
||||
window.visualViewport?.addEventListener('scroll', setSize, eventListerOptions);
|
||||
window.visualViewport?.addEventListener('resize', scheduleSetSize, eventListerOptions);
|
||||
|
||||
return () => {
|
||||
window.visualViewport?.removeEventListener('resize', setSize);
|
||||
window.visualViewport?.removeEventListener('scroll', setSize);
|
||||
}
|
||||
}, [setSize]);
|
||||
window.visualViewport?.removeEventListener('resize', scheduleSetSize);
|
||||
if (rafRef.current !== null) {
|
||||
window.cancelAnimationFrame(rafRef.current);
|
||||
rafRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return windowSize;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useNavigate } from "@tanstack/react-router";
|
||||
import { Match } from "../types";
|
||||
import TeamAvatar from "@/components/team-avatar";
|
||||
import EmojiBar from "@/features/reactions/components/emoji-bar";
|
||||
import { Suspense } from "react";
|
||||
import { memo, Suspense } from "react";
|
||||
import { useSheet } from "@/hooks/use-sheet";
|
||||
import Sheet from "@/components/sheet/sheet";
|
||||
import TeamHeadToHeadSheet from "./team-head-to-head-sheet";
|
||||
@@ -237,4 +237,4 @@ const MatchCard = ({ match, hideH2H = false }: MatchCardProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default MatchCard;
|
||||
export default memo(MatchCard);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Stack, Text } from "@mantine/core";
|
||||
import { useMemo } from "react";
|
||||
import { Match } from "../types";
|
||||
import MatchCard from "./match-card";
|
||||
|
||||
@@ -8,9 +9,15 @@ interface MatchListProps {
|
||||
}
|
||||
|
||||
const MatchList = ({ matches, hideH2H = false }: MatchListProps) => {
|
||||
const filteredMatches = matches?.filter(match =>
|
||||
match.home && match.away && !match.bye && match.status != "tbd"
|
||||
).sort((a, b) => a.start_time < b.start_time ? 1 : -1) || [];
|
||||
const filteredMatches = useMemo(
|
||||
() =>
|
||||
[...(matches ?? [])]
|
||||
.filter(match =>
|
||||
match.home && match.away && !match.bye && match.status != "tbd"
|
||||
)
|
||||
.sort((a, b) => a.start_time < b.start_time ? 1 : -1),
|
||||
[matches]
|
||||
);
|
||||
|
||||
if (!filteredMatches.length) {
|
||||
return undefined;
|
||||
|
||||
@@ -17,7 +17,7 @@ const orderedTeamsSchema = z.object({
|
||||
});
|
||||
|
||||
export const generateTournamentBracket = createServerFn()
|
||||
.inputValidator(orderedTeamsSchema)
|
||||
.validator(orderedTeamsSchema)
|
||||
.middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
.handler(async ({ data: { tournamentId, orderedTeamIds } }) =>
|
||||
toServerResult(async () => {
|
||||
@@ -138,7 +138,7 @@ export const generateTournamentBracket = createServerFn()
|
||||
);
|
||||
|
||||
export const startMatch = createServerFn()
|
||||
.inputValidator(z.string())
|
||||
.validator(z.string())
|
||||
.middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
.handler(async ({ data }) =>
|
||||
toServerResult(async () => {
|
||||
@@ -165,7 +165,7 @@ export const startMatch = createServerFn()
|
||||
);
|
||||
|
||||
export const populateKnockoutBracket = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
.validator(z.object({
|
||||
tournamentId: z.string(),
|
||||
}))
|
||||
.middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
@@ -459,7 +459,7 @@ const endMatchSchema = z.object({
|
||||
ot_count: z.number(),
|
||||
});
|
||||
export const endMatch = createServerFn()
|
||||
.inputValidator(endMatchSchema)
|
||||
.validator(endMatchSchema)
|
||||
.middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
.handler(async ({ data: { matchId, home_cups, away_cups, ot_count } }) =>
|
||||
toServerResult(async () => {
|
||||
@@ -546,7 +546,7 @@ const toggleReactionSchema = z.object({
|
||||
});
|
||||
|
||||
export const toggleMatchReaction = createServerFn()
|
||||
.inputValidator(toggleReactionSchema)
|
||||
.validator(toggleReactionSchema)
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ data: { matchId, emoji }, context }) =>
|
||||
toServerResult(async () => {
|
||||
@@ -606,7 +606,7 @@ export interface Reaction {
|
||||
players: PlayerInfo[];
|
||||
}
|
||||
export const getMatchReactions = createServerFn()
|
||||
.inputValidator(z.string())
|
||||
.validator(z.string())
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ data: matchId, context }) =>
|
||||
toServerResult(async () => {
|
||||
@@ -647,7 +647,7 @@ const matchesBetweenPlayersSchema = z.object({
|
||||
});
|
||||
|
||||
export const getMatchesBetweenPlayers = createServerFn()
|
||||
.inputValidator(matchesBetweenPlayersSchema)
|
||||
.validator(matchesBetweenPlayersSchema)
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ data: { player1Id, player2Id } }) =>
|
||||
toServerResult(async () => {
|
||||
@@ -663,7 +663,7 @@ const matchesBetweenTeamsSchema = z.object({
|
||||
});
|
||||
|
||||
export const getMatchesBetweenTeams = createServerFn()
|
||||
.inputValidator(matchesBetweenTeamsSchema)
|
||||
.validator(matchesBetweenTeamsSchema)
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ data: { team1Id, team2Id } }) =>
|
||||
toServerResult(async () => {
|
||||
|
||||
@@ -3,26 +3,25 @@ import { createServerFn } from "@tanstack/react-start";
|
||||
import { isRedirect } from "@tanstack/react-router";
|
||||
import { Player, playerInputSchema, playerUpdateSchema, PlayerStats } from "@/features/players/types";
|
||||
import { Match } from "@/features/matches/types";
|
||||
import { pbAdmin } from "@/lib/pocketbase/client";
|
||||
import { z } from "zod";
|
||||
import { logger } from ".";
|
||||
import { Logger } from "@/lib/logger";
|
||||
import { getRequest } from "@tanstack/react-start/server";
|
||||
import { toServerResult } from "@/lib/tanstack-query/utils/to-server-result";
|
||||
import { serverFnLoggingMiddleware } from "@/utils/activities";
|
||||
|
||||
const logger = new Logger('Players');
|
||||
|
||||
export const fetchMe = createServerFn()
|
||||
.handler(async () =>
|
||||
.handler(async () =>
|
||||
toServerResult(async () => {
|
||||
const request = getRequest();
|
||||
|
||||
|
||||
try {
|
||||
const context = await getSessionContext(request);
|
||||
|
||||
await pbAdmin.authPromise;
|
||||
const result = await pbAdmin.getPlayerByAuthId(context.userAuthId);
|
||||
|
||||
return {
|
||||
user: result || undefined,
|
||||
roles: context.roles,
|
||||
user: context.player || undefined,
|
||||
roles: context.roles,
|
||||
metadata: context.metadata,
|
||||
phone: context.phone
|
||||
};
|
||||
@@ -35,20 +34,25 @@ export const fetchMe = createServerFn()
|
||||
);
|
||||
|
||||
export const getPlayer = createServerFn()
|
||||
.inputValidator(z.string())
|
||||
.validator(z.string())
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ data }) =>
|
||||
toServerResult<Player>(async () => await pbAdmin.getPlayer(data))
|
||||
.handler(async ({ data }) =>
|
||||
toServerResult<Player>(async () => {
|
||||
const { pbAdmin } = await import("@/lib/pocketbase/client");
|
||||
return await pbAdmin.getPlayer(data);
|
||||
})
|
||||
);
|
||||
|
||||
export const updatePlayer = createServerFn()
|
||||
.inputValidator(playerUpdateSchema)
|
||||
.validator(playerUpdateSchema)
|
||||
.middleware([superTokensFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
.handler(async ({ context, data }) =>
|
||||
.handler(async ({ context, data }) =>
|
||||
toServerResult(async () => {
|
||||
const userAuthId = context.userAuthId;
|
||||
if (!userAuthId) return;
|
||||
|
||||
const { pbAdmin } = await import("@/lib/pocketbase/client");
|
||||
|
||||
const existing = await pbAdmin.getPlayerByAuthId(userAuthId);
|
||||
if (!existing) return;
|
||||
|
||||
@@ -69,13 +73,15 @@ export const updatePlayer = createServerFn()
|
||||
);
|
||||
|
||||
export const createPlayer = createServerFn()
|
||||
.inputValidator(playerInputSchema)
|
||||
.validator(playerInputSchema)
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ context, data }) =>
|
||||
.handler(async ({ context, data }) =>
|
||||
toServerResult(async () => {
|
||||
const userAuthId = context.userAuthId;
|
||||
if (!userAuthId) return;
|
||||
|
||||
const { pbAdmin } = await import("@/lib/pocketbase/client");
|
||||
|
||||
const existing = await pbAdmin.getPlayerByAuthId(userAuthId);
|
||||
if (existing) return;
|
||||
|
||||
@@ -94,21 +100,23 @@ export const createPlayer = createServerFn()
|
||||
);
|
||||
|
||||
export const associatePlayer = createServerFn()
|
||||
.inputValidator(z.string())
|
||||
.validator(z.string())
|
||||
.middleware([superTokensFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
.handler(async ({ context, data }) =>
|
||||
.handler(async ({ context, data }) =>
|
||||
toServerResult(async () => {
|
||||
const userAuthId = context.userAuthId;
|
||||
if (!userAuthId) return;
|
||||
|
||||
const { pbAdmin } = await import("@/lib/pocketbase/client");
|
||||
|
||||
const p = await pbAdmin.getPlayer(data);
|
||||
|
||||
await pbAdmin.updatePlayer(data, {
|
||||
auth_id: userAuthId
|
||||
});
|
||||
|
||||
await setUserMetadata({ data: {
|
||||
player_id: data,
|
||||
await setUserMetadata({ data: {
|
||||
player_id: data,
|
||||
first_name: p?.first_name,
|
||||
last_name: p?.last_name
|
||||
}});
|
||||
@@ -121,49 +129,70 @@ export const associatePlayer = createServerFn()
|
||||
|
||||
export const listPlayers = createServerFn()
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async () =>
|
||||
toServerResult(pbAdmin.listPlayers)
|
||||
.handler(async () =>
|
||||
toServerResult(async () => {
|
||||
const { pbAdmin } = await import("@/lib/pocketbase/client");
|
||||
return await pbAdmin.listPlayers();
|
||||
})
|
||||
);
|
||||
|
||||
export const getUnassociatedPlayers = createServerFn()
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async () =>
|
||||
toServerResult(pbAdmin.getUnassociatedPlayers)
|
||||
.handler(async () =>
|
||||
toServerResult(async () => {
|
||||
const { pbAdmin } = await import("@/lib/pocketbase/client");
|
||||
return await pbAdmin.getUnassociatedPlayers();
|
||||
})
|
||||
);
|
||||
|
||||
export const getPlayerStats = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
.validator(z.object({
|
||||
playerId: z.string(),
|
||||
viewType: z.enum(['all', 'mainline', 'regional']).optional()
|
||||
}))
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ data }) =>
|
||||
toServerResult<PlayerStats>(async () => await pbAdmin.getPlayerStats(data.playerId, data.viewType))
|
||||
toServerResult<PlayerStats>(async () => {
|
||||
const { pbAdmin } = await import("@/lib/pocketbase/client");
|
||||
return await pbAdmin.getPlayerStats(data.playerId, data.viewType);
|
||||
})
|
||||
);
|
||||
|
||||
export const getAllPlayerStats = createServerFn()
|
||||
.inputValidator(z.enum(['all', 'mainline', 'regional']).optional())
|
||||
.validator(z.enum(['all', 'mainline', 'regional']).optional())
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ data }) =>
|
||||
toServerResult<PlayerStats[]>(async () => await pbAdmin.getAllPlayerStats(data))
|
||||
toServerResult<PlayerStats[]>(async () => {
|
||||
const { pbAdmin } = await import("@/lib/pocketbase/client");
|
||||
return await pbAdmin.getAllPlayerStats(data);
|
||||
})
|
||||
);
|
||||
|
||||
export const getPlayerMatches = createServerFn()
|
||||
.inputValidator(z.string())
|
||||
.validator(z.string())
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ data }) =>
|
||||
toServerResult<Match[]>(async () => await pbAdmin.getPlayerMatches(data))
|
||||
toServerResult<Match[]>(async () => {
|
||||
const { pbAdmin } = await import("@/lib/pocketbase/client");
|
||||
return await pbAdmin.getPlayerMatches(data);
|
||||
})
|
||||
);
|
||||
|
||||
export const getUnenrolledPlayers = createServerFn()
|
||||
.inputValidator(z.string())
|
||||
.validator(z.string())
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ data: tournamentId }) =>
|
||||
toServerResult(async () => await pbAdmin.getUnenrolledPlayers(tournamentId))
|
||||
toServerResult(async () => {
|
||||
const { pbAdmin } = await import("@/lib/pocketbase/client");
|
||||
return await pbAdmin.getUnenrolledPlayers(tournamentId);
|
||||
})
|
||||
);
|
||||
|
||||
export const getPlayersActivity = createServerFn()
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async () =>
|
||||
toServerResult<Player[]>(async () => await pbAdmin.getPlayersActivity())
|
||||
toServerResult<Player[]>(async () => {
|
||||
const { pbAdmin } = await import("@/lib/pocketbase/client");
|
||||
return await pbAdmin.getPlayersActivity();
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Box, ColorSwatch, Group, Text } from '@mantine/core';
|
||||
import { updateUserAccentColor } from '@/utils/supertokens';
|
||||
import { updateUserAccentColor } from '@/features/settings/server';
|
||||
import { useAuth } from '@/contexts/auth-context';
|
||||
|
||||
const colors = ['blue', 'red', 'green', 'yellow', 'grape', 'orange', 'pink', 'lime'];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Center, Box, Text, SegmentedControl, MantineColorScheme } from '@mantine/core';
|
||||
import { SunIcon, MoonIcon, Icon, MonitorIcon } from '@phosphor-icons/react'
|
||||
import { updateUserColorScheme } from '@/utils/supertokens';
|
||||
import { updateUserColorScheme } from '@/features/settings/server';
|
||||
import { useAuth } from '@/contexts/auth-context';
|
||||
|
||||
interface ColorSchemeLabelProps {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { createServerFn } from "@tanstack/react-start";
|
||||
import { superTokensFunctionMiddleware } from "@/utils/supertokens";
|
||||
import { serverFnLoggingMiddleware } from "@/utils/activities";
|
||||
|
||||
export const updateUserColorScheme = createServerFn({ method: "POST" })
|
||||
.validator((data: string) => data)
|
||||
.middleware([superTokensFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
.handler(async ({ context, data }) => {
|
||||
const { userAuthId, metadata } = context;
|
||||
if (!userAuthId) return;
|
||||
|
||||
const { updateUserMetadataFields } = await import(
|
||||
"@/utils/supertokens-core.server"
|
||||
);
|
||||
|
||||
await updateUserMetadataFields(userAuthId, {
|
||||
colorScheme: data,
|
||||
});
|
||||
|
||||
return {
|
||||
metadata: {
|
||||
...metadata,
|
||||
colorScheme: data,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const updateUserAccentColor = createServerFn({ method: "POST" })
|
||||
.validator((data: string) => data)
|
||||
.middleware([superTokensFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
.handler(async ({ context, data }) => {
|
||||
const { userAuthId, metadata } = context;
|
||||
if (!userAuthId) return;
|
||||
|
||||
const { updateUserMetadataFields } = await import(
|
||||
"@/utils/supertokens-core.server"
|
||||
);
|
||||
|
||||
await updateUserMetadataFields(userAuthId, {
|
||||
accentColor: data,
|
||||
});
|
||||
|
||||
return {
|
||||
metadata: {
|
||||
...metadata,
|
||||
accentColor: data,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -14,7 +14,6 @@ import { TeamInput } from "../../types";
|
||||
import { teamKeys } from "../../queries";
|
||||
import SongPicker from "./song-picker";
|
||||
import PlayersPicker from "./players-picker";
|
||||
import imageCompression from "browser-image-compression";
|
||||
|
||||
interface TeamFormProps {
|
||||
close: () => void;
|
||||
@@ -120,6 +119,7 @@ const TeamForm = ({
|
||||
};
|
||||
|
||||
try {
|
||||
const { default: imageCompression } = await import("browser-image-compression");
|
||||
processedLogo = await imageCompression(logo, compressionOptions);
|
||||
logger.info("image compressed", {
|
||||
originalSize: logo.size,
|
||||
|
||||
@@ -7,7 +7,6 @@ import { SpotifyTrack } from "@/lib/spotify/types";
|
||||
import SongSearch from "./song-search";
|
||||
import DurationPicker from "./duration-picker";
|
||||
import SongSummary from "./song-summary";
|
||||
import { MusicNote } from "@phosphor-icons/react/dist/ssr";
|
||||
import { MusicNoteIcon } from "@phosphor-icons/react";
|
||||
|
||||
interface Song {
|
||||
|
||||
@@ -16,21 +16,21 @@ export const listTeamInfos = createServerFn()
|
||||
);
|
||||
|
||||
export const getTeam = createServerFn()
|
||||
.inputValidator(z.string())
|
||||
.validator(z.string())
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ data: teamId }) =>
|
||||
toServerResult(() => pbAdmin.getTeam(teamId))
|
||||
);
|
||||
|
||||
export const getTeamInfo = createServerFn()
|
||||
.inputValidator(z.string())
|
||||
.validator(z.string())
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ data: teamId }) =>
|
||||
toServerResult(() => pbAdmin.getTeamInfo(teamId))
|
||||
);
|
||||
|
||||
export const createTeam = createServerFn()
|
||||
.inputValidator(teamInputSchema)
|
||||
.validator(teamInputSchema)
|
||||
.middleware([superTokensFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
.handler(async ({ data, context }) =>
|
||||
toServerResult(async () => {
|
||||
@@ -47,7 +47,7 @@ export const createTeam = createServerFn()
|
||||
);
|
||||
|
||||
export const updateTeam = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
.validator(z.object({
|
||||
id: z.string(),
|
||||
updates: teamUpdateSchema
|
||||
}))
|
||||
@@ -73,14 +73,14 @@ export const updateTeam = createServerFn()
|
||||
);
|
||||
|
||||
export const getTeamStats = createServerFn()
|
||||
.inputValidator(z.string())
|
||||
.validator(z.string())
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ data: teamId }) =>
|
||||
toServerResult(() => pbAdmin.getTeamStats(teamId))
|
||||
);
|
||||
|
||||
export const getTeamMatches = createServerFn()
|
||||
.inputValidator(z.string())
|
||||
.validator(z.string())
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ data }) =>
|
||||
toServerResult<Match[]>(async () => await pbAdmin.getTeamMatches(data))
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { useEditor } from '@tiptap/react';
|
||||
import StarterKit from '@tiptap/starter-kit';
|
||||
import { RichTextEditor } from '@mantine/tiptap';
|
||||
|
||||
interface RulesContentProps {
|
||||
content: string;
|
||||
}
|
||||
|
||||
const RulesContent = ({ content }: RulesContentProps) => {
|
||||
const editor = useEditor({
|
||||
extensions: [StarterKit],
|
||||
content,
|
||||
editable: false,
|
||||
immediatelyRender: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<RichTextEditor editor={editor}>
|
||||
<RichTextEditor.Content />
|
||||
</RichTextEditor>
|
||||
);
|
||||
};
|
||||
|
||||
export default RulesContent;
|
||||
@@ -3,10 +3,10 @@ import Sheet from "@/components/sheet/sheet"
|
||||
import { useSheet } from "@/hooks/use-sheet"
|
||||
import { ListIcon } from "@phosphor-icons/react"
|
||||
import { useTournament } from "../../queries"
|
||||
import { useEditor } from '@tiptap/react';
|
||||
import StarterKit from '@tiptap/starter-kit';
|
||||
import { RichTextEditor } from '@mantine/tiptap';
|
||||
import { Button, Stack } from "@mantine/core"
|
||||
import { Button, Flex, Loader, Stack } from "@mantine/core"
|
||||
import { lazy, Suspense } from "react"
|
||||
|
||||
const RulesContent = lazy(() => import("./rules-content"));
|
||||
|
||||
interface RulesListButtonProps {
|
||||
tournamentId: string;
|
||||
@@ -16,13 +16,6 @@ const RulesListButton: React.FC<RulesListButtonProps> = ({ tournamentId }) => {
|
||||
const { data: tournament } = useTournament(tournamentId);
|
||||
const { open, isOpen, toggle } = useSheet();
|
||||
|
||||
const editor = useEditor({
|
||||
extensions: [StarterKit],
|
||||
content: tournament?.rules || '',
|
||||
editable: false,
|
||||
immediatelyRender: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListButton
|
||||
@@ -33,9 +26,15 @@ const RulesListButton: React.FC<RulesListButtonProps> = ({ tournamentId }) => {
|
||||
|
||||
<Sheet title="Tournament Rules" opened={isOpen} onChange={toggle}>
|
||||
<Stack gap="xs">
|
||||
<RichTextEditor editor={editor}>
|
||||
<RichTextEditor.Content />
|
||||
</RichTextEditor>
|
||||
<Suspense
|
||||
fallback={
|
||||
<Flex justify="center" align="center" w="100%" style={{ minHeight: '25vh' }}>
|
||||
<Loader size="lg" />
|
||||
</Flex>
|
||||
}
|
||||
>
|
||||
<RulesContent content={tournament?.rules || ''} />
|
||||
</Suspense>
|
||||
<Button variant="subtle" c="red" onClick={toggle}>Close</Button>
|
||||
</Stack>
|
||||
</Sheet>
|
||||
@@ -43,4 +42,4 @@ const RulesListButton: React.FC<RulesListButtonProps> = ({ tournamentId }) => {
|
||||
)
|
||||
}
|
||||
|
||||
export default RulesListButton;
|
||||
export default RulesListButton;
|
||||
|
||||
@@ -17,14 +17,14 @@ export const listTournaments = createServerFn()
|
||||
);
|
||||
|
||||
export const createTournament = createServerFn()
|
||||
.inputValidator(tournamentInputSchema)
|
||||
.validator(tournamentInputSchema)
|
||||
.middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
.handler(async ({ data }) =>
|
||||
toServerResult(() => pbAdmin.createTournament(data))
|
||||
);
|
||||
|
||||
export const updateTournament = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
.validator(z.object({
|
||||
id: z.string(),
|
||||
updates: tournamentInputSchema.partial()
|
||||
}))
|
||||
@@ -34,7 +34,7 @@ export const updateTournament = createServerFn()
|
||||
);
|
||||
|
||||
export const getTournament = createServerFn()
|
||||
.inputValidator(z.string())
|
||||
.validator(z.string())
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ data: tournamentId, context }) => {
|
||||
const isAdmin = context.roles.includes("Admin");
|
||||
@@ -47,7 +47,7 @@ export const getCurrentTournament = createServerFn()
|
||||
);
|
||||
|
||||
export const enrollTeam = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
.validator(z.object({
|
||||
tournamentId: z.string(),
|
||||
teamId: z.string()
|
||||
}))
|
||||
@@ -81,7 +81,7 @@ export const enrollTeam = createServerFn()
|
||||
);
|
||||
|
||||
export const unenrollTeam = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
.validator(z.object({
|
||||
tournamentId: z.string(),
|
||||
teamId: z.string()
|
||||
}))
|
||||
@@ -91,21 +91,21 @@ export const unenrollTeam = createServerFn()
|
||||
);
|
||||
|
||||
export const getUnenrolledTeams = createServerFn()
|
||||
.inputValidator(z.string())
|
||||
.validator(z.string())
|
||||
.middleware([superTokensAdminFunctionMiddleware])
|
||||
.handler(async ({ data: tournamentId }) =>
|
||||
toServerResult(() => pbAdmin.getUnenrolledTeams(tournamentId))
|
||||
);
|
||||
|
||||
export const getFreeAgents = createServerFn()
|
||||
.inputValidator(z.string())
|
||||
.validator(z.string())
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ data: tournamentId }) =>
|
||||
toServerResult(() => pbAdmin.getFreeAgents(tournamentId))
|
||||
);
|
||||
|
||||
export const enrollFreeAgent = createServerFn()
|
||||
.inputValidator(z.object({ phone: z.string(), tournamentId: z.string() }))
|
||||
.validator(z.object({ phone: z.string(), tournamentId: z.string() }))
|
||||
.middleware([superTokensFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
.handler(async ({ context, data }) =>
|
||||
toServerResult(async () => {
|
||||
@@ -119,7 +119,7 @@ export const enrollFreeAgent = createServerFn()
|
||||
);
|
||||
|
||||
export const unenrollFreeAgent = createServerFn()
|
||||
.inputValidator(z.object({ tournamentId: z.string() }))
|
||||
.validator(z.object({ tournamentId: z.string() }))
|
||||
.middleware([superTokensFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
.handler(async ({ context, data }) =>
|
||||
toServerResult(async () => {
|
||||
@@ -133,7 +133,7 @@ export const unenrollFreeAgent = createServerFn()
|
||||
);
|
||||
|
||||
export const generateRandomTeams = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
.validator(z.object({
|
||||
tournamentId: z.string(),
|
||||
seed: z.number().optional()
|
||||
}))
|
||||
@@ -313,7 +313,7 @@ export const generateRandomTeams = createServerFn()
|
||||
);
|
||||
|
||||
export const confirmTeamAssignments = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
.validator(z.object({
|
||||
tournamentId: z.string(),
|
||||
assignments: z.array(z.object({
|
||||
player1Id: z.string(),
|
||||
@@ -503,14 +503,14 @@ async function calculateGroupStandings(groupId: string): Promise<GroupStanding[]
|
||||
}
|
||||
|
||||
export const getGroupStandings = createServerFn()
|
||||
.inputValidator(z.string())
|
||||
.validator(z.string())
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ data: groupId }) =>
|
||||
toServerResult(() => calculateGroupStandings(groupId))
|
||||
);
|
||||
|
||||
export const generateKnockoutBracket = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
.validator(z.object({
|
||||
tournamentId: z.string(),
|
||||
}))
|
||||
.middleware([superTokensAdminFunctionMiddleware, serverFnLoggingMiddleware])
|
||||
@@ -711,7 +711,7 @@ export const generateKnockoutBracket = createServerFn()
|
||||
);
|
||||
|
||||
export const adminEnrollPlayer = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
.validator(z.object({
|
||||
playerId: z.string(),
|
||||
tournamentId: z.string()
|
||||
}))
|
||||
@@ -724,7 +724,7 @@ export const adminEnrollPlayer = createServerFn()
|
||||
);
|
||||
|
||||
export const adminUnenrollPlayer = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
.validator(z.object({
|
||||
playerId: z.string(),
|
||||
tournamentId: z.string()
|
||||
}))
|
||||
@@ -737,7 +737,7 @@ export const adminUnenrollPlayer = createServerFn()
|
||||
);
|
||||
|
||||
export const generateGroupStage = createServerFn()
|
||||
.inputValidator(z.object({
|
||||
.validator(z.object({
|
||||
tournamentId: z.string(),
|
||||
groupConfig: z.object({
|
||||
num_groups: z.number(),
|
||||
|
||||
Reference in New Issue
Block a user