volleyballsimtypes 0.0.524 → 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 (75) hide show
  1. package/dist/cjs/src/api/index.d.ts +17 -1
  2. package/dist/cjs/src/data/init-models.js +14 -0
  3. package/dist/cjs/src/data/models/competition.d.ts +1 -1
  4. package/dist/cjs/src/data/models/competition.js +1 -1
  5. package/dist/cjs/src/data/models/index.d.ts +5 -0
  6. package/dist/cjs/src/data/models/index.js +5 -0
  7. package/dist/cjs/src/data/models/jersey-badge.d.ts +1 -1
  8. package/dist/cjs/src/data/models/jersey-badge.js +1 -1
  9. package/dist/cjs/src/data/models/national-team-claim.d.ts +21 -0
  10. package/dist/cjs/src/data/models/national-team-claim.js +44 -0
  11. package/dist/cjs/src/data/models/national-team.d.ts +16 -0
  12. package/dist/cjs/src/data/models/national-team.js +31 -0
  13. package/dist/cjs/src/data/models/wnl-callup.d.ts +21 -0
  14. package/dist/cjs/src/data/models/wnl-callup.js +45 -0
  15. package/dist/cjs/src/data/models/wnl-season.d.ts +17 -0
  16. package/dist/cjs/src/data/models/wnl-season.js +34 -0
  17. package/dist/cjs/src/data/models/wnl-squad.d.ts +27 -0
  18. package/dist/cjs/src/data/models/wnl-squad.js +59 -0
  19. package/dist/cjs/src/service/match/index.d.ts +1 -0
  20. package/dist/cjs/src/service/match/index.js +8 -0
  21. package/dist/cjs/src/service/match/match-generator.d.ts +5 -1
  22. package/dist/cjs/src/service/match/match-generator.js +6 -4
  23. package/dist/cjs/src/service/notification/catalog/en.json +42 -0
  24. package/dist/cjs/src/service/notification/catalog/es.json +42 -0
  25. package/dist/cjs/src/service/notification/catalog/fr.json +42 -0
  26. package/dist/cjs/src/service/notification/catalog/it.json +42 -0
  27. package/dist/cjs/src/service/notification/catalog/pl.json +42 -0
  28. package/dist/cjs/src/service/notification/catalog/pt-BR.json +42 -0
  29. package/dist/cjs/src/service/notification/catalog/tr.json +42 -0
  30. package/dist/cjs/src/service/notification/notification-type.d.ts +1 -1
  31. package/dist/cjs/src/service/notification/notification-type.js +11 -1
  32. package/dist/cjs/src/service/notification/notification.test.js +18 -0
  33. package/dist/cjs/src/service/notification/payloads.d.ts +13 -1
  34. package/dist/cjs/src/service/notification/registry.d.ts +6 -3
  35. package/dist/cjs/src/service/notification/registry.js +134 -20
  36. package/dist/cjs/src/service/premium/premium-tier.d.ts +2 -0
  37. package/dist/cjs/src/service/premium/premium-tier.js +8 -1
  38. package/dist/esm/src/api/index.d.ts +17 -1
  39. package/dist/esm/src/data/init-models.js +15 -1
  40. package/dist/esm/src/data/models/competition.d.ts +1 -1
  41. package/dist/esm/src/data/models/competition.js +1 -1
  42. package/dist/esm/src/data/models/index.d.ts +5 -0
  43. package/dist/esm/src/data/models/index.js +5 -0
  44. package/dist/esm/src/data/models/jersey-badge.d.ts +1 -1
  45. package/dist/esm/src/data/models/jersey-badge.js +1 -1
  46. package/dist/esm/src/data/models/national-team-claim.d.ts +21 -0
  47. package/dist/esm/src/data/models/national-team-claim.js +40 -0
  48. package/dist/esm/src/data/models/national-team.d.ts +16 -0
  49. package/dist/esm/src/data/models/national-team.js +27 -0
  50. package/dist/esm/src/data/models/wnl-callup.d.ts +21 -0
  51. package/dist/esm/src/data/models/wnl-callup.js +41 -0
  52. package/dist/esm/src/data/models/wnl-season.d.ts +17 -0
  53. package/dist/esm/src/data/models/wnl-season.js +30 -0
  54. package/dist/esm/src/data/models/wnl-squad.d.ts +27 -0
  55. package/dist/esm/src/data/models/wnl-squad.js +55 -0
  56. package/dist/esm/src/service/match/index.d.ts +1 -0
  57. package/dist/esm/src/service/match/index.js +3 -0
  58. package/dist/esm/src/service/match/match-generator.d.ts +5 -1
  59. package/dist/esm/src/service/match/match-generator.js +6 -4
  60. package/dist/esm/src/service/notification/catalog/en.json +42 -0
  61. package/dist/esm/src/service/notification/catalog/es.json +42 -0
  62. package/dist/esm/src/service/notification/catalog/fr.json +42 -0
  63. package/dist/esm/src/service/notification/catalog/it.json +42 -0
  64. package/dist/esm/src/service/notification/catalog/pl.json +42 -0
  65. package/dist/esm/src/service/notification/catalog/pt-BR.json +42 -0
  66. package/dist/esm/src/service/notification/catalog/tr.json +42 -0
  67. package/dist/esm/src/service/notification/notification-type.d.ts +1 -1
  68. package/dist/esm/src/service/notification/notification-type.js +11 -1
  69. package/dist/esm/src/service/notification/notification.test.js +19 -1
  70. package/dist/esm/src/service/notification/payloads.d.ts +13 -1
  71. package/dist/esm/src/service/notification/registry.d.ts +6 -3
  72. package/dist/esm/src/service/notification/registry.js +132 -20
  73. package/dist/esm/src/service/premium/premium-tier.d.ts +2 -0
  74. package/dist/esm/src/service/premium/premium-tier.js +6 -0
  75. package/package.json +1 -1
