volleyballsimtypes 0.0.525 → 0.0.526

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.
Files changed (41) hide show
  1. package/dist/cjs/src/api/index.d.ts +1 -1
  2. package/dist/cjs/src/data/init-models.js +4 -22
  3. package/dist/cjs/src/data/models/national-team-claim.d.ts +5 -5
  4. package/dist/cjs/src/data/models/national-team-claim.js +8 -0
  5. package/dist/cjs/src/data/models/national-team.d.ts +1 -7
  6. package/dist/cjs/src/data/models/wnl-callup.d.ts +0 -10
  7. package/dist/cjs/src/data/models/wnl-season.d.ts +0 -7
  8. package/dist/cjs/src/data/models/wnl-squad.d.ts +0 -13
  9. package/dist/cjs/src/service/notification/catalog/en.json +13 -0
  10. package/dist/cjs/src/service/notification/catalog/es.json +13 -0
  11. package/dist/cjs/src/service/notification/catalog/fr.json +13 -0
  12. package/dist/cjs/src/service/notification/catalog/it.json +13 -0
  13. package/dist/cjs/src/service/notification/catalog/pl.json +13 -0
  14. package/dist/cjs/src/service/notification/catalog/pt-BR.json +13 -0
  15. package/dist/cjs/src/service/notification/catalog/tr.json +13 -0
  16. package/dist/cjs/src/service/notification/notification-type.d.ts +1 -1
  17. package/dist/cjs/src/service/notification/notification-type.js +4 -1
  18. package/dist/cjs/src/service/notification/payloads.d.ts +7 -1
  19. package/dist/cjs/src/service/notification/registry.d.ts +1 -2
  20. package/dist/cjs/src/service/notification/registry.js +52 -25
  21. package/dist/esm/src/api/index.d.ts +1 -1
  22. package/dist/esm/src/data/init-models.js +4 -22
  23. package/dist/esm/src/data/models/national-team-claim.d.ts +5 -5
  24. package/dist/esm/src/data/models/national-team-claim.js +8 -0
  25. package/dist/esm/src/data/models/national-team.d.ts +1 -7
  26. package/dist/esm/src/data/models/wnl-callup.d.ts +0 -10
  27. package/dist/esm/src/data/models/wnl-season.d.ts +0 -7
  28. package/dist/esm/src/data/models/wnl-squad.d.ts +0 -13
  29. package/dist/esm/src/service/notification/catalog/en.json +13 -0
  30. package/dist/esm/src/service/notification/catalog/es.json +13 -0
  31. package/dist/esm/src/service/notification/catalog/fr.json +13 -0
  32. package/dist/esm/src/service/notification/catalog/it.json +13 -0
  33. package/dist/esm/src/service/notification/catalog/pl.json +13 -0
  34. package/dist/esm/src/service/notification/catalog/pt-BR.json +13 -0
  35. package/dist/esm/src/service/notification/catalog/tr.json +13 -0
  36. package/dist/esm/src/service/notification/notification-type.d.ts +1 -1
  37. package/dist/esm/src/service/notification/notification-type.js +4 -1
  38. package/dist/esm/src/service/notification/payloads.d.ts +7 -1
  39. package/dist/esm/src/service/notification/registry.d.ts +1 -2
  40. package/dist/esm/src/service/notification/registry.js +51 -24
  41. package/package.json +1 -1
@@ -17,7 +17,7 @@ export interface Results {
17
17
  away: number[];
18
18
  }
