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.
- package/dist/cjs/src/api/index.d.ts +1 -1
- package/dist/cjs/src/data/init-models.js +4 -22
- package/dist/cjs/src/data/models/national-team-claim.d.ts +5 -5
- package/dist/cjs/src/data/models/national-team-claim.js +8 -0
- package/dist/cjs/src/data/models/national-team.d.ts +1 -7
- package/dist/cjs/src/data/models/wnl-callup.d.ts +0 -10
- package/dist/cjs/src/data/models/wnl-season.d.ts +0 -7
- package/dist/cjs/src/data/models/wnl-squad.d.ts +0 -13
- package/dist/cjs/src/service/notification/catalog/en.json +13 -0
- package/dist/cjs/src/service/notification/catalog/es.json +13 -0
- package/dist/cjs/src/service/notification/catalog/fr.json +13 -0
- package/dist/cjs/src/service/notification/catalog/it.json +13 -0
- package/dist/cjs/src/service/notification/catalog/pl.json +13 -0
- package/dist/cjs/src/service/notification/catalog/pt-BR.json +13 -0
- package/dist/cjs/src/service/notification/catalog/tr.json +13 -0
- package/dist/cjs/src/service/notification/notification-type.d.ts +1 -1
- package/dist/cjs/src/service/notification/notification-type.js +4 -1
- package/dist/cjs/src/service/notification/payloads.d.ts +7 -1
- package/dist/cjs/src/service/notification/registry.d.ts +1 -2
- package/dist/cjs/src/service/notification/registry.js +52 -25
- package/dist/esm/src/api/index.d.ts +1 -1
- package/dist/esm/src/data/init-models.js +4 -22
- package/dist/esm/src/data/models/national-team-claim.d.ts +5 -5
- package/dist/esm/src/data/models/national-team-claim.js +8 -0
- package/dist/esm/src/data/models/national-team.d.ts +1 -7
- package/dist/esm/src/data/models/wnl-callup.d.ts +0 -10
- package/dist/esm/src/data/models/wnl-season.d.ts +0 -7
- package/dist/esm/src/data/models/wnl-squad.d.ts +0 -13
- package/dist/esm/src/service/notification/catalog/en.json +13 -0
- package/dist/esm/src/service/notification/catalog/es.json +13 -0
- package/dist/esm/src/service/notification/catalog/fr.json +13 -0
- package/dist/esm/src/service/notification/catalog/it.json +13 -0
- package/dist/esm/src/service/notification/catalog/pl.json +13 -0
- package/dist/esm/src/service/notification/catalog/pt-BR.json +13 -0
- package/dist/esm/src/service/notification/catalog/tr.json +13 -0
- package/dist/esm/src/service/notification/notification-type.d.ts +1 -1
- package/dist/esm/src/service/notification/notification-type.js +4 -1
- package/dist/esm/src/service/notification/payloads.d.ts +7 -1
- package/dist/esm/src/service/notification/registry.d.ts +1 -2
- package/dist/esm/src/service/notification/registry.js +51 -24
- 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)
|
|
240
|
-
//
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
18
|
-
|
|
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 {
|
|
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
|
|
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)
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
18
|
-
|
|
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 {
|
|
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
|
|
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
|