@@ -62,5 +62,47 @@
62
62
  "regionRelocation": {
63
63
  "title": "Votre équipe déménage",
64
64
  "body": "Votre équipe a été choisie pour être transférée vers la nouvelle région : Kyorin. Rien ne change concernant l'équipe que vous gérez, vos joueurs ni quoi que ce soit d'autre."
65
+ },
66
+ "wnlCallupOpen": {
67
+ "title": "Convocation nationale ouverte",
68
+ "body": "La phase de convocation nationale a commencé ! Vous avez jusqu'au {{date}} pour sélectionner votre équipe et participer à la prochaine édition de la WNL."
69
+ },
70
+ "wnlCallupDeadline": {
71
+ "title": "La convocation se termine aujourd'hui",
72
+ "body": "La date limite de la convocation nationale est aujourd'hui. Assurez-vous d'avoir sélectionné les joueurs qui participeront à la prochaine édition de la WNL."
73
+ },
74
+ "wnlCallupClosed": {
75
+ "title": "Convocation nationale terminée",
76
+ "body": "La phase de convocation nationale est terminée. Vous pouvez modifier votre composition pour préparer la WNL qui commence lundi prochain !"
77
+ },
78
+ "wnlLeagueStart": {
79
+ "title": "La ligue WNL a commencé",
80
+ "body": "La phase de ligue de la WNL a commencé !"
81
+ },
82
+ "wnlFinalsStart": {
83
+ "title": "Les finales de la WNL ont commencé",
84
+ "body": "Les finales de la WNL ont commencé !"
85
+ },
86
+ "wnlChampion": {
87
+ "title": "Champions de la WNL",
88
+ "body": "L'édition de la WNL est terminée. {{team}} sont les champions de la WNL !",
89
+ "bodyUnnamed": "L'édition de la WNL est terminée. Les champions sont couronnés !"
90
+ },
91
+ "wnlPlayerCalledUp": {
92
+ "title": "Joueurs convoqués",
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."
65
107
  }
66
108
  }
