significant refactor
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
import { Box, Button, Text, Title } from "@mantine/core";
|
||||
import { Box, Text } from "@mantine/core";
|
||||
import Header from "./header";
|
||||
import { testEvent } from "@/utils/test-event";
|
||||
import { Player } from "@/features/players/types";
|
||||
import TeamList from "@/features/teams/components/team-list";
|
||||
import SwipeableTabs from "@/components/swipeable-tabs";
|
||||
import { usePlayer } from "../../queries";
|
||||
|
||||
interface ProfileProps {
|
||||
player: Player;
|
||||
id: string;
|
||||
}
|
||||
|
||||
const Profile = ({ player }: ProfileProps) => {
|
||||
const Profile = ({ id }: ProfileProps) => {
|
||||
const { data: player } = usePlayer(id);
|
||||
const tabs = [
|
||||
{
|
||||
label: "Overview",
|
||||
@@ -20,10 +20,8 @@ const Profile = ({ player }: ProfileProps) => {
|
||||
content: <Text p="md">Matches feed will go here</Text>
|
||||
},
|
||||
{
|
||||
label: "Teams",
|
||||
content: <>
|
||||
<TeamList teams={player.teams || []} />
|
||||
</>
|
||||
label: "Teams",
|
||||
content: <Text p="md">Teams will go here</Text>
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { updatePlayer } from "@/features/players/server";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { Stack, TextInput } from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import toast from "@/lib/sonner";
|
||||
import { useRouter } from "@tanstack/react-router";
|
||||
import { Player } from "../../types";
|
||||
import Button from "@/components/button";
|
||||
import { useOptimisticMutation } from "@/lib/tanstack-query/hooks";
|
||||
import { playerKeys } from "../../queries";
|
||||
|
||||
interface NameUpdateFormProps {
|
||||
player: Player;
|
||||
@@ -13,8 +12,6 @@ interface NameUpdateFormProps {
|
||||
}
|
||||
|
||||
const NameUpdateForm = ({ player, toggle }: NameUpdateFormProps) => {
|
||||
const router = useRouter();
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
first_name: player.first_name,
|
||||
@@ -23,30 +20,32 @@ const NameUpdateForm = ({ player, toggle }: NameUpdateFormProps) => {
|
||||
validate: {
|
||||
first_name: (value: string | undefined) => {
|
||||
if (!value || value.length === 0) return "First name is required";
|
||||
if (!/^[a-zA-Z\s]{3,20}$/.test(value))
|
||||
return "First name must be 3-20 characters long and contain only letters";
|
||||
if (!/^[a-zA-Z\s]{2,20}$/.test(value))
|
||||
return "First name must be 2-20 characters long and contain only letters";
|
||||
},
|
||||
last_name: (value: string | undefined) => {
|
||||
if (!value || value.length === 0) return "Last name is required";
|
||||
if (!/^[a-zA-Z\s]{3,20}$/.test(value))
|
||||
return "Last name must be 3-20 characters long and contain only letters";
|
||||
if (!/^[a-zA-Z\s]{2,20}$/.test(value))
|
||||
return "Last name must be 2-20 characters long and contain only letters";
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { mutate: updateName, isPending } = useMutation({
|
||||
const { mutate: updateName, isPending } = useOptimisticMutation({
|
||||
mutationFn: async (data: { first_name: string; last_name: string }) =>
|
||||
await updatePlayer({ data }),
|
||||
onSuccess: () => {
|
||||
toggle();
|
||||
toast.success("Name updated successfully!");
|
||||
router.invalidate();
|
||||
},
|
||||
onError: () => {
|
||||
toast.error(
|
||||
"There was an issue updating your name. Please try again later."
|
||||
);
|
||||
onSuccess: toggle,
|
||||
onError: toggle,
|
||||
successMessage: "Name updated successfully!",
|
||||
optimisticUpdate: (oldData, variables) => {
|
||||
if (!oldData) return oldData;
|
||||
return {
|
||||
...oldData,
|
||||
first_name: variables.first_name,
|
||||
last_name: variables.last_name,
|
||||
};
|
||||
},
|
||||
queryKey: playerKeys.details(player.id)
|
||||
});
|
||||
|
||||
const handleSubmit = async (data: {
|
||||
|
||||
@@ -1,23 +1,49 @@
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
import { listPlayers, getPlayer, getUnassociatedPlayers } from "./server";
|
||||
import { useServerSuspenseQuery } from "@/lib/tanstack-query/hooks";
|
||||
import { listPlayers, getPlayer, getUnassociatedPlayers, fetchMe } from "./server";
|
||||
|
||||
const playerKeys = {
|
||||
list: ['players', 'list'] as const,
|
||||
details: (id: string) => ['players', 'details', id] as const,
|
||||
unassociated: ['players','unassociated'] as const,
|
||||
export const playerKeys = {
|
||||
auth: ['auth'],
|
||||
list: ['players', 'list'],
|
||||
details: (id: string) => ['players', 'details', id],
|
||||
unassociated: ['players','unassociated'],
|
||||
};
|
||||
|
||||
export const playerQueries = {
|
||||
list: () => queryOptions({
|
||||
auth: () => ({
|
||||
queryKey: playerKeys.auth,
|
||||
queryFn: async () => await fetchMe()
|
||||
}),
|
||||
list: () => ({
|
||||
queryKey: playerKeys.list,
|
||||
queryFn: listPlayers,
|
||||
queryFn: async () => await listPlayers()
|
||||
}),
|
||||
details: (id: string) => queryOptions({
|
||||
details: (id: string) => ({
|
||||
queryKey: playerKeys.details(id),
|
||||
queryFn: () => getPlayer({ data: id }),
|
||||
queryFn: async () => await getPlayer({ data: id })
|
||||
}),
|
||||
unassociated: () => queryOptions({
|
||||
unassociated: () => ({
|
||||
queryKey: playerKeys.unassociated,
|
||||
queryFn: getUnassociatedPlayers,
|
||||
queryFn: async () => await getUnassociatedPlayers()
|
||||
}),
|
||||
};
|
||||
|
||||
export const useMe = () => {
|
||||
const { queryKey, queryFn } = playerQueries.auth();
|
||||
return useServerSuspenseQuery({
|
||||
queryKey,
|
||||
queryFn,
|
||||
options: {
|
||||
staleTime: 0,
|
||||
refetchOnMount: true
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const usePlayer = (id: string) =>
|
||||
useServerSuspenseQuery(playerQueries.details(id));
|
||||
|
||||
export const usePlayers = () =>
|
||||
useServerSuspenseQuery(playerQueries.list());
|
||||
|
||||
export const useUnassociatedPlayers = () =>
|
||||
useServerSuspenseQuery(playerQueries.unassociated());
|
||||
@@ -1,60 +1,48 @@
|
||||
import { setUserMetadata, superTokensFunctionMiddleware, verifySuperTokensSession } from "@/utils/supertokens";
|
||||
import { createServerFn } from "@tanstack/react-start";
|
||||
import { playerInputSchema, playerUpdateSchema } from "@/features/players/types";
|
||||
import { Player, playerInputSchema, playerUpdateSchema } from "@/features/players/types";
|
||||
import { pbAdmin } from "@/lib/pocketbase/client";
|
||||
import { z } from "zod";
|
||||
import { logger } from ".";
|
||||
import { getWebRequest } from "@tanstack/react-start/server";
|
||||
import { toServerResult } from "@/lib/tanstack-query/utils/to-server-result";
|
||||
|
||||
export const fetchMe = createServerFn()
|
||||
.handler(async ({ response }) => {
|
||||
const request = getWebRequest();
|
||||
const { context } = await verifySuperTokensSession(request, response);
|
||||
.handler(async ({ response }) =>
|
||||
toServerResult(async () => {
|
||||
const request = getWebRequest();
|
||||
const { context } = await verifySuperTokensSession(request, response);
|
||||
|
||||
if (!context || !context.userAuthId) return { user: undefined, roles: [], metadata: {} };
|
||||
if (!context || !context.userAuthId) return { user: undefined, roles: [], metadata: {} };
|
||||
|
||||
try {
|
||||
await pbAdmin.authPromise;
|
||||
const result = await pbAdmin.getPlayerByAuthId(context.userAuthId);
|
||||
logger.info('Fetched player', result);
|
||||
return {
|
||||
user: result || undefined,
|
||||
roles: context.roles,
|
||||
metadata: context.metadata
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Error fetching player:', error);
|
||||
return { user: undefined, roles: context.roles, metadata: context.metadata };
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
export const getPlayer = createServerFn()
|
||||
.validator(z.string())
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ data }) => {
|
||||
try {
|
||||
const player = await pbAdmin.getPlayer(data);
|
||||
return player;
|
||||
} catch (error) {
|
||||
logger.error('Error getting player', error);
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
.handler(async ({ data }) =>
|
||||
toServerResult<Player>(async () => await pbAdmin.getPlayer(data))
|
||||
);
|
||||
|
||||
export const updatePlayer = createServerFn()
|
||||
.validator(playerUpdateSchema)
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ context, data }) => {
|
||||
const userAuthId = context.userAuthId;
|
||||
if (!userAuthId) return;
|
||||
.handler(async ({ context, data }) =>
|
||||
toServerResult(async () => {
|
||||
const userAuthId = context.userAuthId;
|
||||
if (!userAuthId) return;
|
||||
|
||||
try {
|
||||
// Find the player by authId first
|
||||
const existing = await pbAdmin.getPlayerByAuthId(userAuthId);
|
||||
|
||||
if (!existing) return;
|
||||
|
||||
// Update the player
|
||||
const updatedPlayer = await pbAdmin.updatePlayer(
|
||||
existing.id!,
|
||||
{
|
||||
@@ -68,20 +56,17 @@ export const updatePlayer = createServerFn()
|
||||
await setUserMetadata({ data: { first_name: data.first_name, last_name: data.last_name } });
|
||||
|
||||
return updatedPlayer;
|
||||
} catch (error) {
|
||||
logger.error('Error updating player name', error);
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
export const createPlayer = createServerFn()
|
||||
.validator(playerInputSchema)
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ context, data }) => {
|
||||
const userAuthId = context.userAuthId;
|
||||
if (!userAuthId) return;
|
||||
.handler(async ({ context, data }) =>
|
||||
toServerResult(async () => {
|
||||
const userAuthId = context.userAuthId;
|
||||
if (!userAuthId) return;
|
||||
|
||||
try {
|
||||
const existing = await pbAdmin.getPlayerByAuthId(userAuthId);
|
||||
if (existing) return;
|
||||
|
||||
@@ -96,20 +81,17 @@ export const createPlayer = createServerFn()
|
||||
await setUserMetadata({ data: { first_name: data.first_name, last_name: data.last_name, player_id: newPlayer?.id?.toString() } });
|
||||
logger.info('Created player', newPlayer);
|
||||
return newPlayer;
|
||||
} catch (error) {
|
||||
logger.error('Error creating player', error);
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
export const associatePlayer = createServerFn()
|
||||
.validator(z.string())
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async ({ context, data }) => {
|
||||
const userAuthId = context.userAuthId;
|
||||
if (!userAuthId) return;
|
||||
.handler(async ({ context, data }) =>
|
||||
toServerResult(async () => {
|
||||
const userAuthId = context.userAuthId;
|
||||
if (!userAuthId) return;
|
||||
|
||||
try {
|
||||
await pbAdmin.updatePlayer(data, {
|
||||
auth_id: userAuthId
|
||||
});
|
||||
@@ -119,30 +101,17 @@ export const associatePlayer = createServerFn()
|
||||
const player = await pbAdmin.getPlayer(data);
|
||||
logger.info('Associated player', player);
|
||||
return player;
|
||||
} catch (error) {
|
||||
logger.error('Error associating player', error);
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
export const listPlayers = createServerFn()
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async () => {
|
||||
try {
|
||||
return await pbAdmin.listPlayers();
|
||||
} catch (error) {
|
||||
logger.error('Error listing players', error);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
.handler(async () =>
|
||||
toServerResult(pbAdmin.listPlayers)
|
||||
);
|
||||
|
||||
export const getUnassociatedPlayers = createServerFn()
|
||||
.middleware([superTokensFunctionMiddleware])
|
||||
.handler(async () => {
|
||||
try {
|
||||
return await pbAdmin.getUnassociatedPlayers();
|
||||
} catch (error) {
|
||||
logger.error('Error getting unassociated players', error);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
.handler(async () =>
|
||||
toServerResult(pbAdmin.getUnassociatedPlayers)
|
||||
);
|
||||
@@ -13,8 +13,8 @@ export interface Player {
|
||||
|
||||
export const playerInputSchema = z.object({
|
||||
auth_id: z.string().optional(),
|
||||
first_name: z.string().min(3).max(20).regex(/^[a-zA-Z0-9\s]+$/, "First name must be 3-20 characters long and contain only letters and spaces"),
|
||||
last_name: z.string().min(3).max(20).regex(/^[a-zA-Z0-9\s]+$/, "Last name must be 3-20 characters long and contain only letters and spaces"),
|
||||
first_name: z.string().min(2).max(20).regex(/^[a-zA-Z0-9\s]+$/, "First name must be 2-20 characters long and contain only letters and spaces"),
|
||||
last_name: z.string().min(2).max(20).regex(/^[a-zA-Z0-9\s]+$/, "Last name must be 2-20 characters long and contain only letters and spaces"),
|
||||
});
|
||||
|
||||
export const playerUpdateSchema = playerInputSchema.partial();
|
||||
|
||||
Reference in New Issue
Block a user