xpt-shared-types 1.20.0 → 1.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -62,6 +62,8 @@ export type TournamentParticipantStatus = "registered" | "active" | "eliminated"
62
62
  export type TournamentRoleRole = "moderator" | "admin";
63
63
  /** `Tournament.teamSize` */
64
64
  export type TournamentTeamSize = "one" | "two" | "three" | "four" | "five";
65
+ /** `TournamentTemplate.teamSize` */
66
+ export type TournamentTemplateTeamSize = "one" | "two" | "three" | "four" | "five";
65
67
  /** `Tournament.type` */
66
68
  export type TournamentType = "Single Elimination" | "League" | "Double Elimination";
67
69
  /** `Tournament.visibility` */
@@ -1,5 +1,5 @@
1
1
  import type { BlocksContent, MediaInput, RelationInput } from '../contracts';
2
- import type { AdminActionTargetType, FaqCategory, FriendshipStatus, GameAccountCode, GameAccountVerification, GameGenre, GameRequestCustomLobbies, GameRequestGenre, GameRequestTeamPlay, GameVisibility, MatchDisputeReason, MatchLobbyStatus, MatchResultType, MatchRound, MatchStreamPlatform, MatchWinnerSlot, PlatformCode, PlayerBanScope, ReferralStatus, TeamCurrentStatus, TeamInviteStatus, TeamInviteTeamRole, TeamPlayerTeamRole, TournamentAccess, TournamentCurrentStatus, TournamentInviteStatus, TournamentJoinRequestEntryType, TournamentJoinRequestStatus, TournamentParticipantEntryType, TournamentParticipantStatus, TournamentRoleRole, TournamentTeamSize, TournamentType, TournamentVisibility, UserGameAccountVerificationMethod, UserTransactionStripeStatus, UserTransactionType } from './enums';
2
+ import type { AdminActionTargetType, FaqCategory, FriendshipStatus, GameAccountCode, GameAccountVerification, GameGenre, GameRequestCustomLobbies, GameRequestGenre, GameRequestTeamPlay, GameVisibility, MatchDisputeReason, MatchLobbyStatus, MatchResultType, MatchRound, MatchStreamPlatform, MatchWinnerSlot, PlatformCode, PlayerBanScope, ReferralStatus, TeamCurrentStatus, TeamInviteStatus, TeamInviteTeamRole, TeamPlayerTeamRole, TournamentAccess, TournamentCurrentStatus, TournamentInviteStatus, TournamentJoinRequestEntryType, TournamentJoinRequestStatus, TournamentParticipantEntryType, TournamentParticipantStatus, TournamentRoleRole, TournamentTeamSize, TournamentTemplateTeamSize, TournamentType, TournamentVisibility, UserGameAccountVerificationMethod, UserTransactionStripeStatus, UserTransactionType } from './enums';
3
3
  /** Write payload for `about`. */