@@ -62,5 +62,47 @@
62
62
  "regionRelocation": {
63
63
  "title": "La tua squadra si trasferisce",
64
64
  "body": "La tua squadra è stata scelta per essere spostata nella nuova regione: Kyorin. Non cambia nulla riguardo alla squadra che gestisci, ai tuoi giocatori o a qualsiasi altra cosa."
65
+ },
66
+ "wnlCallupOpen": {
67
+ "title": "Convocazione nazionale aperta",
68
+ "body": "La fase di convocazione nazionale è iniziata! Hai tempo fino al {{date}} per selezionare la tua squadra e partecipare alla prossima edizione della WNL."
69
+ },
70
+ "wnlCallupDeadline": {
71
+ "title": "La convocazione scade oggi",
72
+ "body": "Il termine della convocazione nazionale è oggi. Assicurati di aver selezionato i giocatori che parteciperanno alla prossima edizione della WNL."
73
+ },
74
+ "wnlCallupClosed": {
75
+ "title": "Convocazione nazionale chiusa",
76
+ "body": "La fase di convocazione nazionale è terminata. Puoi modificare la tua formazione per preparare la WNL che inizia lunedì prossimo!"
77
+ },
78
+ "wnlLeagueStart": {
79
+ "title": "La lega WNL è iniziata",
80
+ "body": "La fase di lega della WNL è iniziata!"
81
+ },
82
+ "wnlFinalsStart": {
83
+ "title": "Le finali della WNL sono iniziate",
84
+ "body": "Le finali della WNL sono iniziate!"
85
+ },
86
+ "wnlChampion": {
87
+ "title": "Campioni della WNL",
88
+ "body": "L'edizione della WNL è terminata. {{team}} sono i campioni della WNL!",
89
+ "bodyUnnamed": "L'edizione della WNL è terminata. I campioni sono stati incoronati!"
90
+ },
91
+ "wnlPlayerCalledUp": {
92
+ "title": "Giocatori convocati",
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."
65
107
  }
66
108
  }
@@ -62,5 +62,47 @@
62
62
  "regionRelocation": {
63
63
  "title": "Twoja drużyna się przenosi",
64
64
  "body": "Twoja drużyna została wybrana do przeniesienia do nowego regionu: Kyorin. Nic się nie zmienia, jeśli chodzi o zarządzaną przez ciebie drużynę, twoich zawodników ani cokolwiek innego."
65
+ },
66
+ "wnlCallupOpen": {
67
+ "title": "Powołania do reprezentacji otwarte",
68
+ "body": "Faza powołań do reprezentacji rozpoczęła się! Masz czas do {{date}}, aby wybrać swoją drużynę do udziału w następnej edycji WNL."
69
+ },
70
+ "wnlCallupDeadline": {
71
+ "title": "Termin powołań mija dzisiaj",
72
+ "body": "Termin powołań do reprezentacji mija dzisiaj. Upewnij się, że wybrałeś zawodników, którzy wezmą udział w następnej edycji WNL."
73
+ },
74
+ "wnlCallupClosed": {
75
+ "title": "Powołania do reprezentacji zamknięte",
76
+ "body": "Faza powołań do reprezentacji zakończyła się. Możesz edytować swój skład, aby przygotować się do WNL, która zaczyna się w najbliższy poniedziałek!"
77
+ },
78
+ "wnlLeagueStart": {
79
+ "title": "Liga WNL rozpoczęta",
80
+ "body": "Faza ligowa WNL rozpoczęła się!"
81
+ },
82
+ "wnlFinalsStart": {
83
+ "title": "Finały WNL rozpoczęte",
84
+ "body": "Finały WNL rozpoczęły się!"
85
+ },
86
+ "wnlChampion": {
87
+ "title": "Mistrzowie WNL",
88
+ "body": "Edycja WNL zakończyła się. {{team}} to mistrzowie WNL!",
89
+ "bodyUnnamed": "Edycja WNL zakończyła się. Mistrzowie zostali wyłonieni!"
90
+ },
91
+ "wnlPlayerCalledUp": {
92
+ "title": "Zawodnicy powołani",
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ę."
65
107
  }
66
108
  }
