@mundogamernetwork/shared-ui 1.4.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,7 @@ import {
8
8
  fetchKeyRegions,
9
9
  fetchAvailableOptions,
10
10
  requestKey,
11
+ enableKeyCampaignNotifications,
11
12
  } from "../../services/campaignService"
12
13
 
13
14
  const route = useRoute()
@@ -37,6 +38,7 @@ const selectedPlatformId = ref<number | null>(null)
37
38
  const selectedRegionId = ref<number | null>(null)
38
39
  const description = ref("")
39
40
  const quantity = ref<number | null>(1)
41
+ const notifyKeyCampaigns = ref(true)
40
42
 
41
43
  const maxKeysForUser = computed(() => campaign.value?.max_keys_for_user ?? 1)
42
44
 
@@ -137,6 +139,12 @@ async function submit() {
137
139
  quantity_keys: Number(quantity.value) || 1,
138
140
  })
139
141
  const created = response?.data?.data ?? response?.data ?? {}
142
+
143
+ if (notifyKeyCampaigns.value) {
144
+ // Fire-and-forget — never block the request/reveal flow on this.
145
+ enableKeyCampaignNotifications().catch(() => {})
146
+ }
147
+
140
148
  // requires_approval=false campaigns (or Pro/Business TV subscribers) get
141
149
  // auto-approved by the backend observer synchronously — status 3 already
142
150
  // reflects that by the time this response comes back, so skip the
@@ -281,6 +289,13 @@ function goBack() {
281
289
  rows="3"
282
290
  />
283
291
  </div>
292
+
293
+ <div class="form-row form-row--checkbox">
294
+ <label class="checkbox-label">
295
+ <input type="checkbox" v-model="notifyKeyCampaigns" />
296
+ {{ $t("keys.campaigns.notify_opt_in") }}
297
+ </label>
298
+ </div>
284
299
  </div>
285
300
  </div>
286
301
 
@@ -445,6 +460,27 @@ function goBack() {
445
460
  color: var(--card-article-title);
446
461
  }
447
462
 
463
+ .form-row--checkbox {
464
+ flex-direction: row;
465
+ align-items: center;
466
+ }
467
+
468
+ .checkbox-label {
469
+ display: flex;
470
+ align-items: center;
471
+ gap: 8px;
472
+ font-size: 13px;
473
+ color: var(--card-article-title);
474
+ cursor: pointer;
475
+
476
+ input[type="checkbox"] {
477
+ width: 16px;
478
+ height: 16px;
479
+ accent-color: var(--key-accent, var(--primary, #D297FF));
480
+ cursor: pointer;
481
+ }
482
+ }
483
+
448
484
  .select, .input, .textarea {
449
485
  width: 100%;
450
486
  background-color: transparent;
@@ -421,6 +421,20 @@ export const revealKey = (requestId: number) => {
421
421
  return authClient.get(`/public/key-requests/${requestId}/reveal`)
422
422
  }
423
423
 
424
+ /**
425
+ * Explicit opt-in to key campaign alerts — same endpoint used by
426
+ * KeyBrowser.vue's notification prompt (GET/PUT /public/user/notification-preferences).
427
+ */
428
+ export const enableKeyCampaignNotifications = () => {
429
+ return authClient.put("/public/user/notification-preferences", {
430
+ category: "key_campaigns",
431
+ enabled: true,
432
+ channel_email: true,
433
+ channel_push: true,
434
+ channel_in_app: true,
435
+ })
436
+ }
437
+
424
438
  /**
425
439
  * Fetch required actions for a campaign.
426
440
  */
@@ -63,6 +63,8 @@ export interface SubmitBaselineResponsePayload {
63
63
  time_on_task_seconds?: number
64
64
  device?: string
65
65
  os?: string
66
+ // Video Playtest only — id of a ready PlaytestRecording (see requestRecordingUploadUrl()).
67
+ recording_id?: number
66
68
  }
67
69
 
68
70
  export interface PlaytestResponse {
@@ -414,3 +416,64 @@ export const fetchMyPlaytestResponses = (status?: 'submitted' | 'accepted' | 're
414
416
  httpService.get<PaginatedResponse<PlaytestResponse>>('/playtest/my-responses', {
415
417
  params: { status, per_page: perPage, page },
416
418
  })
419
+
420
+ // ---------------------------------------------------------------------------
421
+ // Game access delivery + liability waiver (accepted responses only)
422
+ // ---------------------------------------------------------------------------
423
+
424
+ export interface PlaytestAccessGrant {
425
+ id: number
426
+ access_type: 'key' | 'link'
427
+ revealed: boolean
428
+ revealed_at: string | null
429
+ key_code?: string
430
+ external_link?: string
431
+ }
432
+
433
+ /** Null when the response was accepted without a configured access grant. */
434
+ export const fetchAccessGrant = (responseId: number) =>
435
+ httpService.get<{ data: PlaytestAccessGrant | null }>(`/playtest/responses/${responseId}/access-grant`)
436
+
437
+ /** Sends (or resends) the liability-waiver OTP to the tester's account email. */
438
+ export const initiateAccessGrantWaiver = (responseId: number) =>
439
+ httpService.post<{ data: { document_id: number; status: string } }>(`/playtest/responses/${responseId}/waiver/initiate`)
440
+
441
+ /** Verifies the OTP code; on success the response includes the revealed key/link. */
442
+ export const verifyAccessGrantWaiver = (responseId: number, otpCode: string) =>
443
+ httpService.post<{ data: PlaytestAccessGrant }>(`/playtest/responses/${responseId}/waiver/verify`, { otp_code: otpCode })
444
+
445
+ // ---------------------------------------------------------------------------
446
+ // Video Playtest — recording upload + timestamped markers ("minutagem")
447
+ // ---------------------------------------------------------------------------
448
+
449
+ export interface PlaytestRecording {
450
+ id: number
451
+ status: 'uploading' | 'processing' | 'ready' | 'failed'
452
+ duration_seconds: number | null
453
+ playback_url: string | null
454
+ external_id: string
455
+ markers?: PlaytestRecordingMarker[]
456
+ }
457
+
458
+ export interface PlaytestRecordingMarker {
459
+ id: number
460
+ timestamp_seconds: number
461
+ comment: string
462
+ severity: 'blocker' | 'major' | 'minor' | 'note' | null
463
+ created_by_user_id: number
464
+ }
465
+
466
+ /** Requests a direct-upload URL from the video provider — the browser uploads straight to it, never through this API. */
467
+ export const requestRecordingUploadUrl = (campaignId: number) =>
468
+ httpService.post<{ data: { recording_id: number; upload_url: string } }>(
469
+ `/playtest/campaigns/${campaignId}/recording/upload-url`,
470
+ )
471
+
472
+ /** Poll this until status === 'ready' (set by the provider's webhook once processing finishes). */
473
+ export const fetchRecording = (responseId: number) =>
474
+ httpService.get<{ data: PlaytestRecording | null }>(`/playtest/responses/${responseId}/recording`)
475
+
476
+ export const storeRecordingMarker = (
477
+ responseId: number,
478
+ payload: { timestamp_seconds: number; comment: string; severity?: 'blocker' | 'major' | 'minor' | 'note' },
479
+ ) => httpService.post<{ data: PlaytestRecordingMarker }>(`/playtest/responses/${responseId}/recording/markers`, payload)