19
19
  export interface MatchCompetition {
20
- type: 'LEAGUE' | 'NATIONAL' | 'QUALIFIER' | 'TOURNAMENT' | 'EVENT';
20
+ type: 'LEAGUE' | 'NATIONAL' | 'QUALIFIER' | 'TOURNAMENT' | 'EVENT' | 'WNL' | 'WNLF';
21
21
  countryName?: string;
22
22
  countryAlpha2?: string;
23
23
  countryAlpha3?: string;
@@ -236,28 +236,10 @@ function initModels(sequelize) {
236
236
  Competition.hasOne(NationalCountry, { as: 'NationalCountry', foreignKey: 'competition_id' });
237
237
  Country.hasMany(NationalCountry, { as: 'NationalCountries', foreignKey: 'country_id' });
238
238
  Iteration.hasMany(NationalCountry, { as: 'NationalCountries', foreignKey: 'iteration' });
239
- // National teams (WNL). The registry maps a country to its permanent active=false national Team; the claim links a
240
- // Tier-2 manager to it; per-edition squad + callup + season rows hang off the WNL league Competition.
241
- NationalTeam.belongsTo(Country, { as: 'country', foreignKey: 'country_id' });
242
- NationalTeam.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
243
- Country.hasOne(NationalTeam, { as: 'NationalTeam', foreignKey: 'country_id' });
244
- Team.hasOne(NationalTeam, { as: 'NationalTeam', foreignKey: 'team_id' });
245
- NationalTeam.hasOne(NationalTeamClaim, { as: 'claim', foreignKey: 'country_id' });
246
- NationalTeamClaim.belongsTo(NationalTeam, { as: 'nationalTeam', foreignKey: 'country_id' });
247
- NationalTeamClaim.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
248
- AuthUser.hasOne(NationalTeamClaim, { as: 'NationalTeamClaim', foreignKey: 'user_id' });
249
- WnlSquad.belongsTo(Competition, { as: 'competition', foreignKey: 'competition_id' });
250
- WnlSquad.belongsTo(Country, { as: 'country', foreignKey: 'country_id' });
251
- WnlSquad.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
252
- WnlSquad.belongsTo(AuthUser, { as: 'manager', foreignKey: 'manager_user_id' });
253
- Competition.hasMany(WnlSquad, { as: 'WnlSquads', foreignKey: 'competition_id' });
254
- WnlCallup.belongsTo(Competition, { as: 'competition', foreignKey: 'competition_id' });
255
- WnlCallup.belongsTo(Player, { as: 'player', foreignKey: 'player_id' });
256
- WnlCallup.belongsTo(Country, { as: 'country', foreignKey: 'country_id' });
257
- Competition.hasMany(WnlCallup, { as: 'WnlCallups', foreignKey: 'competition_id' });
258
- WnlSeason.belongsTo(Competition, { as: 'competition', foreignKey: 'competition_id' });
259
- WnlSeason.belongsTo(Competition, { as: 'finalsCompetition', foreignKey: 'finals_competition_id' });
260
- Competition.hasOne(WnlSeason, { as: 'WnlSeason', foreignKey: 'competition_id' });
239
+ // National teams (WNL): NO associations on purpose. Every consumer of the five WNL tables (Sim scheduler,
240
+ // API /wnl routes) reads them with direct model queries or raw SQL joins; the alias wiring that once lived
241
+ // here was confirmed unused in all four repos and removed as dead code (review fleet 2026-08-28). Add an
242
+ // association back only together with the include that uses it.
261
243
  VPER.belongsTo(Player, { as: 'Player', foreignKey: 'player_id' });
262
244
  VPER.belongsTo(Match, { as: 'Match', foreignKey: 'match_id' });
263
245
  Team.hasOne(Coach, { as: 'Coach', foreignKey: 'team_id' });
@@ -1,21 +1,21 @@
1
1
  import * as Sequelize from 'sequelize';
2
2
  import { Model, Optional } from 'sequelize';
3
- import { AuthUserId, AuthUserModel } from '.';
4
3
  export interface NationalTeamClaimAttributes {
5
4
  country_id: string;
6
5
  user_id: string;
7
6
  claimed_at: Date;
7
+ revoke_at?: Date | null;
8
+ warned_step?: number | null;
8
9
  }
9
10
  export type NationalTeamClaimPk = 'country_id';
10
11
  export type NationalTeamClaimId = NationalTeamClaimModel[NationalTeamClaimPk];
11
- export type NationalTeamClaimOptionalAttributes = 'claimed_at';
12
+ export type NationalTeamClaimOptionalAttributes = 'claimed_at' | 'revoke_at' | 'warned_step';
12
13
  export type NationalTeamClaimCreationAttributes = Optional<NationalTeamClaimAttributes, NationalTeamClaimOptionalAttributes>;
13
14
  export declare class NationalTeamClaimModel extends Model<NationalTeamClaimAttributes, NationalTeamClaimCreationAttributes> implements NationalTeamClaimAttributes {
14
15
  country_id: string;
15
16
  user_id: string;
16
17
  claimed_at: Date;
17
- user: AuthUserModel;
18
- getUser: Sequelize.BelongsToGetAssociationMixin<AuthUserModel>;
19
- setUser: Sequelize.BelongsToSetAssociationMixin<AuthUserModel, AuthUserId>;
18
+ revoke_at?: Date | null;
19
+ warned_step?: number | null;
20
20
  static initModel(sequelize: Sequelize.Sequelize): typeof NationalTeamClaimModel;
21
21
  }
@@ -20,6 +20,14 @@ class NationalTeamClaimModel extends sequelize_1.Model {
20
20
  type: sequelize_1.DataTypes.DATE,
21
21
  allowNull: false,
22
22
  defaultValue: sequelize_1.DataTypes.NOW
23
+ },
24
+ revoke_at: {
25
+ type: sequelize_1.DataTypes.DATE,
26
+ allowNull: true
27
+ },
28
+ warned_step: {
29
+ type: sequelize_1.DataTypes.SMALLINT,
30
+ allowNull: true
23
31
  }
24
32
  }, {
25
33
  sequelize,
@@ -1,6 +1,6 @@
1
1
  import * as Sequelize from 'sequelize';
2
2
  import { Model } from 'sequelize';
3
- import { CountryId, CountryModel, TeamAttributes, TeamId, TeamModel } from '.';
3
+ import { TeamAttributes } from '.';
4
4
  export interface NationalTeamAttributes {
5
5
  country_id: string;
6
6
  team_id: string;
@@ -12,11 +12,5 @@ export type NationalTeamCreationAttributes = NationalTeamAttributes;
12
12
  export declare class NationalTeamModel extends Model<NationalTeamAttributes, NationalTeamCreationAttributes> implements NationalTeamAttributes {
13
13
  country_id: string;
14
14
  team_id: string;
15
- country: CountryModel;
16
- getCountry: Sequelize.BelongsToGetAssociationMixin<CountryModel>;
17
- setCountry: Sequelize.BelongsToSetAssociationMixin<CountryModel, CountryId>;
18
- team: TeamModel;
19
- getTeam: Sequelize.BelongsToGetAssociationMixin<TeamModel>;
20
- setTeam: Sequelize.BelongsToSetAssociationMixin<TeamModel, TeamId>;
21
15
  static initModel(sequelize: Sequelize.Sequelize): typeof NationalTeamModel;
22
16
  }
@@ -1,6 +1,5 @@
1
1
  import * as Sequelize from 'sequelize';
2
2
  import { Model } from 'sequelize';
3
- import { CompetitionId, CompetitionModel, CountryId, CountryModel, PlayerId, PlayerModel } from '.';
4
3
  export type WnlCallupRole = 'SELECTION' | 'BACKUP';
5
4
  export interface WnlCallupAttributes {
6
5
  competition_id: string;
@@ -18,14 +17,5 @@ export declare class WnlCallupModel extends Model<WnlCallupAttributes, WnlCallup
18
17
  country_id: string;
19
18
  role: WnlCallupRole;
20
19
  slot_order: number;
21
- competition: CompetitionModel;
22
- getCompetition: Sequelize.BelongsToGetAssociationMixin<CompetitionModel>;
23
- setCompetition: Sequelize.BelongsToSetAssociationMixin<CompetitionModel, CompetitionId>;
24
- player: PlayerModel;
25
- getPlayer: Sequelize.BelongsToGetAssociationMixin<PlayerModel>;
26
- setPlayer: Sequelize.BelongsToSetAssociationMixin<PlayerModel, PlayerId>;
27
- country: CountryModel;
28
- getCountry: Sequelize.BelongsToGetAssociationMixin<CountryModel>;
29
- setCountry: Sequelize.BelongsToSetAssociationMixin<CountryModel, CountryId>;
30
20
  static initModel(sequelize: Sequelize.Sequelize): typeof WnlCallupModel;
31
21
  }
@@ -1,6 +1,5 @@
1
1
  import * as Sequelize from 'sequelize';
2
2
  import { Model, Optional } from 'sequelize';
3
- import { CompetitionId, CompetitionModel } from '.';
4
3
  export interface WnlSeasonAttributes {
5
4
  competition_id: string;
6
5
  season_index: number;
@@ -14,11 +13,5 @@ export declare class WnlSeasonModel extends Model<WnlSeasonAttributes, WnlSeason
14
13
  competition_id: string;
15
14
  season_index: number;
16
15
  finals_competition_id: string | null;
17
- competition: CompetitionModel;
18
- getCompetition: Sequelize.BelongsToGetAssociationMixin<CompetitionModel>;
19
- setCompetition: Sequelize.BelongsToSetAssociationMixin<CompetitionModel, CompetitionId>;
20
- finalsCompetition: CompetitionModel;
21
- getFinalsCompetition: Sequelize.BelongsToGetAssociationMixin<CompetitionModel>;
22
- setFinalsCompetition: Sequelize.BelongsToSetAssociationMixin<CompetitionModel, CompetitionId>;
23
16
  static initModel(sequelize: Sequelize.Sequelize): typeof WnlSeasonModel;
24
17
  }
@@ -1,6 +1,5 @@
1
1
  import * as Sequelize from 'sequelize';
2
2
  import { Model, Optional } from 'sequelize';
3
- import { AuthUserId, AuthUserModel, CompetitionId, CompetitionModel, CountryId, CountryModel, TeamId, TeamModel } from '.';
4
3
  export interface WnlSquadAttributes {
5
4
  competition_id: string;
6
5
  country_id: string;
@@ -24,17 +23,5 @@ export declare class WnlSquadModel extends Model<WnlSquadAttributes, WnlSquadCre
24
23
  auto: boolean;
25
24
  finalized: boolean;
26
25
  finalized_at: Date | null;
27
- competition: CompetitionModel;
28
- getCompetition: Sequelize.BelongsToGetAssociationMixin<CompetitionModel>;
29
- setCompetition: Sequelize.BelongsToSetAssociationMixin<CompetitionModel, CompetitionId>;
30
- country: CountryModel;
31
- getCountry: Sequelize.BelongsToGetAssociationMixin<CountryModel>;
32
- setCountry: Sequelize.BelongsToSetAssociationMixin<CountryModel, CountryId>;
33
- team: TeamModel;
34
- getTeam: Sequelize.BelongsToGetAssociationMixin<TeamModel>;
35
- setTeam: Sequelize.BelongsToSetAssociationMixin<TeamModel, TeamId>;
36
- manager: AuthUserModel;
37
- getManager: Sequelize.BelongsToGetAssociationMixin<AuthUserModel>;
38
- setManager: Sequelize.BelongsToSetAssociationMixin<AuthUserModel, AuthUserId>;
39
26
  static initModel(sequelize: Sequelize.Sequelize): typeof WnlSquadModel;
40
27
  }
@@ -91,5 +91,18 @@
91
91
  "wnlPlayerCalledUp": {
92
92
  "title": "Players called up",
93
93
  "body": "One or more of your players have been called-up to their national team!"
94
+ },
95
+ "nationalTrialGranted": {
96
+ "title": "National team unlocked",
97
+ "body": "You have free National access until {{date}}. Claim a national team and lead it in the World Nations League."
98
+ },
99
+ "nationalAccessExpiring": {
100
+ "title": "National team access ending",
101
+ "bodyDays": "Your national team will be released in {{days}} days unless you subscribe to the National tier on Patreon.",
102
+ "bodyLastDay": "Your national team will be released tomorrow unless you subscribe to the National tier on Patreon."
103
+ },
104
+ "nationalTeamRevoked": {
105
+ "title": "National team released",
106
+ "body": "Your national team has been released and is now available for someone else to claim. Subscribe to the National tier on Patreon to claim one again."
94
107
  }
95
108
  }
@@ -91,5 +91,18 @@
91
91
  "wnlPlayerCalledUp": {
92
92
  "title": "Jugadores convocados",
93
93
  "body": "¡Uno o más de tus jugadores han sido convocados por su selección nacional!"
94
+ },
95
+ "nationalTrialGranted": {
96
+ "title": "Selección nacional desbloqueada",
97
+ "body": "Tienes acceso National gratuito hasta el {{date}}. Reclama una selección nacional y dirígela en la WNL."
98
+ },
99
+ "nationalAccessExpiring": {
100
+ "title": "Tu acceso a la selección nacional termina",
101
+ "bodyDays": "Tu selección nacional quedará libre en {{days}} días a menos que te suscribas al nivel National en Patreon.",
102
+ "bodyLastDay": "Tu selección nacional quedará libre mañana a menos que te suscribas al nivel National en Patreon."
103
+ },
104
+ "nationalTeamRevoked": {
105
+ "title": "Selección nacional liberada",
106
+ "body": "Tu selección nacional ha quedado libre y ahora otra persona puede reclamarla. Suscríbete al nivel National en Patreon para volver a reclamar una."
94
107
  }
95
108
  }
@@ -91,5 +91,18 @@
91
91
  "wnlPlayerCalledUp": {
92
92
  "title": "Joueurs convoqués",
93
93
  "body": "Un ou plusieurs de vos joueurs ont été convoqués par leur équipe nationale !"
94
+ },
95
+ "nationalTrialGranted": {
96
+ "title": "Équipe nationale débloquée",
97
+ "body": "Vous avez un accès National gratuit jusqu'au {{date}}. Réclamez une équipe nationale et dirigez-la dans la WNL."
98
+ },
99
+ "nationalAccessExpiring": {
100
+ "title": "Votre accès à l'équipe nationale se termine",
101
+ "bodyDays": "Votre équipe nationale sera libérée dans {{days}} jours sauf si vous souscrivez au palier National sur Patreon.",
102
+ "bodyLastDay": "Votre équipe nationale sera libérée demain sauf si vous souscrivez au palier National sur Patreon."
103
+ },
104
+ "nationalTeamRevoked": {
105
+ "title": "Équipe nationale libérée",
106
+ "body": "Votre équipe nationale a été libérée et quelqu'un d'autre peut désormais la réclamer. Souscrivez au palier National sur Patreon pour en réclamer une à nouveau."
94
107
  }
95
108
  }
@@ -91,5 +91,18 @@
91
91
  "wnlPlayerCalledUp": {
92
92
  "title": "Giocatori convocati",
93
93
  "body": "Uno o più dei tuoi giocatori sono stati convocati dalla loro nazionale!"
94
+ },
95
+ "nationalTrialGranted": {
96
+ "title": "Nazionale sbloccata",
97
+ "body": "Hai accesso National gratuito fino al {{date}}. Reclama una nazionale e guidala nella WNL."
98
+ },
99
+ "nationalAccessExpiring": {
100
+ "title": "Il tuo accesso alla nazionale sta per finire",
101
+ "bodyDays": "La tua nazionale sarà liberata tra {{days}} giorni a meno che tu non sottoscriva il livello National su Patreon.",
102
+ "bodyLastDay": "La tua nazionale sarà liberata domani a meno che tu non sottoscriva il livello National su Patreon."
103
+ },
104
+ "nationalTeamRevoked": {
105
+ "title": "Nazionale liberata",
106
+ "body": "La tua nazionale è stata liberata e ora può essere reclamata da qualcun altro. Sottoscrivi il livello National su Patreon per reclamarne di nuovo una."
94
107
  }
95
108
  }
@@ -91,5 +91,18 @@
91
91
  "wnlPlayerCalledUp": {
92
92
  "title": "Zawodnicy powołani",
93
93
  "body": "Co najmniej jeden z twoich zawodników został powołany do swojej reprezentacji!"
94
+ },
95
+ "nationalTrialGranted": {
96
+ "title": "Reprezentacja odblokowana",
97
+ "body": "Masz darmowy dostęp National do {{date}}. Przejmij reprezentację i poprowadź ją w WNL."
98
+ },
99
+ "nationalAccessExpiring": {
100
+ "title": "Twój dostęp do reprezentacji się kończy",
101
+ "bodyDays": "Twoja reprezentacja zostanie zwolniona za {{days}} dni, chyba że wykupisz poziom National na Patreonie.",
102
+ "bodyLastDay": "Twoja reprezentacja zostanie zwolniona jutro, chyba że wykupisz poziom National na Patreonie."
103
+ },
104
+ "nationalTeamRevoked": {
105
+ "title": "Reprezentacja zwolniona",
106
+ "body": "Twoja reprezentacja została zwolniona i może ją teraz przejąć ktoś inny. Wykup poziom National na Patreonie, aby ponownie przejąć reprezentację."
94
107
  }
95
108
  }
