Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 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
e681f8d
feat: #112 Add more mercenary team options (#113)
ianpaschal Jul 9, 2025
24eabea
feat: #110 Add manual table assignments (#111)
ianpaschal Jul 9, 2025
2b84ee9
fix: Ensure round 0 rankings can be included
ianpaschal Jul 14, 2025
66302d7
Refactor tournament actions (#114)
ianpaschal Jul 15, 2025
76995ba
chore: Improve mock match result creation
ianpaschal Jul 15, 2025
4e0febc
feat: Allow matchResult.playedAt to be date string or number
ianpaschal Jul 15, 2025
6999f46
Merge branch 'develop' of https://github.com/ianpaschal/combat-comman…
ianpaschal Jul 15, 2025
8bf4fbc
feat: #115 Hide match result battle plans (#116)
ianpaschal Jul 15, 2025
9b0a057
feat: Set page title based on <PageWrapper/> title prop
ianpaschal Jul 15, 2025
31d791f
Merge branch 'develop' into release-v1.7.0
ianpaschal Jul 16, 2025
3de4e63
fix: Use <IdentityBadge/> to fix player name spacing on match results
ianpaschal Jul 18, 2025
427803e
fix: Correctly include match results relevant to a tournament
ianpaschal Jul 18, 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
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"figtree",
"hookform",
"Korps",
"Landsknechte",
"LFTF",
"Mapbox",
"merc",
Expand Down
31 changes: 31 additions & 0 deletions convex/_fixtures/fowV4/createMockFowV4MatchResultData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { CreateMatchResultArgs } from '../../_model/matchResults';

export const createMockFowV4MatchResultData = (
data: Partial<Omit<CreateMatchResultArgs, 'player0UserId'>>,
): CreateMatchResultArgs => {
const outcomeType = Math.random() > 0.25 ? 'objective_taken' : 'time_out';
return {
playedAt: new Date().toISOString(),
details: {
attacker: 0,
firstTurn: 0,
missionId: 'flames_of_war_v4::mission::2023_04_spearpoint',
outcomeType,
player0BattlePlan: 'attack',
player0UnitsLost: Math.round(Math.random() * 5) + 2,
player1BattlePlan: 'attack',
player1UnitsLost: Math.round(Math.random() * 5) + 2,
turnsPlayed: Math.round(Math.random() * 5) + 2,
winner: outcomeType === 'time_out' ? -1 : (Math.random() > 0.5 ? 1 : 0),
},
gameSystemConfig: {
points: 100,
eraId: 'flames_of_war_v4::era::late_war',
lessonsFromTheFrontVersionId: 'flames_of_war_v4::lessons_from_the_front_version::2024_03',
missionPackId: 'flames_of_war_v4::mission_pack::2023_04',
missionMatrixId: 'flames_of_war_v4::mission_matrix::2023_04_extended',
},
gameSystemId: 'flames_of_war_v4',
...data,
};
};
22 changes: 16 additions & 6 deletions convex/_generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
FilterApi,
FunctionReference,
} from "convex/server";
import type * as _fixtures_fowV4_createMockFowV4MatchResultData from "../_fixtures/fowV4/createMockFowV4MatchResultData.js";
import type * as _model_common__helpers_checkAuth from "../_model/common/_helpers/checkAuth.js";
import type * as _model_common__helpers_clamp from "../_model/common/_helpers/clamp.js";
import type * as _model_common__helpers_filterWithSearchTerm from "../_model/common/_helpers/filterWithSearchTerm.js";
Expand Down Expand Up @@ -52,6 +53,7 @@ import type * as _model_matchResultLikes_queries_getMatchResultLike from "../_mo
import type * as _model_matchResultLikes_queries_getMatchResultLikesByMatchResult from "../_model/matchResultLikes/queries/getMatchResultLikesByMatchResult.js";
import type * as _model_matchResultLikes_queries_getMatchResultLikesByUser from "../_model/matchResultLikes/queries/getMatchResultLikesByUser.js";
import type * as _model_matchResults__helpers_checkMatchResultAuth from "../_model/matchResults/_helpers/checkMatchResultAuth.js";
import type * as _model_matchResults__helpers_checkMatchResultBattlePlanVisibility from "../_model/matchResults/_helpers/checkMatchResultBattlePlanVisibility.js";
import type * as _model_matchResults__helpers_deepenMatchResult from "../_model/matchResults/_helpers/deepenMatchResult.js";
import type * as _model_matchResults__helpers_getShallowMatchResult from "../_model/matchResults/_helpers/getShallowMatchResult.js";
import type * as _model_matchResults_fields from "../_model/matchResults/fields.js";
Expand All @@ -66,6 +68,7 @@ import type * as _model_matchResults_queries_getMatchResultsByTournament from ".
import type * as _model_matchResults_queries_getMatchResultsByTournamentPairing from "../_model/matchResults/queries/getMatchResultsByTournamentPairing.js";
import type * as _model_matchResults_queries_getMatchResultsByTournamentRound from "../_model/matchResults/queries/getMatchResultsByTournamentRound.js";
import type * as _model_tournamentCompetitors__helpers_deepenTournamentCompetitor from "../_model/tournamentCompetitors/_helpers/deepenTournamentCompetitor.js";
import type * as _model_tournamentCompetitors__helpers_sortTournamentCompetitorsByName from "../_model/tournamentCompetitors/_helpers/sortTournamentCompetitorsByName.js";
import type * as _model_tournamentCompetitors_fields from "../_model/tournamentCompetitors/fields.js";
import type * as _model_tournamentCompetitors_index from "../_model/tournamentCompetitors/index.js";
import type * as _model_tournamentCompetitors_mutations_addTournamentCompetitorPlayer from "../_model/tournamentCompetitors/mutations/addTournamentCompetitorPlayer.js";
Expand All @@ -80,13 +83,15 @@ import type * as _model_tournamentCompetitors_queries_getTournamentCompetitorsBy
import type * as _model_tournamentPairings__helpers_assignBye from "../_model/tournamentPairings/_helpers/assignBye.js";
import type * as _model_tournamentPairings__helpers_deepenTournamentPairing from "../_model/tournamentPairings/_helpers/deepenTournamentPairing.js";
import type * as _model_tournamentPairings__helpers_generateDraftPairings from "../_model/tournamentPairings/_helpers/generateDraftPairings.js";
import type * as _model_tournamentPairings__helpers_generateTableAssignments from "../_model/tournamentPairings/_helpers/generateTableAssignments.js";
import type * as _model_tournamentPairings__helpers_getTournamentPairingDeep from "../_model/tournamentPairings/_helpers/getTournamentPairingDeep.js";
import type * as _model_tournamentPairings__helpers_getTournamentPairingShallow from "../_model/tournamentPairings/_helpers/getTournamentPairingShallow.js";
import type * as _model_tournamentPairings__helpers_shuffle from "../_model/tournamentPairings/_helpers/shuffle.js";
import type * as _model_tournamentPairings__helpers_sortByRank from "../_model/tournamentPairings/_helpers/sortByRank.js";
import type * as _model_tournamentPairings__helpers_sortCompetitorPairs from "../_model/tournamentPairings/_helpers/sortCompetitorPairs.js";
import type * as _model_tournamentPairings__helpers_sortPairingsByTable from "../_model/tournamentPairings/_helpers/sortPairingsByTable.js";
import type * as _model_tournamentPairings_fields from "../_model/tournamentPairings/fields.js";
import type * as _model_tournamentPairings_index from "../_model/tournamentPairings/index.js";
import type * as _model_tournamentPairings_mutations_createTournamentPairings from "../_model/tournamentPairings/mutations/createTournamentPairings.js";
import type * as _model_tournamentPairings_queries_getActiveTournamentPairingsByUser from "../_model/tournamentPairings/queries/getActiveTournamentPairingsByUser.js";
import type * as _model_tournamentPairings_queries_getDraftTournamentPairings from "../_model/tournamentPairings/queries/getDraftTournamentPairings.js";
import type * as _model_tournamentPairings_queries_getTournamentPairing from "../_model/tournamentPairings/queries/getTournamentPairing.js";
Expand All @@ -112,13 +117,13 @@ import type * as _model_tournaments__helpers_getTournamentShallow from "../_mode
import type * as _model_tournaments__helpers_getTournamentUserIds from "../_model/tournaments/_helpers/getTournamentUserIds.js";
import type * as _model_tournaments_fields from "../_model/tournaments/fields.js";
import type * as _model_tournaments_index from "../_model/tournaments/index.js";
import type * as _model_tournaments_mutations_closeTournamentRound from "../_model/tournaments/mutations/closeTournamentRound.js";
import type * as _model_tournaments_mutations_createTournament from "../_model/tournaments/mutations/createTournament.js";
import type * as _model_tournaments_mutations_deleteTournament from "../_model/tournaments/mutations/deleteTournament.js";
import type * as _model_tournaments_mutations_endTournament from "../_model/tournaments/mutations/endTournament.js";
import type * as _model_tournaments_mutations_openTournamentRound from "../_model/tournaments/mutations/openTournamentRound.js";
import type * as _model_tournaments_mutations_endTournamentRound from "../_model/tournaments/mutations/endTournamentRound.js";
import type * as _model_tournaments_mutations_publishTournament from "../_model/tournaments/mutations/publishTournament.js";
import type * as _model_tournaments_mutations_startTournament from "../_model/tournaments/mutations/startTournament.js";
import type * as _model_tournaments_mutations_startTournamentRound from "../_model/tournaments/mutations/startTournamentRound.js";
import type * as _model_tournaments_mutations_updateTournament from "../_model/tournaments/mutations/updateTournament.js";
import type * as _model_tournaments_queries_getTournament from "../_model/tournaments/queries/getTournament.js";
import type * as _model_tournaments_queries_getTournamentOpenRound from "../_model/tournaments/queries/getTournamentOpenRound.js";
Expand Down Expand Up @@ -196,6 +201,7 @@ import type * as utils from "../utils.js";
* ```
*/
declare const fullApi: ApiFromModules<{
"_fixtures/fowV4/createMockFowV4MatchResultData": typeof _fixtures_fowV4_createMockFowV4MatchResultData;
"_model/common/_helpers/checkAuth": typeof _model_common__helpers_checkAuth;
"_model/common/_helpers/clamp": typeof _model_common__helpers_clamp;
"_model/common/_helpers/filterWithSearchTerm": typeof _model_common__helpers_filterWithSearchTerm;
Expand Down Expand Up @@ -235,6 +241,7 @@ declare const fullApi: ApiFromModules<{
"_model/matchResultLikes/queries/getMatchResultLikesByMatchResult": typeof _model_matchResultLikes_queries_getMatchResultLikesByMatchResult;
"_model/matchResultLikes/queries/getMatchResultLikesByUser": typeof _model_matchResultLikes_queries_getMatchResultLikesByUser;
"_model/matchResults/_helpers/checkMatchResultAuth": typeof _model_matchResults__helpers_checkMatchResultAuth;
"_model/matchResults/_helpers/checkMatchResultBattlePlanVisibility": typeof _model_matchResults__helpers_checkMatchResultBattlePlanVisibility;
"_model/matchResults/_helpers/deepenMatchResult": typeof _model_matchResults__helpers_deepenMatchResult;
"_model/matchResults/_helpers/getShallowMatchResult": typeof _model_matchResults__helpers_getShallowMatchResult;
"_model/matchResults/fields": typeof _model_matchResults_fields;
Expand All @@ -249,6 +256,7 @@ declare const fullApi: ApiFromModules<{
"_model/matchResults/queries/getMatchResultsByTournamentPairing": typeof _model_matchResults_queries_getMatchResultsByTournamentPairing;
"_model/matchResults/queries/getMatchResultsByTournamentRound": typeof _model_matchResults_queries_getMatchResultsByTournamentRound;
"_model/tournamentCompetitors/_helpers/deepenTournamentCompetitor": typeof _model_tournamentCompetitors__helpers_deepenTournamentCompetitor;
"_model/tournamentCompetitors/_helpers/sortTournamentCompetitorsByName": typeof _model_tournamentCompetitors__helpers_sortTournamentCompetitorsByName;
"_model/tournamentCompetitors/fields": typeof _model_tournamentCompetitors_fields;
"_model/tournamentCompetitors/index": typeof _model_tournamentCompetitors_index;
"_model/tournamentCompetitors/mutations/addTournamentCompetitorPlayer": typeof _model_tournamentCompetitors_mutations_addTournamentCompetitorPlayer;
Expand All @@ -263,13 +271,15 @@ declare const fullApi: ApiFromModules<{
"_model/tournamentPairings/_helpers/assignBye": typeof _model_tournamentPairings__helpers_assignBye;
"_model/tournamentPairings/_helpers/deepenTournamentPairing": typeof _model_tournamentPairings__helpers_deepenTournamentPairing;
"_model/tournamentPairings/_helpers/generateDraftPairings": typeof _model_tournamentPairings__helpers_generateDraftPairings;
"_model/tournamentPairings/_helpers/generateTableAssignments": typeof _model_tournamentPairings__helpers_generateTableAssignments;
"_model/tournamentPairings/_helpers/getTournamentPairingDeep": typeof _model_tournamentPairings__helpers_getTournamentPairingDeep;
"_model/tournamentPairings/_helpers/getTournamentPairingShallow": typeof _model_tournamentPairings__helpers_getTournamentPairingShallow;
"_model/tournamentPairings/_helpers/shuffle": typeof _model_tournamentPairings__helpers_shuffle;
"_model/tournamentPairings/_helpers/sortByRank": typeof _model_tournamentPairings__helpers_sortByRank;
"_model/tournamentPairings/_helpers/sortCompetitorPairs": typeof _model_tournamentPairings__helpers_sortCompetitorPairs;
"_model/tournamentPairings/_helpers/sortPairingsByTable": typeof _model_tournamentPairings__helpers_sortPairingsByTable;
"_model/tournamentPairings/fields": typeof _model_tournamentPairings_fields;
"_model/tournamentPairings/index": typeof _model_tournamentPairings_index;
"_model/tournamentPairings/mutations/createTournamentPairings": typeof _model_tournamentPairings_mutations_createTournamentPairings;
"_model/tournamentPairings/queries/getActiveTournamentPairingsByUser": typeof _model_tournamentPairings_queries_getActiveTournamentPairingsByUser;
"_model/tournamentPairings/queries/getDraftTournamentPairings": typeof _model_tournamentPairings_queries_getDraftTournamentPairings;
"_model/tournamentPairings/queries/getTournamentPairing": typeof _model_tournamentPairings_queries_getTournamentPairing;
Expand All @@ -295,13 +305,13 @@ declare const fullApi: ApiFromModules<{
"_model/tournaments/_helpers/getTournamentUserIds": typeof _model_tournaments__helpers_getTournamentUserIds;
"_model/tournaments/fields": typeof _model_tournaments_fields;
"_model/tournaments/index": typeof _model_tournaments_index;
"_model/tournaments/mutations/closeTournamentRound": typeof _model_tournaments_mutations_closeTournamentRound;
"_model/tournaments/mutations/createTournament": typeof _model_tournaments_mutations_createTournament;
"_model/tournaments/mutations/deleteTournament": typeof _model_tournaments_mutations_deleteTournament;
"_model/tournaments/mutations/endTournament": typeof _model_tournaments_mutations_endTournament;
"_model/tournaments/mutations/openTournamentRound": typeof _model_tournaments_mutations_openTournamentRound;
"_model/tournaments/mutations/endTournamentRound": typeof _model_tournaments_mutations_endTournamentRound;
"_model/tournaments/mutations/publishTournament": typeof _model_tournaments_mutations_publishTournament;
"_model/tournaments/mutations/startTournament": typeof _model_tournaments_mutations_startTournament;
"_model/tournaments/mutations/startTournamentRound": typeof _model_tournaments_mutations_startTournamentRound;
"_model/tournaments/mutations/updateTournament": typeof _model_tournaments_mutations_updateTournament;
"_model/tournaments/queries/getTournament": typeof _model_tournaments_queries_getTournament;
"_model/tournaments/queries/getTournamentOpenRound": typeof _model_tournaments_queries_getTournamentOpenRound;
Expand Down
3 changes: 2 additions & 1 deletion convex/_model/fowV4/calculateFowV4MatchResultScore.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Doc } from '../../_generated/dataModel';
import { DeepMatchResult } from '../matchResults';

/**
* Calculate the Victory Points (i.e. score) for a given match result.
Expand All @@ -9,7 +10,7 @@ import { Doc } from '../../_generated/dataModel';
* @param matchResult - The match result to score
* @returns - A tuple with the scores for player 0 and 1 respectively
*/
export const calculateFowV4MatchResultScore = (matchResult: Doc<'matchResults'>): [number, number] => {
export const calculateFowV4MatchResultScore = (matchResult: Doc<'matchResults'> | DeepMatchResult): [number, number] => {

// TODO: Add some guards in case matchResult is not FowV4

Expand Down
3 changes: 2 additions & 1 deletion convex/_model/fowV4/extractFowV4MatchResultBaseStats.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Doc } from '../../_generated/dataModel';
import { DeepMatchResult } from '../matchResults';
import { calculateFowV4MatchResultScore } from './calculateFowV4MatchResultScore';
import { FowV4BaseStats } from './types';

Expand All @@ -9,7 +10,7 @@ import { FowV4BaseStats } from './types';
* @returns
*/

export const extractFowV4MatchResultBaseStats = (matchResult: Doc<'matchResults'>): [FowV4BaseStats, FowV4BaseStats] => {
export const extractFowV4MatchResultBaseStats = (matchResult: Doc<'matchResults'> | DeepMatchResult): [FowV4BaseStats, FowV4BaseStats] => {
const score = calculateFowV4MatchResultScore(matchResult);
return [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { getAuthUserId } from '@convex-dev/auth/server';

import { Doc } from '../../../_generated/dataModel';
import { QueryCtx } from '../../../_generated/server';
import { getTournamentShallow } from '../../../_model/tournaments';
import { deepenTournamentPairing } from '../../tournamentPairings';

/**
* Checks if a match result's battle plans should be visible or not.
*
* @param ctx - Convex query context
* @param matchResult - Raw match result document
* @returns True if the battle plans should be visible, false if not
*/
export const checkMatchResultBattlePlanVisibility = async (
ctx: QueryCtx,
matchResult: Doc<'matchResults'>,
): Promise<boolean> => {
const userId = await getAuthUserId(ctx);

// If the match result doesn't belong to a tournament pairing, battle plans should be visible:
if (!matchResult?.tournamentPairingId) {
return true;
}

const tournamentPairing = await ctx.db.get(matchResult.tournamentPairingId);

// If the match result's pairing has gone missing, treat it the same as a single match:
if (!tournamentPairing) {
return true;
}
const deepTournamentPairing = await deepenTournamentPairing(ctx, tournamentPairing);
const tournament = await getTournamentShallow(ctx, deepTournamentPairing.tournamentId);

// If the match result is not from an on-going tournament, battle plans should be visible:
if (tournament?.status !== 'active') {
return true;
}

if (userId) {

// If the requesting user is an organizer, battle plans should be visible:
if (tournament.organizerUserIds.includes(userId)) {
return true;
}

// If the requesting user is a player within that pairing, battle plans should be visible:
if (deepTournamentPairing.playerUserIds.includes(userId)) {
return true;
}
}

// Hide battle plans in all other cases:
return false;
};
11 changes: 10 additions & 1 deletion convex/_model/matchResults/_helpers/deepenMatchResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Doc } from '../../../_generated/dataModel';
import { QueryCtx } from '../../../_generated/server';
import { getMission } from '../../fowV4/getMission';
import { getUser } from '../../users/queries/getUser';
import { checkMatchResultBattlePlanVisibility } from './checkMatchResultBattlePlanVisibility';

/* eslint-disable @typescript-eslint/explicit-function-return-type */
/**
Expand All @@ -24,19 +25,27 @@ export const deepenMatchResult = async (
const player1User = matchResult?.player1UserId ? await getUser(ctx, {
id: matchResult.player1UserId,
}) : null;
const mission = getMission(matchResult.details.missionId);

// Social
const comments = await ctx.db.query('matchResultComments')
.withIndex('by_match_result_id',((q) => q.eq('matchResultId', matchResult._id)))
.collect();
const likes = await ctx.db.query('matchResultLikes')
.withIndex('by_match_result_id',((q) => q.eq('matchResultId', matchResult._id)))
.collect();

// Details
const mission = getMission(matchResult.details.missionId);
const battlePlansVisible = await checkMatchResultBattlePlanVisibility(ctx, matchResult);

return {
...matchResult,
...(player0User ? { player0User } : {}),
...(player1User ? { player1User } : {}),
details: {
...matchResult.details,
player0BattlePlan: battlePlansVisible ? matchResult.details.player0BattlePlan : undefined,
player1BattlePlan: battlePlansVisible ? matchResult.details.player1BattlePlan : undefined,
missionName: mission?.displayName,
},
likedByUserIds: likes.map((like) => like.userId),
Expand Down
Loading