@mundogamernetwork/shared-ui 1.1.83 → 1.1.84

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/locales/de.json CHANGED
@@ -194,7 +194,14 @@
194
194
  "redeem_description_2": "Gehe zur Option „Code einlösen“, füge den kopierten Code ein und schließe den Vorgang ab.",
195
195
  "redeem_description_3": "Der Inhalt wird auf deinem Konto aktiviert.",
196
196
  "redeem_at": "Auf {platform} einlösen",
197
- "no_redeemers": "Noch niemand hat diese Kampagne eingelöst."
197
+ "no_redeemers": "Noch niemand hat diese Kampagne eingelöst.",
198
+ "required_fields": "Bitte alle Pflichtfelder ausfüllen",
199
+ "plan": "Deckungspläne",
200
+ "plan_placeholder": "Beschreibe deinen Berichterstattungsplan: wo du über das Spiel streamen oder posten wirst, dein Publikum und das geplante Veröffentlichungsdatum",
201
+ "resources_title": "Ressourcen",
202
+ "press_kit_link": "Presse-Kit",
203
+ "review_guideline": "Review-Richtlinie",
204
+ "embargo_date": "Sperrfrist"
198
205
  },
199
206
  "materials": {
200
207
  "title": "Submit material",
package/locales/en.json CHANGED
@@ -194,7 +194,14 @@
194
194
  "redeem_description_2": "Go to the 'Redeem code' option, paste the copied code, and complete the process.",
195
195
  "redeem_description_3": "The content will be activated on your account.",
196
196
  "redeem_at": "Redeem on {platform}",
197
- "no_redeemers": "No one has redeemed this campaign yet."
197
+ "no_redeemers": "No one has redeemed this campaign yet.",
198
+ "required_fields": "Please fill in all required fields",
199
+ "plan": "Coverage plans",
200
+ "plan_placeholder": "Describe your coverage plan: where you will stream or post about this game, your audience, and expected publish date",
201
+ "resources_title": "Resources",
202
+ "press_kit_link": "Press Kit",
203
+ "review_guideline": "Review guideline",
204
+ "embargo_date": "Embargo date"
198
205
  },
199
206
  "materials": {
200
207
  "title": "Submit material",
@@ -194,7 +194,14 @@
194
194
  "redeem_description_2": " Vá até a opção “Resgatar código”, cole o código copiado e finalize.",
195
195
  "redeem_description_3": "O conteúdo será ativado na sua conta.",
196
196
  "redeem_at": "Resgatar em {platform}",
197
- "no_redeemers": "Nenhum criador resgatou esta campanha ainda."
197
+ "no_redeemers": "Nenhum criador resgatou esta campanha ainda.",
198
+ "required_fields": "Por favor, preencha todos os campos obrigatórios",
199
+ "plan": " Planos de cobertura",
200
+ "plan_placeholder": "Descreva seu plano de cobertura: onde vai fazer stream ou publicar sobre o jogo, seu público e a data prevista de publicação",
201
+ "resources_title": "Recursos",
202
+ "press_kit_link": "Press Kit",
203
+ "review_guideline": "Diretriz de review",
204
+ "embargo_date": "Data de embargo"
198
205
  },
199
206
  "materials": {
200
207
  "title": "Submit material",
package/locales/ro.json CHANGED
@@ -194,7 +194,14 @@
194
194
  "redeem_description_2": "Accesează opțiunea „Redeem code”, lipește codul copiat și finalizează procesul.",
195
195
  "redeem_description_3": "Conținutul va fi activat în contul tău.",
196
196
  "redeem_at": "Revendică pe {platform}",
197
- "no_redeemers": "Nimeni nu a revendicat această campanie încă."
197
+ "no_redeemers": "Nimeni nu a revendicat această campanie încă.",
198
+ "required_fields": "Vă rugăm să completați toate câmpurile obligatorii",
199
+ "plan": "Planuri de acoperire",
200
+ "plan_placeholder": "Descrie planul tău de acoperire: unde vei face stream sau vei posta despre joc, publicul tău și data estimată de publicare",
201
+ "resources_title": "Resurse",
202
+ "press_kit_link": "Press Kit",
203
+ "review_guideline": "Ghid de recenzie",
204
+ "embargo_date": "Data embargo"
198
205
  },