@@ -62,5 +62,47 @@
62
62
  "regionRelocation": {
63
63
  "title": "Seu time está mudando",
64
64
  "body": "Seu time foi escolhido para ser movido para a nova região: Kyorin. Nada muda em relação ao time que você gerencia, seus jogadores ou qualquer outra coisa."
65
+ },
66
+ "wnlCallupOpen": {
67
+ "title": "Convocação nacional aberta",
68
+ "body": "A fase de convocação nacional começou! Você tem até {{date}} para selecionar seu time e participar da próxima edição da WNL."
69
+ },
70
+ "wnlCallupDeadline": {
71
+ "title": "A convocação termina hoje",
72
+ "body": "O prazo da convocação nacional termina hoje. Verifique se você selecionou os jogadores que participarão da próxima edição da WNL."
73
+ },
74
+ "wnlCallupClosed": {
75
+ "title": "Convocação nacional encerrada",
76
+ "body": "A fase de convocação nacional terminou. Você pode editar sua escalação para se preparar para a WNL que começa na próxima segunda-feira!"
77
+ },
78
+ "wnlLeagueStart": {
79
+ "title": "A liga WNL começou",
80
+ "body": "A fase de liga da WNL começou!"
81
+ },
82
+ "wnlFinalsStart": {
83
+ "title": "As finais da WNL começaram",
84
+ "body": "As finais da WNL começaram!"
85
+ },
86
+ "wnlChampion": {
87
+ "title": "Campeões da WNL",
88
+ "body": "A edição da WNL terminou. {{team}} são os campeões da WNL!",
89
+ "bodyUnnamed": "A edição da WNL terminou. Os campeões foram coroados!"
90
+ },
91
+ "wnlPlayerCalledUp": {
92
+ "title": "Jogadores convocados",
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."
65
107
  }
66
108
  }
@@ -62,5 +62,47 @@
62
62
  "regionRelocation": {
63
63
  "title": "Takımın taşınıyor",
64
64
  "body": "Takımın yeni bölgeye, Kyorin'e taşınmak üzere seçildi. Yönettiğin takım, oyuncuların ya da başka hiçbir şey açısından hiçbir şey değişmiyor."
65
+ },
66
+ "wnlCallupOpen": {
67
+ "title": "Milli takım kadro çağrısı açıldı",
68
+ "body": "Milli takım kadro çağrısı başladı! Bir sonraki WNL sezonuna katılacak takımını seçmek için {{date}} tarihine kadar vaktin var."
69
+ },
70
+ "wnlCallupDeadline": {
71
+ "title": "Kadro çağrısı bugün sona eriyor",
72
+ "body": "Milli takım kadro çağrısının son günü bugün. Bir sonraki WNL sezonunda oynayacak oyuncuları seçtiğinden emin ol."
73
+ },
74
+ "wnlCallupClosed": {
75
+ "title": "Kadro çağrısı kapandı",
76
+ "body": "Milli takım kadro çağrısı sona erdi. Önümüzdeki pazartesi başlayacak WNL için dizilişini düzenleyebilirsin!"
77
+ },
78
+ "wnlLeagueStart": {
79
+ "title": "WNL ligi başladı",
80
+ "body": "WNL lig aşaması başladı!"
81
+ },
82
+ "wnlFinalsStart": {
83
+ "title": "WNL finalleri başladı",
84
+ "body": "WNL finalleri başladı!"
85
+ },
86
+ "wnlChampion": {
87
+ "title": "WNL şampiyonları",
88
+ "body": "WNL sezonu sona erdi. {{team}} WNL şampiyonu oldu!",
89
+ "bodyUnnamed": "WNL sezonu sona erdi. Şampiyonlar belli oldu!"
90
+ },
91
+ "wnlPlayerCalledUp": {
92
+ "title": "Oyuncular milli takıma çağrıldı",
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."
65
107
  }
66
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"];
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];
@@ -17,7 +17,17 @@ export const NOTIFICATION_TYPES = [
17
17
  'EVENT_STARTED',
18
18
  'EVENT_FINAL_ROUND',
19
19
  'EVENT_FINISHED',
20
- 'REGION_RELOCATION'
20
+ 'REGION_RELOCATION',
21
+ 'WNL_CALLUP_OPEN',
22
+ 'WNL_CALLUP_DEADLINE',
23
+ 'WNL_CALLUP_CLOSED',
24
+ 'WNL_LEAGUE_START',
25
+ 'WNL_FINALS_START',
26
+ 'WNL_CHAMPION',
27
+ 'WNL_PLAYER_CALLED_UP',
28
+ 'NATIONAL_TRIAL_GRANTED',
29
+ 'NATIONAL_ACCESS_EXPIRING',
30
+ 'NATIONAL_TEAM_REVOKED'
21
31
  ];