@@ -91,5 +91,18 @@
91
91
  "wnlPlayerCalledUp": {
92
92
  "title": "Jogadores convocados",
93
93
  "body": "Um ou mais dos seus jogadores foram convocados pela sua seleção nacional!"
94
+ },
95
+ "nationalTrialGranted": {
96
+ "title": "Seleção nacional desbloqueada",
97
+ "body": "Você tem acesso National gratuito até {{date}}. Reivindique uma seleção nacional e comande-a na WNL."
98
+ },
99
+ "nationalAccessExpiring": {
100
+ "title": "Seu acesso à seleção nacional está acabando",
101
+ "bodyDays": "Sua seleção nacional será liberada em {{days}} dias, a menos que você assine o nível National no Patreon.",
102
+ "bodyLastDay": "Sua seleção nacional será liberada amanhã, a menos que você assine o nível National no Patreon."
103
+ },
104
+ "nationalTeamRevoked": {
105
+ "title": "Seleção nacional liberada",
106
+ "body": "Sua seleção nacional foi liberada e agora está disponível para outra pessoa reivindicar. Assine o nível National no Patreon para reivindicar uma novamente."
94
107
  }
95
108
  }
@@ -91,5 +91,18 @@
91
91
  "wnlPlayerCalledUp": {
92
92
  "title": "Oyuncular milli takıma çağrıldı",
93
93
  "body": "Bir veya daha fazla oyuncun milli takımına çağrıldı!"
94
+ },
95
+ "nationalTrialGranted": {
96
+ "title": "Milli takım kilidi açıldı",
97
+ "body": "{{date}} tarihine kadar ücretsiz National erişimin var. Bir milli takım seç ve WNL'de yönet."
98
+ },
99
+ "nationalAccessExpiring": {
100
+ "title": "Milli takım erişimin sona eriyor",
101
+ "bodyDays": "Patreon'da National seviyesine abone olmazsan milli takımın {{days}} gün içinde serbest bırakılacak.",
102
+ "bodyLastDay": "Patreon'da National seviyesine abone olmazsan milli takımın yarın serbest bırakılacak."
103
+ },
104
+ "nationalTeamRevoked": {
105
+ "title": "Milli takım serbest bırakıldı",
106
+ "body": "Milli takımın serbest bırakıldı ve artık başka biri tarafından seçilebilir. Yeniden bir milli takım seçmek için Patreon'da National seviyesine abone ol."
94
107
  }
95
108
  }
@@ -1,4 +1,4 @@
1
- export declare const NOTIFICATION_TYPES: readonly ["MATCH_RESULT", "INJURY", "INJURY_RECOVERED", "SEASON_END", "GRANT_RECEIVED", "PROMOTION", "DAILY_LOGIN", "WELCOME", "COMPENSATION", "EVENT_REGISTRATION_OPEN", "EVENT_REGISTRATION_CLOSING", "EVENT_STARTED", "EVENT_FINAL_ROUND", "EVENT_FINISHED", "REGION_RELOCATION", "WNL_CALLUP_OPEN", "WNL_CALLUP_DEADLINE", "WNL_CALLUP_CLOSED", "WNL_LEAGUE_START", "WNL_FINALS_START", "WNL_CHAMPION", "WNL_PLAYER_CALLED_UP"];
1
+ export declare const NOTIFICATION_TYPES: readonly ["MATCH_RESULT", "INJURY", "INJURY_RECOVERED", "SEASON_END", "GRANT_RECEIVED", "PROMOTION", "DAILY_LOGIN", "WELCOME", "COMPENSATION", "EVENT_REGISTRATION_OPEN", "EVENT_REGISTRATION_CLOSING", "EVENT_STARTED", "EVENT_FINAL_ROUND", "EVENT_FINISHED", "REGION_RELOCATION", "WNL_CALLUP_OPEN", "WNL_CALLUP_DEADLINE", "WNL_CALLUP_CLOSED", "WNL_LEAGUE_START", "WNL_FINALS_START", "WNL_CHAMPION", "WNL_PLAYER_CALLED_UP", "NATIONAL_TRIAL_GRANTED", "NATIONAL_ACCESS_EXPIRING", "NATIONAL_TEAM_REVOKED"];
2
2
  export type NotificationType = typeof NOTIFICATION_TYPES[number];
3
3
  export declare const NOTIFICATION_LOCALES: readonly ["en", "es", "fr", "it", "pl", "pt-BR", "tr"];
4
4
  export type NotificationLocale = typeof NOTIFICATION_LOCALES[number];
@@ -29,7 +29,10 @@ exports.NOTIFICATION_TYPES = [
29
29
  'WNL_LEAGUE_START',
30
30
  'WNL_FINALS_START',
31
31
  'WNL_CHAMPION',
32
- 'WNL_PLAYER_CALLED_UP'
32
+ 'WNL_PLAYER_CALLED_UP',
33
+ 'NATIONAL_TRIAL_GRANTED',
34
+ 'NATIONAL_ACCESS_EXPIRING',
35
+ 'NATIONAL_TEAM_REVOKED'
33
36
  ];
34
37
  // The 7 supported locales. Must stay in sync with the API VALID_LOCALES set and the UI src/locales/*.json.
35
38
  exports.NOTIFICATION_LOCALES = ['en', 'es', 'fr', 'it', 'pl', 'pt-BR', 'tr'];
