Compare commits

3 Commits

Author SHA1 Message Date
yohlo
2dfb7c63d3 smoother team form close 2025-10-01 22:29:40 -05:00
yohlo
03b2b54c1f fix logo not updating 2025-10-01 22:27:32 -05:00
yohlo
0910f11228 pb refresh, profile refresh update 2025-10-01 21:34:59 -05:00
5 changed files with 108 additions and 21 deletions

View File

@@ -1,3 +1,4 @@
import { badgeKeys, badgeQueries } from "@/features/badges/queries";
import Profile from "@/features/players/components/profile"; import Profile from "@/features/players/components/profile";
import HeaderSkeleton from "@/features/players/components/profile/header-skeleton"; import HeaderSkeleton from "@/features/players/components/profile/header-skeleton";
import ProfileSkeleton from "@/features/players/components/profile/skeleton"; import ProfileSkeleton from "@/features/players/components/profile/skeleton";
@@ -24,6 +25,14 @@ export const Route = createFileRoute("/_authed/profile/$playerId")({
queryClient, queryClient,
playerQueries.matches(params.playerId) playerQueries.matches(params.playerId)
), ),
prefetchServerQuery(
queryClient,
playerQueries.stats(params.playerId)
),
prefetchServerQuery(
queryClient,
badgeQueries.playerBadges(params.playerId)
),
]); ]);
}, },
loader: ({ params, context }) => ({ loader: ({ params, context }) => ({
@@ -34,7 +43,7 @@ export const Route = createFileRoute("/_authed/profile/$playerId")({
context?.auth.user.id === params.playerId ? "/settings" : undefined, context?.auth.user.id === params.playerId ? "/settings" : undefined,
}, },
withPadding: false, withPadding: false,
refresh: [playerKeys.details(params.playerId), playerKeys.matches(params.playerId), playerKeys.stats(params.playerId)], refresh: [playerKeys.details(params.playerId), playerKeys.matches(params.playerId), playerKeys.stats(params.playerId), badgeKeys.playerBadges(params.playerId)],
}), }),
component: () => { component: () => {
const { playerId } = Route.useParams(); const { playerId } = Route.useParams();

View File

@@ -107,10 +107,9 @@ export const Route = createFileRoute("/api/teams/upload-logo")({
const pbFormData = new FormData(); const pbFormData = new FormData();
pbFormData.append("logo", logoFile); pbFormData.append("logo", logoFile);
const updatedTeam = await pbAdmin.updateTeam( await pbAdmin.updateTeam(teamId, pbFormData as any);
teamId, const updatedTeam = await pbAdmin.getTeam(teamId);
pbFormData as any if (!updatedTeam) throw new Error("Failed to fetch updated team");
);
logger.info("Team logo uploaded successfully", { logger.info("Team logo uploaded successfully", {
teamId, teamId,

View File

@@ -106,11 +106,7 @@ const TeamForm = ({
mutation(teamData, { mutation(teamData, {
onSuccess: async (team: any) => { onSuccess: async (team: any) => {
queryClient.invalidateQueries({ queryKey: teamKeys.list }); close();
queryClient.invalidateQueries({
queryKey: teamKeys.details(team.id),
});
if (logo && team) { if (logo && team) {
try { try {
let processedLogo = logo; let processedLogo = logo;
@@ -152,7 +148,12 @@ const TeamForm = ({
const result = await response.json(); const result = await response.json();
console.log("Logo upload result:", result);
queryClient.invalidateQueries({ queryKey: teamKeys.list });
queryClient.invalidateQueries({
queryKey: teamKeys.details(team.id),
});
queryClient.setQueryData( queryClient.setQueryData(
tournamentKeys.details(result.team!.id), tournamentKeys.details(result.team!.id),
result.team result.team
@@ -164,12 +165,16 @@ const TeamForm = ({
toast.error(logoErrorMessage); toast.error(logoErrorMessage);
logger.error("Team logo upload error", error); logger.error("Team logo upload error", error);
} }
} else {
queryClient.invalidateQueries({ queryKey: teamKeys.list });
queryClient.invalidateQueries({
queryKey: teamKeys.details(team.id),
});
} }
if (team && team.id) { if (team && team.id) {
onSubmit?.(team.id) onSubmit?.(team.id)
} }
close();
}, },
onError: (error: any) => { onError: (error: any) => {
toast.error(`${errorMessage}: ${error.message}`); toast.error(`${errorMessage}: ${error.message}`);

View File

@@ -12,11 +12,21 @@ dotenv.config();
class PocketBaseAdminClient { class PocketBaseAdminClient {
private pb: PocketBase; private pb: PocketBase;
public authPromise: Promise<void>; public authPromise: Promise<void>;
private refreshInterval: NodeJS.Timeout | null = null;
constructor() { constructor() {
this.pb = new PocketBase(process.env.POCKETBASE_URL); this.pb = new PocketBase(process.env.POCKETBASE_URL);
this.pb.beforeSend = (url, options) => { this.pb.beforeSend = async (url, options) => {
if (this.pb.authStore.isValid && this.isTokenExpiringSoon()) {
try {
await this.refreshAuth();
} catch (error) {
console.error('Failed to refresh admin token, re-authenticating:', error);
await this.authenticate();
}
}
options.cache = "no-store"; options.cache = "no-store";
options.headers = { options.headers = {
...options.headers, ...options.headers,
@@ -39,16 +49,72 @@ class PocketBaseAdminClient {
Object.assign(this, createReactionsService(this.pb)); Object.assign(this, createReactionsService(this.pb));
Object.assign(this, createActivitiesService(this.pb)); Object.assign(this, createActivitiesService(this.pb));
Object.assign(this, createBadgesService(this.pb)); Object.assign(this, createBadgesService(this.pb));
this.startTokenRefresh();
}); });
} }
private async authenticate() { private async authenticate() {
try {
await this.pb await this.pb
.collection("_superusers") .collection("_superusers")
.authWithPassword( .authWithPassword(
process.env.POCKETBASE_ADMIN_EMAIL!, process.env.POCKETBASE_ADMIN_EMAIL!,
process.env.POCKETBASE_ADMIN_PASSWORD! process.env.POCKETBASE_ADMIN_PASSWORD!
); );
console.log('PocketBase admin authenticated successfully');
} catch (error) {
console.error('Failed to authenticate PocketBase admin:', error);
throw error;
}
}
private async refreshAuth() {
try {
await this.pb.collection("_superusers").authRefresh();
console.log('PocketBase admin token refreshed');
} catch (error) {
console.error('Failed to refresh PocketBase admin token:', error);
throw error;
}
}
private isTokenExpiringSoon(): boolean {
if (!this.pb.authStore.token) return false;
try {
const payload = JSON.parse(atob(this.pb.authStore.token.split('.')[1]));
const expiresAt = payload.exp * 1000;
const now = Date.now();
const fiveMinutes = 5 * 60 * 1000;
return expiresAt - now < fiveMinutes;
} catch {
return false;
}
}
private startTokenRefresh() {
this.refreshInterval = setInterval(async () => {
try {
await this.refreshAuth();
} catch (error) {
console.error('Periodic token refresh failed, re-authenticating:', error);
try {
await this.authenticate();
} catch (authError) {
console.error('Re-authentication failed:', authError);
}
}
}, 10 * 60 * 1000);
if (typeof process !== 'undefined') {
process.on('beforeExit', () => {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
}
});
}
} }
} }

View File

@@ -72,9 +72,17 @@ export function createTeamsService(pb: PocketBase) {
const result = await pb.collection("teams").update(id, data); const result = await pb.collection("teams").update(id, data);
return transformTeam(await pb.collection("teams").getOne(result.id, { if (data instanceof FormData && data.has('logo')) {
expand: "players, tournaments" await new Promise(resolve => setTimeout(resolve, 100));
})); }
const updated = await pb.collection("teams").getOne(result.id, {
expand: "players, tournaments",
// @ts-ignore - Add cache busting
$cancelKey: Date.now().toString()
});
return transformTeam(updated);
} catch (error) { } catch (error) {
logger.error("PocketBase | Error updating team", error); logger.error("PocketBase | Error updating team", error);
throw error; throw error;