22
32
  // The 7 supported locales. Must stay in sync with the API VALID_LOCALES set and the UI src/locales/*.json.
23
33
  export const NOTIFICATION_LOCALES = ['en', 'es', 'fr', 'it', 'pl', 'pt-BR', 'tr'];
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect } from '@jest/globals';
2
2
  import { NOTIFICATION_TYPES, NOTIFICATION_LOCALES, pushEnabledFor } from './notification-type';
3
- import { NOTIFICATION_REGISTRY, PUSH_NOTIFICATION_TYPES } from './registry';
3
+ import { NOTIFICATION_REGISTRY, PUSH_NOTIFICATION_TYPES, PUSH_PREF_KEYS, pushEnabledForType, pushPrefKeyFor } from './registry';
4
4
  import { NOTIFICATION_CATALOG } from './catalog';
5
5
  import { renderPush, notificationPath, interpolate, translateNotification } from './render';
6
6
  function readNested(node, dotKey) {
@@ -143,4 +143,22 @@ describe('push preferences (PUSH_NOTIFICATION_TYPES + pushEnabledFor)', () => {
143
143
  // disabling one type does not affect another
144
144
  expect(pushEnabledFor({ MATCH_RESULT: false }, 'INJURY')).toBe(true);
145
145
  });
146
+ it('groups the WNL types under the single WNL preference key', () => {
147
+ // Every WNL type resolves to the shared toggle; ungrouped types resolve to themselves.
148
+ for (const type of NOTIFICATION_TYPES.filter((t) => t.startsWith('WNL_'))) {
149
+ expect(pushPrefKeyFor(type)).toBe('WNL');
150
+ }
151
+ expect(pushPrefKeyFor('MATCH_RESULT')).toBe('MATCH_RESULT');
152
+ expect(pushPrefKeyFor('UNKNOWN_TYPE')).toBe('UNKNOWN_TYPE');
153
+ // The one WNL toggle gates every WNL type's push; individual WNL type keys are never consulted.
154
+ expect(pushEnabledForType({ WNL: false }, 'WNL_CALLUP_OPEN')).toBe(false);
155
+ expect(pushEnabledForType({ WNL: false }, 'WNL_CHAMPION')).toBe(false);
156
+ expect(pushEnabledForType({}, 'WNL_CHAMPION')).toBe(true);
157
+ expect(pushEnabledForType({ WNL_CHAMPION: false }, 'WNL_CHAMPION')).toBe(true);
158
+ expect(pushEnabledForType({ MATCH_RESULT: false }, 'MATCH_RESULT')).toBe(false);
159
+ // The toggleable key set collapses the seven WNL types into one 'WNL' entry and keeps the rest per-type.
160
+ expect(PUSH_PREF_KEYS.filter((k) => k === 'WNL')).toHaveLength(1);
161
+ expect(PUSH_PREF_KEYS).not.toContain('WNL_CALLUP_OPEN');
162
+ expect(PUSH_PREF_KEYS).toContain('MATCH_RESULT');
163
+ });
146
164
  });
@@ -31,4 +31,16 @@ export interface PromotionPayload {
31
31
  teamId?: string;
32
32
  amount?: number;
33
33
  }
34
- export type NotificationPayload = MatchResultPayload | InjuryPayload | InjuryRecoveredPayload | SeasonEndPayload | PromotionPayload | Record<string, unknown>;
34
+ export interface WnlCallupOpenPayload {
35
+ deadline?: string;
36
+ }
37
+ export interface WnlChampionPayload {
38
+ team?: string;
39
+ }
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>;
@@ -1,14 +1,17 @@
1
- import { type NotificationType } from './notification-type';
1
+ import { type NotificationPreferences, type NotificationType } from './notification-type';
2
2
  export type CatalogKeyResolver = string | ((payload: Record<string, unknown>) => string);