@@ -37,4 +37,10 @@ export interface WnlCallupOpenPayload {
37
37
  export interface WnlChampionPayload {
38
38
  team?: string;
39
39
  }
40
- export type NotificationPayload = MatchResultPayload | InjuryPayload | InjuryRecoveredPayload | SeasonEndPayload | PromotionPayload | WnlCallupOpenPayload | WnlChampionPayload | Record<string, unknown>;
40
+ export interface NationalTrialGrantedPayload {
41
+ endsOn?: string;
42
+ }
43
+ export interface NationalAccessExpiringPayload {
44
+ expiresAt?: string;
45
+ }
46
+ export type NotificationPayload = MatchResultPayload | InjuryPayload | InjuryRecoveredPayload | SeasonEndPayload | PromotionPayload | WnlCallupOpenPayload | WnlChampionPayload | NationalTrialGrantedPayload | NationalAccessExpiringPayload | Record<string, unknown>;
@@ -6,11 +6,10 @@ export interface NotificationDescriptor {
6
6
  deepLink?: (payload: Record<string, unknown>) => string;
7
7
  pushTitleKey?: CatalogKeyResolver;
8
8
  pushBodyKey?: CatalogKeyResolver;
9
- inAppMessageKey?: CatalogKeyResolver;
10
- richRender?: NotificationType;
11
9
  params?: (payload: Record<string, unknown>) => Record<string, string>;
12
10
  catalogKeys: string[];
13
11
  }
12
+ export declare const NATIONAL_ACCESS_PREF_KEY = "NATIONAL_ACCESS";
14
13
  export declare const NOTIFICATION_REGISTRY: Record<NotificationType, NotificationDescriptor>;
15
14
  export declare const PUSH_NOTIFICATION_TYPES: NotificationType[];
16
15
  export declare function pushPrefKeyFor(type: string): string;
@@ -1,12 +1,28 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PUSH_PREF_KEYS = exports.PUSH_NOTIFICATION_TYPES = exports.NOTIFICATION_REGISTRY = void 0;
3
+ exports.PUSH_PREF_KEYS = exports.PUSH_NOTIFICATION_TYPES = exports.NOTIFICATION_REGISTRY = exports.NATIONAL_ACCESS_PREF_KEY = void 0;
4
4
  exports.pushPrefKeyFor = pushPrefKeyFor;
5
5
  exports.pushEnabledForType = pushEnabledForType;
6
6
  const notification_type_1 = require("./notification-type");
7
7
  function readString(value) {
8
8
  return typeof value === 'string' && value !== '' ? value : null;
9
9
  }
10
+ // The preference key the three national-team access types share. Deliberately NOT the 'WNL' group: muting routine
11
+ // call-up chatter must never also mute "you are about to lose your national team" (owner 2026-08-29).
12
+ exports.NATIONAL_ACCESS_PREF_KEY = 'NATIONAL_ACCESS';
13
+ // Whole days from now until an ISO instant, floored at 0. NATIONAL_ACCESS_EXPIRING stores the ABSOLUTE deadline
14
+ // rather than a day count because the in-app bell renders a stored payload at READ time: a stored "3" would still
15
+ // read "3 days" two days later. Push resolves the number here at SEND time and the bell resolves it again at render,
16
+ // so neither surface can go stale.
17
+ function daysUntilIso(value) {
18
+ const iso = readString(value);
19
+ if (iso == null)
20
+ return 0;
21
+ const ms = new Date(iso).getTime() - Date.now();
22
+ if (!Number.isFinite(ms) || ms <= 0)
23
+ return 0;
24
+ return Math.ceil(ms / 86400000);
25
+ }
10
26
  function teamPath(payload) {
11
27
  const teamId = readString(payload.teamId);
12
28
  return teamId != null ? `/team/${encodeURIComponent(teamId)}` : '/';
@@ -42,7 +58,6 @@ exports.NOTIFICATION_REGISTRY = {
42
58
  return `matchResult.${outcome}${named}`;
43
59
  },
44
60
  params: (p) => ({ opponent: matchOpponent(p) ?? '' }),
45
- richRender: 'MATCH_RESULT',
46
61
  catalogKeys: ['matchResult.title', 'matchResult.wonNamed', 'matchResult.wonUnnamed', 'matchResult.lostNamed', 'matchResult.lostUnnamed']
47
62
  },
48
63
  INJURY: {
@@ -50,7 +65,6 @@ exports.NOTIFICATION_REGISTRY = {
50
65
  deepLink: () => '/infirmary',
51
66
  pushTitleKey: 'injury.title',
52
67
  pushBodyKey: 'injury.body',
53
- inAppMessageKey: 'injury.body',
54
68
  params: (p) => ({ name: injuredName(p) }),
55
69
  catalogKeys: ['injury.title', 'injury.body']
56
70
  },
@@ -59,7 +73,6 @@ exports.NOTIFICATION_REGISTRY = {
59
73
  deepLink: teamPath,
60
74
  pushTitleKey: 'injuryRecovered.title',
61
75
  pushBodyKey: 'injuryRecovered.body',
62
- inAppMessageKey: 'injuryRecovered.body',
63
76
  params: (p) => ({ name: injuredName(p) }),
64
77
  catalogKeys: ['injuryRecovered.title', 'injuryRecovered.body']
65
78
  },
@@ -71,7 +84,6 @@ exports.NOTIFICATION_REGISTRY = {
71
84
  },
72
85
  pushTitleKey: 'seasonEnd.title',
73
86
  pushBodyKey: 'seasonEnd.body',
74
- inAppMessageKey: 'seasonEnd.body',
75
87
  catalogKeys: ['seasonEnd.title', 'seasonEnd.body']
76
88
  },
77
89
  GRANT_RECEIVED: {
@@ -79,7 +91,6 @@ exports.NOTIFICATION_REGISTRY = {
79
91
  deepLink: () => '/recruit',
80
92
  pushTitleKey: 'grant.title',
81
93
  pushBodyKey: 'grant.body',
82
- inAppMessageKey: 'grant.body',
83
94
  catalogKeys: ['grant.title', 'grant.body']
84
95
  },
85
96
  PROMOTION: {
@@ -87,7 +98,6 @@ exports.NOTIFICATION_REGISTRY = {
87
98
  deepLink: teamPath,
88
99
  pushTitleKey: 'promotion.title',
89
100
  pushBodyKey: 'promotion.body',
90
- inAppMessageKey: 'promotion.body',
91
101
  catalogKeys: ['promotion.title', 'promotion.body']
92
102
  },
93
103
  // In-game events. All five deep-link to the Events section, which is where every phase of an event lives.
@@ -98,7 +108,6 @@ exports.NOTIFICATION_REGISTRY = {
98
108
  deepLink: () => '/events',
99
109
  pushTitleKey: 'eventRegistrationOpen.title',
100
110
  pushBodyKey: 'eventRegistrationOpen.body',
101
- inAppMessageKey: 'eventRegistrationOpen.body',
102
111
  catalogKeys: ['eventRegistrationOpen.title', 'eventRegistrationOpen.body']
103
112
  },
104
113
  EVENT_REGISTRATION_CLOSING: {
@@ -106,7 +115,6 @@ exports.NOTIFICATION_REGISTRY = {
106
115
  deepLink: () => '/events',
107
116
  pushTitleKey: 'eventRegistrationClosing.title',
108
117
  pushBodyKey: 'eventRegistrationClosing.body',
109
- inAppMessageKey: 'eventRegistrationClosing.body',
110
118
  catalogKeys: ['eventRegistrationClosing.title', 'eventRegistrationClosing.body']
111
119
  },
112
120
  EVENT_STARTED: {
@@ -114,7 +122,6 @@ exports.NOTIFICATION_REGISTRY = {
114
122
  deepLink: () => '/events',
115
123
  pushTitleKey: 'eventStarted.title',
116
124
  pushBodyKey: 'eventStarted.body',
117
- inAppMessageKey: 'eventStarted.body',
118
125
  catalogKeys: ['eventStarted.title', 'eventStarted.body']
119
126
  },
120
127
  EVENT_FINAL_ROUND: {
@@ -122,7 +129,6 @@ exports.NOTIFICATION_REGISTRY = {
122
129
  deepLink: () => '/events',
123
130
  pushTitleKey: 'eventFinalRound.title',
124
131
  pushBodyKey: 'eventFinalRound.body',
125
- inAppMessageKey: 'eventFinalRound.body',
126
132
  catalogKeys: ['eventFinalRound.title', 'eventFinalRound.body']
127
133
  },
128
134
  EVENT_FINISHED: {
@@ -133,9 +139,6 @@ exports.NOTIFICATION_REGISTRY = {
133
139
  pushBodyKey: (p) => readString(p.position) != null || typeof p.position === 'number'
134
140
  ? 'eventFinished.placed'
135
141
  : 'eventFinished.body',
136
- inAppMessageKey: (p) => readString(p.position) != null || typeof p.position === 'number'
137
- ? 'eventFinished.placed'
138
- : 'eventFinished.body',
139
142
  params: (p) => ({
140
143
  position: typeof p.position === 'number' ? String(p.position) : (readString(p.position) ?? ''),
141
144
  entrants: typeof p.entrants === 'number' ? String(p.entrants) : (readString(p.entrants) ?? '')
@@ -148,7 +151,6 @@ exports.NOTIFICATION_REGISTRY = {
148
151
  deepLink: () => '/daily-login',
149
152
  pushTitleKey: 'dailyLogin.title',
150
153
  pushBodyKey: 'dailyLogin.body',
151
- inAppMessageKey: 'dailyLogin.body',
152
154
  catalogKeys: ['dailyLogin.title', 'dailyLogin.body']
153
155
  },
154
156
  WELCOME: {
@@ -156,13 +158,11 @@ exports.NOTIFICATION_REGISTRY = {
156
158
  deepLink: () => '/recruit',
157
159
  pushTitleKey: 'welcome.title',
158
160
  pushBodyKey: 'welcome.body',
159
- richRender: 'WELCOME',
160
161
  catalogKeys: ['welcome.title', 'welcome.body']
161
162
  },
162
163
  // In-app only (no push); the UI keeps a bespoke renderer (amount formatting) and its own `compensation` label.
163
164
  COMPENSATION: {
164
165
  emitsPush: false,
165
- richRender: 'COMPENSATION',
166
166
  catalogKeys: []
167
167
  },
168
168
  // One-off relocation warning: sent to users whose team is being moved to Kyorin. Informational; opens their team.
@@ -171,7 +171,6 @@ exports.NOTIFICATION_REGISTRY = {
171
171
  deepLink: teamPath,
172
172
  pushTitleKey: 'regionRelocation.title',
173
173
  pushBodyKey: 'regionRelocation.body',
174
- inAppMessageKey: 'regionRelocation.body',
175
174
  catalogKeys: ['regionRelocation.title', 'regionRelocation.body']
176
175
  },
177
176
  // World Nations League (owner 2026-08-28). All seven share the single 'WNL' preference toggle. The three callup
@@ -183,7 +182,6 @@ exports.NOTIFICATION_REGISTRY = {
183
182
  deepLink: () => '/wnl/callup',
184
183
  pushTitleKey: 'wnlCallupOpen.title',
185
184
  pushBodyKey: 'wnlCallupOpen.body',
186
- inAppMessageKey: 'wnlCallupOpen.body',
187
185
  // The deadline arrives pre-formatted (locale-neutral 'YYYY-MM-DD HH:mm UTC') because the stored payload is
188
186
  // shared by the push and the in-app bell and cannot be re-localised per render.
189
187
  params: (p) => ({ date: readString(p.deadline) ?? '' }),
@@ -195,7 +193,6 @@ exports.NOTIFICATION_REGISTRY = {
195
193
  deepLink: () => '/wnl/callup',
196
194
  pushTitleKey: 'wnlCallupDeadline.title',
197
195
  pushBodyKey: 'wnlCallupDeadline.body',
198
- inAppMessageKey: 'wnlCallupDeadline.body',
199
196
  catalogKeys: ['wnlCallupDeadline.title', 'wnlCallupDeadline.body']
200
197
  },
201
198
  WNL_CALLUP_CLOSED: {
@@ -204,7 +201,6 @@ exports.NOTIFICATION_REGISTRY = {
204
201
  deepLink: () => '/wnl/manage/lineup',
205
202
  pushTitleKey: 'wnlCallupClosed.title',
206
203
  pushBodyKey: 'wnlCallupClosed.body',
207
- inAppMessageKey: 'wnlCallupClosed.body',
208
204
  catalogKeys: ['wnlCallupClosed.title', 'wnlCallupClosed.body']
209
205
  },
210
206
  WNL_LEAGUE_START: {
@@ -213,7 +209,6 @@ exports.NOTIFICATION_REGISTRY = {
213
209
  deepLink: () => '/wnl',
214
210
  pushTitleKey: 'wnlLeagueStart.title',
215
211
  pushBodyKey: 'wnlLeagueStart.body',
216
- inAppMessageKey: 'wnlLeagueStart.body',
217
212
  catalogKeys: ['wnlLeagueStart.title', 'wnlLeagueStart.body']
218
213
  },
219
214
  WNL_FINALS_START: {
@@ -222,7 +217,6 @@ exports.NOTIFICATION_REGISTRY = {
222
217
  deepLink: () => '/wnl/finals',
223
218
  pushTitleKey: 'wnlFinalsStart.title',
224
219
  pushBodyKey: 'wnlFinalsStart.body',
225
- inAppMessageKey: 'wnlFinalsStart.body',
226
220
  catalogKeys: ['wnlFinalsStart.title', 'wnlFinalsStart.body']
227
221
  },
228
222
  WNL_CHAMPION: {
@@ -232,7 +226,6 @@ exports.NOTIFICATION_REGISTRY = {
232
226
  pushTitleKey: 'wnlChampion.title',
233
227
  // A missing team name never renders a subjectless sentence (mirrors the MATCH_RESULT named/unnamed split).
234
228
  pushBodyKey: (p) => readString(p.team) != null ? 'wnlChampion.body' : 'wnlChampion.bodyUnnamed',
235
- inAppMessageKey: (p) => readString(p.team) != null ? 'wnlChampion.body' : 'wnlChampion.bodyUnnamed',
236
229
  params: (p) => ({ team: readString(p.team) ?? '' }),
237
230
  catalogKeys: ['wnlChampion.title', 'wnlChampion.body', 'wnlChampion.bodyUnnamed']
238
231
  },
@@ -242,8 +235,42 @@ exports.NOTIFICATION_REGISTRY = {
242
235
  deepLink: () => '/wnl/teams',
243
236
  pushTitleKey: 'wnlPlayerCalledUp.title',
244
237
  pushBodyKey: 'wnlPlayerCalledUp.body',
245
- inAppMessageKey: 'wnlPlayerCalledUp.body',
246
238
  catalogKeys: ['wnlPlayerCalledUp.title', 'wnlPlayerCalledUp.body']
239
+ },
240
+ // National-team ACCESS lifecycle (owner 2026-08-29): the free Tier-2 trial is granted, is about to end, and the
241
+ // team is released. These share their OWN toggle rather than the 'WNL' group so that muting call-up chatter
242
+ // cannot silence the warning that a team is about to be lost. They are also intentionally not named WNL_*: the
243
+ // completeness test asserts every WNL_-prefixed type resolves to the 'WNL' key.
244
+ NATIONAL_TRIAL_GRANTED: {
245
+ emitsPush: true,
246
+ prefKey: exports.NATIONAL_ACCESS_PREF_KEY,
247
+ deepLink: () => '/wnl/claim',
248
+ pushTitleKey: 'nationalTrialGranted.title',
249
+ pushBodyKey: 'nationalTrialGranted.body',
250
+ // The end date arrives pre-formatted (locale-neutral 'YYYY-MM-DD'), same reason as wnlCallupOpen: one stored
251
+ // payload serves both the push and the bell and cannot be re-localised per render.
252
+ params: (p) => ({ date: readString(p.endsOn) ?? '' }),
253
+ catalogKeys: ['nationalTrialGranted.title', 'nationalTrialGranted.body']
254
+ },
255
+ NATIONAL_ACCESS_EXPIRING: {
256
+ emitsPush: true,
257
+ prefKey: exports.NATIONAL_ACCESS_PREF_KEY,
258
+ // Settings is where the Patreon card lives, so the action the copy asks for is one tap away.
259
+ deepLink: () => '/settings',
260
+ pushTitleKey: 'nationalAccessExpiring.title',
261
+ // The catalog has no pluralisation (interpolate is a bare {{param}} replace), so the final day is its own key
262
+ // instead of rendering "1 days" (mirrors the wnlChampion named/unnamed split).
263
+ pushBodyKey: (p) => daysUntilIso(p.expiresAt) <= 1 ? 'nationalAccessExpiring.bodyLastDay' : 'nationalAccessExpiring.bodyDays',
264
+ params: (p) => ({ days: String(daysUntilIso(p.expiresAt)) }),
265
+ catalogKeys: ['nationalAccessExpiring.title', 'nationalAccessExpiring.bodyDays', 'nationalAccessExpiring.bodyLastDay']
266
+ },
267
+ NATIONAL_TEAM_REVOKED: {
268
+ emitsPush: true,
269
+ prefKey: exports.NATIONAL_ACCESS_PREF_KEY,
270
+ deepLink: () => '/wnl/teams',
271
+ pushTitleKey: 'nationalTeamRevoked.title',
272
+ pushBodyKey: 'nationalTeamRevoked.body',
273
+ catalogKeys: ['nationalTeamRevoked.title', 'nationalTeamRevoked.body']
247
274
  }
248
275
  };
249
276
  // The notification types that emit a mobile push (everything except COMPENSATION). Source of truth = each
@@ -17,7 +17,7 @@ export interface Results {
17
17
  away: number[];
18
18
  }
19
19
  export interface MatchCompetition {
20
- type: 'LEAGUE' | 'NATIONAL' | 'QUALIFIER' | 'TOURNAMENT' | 'EVENT';
20
+ type: 'LEAGUE' | 'NATIONAL' | 'QUALIFIER' | 'TOURNAMENT' | 'EVENT' | 'WNL' | 'WNLF';
21
21
  countryName?: string;
22
22
  countryAlpha2?: string;
23
23
  countryAlpha3?: string;
@@ -233,28 +233,10 @@ export function initModels(sequelize) {
233
233
  Competition.hasOne(NationalCountry, { as: 'NationalCountry', foreignKey: 'competition_id' });
234
234
  Country.hasMany(NationalCountry, { as: 'NationalCountries', foreignKey: 'country_id' });
235
235
  Iteration.hasMany(NationalCountry, { as: 'NationalCountries', foreignKey: 'iteration' });
236
- // National teams (WNL). The registry maps a country to its permanent active=false national Team; the claim links a
237
- // Tier-2 manager to it; per-edition squad + callup + season rows hang off the WNL league Competition.
238
- NationalTeam.belongsTo(Country, { as: 'country', foreignKey: 'country_id' });
239
- NationalTeam.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
240
- Country.hasOne(NationalTeam, { as: 'NationalTeam', foreignKey: 'country_id' });
241
- Team.hasOne(NationalTeam, { as: 'NationalTeam', foreignKey: 'team_id' });
242
- NationalTeam.hasOne(NationalTeamClaim, { as: 'claim', foreignKey: 'country_id' });
243
- NationalTeamClaim.belongsTo(NationalTeam, { as: 'nationalTeam', foreignKey: 'country_id' });
244
- NationalTeamClaim.belongsTo(AuthUser, { as: 'user', foreignKey: 'user_id' });
245
- AuthUser.hasOne(NationalTeamClaim, { as: 'NationalTeamClaim', foreignKey: 'user_id' });
246
- WnlSquad.belongsTo(Competition, { as: 'competition', foreignKey: 'competition_id' });
247
- WnlSquad.belongsTo(Country, { as: 'country', foreignKey: 'country_id' });
248
- WnlSquad.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
249
- WnlSquad.belongsTo(AuthUser, { as: 'manager', foreignKey: 'manager_user_id' });
250
- Competition.hasMany(WnlSquad, { as: 'WnlSquads', foreignKey: 'competition_id' });
251
- WnlCallup.belongsTo(Competition, { as: 'competition', foreignKey: 'competition_id' });
252
- WnlCallup.belongsTo(Player, { as: 'player', foreignKey: 'player_id' });
253
- WnlCallup.belongsTo(Country, { as: 'country', foreignKey: 'country_id' });
254
- Competition.hasMany(WnlCallup, { as: 'WnlCallups', foreignKey: 'competition_id' });
255
- WnlSeason.belongsTo(Competition, { as: 'competition', foreignKey: 'competition_id' });
256
- WnlSeason.belongsTo(Competition, { as: 'finalsCompetition', foreignKey: 'finals_competition_id' });
257
- Competition.hasOne(WnlSeason, { as: 'WnlSeason', foreignKey: 'competition_id' });
236
+ // National teams (WNL): NO associations on purpose. Every consumer of the five WNL tables (Sim scheduler,
237
+ // API /wnl routes) reads them with direct model queries or raw SQL joins; the alias wiring that once lived
238
+ // here was confirmed unused in all four repos and removed as dead code (review fleet 2026-08-28). Add an
239
+ // association back only together with the include that uses it.
258
240
  VPER.belongsTo(Player, { as: 'Player', foreignKey: 'player_id' });
259
241
  VPER.belongsTo(Match, { as: 'Match', foreignKey: 'match_id' });
260
242
  Team.hasOne(Coach, { as: 'Coach', foreignKey: 'team_id' });
@@ -1,21 +1,21 @@
1
1
  import * as Sequelize from 'sequelize';
2
2
  import { Model, Optional } from 'sequelize';
3
- import { AuthUserId, AuthUserModel } from '.';
4
3
  export interface NationalTeamClaimAttributes {
5
4
  country_id: string;
6
5
  user_id: string;
7
6
  claimed_at: Date;
7
+ revoke_at?: Date | null;
8
+ warned_step?: number | null;
8
9
  }
9
10
  export type NationalTeamClaimPk = 'country_id';
10
11
  export type NationalTeamClaimId = NationalTeamClaimModel[NationalTeamClaimPk];
11
- export type NationalTeamClaimOptionalAttributes = 'claimed_at';
12
+ export type NationalTeamClaimOptionalAttributes = 'claimed_at' | 'revoke_at' | 'warned_step';
12
13
  export type NationalTeamClaimCreationAttributes = Optional<NationalTeamClaimAttributes, NationalTeamClaimOptionalAttributes>;
13
14
  export declare class NationalTeamClaimModel extends Model<NationalTeamClaimAttributes, NationalTeamClaimCreationAttributes> implements NationalTeamClaimAttributes {
14
15
  country_id: string;
15
16
  user_id: string;
16
17
  claimed_at: Date;
17
- user: AuthUserModel;
18
- getUser: Sequelize.BelongsToGetAssociationMixin<AuthUserModel>;
19
- setUser: Sequelize.BelongsToSetAssociationMixin<AuthUserModel, AuthUserId>;
18
+ revoke_at?: Date | null;
19
+ warned_step?: number | null;
20
20
  static initModel(sequelize: Sequelize.Sequelize): typeof NationalTeamClaimModel;
21
21
  }
@@ -17,6 +17,14 @@ export class NationalTeamClaimModel extends Model {
17
17
  type: DataTypes.DATE,
18
18
  allowNull: false,
19
19
  defaultValue: DataTypes.NOW
20
+ },
21
+ revoke_at: {
22
+ type: DataTypes.DATE,
23
+ allowNull: true
24
+ },
25
+ warned_step: {
26
+ type: DataTypes.SMALLINT,
27
+ allowNull: true
20
28
  }
21
29
  }, {
22
30
  sequelize,
@@ -1,6 +1,6 @@
1
1
  import * as Sequelize from 'sequelize';
2
2
  import { Model } from 'sequelize';
3
- import { CountryId, CountryModel, TeamAttributes, TeamId, TeamModel } from '.';
3
+ import { TeamAttributes } from '.';
4
4
  export interface NationalTeamAttributes {
5
5
  country_id: string;
6
6
  team_id: string;
@@ -12,11 +12,5 @@ export type NationalTeamCreationAttributes = NationalTeamAttributes;
12
12
  export declare class NationalTeamModel extends Model<NationalTeamAttributes, NationalTeamCreationAttributes> implements NationalTeamAttributes {
13
13
  country_id: string;
14
14
  team_id: string;
15
- country: CountryModel;
16
- getCountry: Sequelize.BelongsToGetAssociationMixin<CountryModel>;
17
- setCountry: Sequelize.BelongsToSetAssociationMixin<CountryModel, CountryId>;
18
- team: TeamModel;
19
- getTeam: Sequelize.BelongsToGetAssociationMixin<TeamModel>;
20
- setTeam: Sequelize.BelongsToSetAssociationMixin<TeamModel, TeamId>;
21
15
  static initModel(sequelize: Sequelize.Sequelize): typeof NationalTeamModel;
22
16
  }
@@ -1,6 +1,5 @@
1
1
  import * as Sequelize from 'sequelize';
2
2
  import { Model } from 'sequelize';
3
- import { CompetitionId, CompetitionModel, CountryId, CountryModel, PlayerId, PlayerModel } from '.';
4
3
  export type WnlCallupRole = 'SELECTION' | 'BACKUP';
5
4
  export interface WnlCallupAttributes {
6
5
  competition_id: string;
@@ -18,14 +17,5 @@ export declare class WnlCallupModel extends Model<WnlCallupAttributes, WnlCallup
18
17
  country_id: string;
19
18
  role: WnlCallupRole;
20
19
  slot_order: number;
21
- competition: CompetitionModel;
22
- getCompetition: Sequelize.BelongsToGetAssociationMixin<CompetitionModel>;
23
- setCompetition: Sequelize.BelongsToSetAssociationMixin<CompetitionModel, CompetitionId>;
24
- player: PlayerModel;
25
- getPlayer: Sequelize.BelongsToGetAssociationMixin<PlayerModel>;
26
- setPlayer: Sequelize.BelongsToSetAssociationMixin<PlayerModel, PlayerId>;
27
- country: CountryModel;
28
- getCountry: Sequelize.BelongsToGetAssociationMixin<CountryModel>;
29
- setCountry: Sequelize.BelongsToSetAssociationMixin<CountryModel, CountryId>;
30
20
  static initModel(sequelize: Sequelize.Sequelize): typeof WnlCallupModel;
31
21
  }
@@ -1,6 +1,5 @@
1
1
  import * as Sequelize from 'sequelize';
2
2
  import { Model, Optional } from 'sequelize';
3
- import { CompetitionId, CompetitionModel } from '.';
4
3
  export interface WnlSeasonAttributes {
5
4
  competition_id: string;
6
5
  season_index: number;
@@ -14,11 +13,5 @@ export declare class WnlSeasonModel extends Model<WnlSeasonAttributes, WnlSeason
14
13
  competition_id: string;
15
14
  season_index: number;
16
15
  finals_competition_id: string | null;
17
- competition: CompetitionModel;
18
- getCompetition: Sequelize.BelongsToGetAssociationMixin<CompetitionModel>;
19
- setCompetition: Sequelize.BelongsToSetAssociationMixin<CompetitionModel, CompetitionId>;
20
- finalsCompetition: CompetitionModel;
21
- getFinalsCompetition: Sequelize.BelongsToGetAssociationMixin<CompetitionModel>;
22
- setFinalsCompetition: Sequelize.BelongsToSetAssociationMixin<CompetitionModel, CompetitionId>;
23
16
  static initModel(sequelize: Sequelize.Sequelize): typeof WnlSeasonModel;
24
17
  }
@@ -1,6 +1,5 @@
1
1
  import * as Sequelize from 'sequelize';
2
2
  import { Model, Optional } from 'sequelize';
3
- import { AuthUserId, AuthUserModel, CompetitionId, CompetitionModel, CountryId, CountryModel, TeamId, TeamModel } from '.';
4
3
  export interface WnlSquadAttributes {
5
4
  competition_id: string;
6
5
  country_id: string;
@@ -24,17 +23,5 @@ export declare class WnlSquadModel extends Model<WnlSquadAttributes, WnlSquadCre
24
23
  auto: boolean;
25
24
  finalized: boolean;
26
25
  finalized_at: Date | null;
27
- competition: CompetitionModel;
28
- getCompetition: Sequelize.BelongsToGetAssociationMixin<CompetitionModel>;
29
- setCompetition: Sequelize.BelongsToSetAssociationMixin<CompetitionModel, CompetitionId>;
30
- country: CountryModel;
31
- getCountry: Sequelize.BelongsToGetAssociationMixin<CountryModel>;
32
- setCountry: Sequelize.BelongsToSetAssociationMixin<CountryModel, CountryId>;
33
- team: TeamModel;
34
- getTeam: Sequelize.BelongsToGetAssociationMixin<TeamModel>;
35
- setTeam: Sequelize.BelongsToSetAssociationMixin<TeamModel, TeamId>;
36
- manager: AuthUserModel;
37
- getManager: Sequelize.BelongsToGetAssociationMixin<AuthUserModel>;
38
- setManager: Sequelize.BelongsToSetAssociationMixin<AuthUserModel, AuthUserId>;
39
26
  static initModel(sequelize: Sequelize.Sequelize): typeof WnlSquadModel;
40
27
  }
@@ -91,5 +91,18 @@
91
91
  "wnlPlayerCalledUp": {
92
92
  "title": "Players called up",
93
93
  "body": "One or more of your players have been called-up to their national team!"
94
+ },
95
+ "nationalTrialGranted": {
96
+ "title": "National team unlocked",
97
+ "body": "You have free National access until {{date}}. Claim a national team and lead it in the World Nations League."
98
+ },
99
+ "nationalAccessExpiring": {
100
+ "title": "National team access ending",
101
+ "bodyDays": "Your national team will be released in {{days}} days unless you subscribe to the National tier on Patreon.",
102
+ "bodyLastDay": "Your national team will be released tomorrow unless you subscribe to the National tier on Patreon."
103
+ },
104
+ "nationalTeamRevoked": {
105
+ "title": "National team released",
106
+ "body": "Your national team has been released and is now available for someone else to claim. Subscribe to the National tier on Patreon to claim one again."
94
107
  }
95
108
  }
@@ -91,5 +91,18 @@
91
91
  "wnlPlayerCalledUp": {
92
92
  "title": "Jugadores convocados",
93
93
  "body": "¡Uno o más de tus jugadores han sido convocados por su selección nacional!"
94
+ },
95
+ "nationalTrialGranted": {
96
+ "title": "Selección nacional desbloqueada",
97
+ "body": "Tienes acceso National gratuito hasta el {{date}}. Reclama una selección nacional y dirígela en la WNL."
98
+ },
99
+ "nationalAccessExpiring": {
100
+ "title": "Tu acceso a la selección nacional termina",
101
+ "bodyDays": "Tu selección nacional quedará libre en {{days}} días a menos que te suscribas al nivel National en Patreon.",
102
+ "bodyLastDay": "Tu selección nacional quedará libre mañana a menos que te suscribas al nivel National en Patreon."
103
+ },
104
+ "nationalTeamRevoked": {
105
+ "title": "Selección nacional liberada",
106
+ "body": "Tu selección nacional ha quedado libre y ahora otra persona puede reclamarla. Suscríbete al nivel National en Patreon para volver a reclamar una."
94
107
  }
95
108
  }
@@ -91,5 +91,18 @@
91
91
  "wnlPlayerCalledUp": {
92
92
  "title": "Joueurs convoqués",
93
93
  "body": "Un ou plusieurs de vos joueurs ont été convoqués par leur équipe nationale !"
94
+ },
95
+ "nationalTrialGranted": {
96
+ "title": "Équipe nationale débloquée",
97
+ "body": "Vous avez un accès National gratuit jusqu'au {{date}}. Réclamez une équipe nationale et dirigez-la dans la WNL."
98
+ },
99
+ "nationalAccessExpiring": {
100
+ "title": "Votre accès à l'équipe nationale se termine",
101
+ "bodyDays": "Votre équipe nationale sera libérée dans {{days}} jours sauf si vous souscrivez au palier National sur Patreon.",
102
+ "bodyLastDay": "Votre équipe nationale sera libérée demain sauf si vous souscrivez au palier National sur Patreon."
103
+ },
104
+ "nationalTeamRevoked": {
105
+ "title": "Équipe nationale libérée",
106
+ "body": "Votre équipe nationale a été libérée et quelqu'un d'autre peut désormais la réclamer. Souscrivez au palier National sur Patreon pour en réclamer une à nouveau."
94
107
  }
95
108
  }
@@ -91,5 +91,18 @@
91
91
  "wnlPlayerCalledUp": {
92
92
  "title": "Giocatori convocati",
93
93
  "body": "Uno o più dei tuoi giocatori sono stati convocati dalla loro nazionale!"
94
+ },
95
+ "nationalTrialGranted": {
96
+ "title": "Nazionale sbloccata",
97
+ "body": "Hai accesso National gratuito fino al {{date}}. Reclama una nazionale e guidala nella WNL."
98
+ },
99
+ "nationalAccessExpiring": {
100
+ "title": "Il tuo accesso alla nazionale sta per finire",
101
+ "bodyDays": "La tua nazionale sarà liberata tra {{days}} giorni a meno che tu non sottoscriva il livello National su Patreon.",
102
+ "bodyLastDay": "La tua nazionale sarà liberata domani a meno che tu non sottoscriva il livello National su Patreon."
103
+ },
104
+ "nationalTeamRevoked": {
105
+ "title": "Nazionale liberata",
106
+ "body": "La tua nazionale è stata liberata e ora può essere reclamata da qualcun altro. Sottoscrivi il livello National su Patreon per reclamarne di nuovo una."
94
107
  }
95
108
  }
@@ -91,5 +91,18 @@
91
91
  "wnlPlayerCalledUp": {
92
92
  "title": "Zawodnicy powołani",
93
93
  "body": "Co najmniej jeden z twoich zawodników został powołany do swojej reprezentacji!"
94
+ },
95
+ "nationalTrialGranted": {
96
+ "title": "Reprezentacja odblokowana",
97
+ "body": "Masz darmowy dostęp National do {{date}}. Przejmij reprezentację i poprowadź ją w WNL."
98
+ },
99
+ "nationalAccessExpiring": {
100
+ "title": "Twój dostęp do reprezentacji się kończy",
101
+ "bodyDays": "Twoja reprezentacja zostanie zwolniona za {{days}} dni, chyba że wykupisz poziom National na Patreonie.",
102
+ "bodyLastDay": "Twoja reprezentacja zostanie zwolniona jutro, chyba że wykupisz poziom National na Patreonie."
103
+ },
104
+ "nationalTeamRevoked": {
105
+ "title": "Reprezentacja zwolniona",
106
+ "body": "Twoja reprezentacja została zwolniona i może ją teraz przejąć ktoś inny. Wykup poziom National na Patreonie, aby ponownie przejąć reprezentację."
94
107
  }
95
108
  }
@@ -91,5 +91,18 @@
91
91
  "wnlPlayerCalledUp": {
92
92
  "title": "Jogadores convocados",
93
93
  "body": "Um ou mais dos seus jogadores foram convocados pela sua seleção nacional!"
94
+ },
95
+ "nationalTrialGranted": {
96
+ "title": "Seleção nacional desbloqueada",
97
+ "body": "Você tem acesso National gratuito até {{date}}. Reivindique uma seleção nacional e comande-a na WNL."
98
+ },
99
+ "nationalAccessExpiring": {
100
+ "title": "Seu acesso à seleção nacional está acabando",
101
+ "bodyDays": "Sua seleção nacional será liberada em {{days}} dias, a menos que você assine o nível National no Patreon.",
102
+ "bodyLastDay": "Sua seleção nacional será liberada amanhã, a menos que você assine o nível National no Patreon."
103
+ },
104
+ "nationalTeamRevoked": {
105
+ "title": "Seleção nacional liberada",
106
+ "body": "Sua seleção nacional foi liberada e agora está disponível para outra pessoa reivindicar. Assine o nível National no Patreon para reivindicar uma novamente."
94
107
  }
95
108
  }
@@ -91,5 +91,18 @@
91
91
  "wnlPlayerCalledUp": {
92
92
  "title": "Oyuncular milli takıma çağrıldı",
93
93
  "body": "Bir veya daha fazla oyuncun milli takımına çağrıldı!"
94
+ },
95
+ "nationalTrialGranted": {
96
+ "title": "Milli takım kilidi açıldı",
97
+ "body": "{{date}} tarihine kadar ücretsiz National erişimin var. Bir milli takım seç ve WNL'de yönet."
98
+ },
99
+ "nationalAccessExpiring": {
100
+ "title": "Milli takım erişimin sona eriyor",
101
+ "bodyDays": "Patreon'da National seviyesine abone olmazsan milli takımın {{days}} gün içinde serbest bırakılacak.",
102
+ "bodyLastDay": "Patreon'da National seviyesine abone olmazsan milli takımın yarın serbest bırakılacak."
103
+ },
104
+ "nationalTeamRevoked": {
105
+ "title": "Milli takım serbest bırakıldı",
106
+ "body": "Milli takımın serbest bırakıldı ve artık başka biri tarafından seçilebilir. Yeniden bir milli takım seçmek için Patreon'da National seviyesine abone ol."
94
107
  }
95
108
  }
@@ -1,4 +1,4 @@
1
- export declare const NOTIFICATION_TYPES: readonly ["MATCH_RESULT", "INJURY", "INJURY_RECOVERED", "SEASON_END", "GRANT_RECEIVED", "PROMOTION", "DAILY_LOGIN", "WELCOME", "COMPENSATION", "EVENT_REGISTRATION_OPEN", "EVENT_REGISTRATION_CLOSING", "EVENT_STARTED", "EVENT_FINAL_ROUND", "EVENT_FINISHED", "REGION_RELOCATION", "WNL_CALLUP_OPEN", "WNL_CALLUP_DEADLINE", "WNL_CALLUP_CLOSED", "WNL_LEAGUE_START", "WNL_FINALS_START", "WNL_CHAMPION", "WNL_PLAYER_CALLED_UP"];
1
+ export declare const NOTIFICATION_TYPES: readonly ["MATCH_RESULT", "INJURY", "INJURY_RECOVERED", "SEASON_END", "GRANT_RECEIVED", "PROMOTION", "DAILY_LOGIN", "WELCOME", "COMPENSATION", "EVENT_REGISTRATION_OPEN", "EVENT_REGISTRATION_CLOSING", "EVENT_STARTED", "EVENT_FINAL_ROUND", "EVENT_FINISHED", "REGION_RELOCATION", "WNL_CALLUP_OPEN", "WNL_CALLUP_DEADLINE", "WNL_CALLUP_CLOSED", "WNL_LEAGUE_START", "WNL_FINALS_START", "WNL_CHAMPION", "WNL_PLAYER_CALLED_UP", "NATIONAL_TRIAL_GRANTED", "NATIONAL_ACCESS_EXPIRING", "NATIONAL_TEAM_REVOKED"];
2
2
  export type NotificationType = typeof NOTIFICATION_TYPES[number];
3
3
  export declare const NOTIFICATION_LOCALES: readonly ["en", "es", "fr", "it", "pl", "pt-BR", "tr"];
4
4
  export type NotificationLocale = typeof NOTIFICATION_LOCALES[number];
@@ -24,7 +24,10 @@ export const NOTIFICATION_TYPES = [
24
24
  'WNL_LEAGUE_START',
25
25
  'WNL_FINALS_START',
26
26
  'WNL_CHAMPION',
27
- 'WNL_PLAYER_CALLED_UP'
27
+ 'WNL_PLAYER_CALLED_UP',
28
+ 'NATIONAL_TRIAL_GRANTED',
29
+ 'NATIONAL_ACCESS_EXPIRING',
30
+ 'NATIONAL_TEAM_REVOKED'
28
31
  ];
29
32
  // The 7 supported locales. Must stay in sync with the API VALID_LOCALES set and the UI src/locales/*.json.
30
33
  export const NOTIFICATION_LOCALES = ['en', 'es', 'fr', 'it', 'pl', 'pt-BR', 'tr'];
@@ -37,4 +37,10 @@ export interface WnlCallupOpenPayload {
37
37
  export interface WnlChampionPayload {
38
38
  team?: string;
39
39
  }
40
- export type NotificationPayload = MatchResultPayload | InjuryPayload | InjuryRecoveredPayload | SeasonEndPayload | PromotionPayload | WnlCallupOpenPayload | WnlChampionPayload | Record<string, unknown>;
40
+ export interface NationalTrialGrantedPayload {
41
+ endsOn?: string;
42
+ }
43
+ export interface NationalAccessExpiringPayload {
44
+ expiresAt?: string;
45
+ }
46
+ export type NotificationPayload = MatchResultPayload | InjuryPayload | InjuryRecoveredPayload | SeasonEndPayload | PromotionPayload | WnlCallupOpenPayload | WnlChampionPayload | NationalTrialGrantedPayload | NationalAccessExpiringPayload | Record<string, unknown>;
@@ -6,11 +6,10 @@ export interface NotificationDescriptor {
6
6
  deepLink?: (payload: Record<string, unknown>) => string;
7
7
  pushTitleKey?: CatalogKeyResolver;
8
8
  pushBodyKey?: CatalogKeyResolver;
9
- inAppMessageKey?: CatalogKeyResolver;
10
- richRender?: NotificationType;
11
9
  params?: (payload: Record<string, unknown>) => Record<string, string>;
12
10
  catalogKeys: string[];
13
11
  }
12
+ export declare const NATIONAL_ACCESS_PREF_KEY = "NATIONAL_ACCESS";
14
13
  export declare const NOTIFICATION_REGISTRY: Record<NotificationType, NotificationDescriptor>;
15
14
  export declare const PUSH_NOTIFICATION_TYPES: NotificationType[];
16
15
  export declare function pushPrefKeyFor(type: string): string;
@@ -2,6 +2,22 @@ import { NOTIFICATION_TYPES, pushEnabledFor } from './notification-type';
2
2
  function readString(value) {
3
3
  return typeof value === 'string' && value !== '' ? value : null;
4
4
  }
5
+ // The preference key the three national-team access types share. Deliberately NOT the 'WNL' group: muting routine
6
+ // call-up chatter must never also mute "you are about to lose your national team" (owner 2026-08-29).
7
+ export const NATIONAL_ACCESS_PREF_KEY = 'NATIONAL_ACCESS';
8
+ // Whole days from now until an ISO instant, floored at 0. NATIONAL_ACCESS_EXPIRING stores the ABSOLUTE deadline
9
+ // rather than a day count because the in-app bell renders a stored payload at READ time: a stored "3" would still
10
+ // read "3 days" two days later. Push resolves the number here at SEND time and the bell resolves it again at render,
11
+ // so neither surface can go stale.
12
+ function daysUntilIso(value) {
13
+ const iso = readString(value);
14
+ if (iso == null)
15
+ return 0;
16
+ const ms = new Date(iso).getTime() - Date.now();
17
+ if (!Number.isFinite(ms) || ms <= 0)
18
+ return 0;
19
+ return Math.ceil(ms / 86400000);
20
+ }
5
21
  function teamPath(payload) {
6
22
  const teamId = readString(payload.teamId);
7
23
  return teamId != null ? `/team/${encodeURIComponent(teamId)}` : '/';
@@ -37,7 +53,6 @@ export const NOTIFICATION_REGISTRY = {
37
53
  return `matchResult.${outcome}${named}`;
38
54
  },
39
55
  params: (p) => ({ opponent: matchOpponent(p) ?? '' }),
40
- richRender: 'MATCH_RESULT',
41
56
  catalogKeys: ['matchResult.title', 'matchResult.wonNamed', 'matchResult.wonUnnamed', 'matchResult.lostNamed', 'matchResult.lostUnnamed']
42
57
  },
43
58
  INJURY: {
@@ -45,7 +60,6 @@ export const NOTIFICATION_REGISTRY = {
45
60
  deepLink: () => '/infirmary',
46
61
  pushTitleKey: 'injury.title',
47
62
  pushBodyKey: 'injury.body',
48
- inAppMessageKey: 'injury.body',
49
63
  params: (p) => ({ name: injuredName(p) }),
50
64
  catalogKeys: ['injury.title', 'injury.body']
51
65
  },
@@ -54,7 +68,6 @@ export const NOTIFICATION_REGISTRY = {
54
68
  deepLink: teamPath,
55
69
  pushTitleKey: 'injuryRecovered.title',
56
70
  pushBodyKey: 'injuryRecovered.body',
57
- inAppMessageKey: 'injuryRecovered.body',
58
71
  params: (p) => ({ name: injuredName(p) }),
59
72
  catalogKeys: ['injuryRecovered.title', 'injuryRecovered.body']
60
73
  },
@@ -66,7 +79,6 @@ export const NOTIFICATION_REGISTRY = {
66
79
  },
67
80
  pushTitleKey: 'seasonEnd.title',
68
81
  pushBodyKey: 'seasonEnd.body',
69
- inAppMessageKey: 'seasonEnd.body',
70
82
  catalogKeys: ['seasonEnd.title', 'seasonEnd.body']
71
83
  },
72
84
  GRANT_RECEIVED: {
@@ -74,7 +86,6 @@ export const NOTIFICATION_REGISTRY = {
74
86
  deepLink: () => '/recruit',
75
87
  pushTitleKey: 'grant.title',
76
88
  pushBodyKey: 'grant.body',
77
- inAppMessageKey: 'grant.body',
78
89
  catalogKeys: ['grant.title', 'grant.body']
79
90
  },
80
91
  PROMOTION: {
@@ -82,7 +93,6 @@ export const NOTIFICATION_REGISTRY = {
82
93
  deepLink: teamPath,
83
94
  pushTitleKey: 'promotion.title',
84
95
  pushBodyKey: 'promotion.body',
85
- inAppMessageKey: 'promotion.body',
86
96
  catalogKeys: ['promotion.title', 'promotion.body']
87
97
  },
88
98
  // In-game events. All five deep-link to the Events section, which is where every phase of an event lives.
@@ -93,7 +103,6 @@ export const NOTIFICATION_REGISTRY = {
93
103
  deepLink: () => '/events',
94
104
  pushTitleKey: 'eventRegistrationOpen.title',
95
105
  pushBodyKey: 'eventRegistrationOpen.body',
96
- inAppMessageKey: 'eventRegistrationOpen.body',
97
106
  catalogKeys: ['eventRegistrationOpen.title', 'eventRegistrationOpen.body']
98
107
  },
99
108
  EVENT_REGISTRATION_CLOSING: {
@@ -101,7 +110,6 @@ export const NOTIFICATION_REGISTRY = {
101
110
  deepLink: () => '/events',
102
111
  pushTitleKey: 'eventRegistrationClosing.title',
103
112
  pushBodyKey: 'eventRegistrationClosing.body',
104
- inAppMessageKey: 'eventRegistrationClosing.body',
105
113
  catalogKeys: ['eventRegistrationClosing.title', 'eventRegistrationClosing.body']
106
114
  },
107
115
  EVENT_STARTED: {
@@ -109,7 +117,6 @@ export const NOTIFICATION_REGISTRY = {
109
117
  deepLink: () => '/events',
110
118
  pushTitleKey: 'eventStarted.title',
111
119
  pushBodyKey: 'eventStarted.body',
112
- inAppMessageKey: 'eventStarted.body',
113
120
  catalogKeys: ['eventStarted.title', 'eventStarted.body']
114
121
  },
115
122
  EVENT_FINAL_ROUND: {
@@ -117,7 +124,6 @@ export const NOTIFICATION_REGISTRY = {
117
124
  deepLink: () => '/events',
118
125
  pushTitleKey: 'eventFinalRound.title',
119
126
  pushBodyKey: 'eventFinalRound.body',
120
- inAppMessageKey: 'eventFinalRound.body',
121
127
  catalogKeys: ['eventFinalRound.title', 'eventFinalRound.body']
122
128
  },
123
129
  EVENT_FINISHED: {
@@ -128,9 +134,6 @@ export const NOTIFICATION_REGISTRY = {
128
134
  pushBodyKey: (p) => readString(p.position) != null || typeof p.position === 'number'
129
135
  ? 'eventFinished.placed'
130
136
  : 'eventFinished.body',
131
- inAppMessageKey: (p) => readString(p.position) != null || typeof p.position === 'number'
132
- ? 'eventFinished.placed'
133
- : 'eventFinished.body',
134
137
  params: (p) => ({
135
138
  position: typeof p.position === 'number' ? String(p.position) : (readString(p.position) ?? ''),
136
139
  entrants: typeof p.entrants === 'number' ? String(p.entrants) : (readString(p.entrants) ?? '')
@@ -143,7 +146,6 @@ export const NOTIFICATION_REGISTRY = {
143
146
  deepLink: () => '/daily-login',
144
147
  pushTitleKey: 'dailyLogin.title',
145
148
  pushBodyKey: 'dailyLogin.body',
146
- inAppMessageKey: 'dailyLogin.body',
147
149
  catalogKeys: ['dailyLogin.title', 'dailyLogin.body']
148
150
  },
149
151
  WELCOME: {
@@ -151,13 +153,11 @@ export const NOTIFICATION_REGISTRY = {
151
153
  deepLink: () => '/recruit',
152
154
  pushTitleKey: 'welcome.title',
153
155
  pushBodyKey: 'welcome.body',
154
- richRender: 'WELCOME',
155
156
  catalogKeys: ['welcome.title', 'welcome.body']
156
157
  },
157
158
  // In-app only (no push); the UI keeps a bespoke renderer (amount formatting) and its own `compensation` label.
158
159
  COMPENSATION: {
159
160
  emitsPush: false,
160
- richRender: 'COMPENSATION',
161
161
  catalogKeys: []
162
162
  },
163
163
  // One-off relocation warning: sent to users whose team is being moved to Kyorin. Informational; opens their team.
@@ -166,7 +166,6 @@ export const NOTIFICATION_REGISTRY = {
166
166
  deepLink: teamPath,
167
167
  pushTitleKey: 'regionRelocation.title',
168
168
  pushBodyKey: 'regionRelocation.body',
169
- inAppMessageKey: 'regionRelocation.body',
170
169
  catalogKeys: ['regionRelocation.title', 'regionRelocation.body']
171
170
  },
172
171
  // World Nations League (owner 2026-08-28). All seven share the single 'WNL' preference toggle. The three callup
@@ -178,7 +177,6 @@ export const NOTIFICATION_REGISTRY = {
178
177
  deepLink: () => '/wnl/callup',
179
178
  pushTitleKey: 'wnlCallupOpen.title',
180
179
  pushBodyKey: 'wnlCallupOpen.body',
181
- inAppMessageKey: 'wnlCallupOpen.body',
182
180
  // The deadline arrives pre-formatted (locale-neutral 'YYYY-MM-DD HH:mm UTC') because the stored payload is
183
181
  // shared by the push and the in-app bell and cannot be re-localised per render.
184
182
  params: (p) => ({ date: readString(p.deadline) ?? '' }),
@@ -190,7 +188,6 @@ export const NOTIFICATION_REGISTRY = {
190
188
  deepLink: () => '/wnl/callup',
191
189
  pushTitleKey: 'wnlCallupDeadline.title',
192
190
  pushBodyKey: 'wnlCallupDeadline.body',
193
- inAppMessageKey: 'wnlCallupDeadline.body',
194
191
  catalogKeys: ['wnlCallupDeadline.title', 'wnlCallupDeadline.body']
195
192
  },
196
193
  WNL_CALLUP_CLOSED: {
@@ -199,7 +196,6 @@ export const NOTIFICATION_REGISTRY = {
199
196
  deepLink: () => '/wnl/manage/lineup',
200
197
  pushTitleKey: 'wnlCallupClosed.title',
201
198
  pushBodyKey: 'wnlCallupClosed.body',
202
- inAppMessageKey: 'wnlCallupClosed.body',
203
199
  catalogKeys: ['wnlCallupClosed.title', 'wnlCallupClosed.body']
204
200
  },
205
201
  WNL_LEAGUE_START: {
@@ -208,7 +204,6 @@ export const NOTIFICATION_REGISTRY = {
208
204
  deepLink: () => '/wnl',
209
205
  pushTitleKey: 'wnlLeagueStart.title',
210
206
  pushBodyKey: 'wnlLeagueStart.body',
211
- inAppMessageKey: 'wnlLeagueStart.body',
212
207
  catalogKeys: ['wnlLeagueStart.title', 'wnlLeagueStart.body']
213
208
  },
214
209
  WNL_FINALS_START: {
@@ -217,7 +212,6 @@ export const NOTIFICATION_REGISTRY = {
217
212
  deepLink: () => '/wnl/finals',
218
213
  pushTitleKey: 'wnlFinalsStart.title',
219
214
  pushBodyKey: 'wnlFinalsStart.body',
220
- inAppMessageKey: 'wnlFinalsStart.body',
221
215
  catalogKeys: ['wnlFinalsStart.title', 'wnlFinalsStart.body']
222
216
  },
223
217
  WNL_CHAMPION: {
@@ -227,7 +221,6 @@ export const NOTIFICATION_REGISTRY = {
227
221
  pushTitleKey: 'wnlChampion.title',
228
222
  // A missing team name never renders a subjectless sentence (mirrors the MATCH_RESULT named/unnamed split).
229
223
  pushBodyKey: (p) => readString(p.team) != null ? 'wnlChampion.body' : 'wnlChampion.bodyUnnamed',
230
- inAppMessageKey: (p) => readString(p.team) != null ? 'wnlChampion.body' : 'wnlChampion.bodyUnnamed',
231
224
  params: (p) => ({ team: readString(p.team) ?? '' }),
232
225
  catalogKeys: ['wnlChampion.title', 'wnlChampion.body', 'wnlChampion.bodyUnnamed']
233
226
  },
@@ -237,8 +230,42 @@ export const NOTIFICATION_REGISTRY = {
237
230
  deepLink: () => '/wnl/teams',
238
231
  pushTitleKey: 'wnlPlayerCalledUp.title',
239
232
  pushBodyKey: 'wnlPlayerCalledUp.body',
240
- inAppMessageKey: 'wnlPlayerCalledUp.body',
241
233
  catalogKeys: ['wnlPlayerCalledUp.title', 'wnlPlayerCalledUp.body']
234
+ },
235
+ // National-team ACCESS lifecycle (owner 2026-08-29): the free Tier-2 trial is granted, is about to end, and the
236
+ // team is released. These share their OWN toggle rather than the 'WNL' group so that muting call-up chatter
237
+ // cannot silence the warning that a team is about to be lost. They are also intentionally not named WNL_*: the
238
+ // completeness test asserts every WNL_-prefixed type resolves to the 'WNL' key.
239
+ NATIONAL_TRIAL_GRANTED: {
240
+ emitsPush: true,
241
+ prefKey: NATIONAL_ACCESS_PREF_KEY,
242
+ deepLink: () => '/wnl/claim',
243
+ pushTitleKey: 'nationalTrialGranted.title',
244
+ pushBodyKey: 'nationalTrialGranted.body',
245
+ // The end date arrives pre-formatted (locale-neutral 'YYYY-MM-DD'), same reason as wnlCallupOpen: one stored
246
+ // payload serves both the push and the bell and cannot be re-localised per render.
247
+ params: (p) => ({ date: readString(p.endsOn) ?? '' }),
248
+ catalogKeys: ['nationalTrialGranted.title', 'nationalTrialGranted.body']
249
+ },
250
+ NATIONAL_ACCESS_EXPIRING: {
251
+ emitsPush: true,
252
+ prefKey: NATIONAL_ACCESS_PREF_KEY,
253
+ // Settings is where the Patreon card lives, so the action the copy asks for is one tap away.
254
+ deepLink: () => '/settings',
255
+ pushTitleKey: 'nationalAccessExpiring.title',
256
+ // The catalog has no pluralisation (interpolate is a bare {{param}} replace), so the final day is its own key
257
+ // instead of rendering "1 days" (mirrors the wnlChampion named/unnamed split).
258
+ pushBodyKey: (p) => daysUntilIso(p.expiresAt) <= 1 ? 'nationalAccessExpiring.bodyLastDay' : 'nationalAccessExpiring.bodyDays',
259
+ params: (p) => ({ days: String(daysUntilIso(p.expiresAt)) }),
260
+ catalogKeys: ['nationalAccessExpiring.title', 'nationalAccessExpiring.bodyDays', 'nationalAccessExpiring.bodyLastDay']
261
+ },
262
+ NATIONAL_TEAM_REVOKED: {
263
+ emitsPush: true,
264
+ prefKey: NATIONAL_ACCESS_PREF_KEY,
265
+ deepLink: () => '/wnl/teams',
266
+ pushTitleKey: 'nationalTeamRevoked.title',
267
+ pushBodyKey: 'nationalTeamRevoked.body',
268
+ catalogKeys: ['nationalTeamRevoked.title', 'nationalTeamRevoked.body']
242
269
  }
243
270
  };
244
271
  // The notification types that emit a mobile push (everything except COMPENSATION). Source of truth = each
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "volleyballsimtypes",
3
- "version": "0.0.525",
3
+ "version": "0.0.526",
4
4
  "description": "vbsim types",
5
5
  "main": "./dist/cjs/src/index.js",
6
6
  "module": "./dist/esm/src/index.js",