Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
579071f
Update update-project-status.yml
ianpaschal Jun 23, 2025
5360b13
Update updateProjectStatus.js
ianpaschal Jun 23, 2025
8f29c8d
fix: Toast text does not wrap (#87)
ianpaschal Jun 23, 2025
758b72a
fix: Sanitize sign in/sign up inputs (#91)
ianpaschal Jun 23, 2025
38a7ab9
feat: #32 Auto generate avatars & refactor users (#90)
ianpaschal Jun 23, 2025
85fcad1
feat: Improve <TournamentDetailPage/> default tab
ianpaschal Jun 23, 2025
7262497
feat: Improve <TournamentCard/> styling
ianpaschal Jun 23, 2025
44c9e3b
Update mockData.ts
ianpaschal Jun 23, 2025
c83a4e4
feat: Improve <AccordionItem/> disabled state (#97)
ianpaschal Jun 25, 2025
705a854
feat: Hide completed pairings from match check-in (#96)
ianpaschal Jun 25, 2025
62d1e95
bug: Preserve <TournamentPairingsGrid/> internal state (#98)
ianpaschal Jun 25, 2025
e2aa511
feat: #101 Add player count to roster (#103)
ianpaschal Jun 30, 2025
164229b
feat: Show full player names when tournaments require it
ianpaschal Jun 30, 2025
8caccb1
feat: Add activePlayerCount to deep tournaments
ianpaschal Jun 30, 2025
6a6d187
feat: Sort tournament competitors by name
ianpaschal Jun 30, 2025
89594e9
task: Clean-up .card mixin (#102)
ianpaschal Jun 30, 2025
06ecd9d
feat: #99 Improve tournament competitor edit dialog (#104)
ianpaschal Jun 30, 2025
7839d89
Merge branch 'master' into develop
ianpaschal Jun 30, 2025
87452e4
feat: #106 Improve signIn error handling (#107)
ianpaschal Jun 30, 2025
5ded5b6
Update convex/_model/tournamentCompetitors/queries/getTournamentCompe…
ianpaschal Jun 30, 2025
08df6c7
Update convex/_model/users/_helpers/checkUserTournamentForcedName.ts
ianpaschal Jun 30, 2025
85245b1
feat: Hide players with 0 matches from rankings
ianpaschal Jul 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions convex/_generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ import type * as _model_tournaments_queries_getTournamentRankings from "../_mode
import type * as _model_tournaments_queries_getTournaments from "../_model/tournaments/queries/getTournaments.js";
import type * as _model_tournaments_queries_getTournamentsByStatus from "../_model/tournaments/queries/getTournamentsByStatus.js";
import type * as _model_users__helpers_checkUserAuth from "../_model/users/_helpers/checkUserAuth.js";
import type * as _model_users__helpers_checkUserTournamentForcedName from "../_model/users/_helpers/checkUserTournamentForcedName.js";
import type * as _model_users__helpers_checkUserTournamentRelationship from "../_model/users/_helpers/checkUserTournamentRelationship.js";
import type * as _model_users__helpers_getShallowUser from "../_model/users/_helpers/getShallowUser.js";
import type * as _model_users__helpers_redactUser from "../_model/users/_helpers/redactUser.js";
Expand Down Expand Up @@ -308,6 +309,7 @@ declare const fullApi: ApiFromModules<{
"_model/tournaments/queries/getTournaments": typeof _model_tournaments_queries_getTournaments;
"_model/tournaments/queries/getTournamentsByStatus": typeof _model_tournaments_queries_getTournamentsByStatus;
"_model/users/_helpers/checkUserAuth": typeof _model_users__helpers_checkUserAuth;
"_model/users/_helpers/checkUserTournamentForcedName": typeof _model_users__helpers_checkUserTournamentForcedName;
"_model/users/_helpers/checkUserTournamentRelationship": typeof _model_users__helpers_checkUserTournamentRelationship;
"_model/users/_helpers/getShallowUser": typeof _model_users__helpers_getShallowUser;
"_model/users/_helpers/redactUser": typeof _model_users__helpers_redactUser;
Expand Down
4 changes: 2 additions & 2 deletions convex/_model/fowV4/aggregateFowV4TournamentData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,11 @@ export const aggregateFowV4TournamentData = async (
}

return {
players: flattenFowV4StatMap(playerStats).map(({ id, stats }) => ({
players: flattenFowV4StatMap(playerStats).filter(({ gamesPlayed }) => gamesPlayed).map(({ id, stats }) => ({
id,
stats,
})),
competitors: flattenFowV4StatMap(competitorStats).map(({ id, stats }) => ({
competitors: flattenFowV4StatMap(competitorStats).filter(({ gamesPlayed }) => gamesPlayed).map(({ id, stats }) => ({
id,
stats,
...competitorMeta[id],
Expand Down
3 changes: 2 additions & 1 deletion convex/_model/fowV4/flattenFowV4StatMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {

export const flattenFowV4StatMap = <T extends FowV4StatId>(
statMap: Record<T, FowV4TournamentExtendedStats>,
): { id: T; stats: FowV4TournamentFlatExtendedStats }[] => {
): { id: T; stats: FowV4TournamentFlatExtendedStats, gamesPlayed: number }[] => {
const statList = Object.entries(statMap) as [T, FowV4TournamentExtendedStats][];
return statList.map(([key, stats]) => {
const flatStats = {} as FowV4TournamentFlatExtendedStats;
Expand All @@ -27,6 +27,7 @@ export const flattenFowV4StatMap = <T extends FowV4StatId>(
return {
id: key,
stats: flatStats,
gamesPlayed: stats.gamesPlayed ?? 0,
};
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,22 @@ export const getTournamentCompetitors = async (
ctx: QueryCtx,
): Promise<DeepTournamentCompetitor[]> => {
const tournamentCompetitors = await ctx.db.query('tournamentCompetitors').collect();
return await Promise.all(tournamentCompetitors.map(
const deepTournamentCompetitors = await Promise.all(tournamentCompetitors.map(
async (item) => await deepenTournamentCompetitor(ctx, item),
));
return deepTournamentCompetitors.sort((a, b) => {
const getSortValue = (competitor: DeepTournamentCompetitor): string => {
if (competitor.teamName) {
return competitor.teamName;
}
if (competitor.players[0].user.familyName) {
return competitor.players[0].user.familyName;
}
if (competitor.players[0].user.username) {
return competitor.players[0].user.username;
}
return '';
};
return getSortValue(a).localeCompare(getSortValue(b));
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,22 @@ export const getTournamentCompetitorsByTournament = async (
const tournamentCompetitors = await ctx.db.query('tournamentCompetitors')
.withIndex('by_tournament_id', (q) => q.eq('tournamentId', args.tournamentId))
.collect();
return await Promise.all(tournamentCompetitors.map(
const deepTournamentCompetitors = await Promise.all(tournamentCompetitors.map(
async (item) => await deepenTournamentCompetitor(ctx, item),
));
return deepTournamentCompetitors.sort((a, b) => {
const getSortValue = (competitor: DeepTournamentCompetitor): string => {
if (competitor.teamName) {
return competitor.teamName;
}
if (competitor.players[0]?.user.familyName) {
return competitor.players[0].user.familyName;
}
if (competitor.players[0]?.user.username) {
return competitor.players[0].user.username;
}
return '';
};
return getSortValue(a).localeCompare(getSortValue(b));
});
};
2 changes: 2 additions & 0 deletions convex/_model/tournaments/_helpers/deepenTournament.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const deepenTournament = async (

// Computed properties (easy to do, but used so frequently, it's nice to include them by default)
const playerCount = playerUserIds.length;
const activePlayerCount = activePlayerUserIds.length;
const maxPlayers = tournament.maxCompetitors * tournament.competitorSize;
const useTeams = tournament.competitorSize > 1;

Expand All @@ -45,6 +46,7 @@ export const deepenTournament = async (
logoUrl,
bannerUrl,
competitorCount,
activePlayerCount,
playerCount,
playerUserIds,
activePlayerUserIds,
Expand Down
34 changes: 34 additions & 0 deletions convex/_model/users/_helpers/checkUserTournamentForcedName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Id } from '../../../_generated/dataModel';
import { QueryCtx } from '../../../_generated/server';
import { getTournamentUserIds } from '../../../_model/tournaments';

export const checkUserTournamentForcedName = async (
ctx: QueryCtx,
userIdA?: Id<'users'> | null,
userIdB?: Id<'users'> | null,
): Promise<boolean> => {
if (!userIdA || !userIdB) {
return false;
}

const tournaments = await ctx.db.query('tournaments').collect();

// Check each tournament for a relationship, return true if one is found
// Check each tournament for a relationship, return true if one is found
for (const { _id, organizerUserIds, requireRealNames } of tournaments) {
const playerUserIds = await getTournamentUserIds(ctx, _id);

// Merge all organizer IDs and player IDs into one set
const allTournamentUserIds = new Set([
...organizerUserIds,
...playerUserIds,
]);

// If the set contains both user IDs, they were at the same tournament
if (allTournamentUserIds.has(userIdA) && allTournamentUserIds.has(userIdB) && requireRealNames) {
return true;
}
}

return false;
};
7 changes: 6 additions & 1 deletion convex/_model/users/_helpers/redactUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { getAuthUserId } from '@convex-dev/auth/server';
import { Doc } from '../../../_generated/dataModel';
import { QueryCtx } from '../../../_generated/server';
import { getStorageUrl } from '../../common/_helpers/getStorageUrl';
import { checkUserTournamentForcedName } from './checkUserTournamentForcedName';
import { checkUserTournamentRelationship } from './checkUserTournamentRelationship';

/**
Expand Down Expand Up @@ -51,11 +52,15 @@ export const redactUser = async (
// If user is querying someone they are in a tournament with
const hasTournamentRelationship = await checkUserTournamentRelationship(ctx, userId, user._id);

// If user is querying someone they are in a tournament with which requires real names
const requiredByTournament = await checkUserTournamentForcedName(ctx, userId, user._id);

// Add name information if allowed
if (
(user?.nameVisibility === 'public') ||
(user?.nameVisibility === 'friends' && hasFriendRelationship) ||
(user?.nameVisibility === 'tournaments' && (hasFriendRelationship || hasTournamentRelationship))
(user?.nameVisibility === 'tournaments' && (hasFriendRelationship || hasTournamentRelationship)) ||
requiredByTournament
) {
limitedUser.givenName = user.givenName;
limitedUser.familyName = user.familyName;
Expand Down
5 changes: 1 addition & 4 deletions src/components/AccountMenu/AccountMenu.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,11 @@

.Content {
@include flex.column($gap: 0.25rem);
@include variants.card($elevated: true);
@include animate.duration-quick;
@include corners.normal;
@include shadows.elevated;
@include variants.card;

margin: 0.25rem 0;
padding: 0.25rem;
background-color: var(--card-bg);

@include animate.style-pop; // Must list last because it contains nested declarations
}
Expand Down
5 changes: 1 addition & 4 deletions src/components/AvatarEditable/AvatarEditable.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,11 @@
.ActionsMenu {
@include flex.column($gap: 0.25rem);
@include animate.duration-quick;
@include corners.normal;
@include shadows.elevated;
@include variants.card;
@include variants.card($elevated: true);

z-index: 10000;
margin: 0.25rem 0;
padding: 0.25rem;
background-color: var(--card-bg);

@include animate.style-pop; // Must list last because it contains nested declarations
}
Expand Down
12 changes: 10 additions & 2 deletions src/components/FowV4MatchResultForm/FowV4MatchResultForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { SubmitHandler, useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import clsx from 'clsx';

import { MatchResultId, TournamentPairingId } from '~/api';
import {
MatchResultId,
TournamentPairingId,
UserId,
} from '~/api';
import { useAuth } from '~/components/AuthProvider';
import { ConfirmationDialog, useConfirmationDialog } from '~/components/ConfirmationDialog';
import { FowV4MatchResultDetails } from '~/components/FowV4MatchResultDetails';
Expand Down Expand Up @@ -119,7 +123,7 @@ export const FowV4MatchResultForm = ({

const resultForOptions = [
{ value: 'single', label: 'Single Match' },
...(tournamentPairings || []).map((pairing) => ({
...(tournamentPairings || []).filter((pairing) => pairing.matchResultsProgress.submitted < pairing.matchResultsProgress.required).map((pairing) => ({
value: pairing._id,
label: getTournamentPairingDisplayName(pairing),
})),
Expand All @@ -128,6 +132,10 @@ export const FowV4MatchResultForm = ({
const handleChangeResultFor = (value?: SelectValue): void => {
if (value) {
setTournamentPairingId(value as TournamentPairingId);
if (user && value === 'single') {
form.setValue('player0UserId', user._id);
form.setValue('player1UserId', '' as UserId);
}
}
};

Expand Down
3 changes: 0 additions & 3 deletions src/components/MatchResultCard/MatchResultCard.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@

.MatchResultCard {
@include flex.column($gap: 0);

@include variants.card;
@include shadows.surface;
@include corners.normal;

position: relative;
overflow: hidden;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,10 @@
}

.FactionDetails {
@include variants.card;
@include shadows.elevated;
@include variants.card($elevated: true);
@include animate.duration-quick;
@include animate.style-pop;
@include flex.column($gap: 0);

margin: 0.25rem 0;
background-color: var(--card-bg);
border-radius: variables.$corner-radius;
}
3 changes: 1 addition & 2 deletions src/components/ToastProvider/ToastProvider.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@
.Root {
--icon-size: 1.25rem;

@include corners.wide;
@include variants.card;
@include variants.card($elevated: true);
@include text.small;

display: flex;
Expand Down
2 changes: 0 additions & 2 deletions src/components/TournamentCard/TournamentCard.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
--banner-wide-size: 14rem;

@include variants.card;
@include shadows.surface;
@include corners.normal;
@include text.ui;

position: relative;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
DialogActions,
DialogHeader,
} from '~/components/generic/Dialog';
import { ScrollArea } from '~/components/generic/ScrollArea';
import { TournamentCompetitorForm, TournamentCompetitorSubmitData } from '~/components/TournamentCompetitorForm';
import { useUpdateTournamentCompetitor } from '~/services/tournamentCompetitors';
import { useTournamentCompetitorEditDialog } from './TournamentCompetitorEditDialog.hooks';
Expand All @@ -25,22 +26,27 @@ export const TournamentCompetitorEditDialog = (): JSX.Element => {
if (!data) {
return;
}

const { players, ...restData } = formData;
updateTournamentCompetitor({
id: data?.tournamentCompetitor._id,
...formData,
...restData,
players: players.filter((player) => player.userId),
});
};

return (
<ControlledDialog id={id} disabled={loading} width="small">
<DialogHeader title="Edit Team" onCancel={close} />
<TournamentCompetitorForm
id={FORM_ID}
className={styles.Form}
tournamentCompetitor={data?.tournamentCompetitor}
onSubmit={handleSubmit}
loading={loading}
/>
<ScrollArea>
<TournamentCompetitorForm
id={FORM_ID}
className={styles.Form}
tournamentCompetitor={data?.tournamentCompetitor}
onSubmit={handleSubmit}
loading={loading}
/>
</ScrollArea>
<DialogActions>
<Button variant="secondary" onClick={close} disabled={loading}>Cancel</Button>
<Button form={FORM_ID} type="submit" disabled={loading}>Save Changes</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
row-gap: 0.5rem;
column-gap: 1rem;
align-items: center;

&_AddButton {
grid-column: 1/-1;
}
}

&_SinglePlayer {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,19 @@ export const createSchema = (
),
}).superRefine((data, ctx) => {
const activeCount = data.players.filter((player) => player.active).length;
if (status === 'active') {
if (activeCount < competitorSize) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `At least ${competitorSize} player(s) must be active.`,
path: ['players'],
});
}
if (activeCount > competitorSize) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Only ${competitorSize} player(s) may be active.`,
path: ['players'],
});
}
if (activeCount < competitorSize && status === 'active') {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `At least ${competitorSize} players must be active.`,
path: ['players'],
});
}
if (activeCount > competitorSize) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Only ${competitorSize} players may be active.`,
path: ['players'],
});
}
if (otherCompetitors.find((c) => c.teamName?.toLowerCase() === data.teamName.trim().toLowerCase())) {
ctx.addIssue({
Expand All @@ -49,17 +47,10 @@ export const defaultValues: DeepPartial<FormData> = {
players: [],
};

export const getDefaultValues = (competitorSize: number, existingCompetitor?: TournamentCompetitor): DeepPartial<FormData> => {
const emptyPlayers = Array.from({ length: competitorSize }, () => ({
active: true,
userId: '',
}));
const existingPlayers = (existingCompetitor?.players || []).map(({ active, user }) => ({
export const getDefaultValues = (competitorSize: number, existingCompetitor?: TournamentCompetitor): DeepPartial<FormData> => ({
teamName: existingCompetitor?.teamName ?? '',
players: (existingCompetitor?.players || []).map(({ active, user }) => ({
active,
userId: user._id,
}));
return {
teamName: existingCompetitor?.teamName ?? '',
players: existingPlayers.length ? existingPlayers : emptyPlayers,
};
};
})),
});
Loading