3
3
  export interface NotificationDescriptor {
4
4
  emitsPush: boolean;
5
+ prefKey?: string;
5
6
  deepLink?: (payload: Record<string, unknown>) => string;
6
7
  pushTitleKey?: CatalogKeyResolver;
7
8
  pushBodyKey?: CatalogKeyResolver;
8
- inAppMessageKey?: CatalogKeyResolver;
9
- richRender?: NotificationType;
10
9
  params?: (payload: Record<string, unknown>) => Record<string, string>;
11
10
  catalogKeys: string[];
12
11
  }
12
+ export declare const NATIONAL_ACCESS_PREF_KEY = "NATIONAL_ACCESS";
13
13
  export declare const NOTIFICATION_REGISTRY: Record<NotificationType, NotificationDescriptor>;
14
14
  export declare const PUSH_NOTIFICATION_TYPES: NotificationType[];
15
+ export declare function pushPrefKeyFor(type: string): string;
16
+ export declare function pushEnabledForType(prefs: NotificationPreferences | null | undefined, type: string): boolean;
17
+ export declare const PUSH_PREF_KEYS: string[];
@@ -1,7 +1,23 @@
1
- import { NOTIFICATION_TYPES } from './notification-type';
1
+ 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,11 +166,123 @@ 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']
170
+ },
171
+ // World Nations League (owner 2026-08-28). All seven share the single 'WNL' preference toggle. The three callup
172
+ // types go to national team managers, LEAGUE_START/FINALS_START/CHAMPION are broadcast to every active user, and
173
+ // PLAYER_CALLED_UP goes to club owners with at least one player in a finalized squad.
174
+ WNL_CALLUP_OPEN: {
175
+ emitsPush: true,
176
+ prefKey: 'WNL',
177
+ deepLink: () => '/wnl/callup',
178
+ pushTitleKey: 'wnlCallupOpen.title',
179
+ pushBodyKey: 'wnlCallupOpen.body',
180
+ // The deadline arrives pre-formatted (locale-neutral 'YYYY-MM-DD HH:mm UTC') because the stored payload is
181
+ // shared by the push and the in-app bell and cannot be re-localised per render.
182
+ params: (p) => ({ date: readString(p.deadline) ?? '' }),
183
+ catalogKeys: ['wnlCallupOpen.title', 'wnlCallupOpen.body']
184
+ },
185
+ WNL_CALLUP_DEADLINE: {
186
+ emitsPush: true,
187
+ prefKey: 'WNL',
188
+ deepLink: () => '/wnl/callup',
189
+ pushTitleKey: 'wnlCallupDeadline.title',
190
+ pushBodyKey: 'wnlCallupDeadline.body',
191
+ catalogKeys: ['wnlCallupDeadline.title', 'wnlCallupDeadline.body']
192
+ },
193
+ WNL_CALLUP_CLOSED: {
194
+ emitsPush: true,
195
+ prefKey: 'WNL',
196
+ deepLink: () => '/wnl/manage/lineup',
197
+ pushTitleKey: 'wnlCallupClosed.title',
198
+ pushBodyKey: 'wnlCallupClosed.body',
199
+ catalogKeys: ['wnlCallupClosed.title', 'wnlCallupClosed.body']
200
+ },
201
+ WNL_LEAGUE_START: {
202
+ emitsPush: true,
203
+ prefKey: 'WNL',
204
+ deepLink: () => '/wnl',
205
+ pushTitleKey: 'wnlLeagueStart.title',
206
+ pushBodyKey: 'wnlLeagueStart.body',
207
+ catalogKeys: ['wnlLeagueStart.title', 'wnlLeagueStart.body']
208
+ },
209
+ WNL_FINALS_START: {
210
+ emitsPush: true,
211
+ prefKey: 'WNL',
212
+ deepLink: () => '/wnl/finals',
213
+ pushTitleKey: 'wnlFinalsStart.title',
214
+ pushBodyKey: 'wnlFinalsStart.body',
215
+ catalogKeys: ['wnlFinalsStart.title', 'wnlFinalsStart.body']
216
+ },
217
+ WNL_CHAMPION: {
218
+ emitsPush: true,
219
+ prefKey: 'WNL',
220
+ deepLink: () => '/wnl/finals',
221
+ pushTitleKey: 'wnlChampion.title',
222
+ // A missing team name never renders a subjectless sentence (mirrors the MATCH_RESULT named/unnamed split).
223
+ pushBodyKey: (p) => readString(p.team) != null ? 'wnlChampion.body' : 'wnlChampion.bodyUnnamed',
224
+ params: (p) => ({ team: readString(p.team) ?? '' }),
225
+ catalogKeys: ['wnlChampion.title', 'wnlChampion.body', 'wnlChampion.bodyUnnamed']
226
+ },
227
+ WNL_PLAYER_CALLED_UP: {
228
+ emitsPush: true,
229
+ prefKey: 'WNL',
230
+ deepLink: () => '/wnl/teams',
231
+ pushTitleKey: 'wnlPlayerCalledUp.title',
232
+ pushBodyKey: 'wnlPlayerCalledUp.body',
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']
171
269
  }