199
206
  "materials": {
200
207
  "title": "Submit material",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mundogamernetwork/shared-ui",
3
- "version": "1.1.83",
3
+ "version": "1.1.84",
4
4
  "description": "Mundo Gamer Network - Shared UI Layer (Nuxt 3)",
5
5
  "type": "module",
6
6
  "main": "./nuxt.config.ts",
@@ -604,7 +604,9 @@ onMounted(async () => {
604
604
  </div>
605
605
  </div>
606
606
 
607
- <div class="content-key">
607
+ <div v-if="!campaign" class="state error">{{ $t("keys.campaigns.error_loading") }}</div>
608
+
609
+ <div v-else class="content-key">
608
610
  <div class="content-key-header">
609
611
  <h4>{{ campaign?.name }}</h4>
610
612
 
@@ -1031,6 +1033,13 @@ onMounted(async () => {
1031
1033
  img { width: 100%; height: 400px; object-fit: cover; object-position: center top; }
1032
1034
  }
1033
1035
 
1036
+ .state.error {
1037
+ padding: 64px 0;
1038
+ text-align: center;
1039
+ font-size: 14px;
1040
+ color: var(--danger, #e53935);
1041
+ }
1042
+
1034
1043
  .content-key {
1035
1044
  display: flex;
1036
1045
  flex-direction: column;
@@ -267,7 +267,11 @@
267
267
  </div>
268
268
  </div>
269
269
 
270
- <div v-if="!loading && filteredCards.length === 0" class="d-flex flex-column align-items-center">
270
+ <div v-if="!loading && loadError" class="d-flex flex-column align-items-center">
271
+ <div class="no-content"><MGIcon icon="eye-slash" /></div>
272
+ <h4>{{ $t("keys.campaigns.error_loading") }}</h4>
273
+ </div>
274
+ <div v-else-if="!loading && filteredCards.length === 0" class="d-flex flex-column align-items-center">
271
275
  <div class="no-content"><MGIcon icon="eye-slash" /></div>
272
276
  <h4>{{ $t("keys.campaigns.no_results") }}</h4>
273
277
  </div>
@@ -364,6 +368,7 @@ let slideInterval: ReturnType<typeof setInterval> | null = null
364
368
  const loading = ref(true)
365
369
  const loadingFeatured = ref(true)
366
370
  const loadingRequests = ref(false)
371
+ const loadError = ref(false)
367
372
 
368
373
  // ------------------------------------------------------------------
369
374
  // Filters
@@ -556,9 +561,11 @@ async function loadCampaigns(page = 1) {
556
561
  }
557
562
 
558
563
  currentPage.value = page
564
+ loadError.value = false
559
565
  applyUserRequestStatuses()
560
566
  } catch {
561
567
  hasMoreItems.value = false
568
+ if (page === 1) loadError.value = true
562
569
  } finally {
563
570
  loading.value = false
564
571
  loadingMore.value = false
@@ -59,6 +59,9 @@ onMounted(async () => {
59
59
  try {
60
60
  const res = await fetchKeyRequestById(requestId)
61
61
  requestData.value = res?.data?.data || res?.data || null
62
+ // Platform the user chose at request time — shown as a fallback/preview
63
+ // even before reveal, in case the assigned key item has no platform set.
64
+ platform.value = requestData.value?.platforms?.[0]?.name || ""
62
65
  } catch {
63
66
  error.value = $i18n.t("keys.campaigns.error_loading")
64
67
  } finally {
@@ -72,8 +75,10 @@ async function reveal() {
72
75
  try {
73
76
  const res = await revealKey(requestId)
74
77
  const data = res?.data?.data || res?.data || null
75
- keyCode.value = data?.key || data?.code || data?.value || null
76
- platform.value = data?.platform || data?.platforms?.[0]?.name || ""
78
+ keyCode.value = data?.code || (typeof data?.key === "string" ? data.key : null) || data?.value || null
79
+ // Keep the pre-reveal platform (chosen at request time) as fallback if the
80
+ // revealed item has no platform of its own.
81
+ platform.value = data?.platform || data?.platforms?.[0]?.name || platform.value
77
82
  if (!keyCode.value) {
78
83
  error.value = $i18n.t("keys.campaigns.error_reveal")
79
84
  }
@@ -102,6 +107,13 @@ async function copyCode() {
102
107
  }
103
108
  }
104
109
 
110
+ function formatDate(dateString: string | null | undefined): string {
111
+ if (!dateString) return "-"
112
+ const date = new Date(dateString)
113
+ if (isNaN(date.getTime())) return "-"
114
+ return new Intl.DateTimeFormat(locale.value, { day: "numeric", month: "long", year: "numeric" }).format(date)
115
+ }
116
+
105
117
  function goBack() {
106
118
  if (typeof window !== "undefined" && window.history.length > 1) {
107
119
  window.history.back()
@@ -135,6 +147,8 @@ function goBack() {
135
147
  <span class="accent-text">{{ currentUser?.nickname || currentUser?.name }}</span>
136
148
  </h3>
137
149
  <p class="description">{{ $t("keys.campaigns.approved_subtitle") }}</p>
150
+ <p v-if="requestData?.key?.name" class="campaign-name">{{ requestData.key.name }}</p>
151
+ <p v-if="platform" class="platform-name">{{ $t("keys.campaigns.platform") }}: {{ platform }}</p>
138
152
  </div>
139
153
 
140
154
  <div class="cards">
@@ -194,12 +208,30 @@ function goBack() {
194
208
  </div>
195
209
  </div>
196
210
  </div>
197
- </div>
198
211
 
199
- <div class="side-border">
200
- <div class="title">{{ requestData?.key?.name }}</div>
201
- <div class="imagem" v-if="requestData?.key?.cover_original_src">
202
- <img :src="requestData.key.cover_original_src" :alt="requestData.key.name" />
212
+ <div class="resources" v-if="requestData?.key?.press_kit_link || requestData?.key?.review_guideline || requestData?.key?.embargo_date">
213
+ <div class="resources-title">{{ $t("keys.campaigns.resources_title") }}</div>
214
+
215
+ <a
216
+ v-if="requestData?.key?.press_kit_link"
217
+ :href="requestData.key.press_kit_link"
218
+ target="_blank"
219
+ rel="noopener"
220
+ class="resource-link"
221
+ >
222
+ <MGIcon size="1x" icon="link" />
223
+ {{ $t("keys.campaigns.press_kit_link") }}
224
+ </a>
225
+
226
+ <div v-if="requestData?.key?.review_guideline" class="resource-item">
227
+ <div class="resource-label">{{ $t("keys.campaigns.review_guideline") }}</div>
228
+ <div class="resource-text">{{ requestData.key.review_guideline }}</div>
229
+ </div>
230
+
231
+ <div v-if="requestData?.key?.embargo_date" class="resource-item">
232
+ <div class="resource-label">{{ $t("keys.campaigns.embargo_date") }}</div>
233
+ <div class="resource-text">{{ formatDate(requestData.key.embargo_date) }}</div>
234
+ </div>
203
235
  </div>
204
236
  </div>
205
237
  </div>
@@ -262,19 +294,13 @@ function goBack() {
262
294
  }
263
295
  .accent-text { color: var(--key-accent, var(--primary, #D297FF)); }
264
296
  .description { font-size: 14px; color: var(--secondary-info-fg); margin: 0; }
297
+ .campaign-name { font-size: 13px; font-weight: 600; color: var(--card-article-title); margin: 4px 0 0; }
298
+ .platform-name { font-size: 12px; color: var(--secondary-info-fg); margin: 2px 0 0; }
265
299
  }
266
300
 
267
301
  .cards {
268
302
  display: flex;
269
- align-items: flex-start;
270
- gap: 84px;
271
303
  width: 100%;
272
- justify-content: space-between;
273
-
274
- @media (max-width: 767px) {
275
- flex-direction: column;
276
- gap: 32px;
277
- }
278
304
  }
279
305
 
280
306
  .side {
@@ -377,27 +403,33 @@ function goBack() {
377
403
  }
378
404
  }
379
405
 
380
- .side-border {
406
+ .req-error { font-size: 12px; color: var(--danger, #e53935); margin-top: 4px; }
407
+
408
+ .resources {
381
409
  display: flex;
382
- padding: 18px;
383
410
  flex-direction: column;
384
- gap: 24px;
385
- flex: 1 0 0;
411
+ gap: 12px;
412
+ padding: 20px;
413
+ background: var(--bg-app-badge);
386
414
  border: 1px solid var(--button-secondary-default-bg);
387
- max-width: 391px;
415
+ }
388
416
 
389
- .title { font-size: 14px; color: var(--card-article-title); font-weight: 600; }
417
+ .resources-title { font-size: 13px; font-weight: 600; color: var(--card-article-title); }
390
418
 
391
- .imagem {
392
- width: 100%;
393
- max-height: 200px;
394
- overflow: hidden;
395
- text-align: center;
396
- img { width: auto; height: 100%; max-width: 100%; }
397
- }
419
+ .resource-link {
420
+ display: inline-flex;
421
+ align-items: center;
422
+ gap: 6px;
423
+ width: fit-content;
424
+ font-size: 13px;
425
+ color: var(--key-accent, var(--primary, #D297FF));
426
+ text-decoration: none;
427
+ &:hover { text-decoration: underline; }
398
428
  }
399
429
 
400
- .req-error { font-size: 12px; color: var(--danger, #e53935); margin-top: 4px; }
430
+ .resource-item { display: flex; flex-direction: column; gap: 2px; }
431
+ .resource-label { font-size: 12px; font-weight: 600; color: var(--card-article-title); }
432
+ .resource-text { font-size: 13px; color: var(--secondary-info-fg); }
401
433
  }
402
434
 
403
435
  @keyframes pulse {
@@ -71,9 +71,7 @@ onMounted(async () => {
71
71
  }
72
72
  })
73
73
 
74
- const canSubmit = computed(() =>
75
- !!selectedPlatformId.value && !!selectedRegionId.value && !!quantity.value && !submitting.value,
76
- )
74
+ const canSubmit = computed(() => !submitting.value)
77
75
 
78
76
  // Collapsible "Required data" box — matches tv's original toggle behavior
79
77
  const isBoxOpen = ref(true)
@@ -104,16 +102,30 @@ const refusedReason = computed(() => campaign.value?.request_admin_notes || "")
104
102
 
105
103
  async function submit() {
106
104
  if (!canSubmit.value) return
107
- submitting.value = true
108
105
  requestError.value = ""
106
+ if (!selectedPlatformId.value || !selectedRegionId.value || !quantity.value || !description.value?.trim()) {
107
+ requestError.value = $i18n.t("keys.campaigns.required_fields")
108
+ return
109
+ }
110
+ submitting.value = true
109
111
  try {
110
- await requestKey({
112
+ const response = await requestKey({
111
113
  key_id: keyId,
112
114
  platform_ids: [selectedPlatformId.value],
113
115
  region_ids: [selectedRegionId.value],
114
116
  description: description.value || "-",
115
117
  quantity_keys: Number(quantity.value) || 1,
116
118
  })
119
+ const created = response?.data?.data ?? response?.data ?? {}
120
+ // requires_approval=false campaigns (or Pro/Business TV subscribers) get
121
+ // auto-approved by the backend observer synchronously — status 3 already
122
+ // reflects that by the time this response comes back, so skip the
123
+ // "request sent" wait screen and go straight to reveal.
124
+ const autoApproved = created.key_status_request_id === 3
125
+ if (autoApproved && created.id) {
126
+ navigateTo(`/${locale.value}/key-campaigns/redeem-key-approved?requestId=${created.id}`)
127
+ return
128
+ }
117
129
  success.value = true
118
130
  setTimeout(() => {
119
131
  navigateTo(`/${locale.value}/key-campaigns`)
@@ -233,11 +245,11 @@ function goBack() {
233
245
  </div>
234
246
 
235
247
  <div class="form-row">
236
- <label class="label">{{ $t("keys.campaigns.description_optional") }}</label>
248
+ <label class="label">{{ $t("keys.campaigns.plan") }}</label>
237
249
  <textarea
238
250
  v-model="description"
239
251
  class="textarea"
240
- :placeholder="$t('keys.campaigns.description_placeholder')"
252
+ :placeholder="$t('keys.campaigns.plan_placeholder')"
241
253
  rows="3"
242
254
  />
243
255
  </div>
@@ -283,6 +283,9 @@ export const fetchCampaignBySlug = async (slug: string) => {
283
283
  max_keys_for_user: k.max_keys_for_user ?? 1,
284
284
  game_summary: k.game?.summary,
285
285
  game_videos: (k.game?.videos || []) as Array<{ external_id: string; name?: string }>,
286
+ press_kit_link: k.press_kit_link ?? null,
287
+ review_guideline: k.review_guideline ?? null,
288
+ embargo_date: k.embargo_date ?? null,
286
289
  request_id: k.requests?.[0]?.id,
287
290
  request_item_id: k.requests?.[0]?.key_item_id,
288
291
  request_admin_notes: k.requests?.[0]?.admin_notes ?? null,