4
4
  export interface AboutInput {
5
5
  heroTitle?: string;
@@ -77,6 +77,7 @@ export interface GameAccountInput {
77
77
  name?: string;
78
78
  code?: GameAccountCode;
79
79
  verification?: GameAccountVerification;
80
+ sortOrder?: number;
80
81
  tournaments?: RelationInput | RelationInput[];
81
82
  user_game_accounts?: RelationInput | RelationInput[];
82
83
  imgThumb?: MediaInput;
@@ -170,6 +171,7 @@ export interface MatchInput {
170
171
  disputeReason?: MatchDisputeReason;
171
172
  disputeNote?: string;
172
173
  disputedBy?: RelationInput;
174
+ resultRecordedBy?: RelationInput;
173
175
  }
174
176
  /** Write payload for `match-message`. */
175
177
  export interface MatchMessageInput {
@@ -345,6 +347,9 @@ export interface TournamentInput {
345
347
  gameAccount?: RelationInput;
346
348
  tournament_stages?: unknown;
347
349
  prizesDistributed?: boolean;
350
+ entryFeesCollected?: number;
351
+ entryFeesSettledAt?: string;
352
+ hostXpAwarded?: number;
348
353
  discordUrl?: string;
349
354
  twitterUrl?: string;
350
355
  twitchUrl?: string;
@@ -390,6 +395,7 @@ export interface TournamentParticipantInput {
390
395
  status?: TournamentParticipantStatus;
391
396
  captainUser?: RelationInput;
392
397
  membersSnapshot?: unknown;
398
+ feePaid?: number;
393
399
  }
394
400
  /** Write payload for `tournament-preset-banner`. */
395
401
  export interface TournamentPresetBannerInput {
@@ -401,6 +407,27 @@ export interface TournamentRoleInput {
401
407
  users_permissions_user?: RelationInput;
402
408
  tournament?: RelationInput;
403
409
  }
410
+ /** Write payload for `tournament-template`. */
411
+ export interface TournamentTemplateInput {
412
+ name?: string;
413
+ host?: RelationInput;
414
+ game?: RelationInput;
415
+ participants?: number;
416
+ teamSize?: TournamentTemplateTeamSize;
417
+ entryFee?: number;
418
+ checkInTime?: number;
419
+ hasThirdPlace?: boolean;
420
+ hasMatchLobby?: boolean;
421
+ rules?: string;
422
+ platform?: RelationInput;
423
+ gameAccount?: RelationInput;
424
+ region?: RelationInput;
425
+ imgMain?: MediaInput;
426
+ prizes?: unknown;
427
+ isDefault?: boolean;
428
+ useCount?: number;
429
+ lastUsedAt?: string;
430
+ }
404
431
  /** Write payload for `user`. */
405
432
  export interface UserInput {
406
433
  username?: string;
@@ -1,5 +1,5 @@
1
1
  import type { BlocksContent, StrapiDocument, StrapiMedia, UserRole } from '../contracts';
2
- import type { AdminActionTargetType, FaqCategory, FriendshipStatus, GameAccountCode, GameAccountVerification, GameGenre, GameRequestCustomLobbies, GameRequestGenre, GameRequestTeamPlay, GameVisibility, MatchDisputeReason, MatchLobbyStatus, MatchResultType, MatchRound, MatchStreamPlatform, MatchWinnerSlot, PlatformCode, PlayerBanScope, ReferralStatus, TeamCurrentStatus, TeamInviteStatus, TeamInviteTeamRole, TeamPlayerTeamRole, TournamentAccess, TournamentCurrentStatus, TournamentInviteStatus, TournamentJoinRequestEntryType, TournamentJoinRequestStatus, TournamentParticipantEntryType, TournamentParticipantStatus, TournamentRoleRole, TournamentTeamSize, TournamentType, TournamentVisibility, UserGameAccountVerificationMethod, UserTransactionStripeStatus, UserTransactionType } from './enums';
2
+ import type { AdminActionTargetType, FaqCategory, FriendshipStatus, GameAccountCode, GameAccountVerification, GameGenre, GameRequestCustomLobbies, GameRequestGenre, GameRequestTeamPlay, GameVisibility, MatchDisputeReason, MatchLobbyStatus, MatchResultType, MatchRound, MatchStreamPlatform, MatchWinnerSlot, PlatformCode, PlayerBanScope, ReferralStatus, TeamCurrentStatus, TeamInviteStatus, TeamInviteTeamRole, TeamPlayerTeamRole, TournamentAccess, TournamentCurrentStatus, TournamentInviteStatus, TournamentJoinRequestEntryType, TournamentJoinRequestStatus, TournamentParticipantEntryType, TournamentParticipantStatus, TournamentRoleRole, TournamentTeamSize, TournamentTemplateTeamSize, TournamentType, TournamentVisibility, UserGameAccountVerificationMethod, UserTransactionStripeStatus, UserTransactionType } from './enums';
3
3
  /** `about` */
4
4
  export interface About extends StrapiDocument {
5
5
  heroTitle?: string | null;
@@ -77,6 +77,7 @@ export interface GameAccount extends StrapiDocument {
77
77
  name?: string | null;
78
78
  code?: GameAccountCode | null;
79
79
  verification?: GameAccountVerification | null;
80
+ sortOrder?: number | null;
80
81
  tournaments?: Tournament[];
81
82
  user_game_accounts?: UserGameAccount[];
82
83
  imgThumb?: StrapiMedia | null;
@@ -170,6 +171,7 @@ export interface Match extends StrapiDocument {
170
171
  disputeReason?: MatchDisputeReason | null;
171
172
  disputeNote?: string | null;
172
173
  disputedBy?: User | null;
174
+ resultRecordedBy?: User | null;
173
175
  }
174
176
  /** `match-message` */
175
177
  export interface MatchMessage extends StrapiDocument {
@@ -345,6 +347,9 @@ export interface Tournament extends StrapiDocument {
345
347
  gameAccount?: GameAccount | null;
346
348
  tournament_stages?: unknown | null;
347
349
  prizesDistributed?: boolean | null;
350
+ entryFeesCollected?: number | null;
351
+ entryFeesSettledAt?: string | null;
352
+ hostXpAwarded?: number | null;
348
353
  discordUrl?: string | null;
349
354
  twitterUrl?: string | null;
350
355
  twitchUrl?: string | null;
@@ -390,6 +395,7 @@ export interface TournamentParticipant extends StrapiDocument {
390
395
  status?: TournamentParticipantStatus | null;
391
396
  captainUser?: User | null;
392
397
  membersSnapshot?: unknown | null;
398
+ feePaid?: number | null;
393
399
  }
394
400
  /** `tournament-preset-banner` */
395
401
  export interface TournamentPresetBanner extends StrapiDocument {
@@ -401,6 +407,27 @@ export interface TournamentRole extends StrapiDocument {
401
407
  users_permissions_user?: User | null;
402
408
  tournament?: Tournament | null;
403
409
  }
410
+ /** `tournament-template` */
411
+ export interface TournamentTemplate extends StrapiDocument {
412
+ name?: string | null;
413
+ host?: User | null;
414
+ game?: Game | null;
415
+ participants?: number | null;
416
+ teamSize?: TournamentTemplateTeamSize | null;
417
+ entryFee?: number | null;
418
+ checkInTime?: number | null;
419
+ hasThirdPlace?: boolean | null;
420
+ hasMatchLobby?: boolean | null;
421
+ rules?: string | null;
422
+ platform?: Platform | null;
423
+ gameAccount?: GameAccount | null;
424
+ region?: Region | null;
425
+ imgMain?: StrapiMedia | null;
426
+ prizes?: unknown | null;
427
+ isDefault?: boolean | null;
428
+ useCount?: number | null;
429
+ lastUsedAt?: string | null;
430
+ }
404
431
  /** `user` */
405
432
  export interface User extends StrapiDocument {
406
433
  username?: string | null;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The experience ladder — EXP, which is **not** XPT.
3
+ *
4
+ * XPT is the spendable currency: it lives on the user as `xptCoins`, moves
5
+ * through `createWalletTransaction`, and leaves a ledger row. EXP is
6
+ * progression: it lives in `*_exp_earned` columns on the stat tables, is never
7
+ * spendable, and surfaces only as a level name. Nothing converts one into the
8
+ * other.
9
+ *
10
+ * Three independent ladders share the maths below — a player's
11
+ * (`user_summary_stat.total_exp_earned`), a host's
12
+ * (`host_summary_stat.host_exp_earned`) and a team's
13
+ * (`team_summary_stat.total_exp_earned`) — so a change here moves all three.
14
+ *
15
+ * It lives in the shared package because the server needs `getLevelFromExp` to
16
+ * notice a level *crossing* (the only EXP event worth a notification; see the
17
+ * note on `getLevelName`), while the client needs the same ladder to render
18
+ * the level bar. Two copies would drift into telling a user two different
19
+ * things about the same number.
20
+ */
21
+ export declare const levelNames: string[];
22
+ export declare const BASE_EXP = 500;
23
+ export declare const EXP_MULTIPLIER = 1.25;
24
+ /** Total EXP needed to have reached `level`. Level 1 is the floor, at 0. */
25
+ export declare const getExpForLevel: (level: number) => number;
26
+ /** 1-based: everyone starts at level 1, which is `levelNames[0]`. */
27
+ export declare const getLevelFromExp: (totalExp: number, maxLevel: number) => number;
28
+ /**
29
+ * `MAX_LEVEL` is the number of rungs, so every name is reachable. It was 20
30
+ * against 21 names, which stranded 'Zenith' at the top of a ladder nobody
31
+ * could finish climbing.
32
+ */
33
+ export declare const MAX_LEVEL: number;
34
+ /**
35
+ * `getLevelFromExp` is 1-based and `levelNames` is 0-based, so the index is
36
+ * `level - 1`. It used to be `levelNames[level]`, which skipped 'Beginner'
37
+ * entirely and greeted a brand-new account with 0 EXP as 'Novice'.
38
+ */
39
+ export declare const getLevelName: (totalExp: number, maxLevel?: number) => string;
40
+ /**
41
+ * Whether adding EXP moved someone up a rung, and to what.
42
+ *
43
+ * This is the only EXP event worth a notification. Awards themselves are a
44
+ * drip — a match is 10 EXP, so a bracket winner would collect five bells on
45
+ * top of the five they already get for the results — whereas a crossing is
46
+ * rare and is the thing the ladder exists to celebrate.
47
+ */
48
+ export declare const levelCrossing: (before: number, after: number) => {
49
+ crossed: boolean;
50
+ level: number;
51
+ name: string;
52
+ };
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ /**
3
+ * The experience ladder — EXP, which is **not** XPT.
4
+ *
5
+ * XPT is the spendable currency: it lives on the user as `xptCoins`, moves
6
+ * through `createWalletTransaction`, and leaves a ledger row. EXP is
7
+ * progression: it lives in `*_exp_earned` columns on the stat tables, is never
8
+ * spendable, and surfaces only as a level name. Nothing converts one into the
9
+ * other.
10
+ *
11
+ * Three independent ladders share the maths below — a player's
12
+ * (`user_summary_stat.total_exp_earned`), a host's
13
+ * (`host_summary_stat.host_exp_earned`) and a team's
14
+ * (`team_summary_stat.total_exp_earned`) — so a change here moves all three.
15
+ *
16
+ * It lives in the shared package because the server needs `getLevelFromExp` to
17
+ * notice a level *crossing* (the only EXP event worth a notification; see the
18
+ * note on `getLevelName`), while the client needs the same ladder to render
19
+ * the level bar. Two copies would drift into telling a user two different
20
+ * things about the same number.
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.levelCrossing = exports.getLevelName = exports.MAX_LEVEL = exports.getLevelFromExp = exports.getExpForLevel = exports.EXP_MULTIPLIER = exports.BASE_EXP = exports.levelNames = void 0;
24
+ exports.levelNames = [
25
+ 'Beginner',
26
+ 'Novice',
27
+ 'Rookie',
28
+ 'Apprentice',
29
+ 'Initiate',
30
+ 'Trainee',
31
+ 'Competitor',
32
+ 'Contender',
33
+ 'Veteran',
34
+ 'Expert',
35
+ 'Elite',
36
+ 'Master',
37
+ 'Grandmaster',
38
+ 'Champion',
39
+ 'Legend',
40
+ 'Mythic',
41
+ 'Godlike',
42
+ 'Immortal',
43
+ 'Supreme',
44
+ 'Transcendent',
45
+ 'Zenith',
46
+ ];
47
+ exports.BASE_EXP = 500;
48
+ exports.EXP_MULTIPLIER = 1.25;
49
+ /** Total EXP needed to have reached `level`. Level 1 is the floor, at 0. */
50
+ const getExpForLevel = (level) => {
51
+ if (level <= 0)
52
+ return 0;
53
+ let total = 0;
54
+ for (let i = 0; i < level; i++) {
55
+ total += Math.floor(exports.BASE_EXP * Math.pow(exports.EXP_MULTIPLIER, i));
56
+ }
57
+ return total;
58
+ };
59
+ exports.getExpForLevel = getExpForLevel;
60
+ /** 1-based: everyone starts at level 1, which is `levelNames[0]`. */
61
+ const getLevelFromExp = (totalExp, maxLevel) => {
62
+ let level = 1;
63
+ while (level < maxLevel && totalExp >= (0, exports.getExpForLevel)(level + 1)) {
64
+ level++;
65
+ }
66
+ return level;
67
+ };
68
+ exports.getLevelFromExp = getLevelFromExp;
69
+ /**
70
+ * `MAX_LEVEL` is the number of rungs, so every name is reachable. It was 20
71
+ * against 21 names, which stranded 'Zenith' at the top of a ladder nobody
72
+ * could finish climbing.
73
+ */
74
+ exports.MAX_LEVEL = exports.levelNames.length;
75
+ /**
76
+ * `getLevelFromExp` is 1-based and `levelNames` is 0-based, so the index is
77
+ * `level - 1`. It used to be `levelNames[level]`, which skipped 'Beginner'
78
+ * entirely and greeted a brand-new account with 0 EXP as 'Novice'.
79
+ */
80
+ const getLevelName = (totalExp, maxLevel = exports.MAX_LEVEL) => {
81
+ var _a;
82
+ const level = (0, exports.getLevelFromExp)(totalExp, maxLevel);
83
+ return (_a = exports.levelNames[level - 1]) !== null && _a !== void 0 ? _a : 'Unknown';
84
+ };
85
+ exports.getLevelName = getLevelName;
86
+ /**
87
+ * Whether adding EXP moved someone up a rung, and to what.
88
+ *
89
+ * This is the only EXP event worth a notification. Awards themselves are a
90
+ * drip — a match is 10 EXP, so a bracket winner would collect five bells on
91
+ * top of the five they already get for the results — whereas a crossing is
92
+ * rare and is the thing the ladder exists to celebrate.
93
+ */
94
+ const levelCrossing = (before, after) => {
95
+ var _a;
96
+ const from = (0, exports.getLevelFromExp)(before, exports.MAX_LEVEL);
97
+ const to = (0, exports.getLevelFromExp)(after, exports.MAX_LEVEL);
98
+ return {
99
+ crossed: to > from,
100
+ level: to,
101
+ name: (_a = exports.levelNames[to - 1]) !== null && _a !== void 0 ? _a : 'Unknown',
102
+ };
103
+ };
104
+ exports.levelCrossing = levelCrossing;
@@ -2,6 +2,7 @@
2
2
  * Hand-written types that are not derived from a Strapi content type.
3
3
  */
4
4
  export * from './country';
5
+ export * from './experience';
5
6
  export * from './gameAccountHandle';
6
7
  export * from './lobby';
7
8
  export * from './notification';
@@ -10,3 +11,5 @@ export * from './tournamentEntry';
10
11
  export * from './tournamentInvite';
11
12
  export * from './tournamentJoinRequest';
12
13
  export * from './tournamentStaff';
14
+ export * from './tournamentTemplate';
15
+ export * from './userUiState';
@@ -18,6 +18,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
18
18
  * Hand-written types that are not derived from a Strapi content type.
19
19
  */
20
20
  __exportStar(require("./country"), exports);
21
+ __exportStar(require("./experience"), exports);
21
22
  __exportStar(require("./gameAccountHandle"), exports);
22
23
  __exportStar(require("./lobby"), exports);
23
24
  __exportStar(require("./notification"), exports);
@@ -26,3 +27,5 @@ __exportStar(require("./tournamentEntry"), exports);
26
27
  __exportStar(require("./tournamentInvite"), exports);
27
28
  __exportStar(require("./tournamentJoinRequest"), exports);
28
29
  __exportStar(require("./tournamentStaff"), exports);
30
+ __exportStar(require("./tournamentTemplate"), exports);
31
+ __exportStar(require("./userUiState"), exports);
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Tournament templates — the contract between `utils/tournamentTemplate.ts`
3
+ * in xpt-strapi and the create dialog's setup step, the post-publish
4
+ * "Save as template" prompt and the Settings → Templates page in xpt-client.
5
+ *
6
+ * A template holds settings only: never entries, dates or results. A null
7
+ * setting means the host chose not to save it and the XPT default applies
8
+ * (`resolveSetup`). Alongside the host's templates, the API derives their
9
+ * **last tournament per game** — the newest one taken out of draft in the
10
+ * last `LAST_RUN_WINDOW_DAYS` — so a host can repeat a night without ever
11
+ * having saved anything.
12
+ *
13
+ * What a setup carries is close to the whole publish checklist: the catalogue
14
+ * choice (platform and account network), the region, the banner and the prize
15
+ * amounts ride along with the shape and the rules. Only the date is left out,
16
+ * because it belongs to one event rather than to the setup.
17
+ *
18
+ * Routes (all scoped to the caller):
19
+ * - `GET /tournament-templates/setups` → `{ data: TournamentSetupsResponse }`
20
+ * - `POST /tournament-templates` with `SaveTemplateBody` → `{ data: TournamentTemplateSummary }`
21
+ * - `PUT /tournament-templates/:id/default` → `{ data: TournamentTemplateSummary }`
22
+ * - `DELETE /tournament-templates/:id`
23
+ * - `POST /tournaments` accepts `entryFee`, `checkInTime`, `rules`, `region`,
24
+ * `platform`, `gameAccount`, `imgMain`, `prizes` and `template` (the
25
+ * documentId, for usage meta) alongside the usual fields.
26
+ */
27
+ import type { StrapiMedia } from '../contracts';
28
+ import type { TournamentTeamSize } from '../generated/enums';
29
+ /** How far back the automatic "last tournament" looks. */
30
+ export declare const LAST_RUN_WINDOW_DAYS = 30;
31
+ /** Templates a host may keep; the save route refuses the next one. */
32
+ export declare const MAX_TEMPLATES_PER_HOST = 20;
33
+ /**
34
+ * The settings a host may leave out of a setup — the "Also copied" rows.
35
+ * `platform` covers the account network too: the two are chosen together and
36
+ * publish readiness counts them as one detail.
37
+ */
38
+ export declare const TEMPLATE_COPY_KEYS: readonly ["entryFee", "prizes", "checkInTime", "platform", "region", "hasMatchLobby", "hasThirdPlace", "rules", "imgMain"];
39
+ export type TemplateCopyKey = (typeof TEMPLATE_COPY_KEYS)[number];
40
+ export type TemplateIncludeFlags = Record<TemplateCopyKey, boolean>;
41
+ /** A catalogue row as a setup names it. */
42
+ export interface SetupRef {
43
+ documentId: string;
44
+ name: string | null;
45
+ }
46
+ /** One placement's payout. The set of ranks is derived, never carried. */
47
+ export interface SetupPrize {
48
+ rank: string;
49
+ value: number | null;
50
+ }
51
+ /** The banner, as the media row a new tournament reuses. */
52
+ export interface SetupBanner {
53
+ id: number;
54
+ url: string | null;
55
+ }
56
+ export interface TournamentSetupFields {
57
+ participants: number;
58
+ teamSize: TournamentTeamSize;
59
+ /** Null: not saved, XPT default applies. */
60
+ entryFee: number | null;
61
+ /** Minutes; 0 is "no check-in". Null: not saved. */
62
+ checkInTime: number | null;
63
+ hasThirdPlace: boolean | null;
64
+ hasMatchLobby: boolean | null;
65
+ rules: string | null;
66
+ platform: SetupRef | null;
67
+ /** Rides with `platform`; the create refuses it if the catalogue has moved. */
68
+ gameAccount: SetupRef | null;
69
+ region: SetupRef | null;
70
+ imgMain: SetupBanner | null;
71
+ prizes: SetupPrize[];
72
+ }
73
+ /** What a blank tournament starts from, and what a null setting falls back to. */
74
+ export declare const TOURNAMENT_SETUP_DEFAULTS: TournamentSetupFields;
75
+ /** Enough of a game for the client to open the create dialog on it. */
76
+ export interface SetupGame {
77
+ id: number;
78
+ documentId: string;
79
+ title: string | null;
80
+ slug: string | null;
81
+ imgThumb: StrapiMedia | null;
82
+ }
83
+ /** A saved template as the routes return it. */
84
+ export interface TournamentTemplateSummary extends TournamentSetupFields {
85
+ documentId: string;
86
+ name: string;
87
+ game: SetupGame | null;
88
+ isDefault: boolean;
89
+ useCount: number;
90
+ lastUsedAt: string | null;
91
+ createdAt: string | null;
92
+ }
93
+ /** The host's last tournament for one game, derived at read time. */
94
+ export interface LastRunSummary extends TournamentSetupFields {
95
+ documentId: string;
96
+ title: string;
97
+ slug: string;
98
+ game: SetupGame | null;
99
+ dateTime: string | null;
100
+ createdAt: string | null;
101
+ }
102
+ /** `GET /tournament-templates/setups` — templates default first, runs newest first. */
103
+ export interface TournamentSetupsResponse {
104
+ templates: TournamentTemplateSummary[];
105
+ lastRuns: LastRunSummary[];
106
+ }
107
+ /** `POST /tournament-templates`. Missing include flags read as ticked. */
108
+ export interface SaveTemplateBody {
109
+ tournamentDocumentId: string;
110
+ name: string;
111
+ include?: Partial<TemplateIncludeFlags>;
112
+ isDefault?: boolean;
113
+ }
114
+ /** What the setup step offers, and what the details step was prefilled from. */
115
+ export type SetupChoice = {
116
+ kind: 'template';
117
+ template: TournamentTemplateSummary;
118
+ } | {
119
+ kind: 'lastRun';
120
+ lastRun: LastRunSummary;
121
+ } | {
122
+ kind: 'blank';
123
+ };
124
+ /** Every setting resolved: nulls replaced by the XPT default. */
125
+ export declare function resolveSetup(fields: Partial<TournamentSetupFields> | null | undefined): TournamentSetupFields;
126
+ /**
127
+ * The copy keys this setup actually has something for. A setting the host
128
+ * unticked when saving, or never set on the tournament, carries nothing — so
129
+ * the "Also copied" list offers it no row rather than a tickable blank.
130
+ */
131
+ export declare function copyableSetupKeys(fields: Partial<TournamentSetupFields> | null | undefined): TemplateCopyKey[];
132
+ /** The total a setup's prize amounts add up to. */
133
+ export declare const setupPrizePot: (prizes: SetupPrize[] | null | undefined) => number;
134
+ /** The tournament title rules' limit; a bumped name has to stay inside it. */
135
+ export declare const MAX_TOURNAMENT_NAME = 50;
136
+ /**
137
+ * The name a tournament started from a setup gets. A trailing `#N` is bumped
138
+ * (`Friday Night Ops #3` → `Friday Night Ops #4`); a name without one is
139
+ * treated as the first of its series and becomes `#2`. Either way the host
140
+ * sees a name they can keep or type over, and never one that silently
141
+ * duplicates the tournament it came from.
142
+ */
143
+ export declare function nextTournamentName(name: string): string;
144
+ /** A template name from a tournament title: the trailing `#N`, if any, dropped. */
145
+ export declare function templateNameFrom(title: string): string;
146
+ /** The setups offered for one game: its templates and, if any, its last run. */
147
+ export declare function setupsForGame(setups: TournamentSetupsResponse | null | undefined, gameDocumentId: string | null | undefined): {
148
+ templates: TournamentTemplateSummary[];
149
+ lastRun: LastRunSummary | null;
150
+ };
151
+ /** Badge counts for the game grid: templates for the game plus its last run. */
152
+ export declare function setupCountByGame(setups: TournamentSetupsResponse | null | undefined): Record<string, number>;
153
+ /**
154
+ * What the setup step starts on: the default template if the game has one,
155
+ * else the game's last run, else blank — so Continue is a single tap.
156
+ */
157
+ export declare function preselectSetup(templates: TournamentTemplateSummary[], lastRun: LastRunSummary | null | undefined): SetupChoice;
158
+ /** The setup fields of a choice, or null for blank. */
159
+ export declare function setupOfChoice(choice: SetupChoice): TournamentSetupFields | null;
160
+ /** The name a choice was made under, for the provenance strip. */
161
+ export declare function nameOfChoice(choice: SetupChoice): string | null;
162
+ export interface DescribeSetupOptions {
163
+ /** `players` or `teams` for the team size. */
164
+ entryNoun: (teamSize: TournamentTeamSize) => string;
165
+ /** `1v1` … `5v5`. */
166
+ teamSizeLabel: (teamSize: TournamentTeamSize) => string;
167
+ /** `Free entry`, `50 XPT` — the app's own money formatting. */
168
+ feeLabel: (fee: number) => string;
169
+ /** Mention rules when the setup carries them. Default true. */
170
+ withRules?: boolean;
171
+ }
172
+ /**
173
+ * The detail line under a setup: `16 teams · 2v2 · 50 XPT · check-in 30 min
174
+ * · rules`. Nulls read as their defaults. The labels come from the caller
175
+ * so this package does not carry the app's copy.
176
+ */
177
+ export declare function describeSetup(fields: Partial<TournamentSetupFields> | null | undefined, options: DescribeSetupOptions): string;