172
270
  };
173
271
  // The notification types that emit a mobile push (everything except COMPENSATION). Source of truth = each
174
- // descriptor's emitsPush, so this cannot drift. This is the user-toggleable set surfaced in the mobile app's
175
- // notification settings and validated by the API before persisting a preference.
272
+ // descriptor's emitsPush, so this cannot drift.
176
273
  export const PUSH_NOTIFICATION_TYPES = NOTIFICATION_TYPES.filter((type) => NOTIFICATION_REGISTRY[type].emitsPush);
274
+ // The preference-map key a type's push toggle lives under: the descriptor's prefKey (group toggle) or the type
275
+ // itself. Unknown types resolve to themselves, mirroring pushEnabledFor's loose-string tolerance.
276
+ export function pushPrefKeyFor(type) {
277
+ const descriptor = NOTIFICATION_REGISTRY[type];
278
+ return descriptor?.prefKey ?? type;
279
+ }
280
+ // Group-aware push gate: like pushEnabledFor, but resolves the type's preference GROUP first, so muting the one
281
+ // 'WNL' toggle suppresses every WNL type's push. Senders (Sim/API insertNotification) use THIS, not pushEnabledFor.
282
+ export function pushEnabledForType(prefs, type) {
283
+ return pushEnabledFor(prefs, pushPrefKeyFor(type));
284
+ }
285
+ // The user-toggleable keys surfaced in the mobile app's notification settings and validated by the API before
286
+ // persisting a preference: one entry per DISTINCT pref key (individual types plus each group toggle once),
287
+ // in first-appearance order.
288
+ export const PUSH_PREF_KEYS = [...new Set(PUSH_NOTIFICATION_TYPES.map((type) => pushPrefKeyFor(type)))];
@@ -8,3 +8,5 @@ export declare const PREMIUM_TIERS: PremiumTierDef[];
8
8
  export declare const MAX_PREMIUM_TIER: number;
9
9
  export declare function premiumTierDef(tier: number): PremiumTierDef | undefined;
10
10
  export declare function hasTier(effectiveTier: number, required: number): boolean;
11
+ export declare const TIER_NATIONAL = 2;
12
+ export declare function canManageNationalTeam(effectiveTier: number): boolean;
@@ -17,3 +17,9 @@ export function premiumTierDef(tier) {
17
17
  export function hasTier(effectiveTier, required) {
18
18
  return effectiveTier >= required;
19
19
  }
20
+ // Tier 2 (NATIONAL) unlocks World Cup / national-team management (claiming and managing a national team, the WNL
21
+ // callup, lineup and kit). Gate every national-team write on this.
22
+ export const TIER_NATIONAL = 2;
23
+ export function canManageNationalTeam(effectiveTier) {
24
+ return hasTier(effectiveTier, TIER_NATIONAL);
25
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "volleyballsimtypes",
3
- "version": "0.0.524",
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",