@mundogamernetwork/shared-ui 1.16.23 → 1.16.24

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.
@@ -4,10 +4,12 @@
4
4
  // role for the (unrelated, accept-time) key/link grant. Renders nothing if
5
5
  // the campaign has no ready build asset at all, so campaigns that don't use
6
6
  // this feature (concept polls, etc.) show no empty panel.
7
- import { onMounted, computed } from "vue";
7
+ import { onMounted, computed, ref } from "vue";
8
8
  import { usePlaytestBuildAccess } from "../../../composables/usePlaytestBuildAccess";
9
9
  import NdaSign from "./NdaSign.vue";
10
10
  import BuildDownload from "./BuildDownload.vue";
11
+ import ReportProblemForm from "./ReportProblemForm.vue";
12
+ import type { StoreBuildProblemReportPayload } from "../../../services/playtestTesterService";
11
13
 
12
14
  const props = defineProps<{
13
15
  campaignId: number;
@@ -17,6 +19,13 @@ const state = usePlaytestBuildAccess(props.campaignId);
17
19
 
18
20
  const hasAnyBuild = computed(() => state.assets.value.length > 0);
19
21
 
22
+ // Available at ANY grant status, including pending_nda/revoked — a tester
23
+ // stuck in the sign flow itself (e.g. never got the OTP email) or whose
24
+ // access was pulled still needs a way to reach the studio privately. Never
25
+ // a public wall.
26
+ const showReportForm = ref(false);
27
+ const reportFormRef = ref<InstanceType<typeof ReportProblemForm> | null>(null);
28
+
20
29
  onMounted(async () => {
21
30
  await state.loadAssets();
22
31
  if (hasAnyBuild.value) {
@@ -28,6 +37,22 @@ async function onDownload(assetId: number) {
28
37
  const url = await state.getDownloadUrl(assetId);
29
38
  if (url) window.open(url, "_blank", "noopener");
30
39
  }
40
+
41
+ function openReportForm() {
42
+ state.reportSuccess.value = false;
43
+ showReportForm.value = true;
44
+ }
45
+
46
+ async function onReportSubmit(payload: StoreBuildProblemReportPayload) {
47
+ // showReportForm stays true on success — the reportSuccess branch inside
48
+ // the v-else block below is what actually renders the confirmation.
49
+ await state.submitProblemReport(payload);
50
+ }
51
+
52
+ async function onUploadReportMedia(file: File) {
53
+ const url = await state.uploadReportMedia(file);
54
+ reportFormRef.value?.setMediaUrl(url);
55
+ }
31
56
  </script>
32
57
 
33
58
  <template>
@@ -57,9 +82,50 @@ async function onDownload(assetId: number) {
57
82
  @download="onDownload"
58
83
  />
59
84
 
60
- <p v-else-if="state.grant.value?.status === 'revoked'" class="build-access-panel__revoked">
61
- {{ $t("playtest.build_access.revoked", "Your access to this build has been revoked by the studio.") }}
62
- </p>
85
+ <div v-else-if="state.grant.value?.status === 'revoked'" class="build-access-panel__revoked">
86
+ <p class="build-access-panel__revoked-title">
87
+ {{ $t("playtest.build_access.revoked", "Your access to this build has been revoked.") }}
88
+ </p>
89
+ <p class="build-access-panel__revoked-hint">
90
+ {{ $t("playtest.build_access.revoked_hint", "This is usually done by the studio running the campaign.") }}
91
+ <button type="button" class="build-access-panel__revoked-link" @click="openReportForm">
92
+ {{ $t("playtest.build_access.revoked_contact_link", "If you think this is a mistake, let them know.") }}
93
+ </button>
94
+ </p>
95
+ </div>
96
+
97
+ <div v-if="state.grant.value" class="build-access-panel__contact">
98
+ <button
99
+ v-if="!showReportForm"
100
+ type="button"
101
+ class="build-access-panel__contact-toggle"
102
+ @click="openReportForm"
103
+ >
104
+ {{ $t("playtest.build_access.report_problem", "Report a problem") }}
105
+ </button>
106
+
107
+ <template v-else>
108
+ <div v-if="state.reportSuccess.value" class="build-access-panel__report-sent-wrap">
109
+ <p class="build-access-panel__report-sent">
110
+ {{ $t("playtest.build_access.report_sent", "Sent — only the studio can see this.") }}
111
+ </p>
112
+ <button type="button" class="build-access-panel__contact-toggle" @click="openReportForm">
113
+ {{ $t("playtest.build_access.report_another", "Send another report") }}
114
+ </button>
115
+ </div>
116
+ <template v-else>
117
+ <ReportProblemForm
118
+ ref="reportFormRef"
119
+ :assets="state.assets.value"
120
+ :submitting="state.reportSubmitting.value"
121
+ :uploading="state.reportUploading.value"
122
+ @submit="onReportSubmit"
123
+ @upload-media="onUploadReportMedia"
124
+ />
125
+ <p v-if="state.reportError.value" class="build-access-panel__report-error">{{ state.reportError.value }}</p>
126
+ </template>
127
+ </template>
128
+ </div>
63
129
  </div>
64
130
  </template>
65
131
 
@@ -83,10 +149,67 @@ async function onDownload(assetId: number) {
83
149
  }
84
150
 
85
151
  &__revoked {
152
+ display: flex;
153
+ flex-direction: column;
154
+ gap: 6px;
155
+ }
156
+
157
+ &__revoked-title {
86
158
  font-size: 13px;
87
159
  color: var(--danger, #e53935);
88
160
  margin: 0;
89
161
  }
162
+
163
+ &__revoked-hint {
164
+ font-size: 12px;
165
+ color: var(--secondary-info-fg, #888);
166
+ margin: 0;
167
+ }
168
+
169
+ &__revoked-link {
170
+ background: none;
171
+ border: none;
172
+ padding: 0;
173
+ margin-left: 4px;
174
+ font-size: 12px;
175
+ color: var(--primary, #d297ff);
176
+ cursor: pointer;
177
+ text-decoration: underline;
178
+ }
179
+
180
+ &__contact {
181
+ margin-top: 14px;
182
+ padding-top: 14px;
183
+ border-top: 1px solid var(--button-secondary-default-bg);
184
+ }
185
+
186
+ &__contact-toggle {
187
+ background: none;
188
+ border: none;
189
+ padding: 0;
190
+ font-size: 12px;
191
+ color: var(--primary, #d297ff);
192
+ cursor: pointer;
193
+ }
194
+
195
+ &__report-sent-wrap {
196
+ display: flex;
197
+ flex-direction: column;
198
+ gap: 8px;
199
+ align-items: flex-start;
200
+ }
201
+
202
+ &__report-sent {
203
+ font-size: 12px;
204
+ color: var(--primary, #d297ff);
205
+ margin: 0;
206
+ }
207
+
208
+ &__report-error {
209
+ font-size: 12px;
210
+ color: var(--danger, #e53935);
211
+ margin: 8px 0 0;
212
+ }
90
213
  }
91
214
 
92
215
  @keyframes build-access-panel-pulse {
@@ -37,11 +37,14 @@ function formatSize(bytes: number | null): string {
37
37
 
38
38
  <div v-else class="build-download__list">
39
39
  <div v-for="asset in assets" :key="asset.id" class="build-download__row">
40
- <div class="build-download__info">
41
- <span class="build-download__platform">{{ asset.platform }}</span>
42
- <span class="build-download__filename">{{ asset.original_filename }}</span>
43
- <span v-if="asset.version_label" class="build-download__version">{{ asset.version_label }}</span>
44
- <span v-if="asset.size_bytes" class="build-download__size">{{ formatSize(asset.size_bytes) }}</span>
40
+ <div class="build-download__main">
41
+ <div class="build-download__info">
42
+ <span class="build-download__platform">{{ asset.platform }}</span>
43
+ <span class="build-download__filename">{{ asset.original_filename }}</span>
44
+ <span v-if="asset.version_label" class="build-download__version">{{ asset.version_label }}</span>
45
+ <span v-if="asset.size_bytes" class="build-download__size">{{ formatSize(asset.size_bytes) }}</span>
46
+ </div>
47
+ <p v-if="asset.notes" class="build-download__notes">{{ asset.notes }}</p>
45
48
  </div>
46
49
  <button
47
50
  type="button"
@@ -96,6 +99,13 @@ function formatSize(bytes: number | null): string {
96
99
  border: 1px solid var(--bg-app-badge);
97
100
  }
98
101
 
102
+ &__main {
103
+ display: flex;
104
+ flex-direction: column;
105
+ gap: 4px;
106
+ flex: 1;
107
+ }
108
+
99
109
  &__info {
100
110
  display: flex;
101
111
  flex-wrap: wrap;
@@ -104,6 +114,13 @@ function formatSize(bytes: number | null): string {
104
114
  font-size: 13px;
105
115
  }
106
116
 
117
+ &__notes {
118
+ white-space: pre-wrap;
119
+ font-size: 11px;
120
+ color: var(--secondary-info-fg, #888);
121
+ margin: 0;
122
+ }
123
+
107
124
  &__platform {
108
125
  text-transform: uppercase;
109
126
  font-size: 10px;
@@ -0,0 +1,143 @@
1
+ <script setup lang="ts">
2
+ // Private tester -> studio message about a build-access grant — never a
3
+ // public wall. Structural mirror of playtest/lqa/FileIssueForm.vue, minus
4
+ // the LQA-specific issue_type/severity taxonomy and with evidence made
5
+ // OPTIONAL (a question doesn't need a screenshot, unlike a defect report).
6
+ import { ref, computed } from "vue";
7
+ import type { BuildProblemReportCategory, StoreBuildProblemReportPayload, PlaytestBuildAsset } from "../../../services/playtestTesterService";
8
+
9
+ const props = defineProps<{
10
+ assets: PlaytestBuildAsset[];
11
+ submitting?: boolean;
12
+ uploading?: boolean;
13
+ }>();
14
+
15
+ const emit = defineEmits<{
16
+ (e: "submit", payload: StoreBuildProblemReportPayload): void
17
+ (e: "upload-media", file: File): void
18
+ }>();
19
+
20
+ const CATEGORIES: BuildProblemReportCategory[] = ["question", "installation_problem", "access_problem", "other"];
21
+
22
+ const category = ref<BuildProblemReportCategory | "">("");
23
+ const assetId = ref<number | "">("");
24
+ const message = ref("");
25
+ const mediaUrl = ref<string | null>(null);
26
+
27
+ const canSubmit = computed(() =>
28
+ !!category.value &&
29
+ message.value.trim().length > 0 &&
30
+ !props.submitting
31
+ );
32
+
33
+ function onFileChange(e: Event) {
34
+ const file = (e.target as HTMLInputElement).files?.[0];
35
+ if (file) emit("upload-media", file);
36
+ }
37
+
38
+ function setMediaUrl(url: string | null) {
39
+ mediaUrl.value = url;
40
+ }
41
+
42
+ defineExpose({ setMediaUrl });
43
+
44
+ function submit() {
45
+ if (!canSubmit.value || !category.value) return;
46
+ emit("submit", {
47
+ category: category.value,
48
+ message: message.value.trim(),
49
+ playtest_build_asset_id: assetId.value ? Number(assetId.value) : undefined,
50
+ media_url: mediaUrl.value ?? undefined,
51
+ });
52
+
53
+ category.value = "";
54
+ assetId.value = "";
55
+ message.value = "";
56
+ mediaUrl.value = null;
57
+ }
58
+ </script>
59
+
60
+ <template>
61
+ <div class="report-problem-form">
62
+ <div class="report-problem-form__row">
63
+ <label class="report-problem-form__label">{{ $t("playtest.build_access.report_category", "What's this about?") }}</label>
64
+ <select v-model="category" class="report-problem-form__select">
65
+ <option value="" disabled>{{ $t("playtest.build_access.select_category_placeholder", "Select…") }}</option>
66
+ <option v-for="c in CATEGORIES" :key="c" :value="c">
67
+ {{ $t(`playtest.build_access.report_category_${c}`) }}
68
+ </option>
69
+ </select>
70
+ </div>
71
+
72
+ <div v-if="assets.length" class="report-problem-form__row">
73
+ <label class="report-problem-form__label">{{ $t("playtest.build_access.report_related_build", "Related build (optional)") }}</label>
74
+ <select v-model="assetId" class="report-problem-form__select">
75
+ <option value="">{{ $t("playtest.build_access.report_select_build_placeholder", "General question / not build-specific") }}</option>
76
+ <option v-for="asset in assets" :key="asset.id" :value="asset.id">
77
+ {{ asset.platform }} — {{ asset.original_filename }}
78
+ </option>
79
+ </select>
80
+ </div>
81
+
82
+ <div class="report-problem-form__row">
83
+ <label class="report-problem-form__label">{{ $t("playtest.build_access.report_message", "Message (required)") }}</label>
84
+ <textarea v-model="message" rows="3" class="report-problem-form__textarea" />
85
+ </div>
86
+
87
+ <div class="report-problem-form__row">
88
+ <label class="report-problem-form__label">{{ $t("playtest.build_access.report_attach_screenshot", "Attach a screenshot (optional)") }}</label>
89
+ <input type="file" accept="image/*" @change="onFileChange">
90
+ <span v-if="uploading" class="report-problem-form__uploading">{{ $t("playtest.lqa.uploading", "Uploading…") }}</span>
91
+ <span v-else-if="mediaUrl" class="report-problem-form__uploaded">{{ $t("playtest.lqa.uploaded", "Attached") }}</span>
92
+ </div>
93
+
94
+ <button type="button" class="btn primary report-problem-form__submit" :disabled="!canSubmit" @click="submit">
95
+ {{ submitting ? $t("playtest.build_access.sending_report", "Sending…") : $t("playtest.build_access.report_submit", "Send to studio") }}
96
+ </button>
97
+ </div>
98
+ </template>
99
+
100
+ <style scoped lang="scss">
101
+ .report-problem-form {
102
+ &__row {
103
+ margin-bottom: 14px;
104
+ }
105
+
106
+ &__label {
107
+ display: block;
108
+ font-size: 12px;
109
+ color: var(--secondary-info-fg, #aaa);
110
+ margin-bottom: 6px;
111
+ }
112
+
113
+ &__select,
114
+ &__textarea {
115
+ width: 100%;
116
+ background: var(--bg-app-badge, #1a1a1a);
117
+ border: 1px solid var(--button-secondary-default-bg, #2a2a2a);
118
+ color: var(--text-primary, #fff);
119
+ padding: 10px;
120
+ font-size: 13px;
121
+ }
122
+
123
+ &__textarea {
124
+ resize: vertical;
125
+ }
126
+
127
+ &__uploading,
128
+ &__uploaded {
129
+ display: inline-block;
130
+ margin-left: 8px;
131
+ font-size: 11px;
132
+ color: var(--secondary-info-fg, #888);
133
+ }
134
+
135
+ &__uploaded {
136
+ color: var(--primary, #D297FF);
137
+ }
138
+
139
+ &__submit {
140
+ margin-top: 4px;
141
+ }
142
+ }
143
+ </style>
@@ -4,8 +4,11 @@ import {
4
4
  initiateBuildNda,
5
5
  fetchBuildAssets,
6
6
  fetchBuildDownloadUrl,
7
+ storeBuildProblemReport,
8
+ uploadPlaytestMedia,
7
9
  type PlaytestBuildGrant,
8
10
  type PlaytestBuildAsset,
11
+ type StoreBuildProblemReportPayload,
9
12
  } from "../services/playtestTesterService";
10
13
  import {
11
14
  fetchSigningSession,
@@ -194,6 +197,41 @@ export function usePlaytestBuildAccess(campaignId: number) {
194
197
  }
195
198
  }
196
199
 
200
+ // ── Problem reports (private, any grant status) ──────────────────────
201
+ const reportSubmitting = ref(false);
202
+ const reportUploading = ref(false);
203
+ const reportSuccess = ref(false);
204
+ const reportError = ref<string | null>(null);
205
+
206
+ /** Never gated on grant status — a tester stuck before NDA sign still needs to reach the studio. */
207
+ async function submitProblemReport(payload: StoreBuildProblemReportPayload) {
208
+ if (!grant.value) return false;
209
+ reportError.value = null;
210
+ reportSubmitting.value = true;
211
+ try {
212
+ await storeBuildProblemReport(grant.value.id, payload);
213
+ reportSuccess.value = true;
214
+ return true;
215
+ } catch (e: any) {
216
+ reportError.value = errorMessage(e, "Could not send your report. Please try again.");
217
+ return false;
218
+ } finally {
219
+ reportSubmitting.value = false;
220
+ }
221
+ }
222
+
223
+ async function uploadReportMedia(file: File): Promise<string | null> {
224
+ reportUploading.value = true;
225
+ try {
226
+ const res = await uploadPlaytestMedia(file);
227
+ return (res.data as any)?.data?.url ?? null;
228
+ } catch {
229
+ return null;
230
+ } finally {
231
+ reportUploading.value = false;
232
+ }
233
+ }
234
+
197
235
  return {
198
236
  loading,
199
237
  submitting,
@@ -213,6 +251,12 @@ export function usePlaytestBuildAccess(campaignId: number) {
213
251
  sign,
214
252
  getDownloadUrl,
215
253
  resetErrors,
254
+ reportSubmitting,
255
+ reportUploading,
256
+ reportSuccess,
257
+ reportError,
258
+ submitProblemReport,
259
+ uploadReportMedia,
216
260
  };
217
261
  }
218
262
 
package/locales/de.json CHANGED
@@ -24,10 +24,27 @@
24
24
  "enter_code": "Gib den 6-stelligen Code ein, den wir dir per E-Mail geschickt haben",
25
25
  "nda_title": "Unterschreibe die NDA, um die Build freizuschalten",
26
26
  "no_builds": "Noch keine Builds verfügbar",
27
+ "report_another": "Weitere Meldung senden",
28
+ "report_attach_screenshot": "Screenshot anhängen (optional)",
29
+ "report_category": "Worum geht es?",
30
+ "report_category_access_problem": "Zugriffs-/Download-Problem",
31
+ "report_category_installation_problem": "Installationsproblem",
32
+ "report_category_other": "Sonstiges",
33
+ "report_category_question": "Frage",
34
+ "report_message": "Nachricht (erforderlich)",
35
+ "report_problem": "Problem melden",
36
+ "report_related_build": "Zugehörige Build (optional)",
37
+ "report_select_build_placeholder": "Allgemeine Frage / nicht build-spezifisch",
38
+ "report_sent": "Gesendet — nur das Studio sieht das.",
39
+ "report_submit": "An Studio senden",
27
40
  "resend_code": "Code erneut senden",
28
- "revoked": "Dein Build-Zugang wurde widerrufen",
41
+ "revoked": "Dein Zugriff auf diese Build wurde widerrufen.",
42
+ "revoked_contact_link": "Falls du denkst, das ist ein Irrtum, lass es das Studio wissen.",
43
+ "revoked_hint": "Das wird normalerweise vom Studio veranlasst, das die Kampagne betreibt.",
44
+ "select_category_placeholder": "Auswählen…",
29
45
  "send_code": "Bestätigungscode senden",
30
46
  "sending_code": "Code wird gesendet…",
47
+ "sending_report": "Wird gesendet…",
31
48
  "sign": "Unterschreiben",
32
49
  "signing": "Wird unterschrieben…",
33
50
  "verify": "Bestätigen",
package/locales/en.json CHANGED
@@ -24,10 +24,27 @@
24
24
  "enter_code": "Enter the 6-digit code we emailed you",
25
25
  "nda_title": "Sign the NDA to unlock the build",
26
26
  "no_builds": "No builds available yet",
27
+ "report_another": "Send another report",
28
+ "report_attach_screenshot": "Attach a screenshot (optional)",
29
+ "report_category": "What's this about?",
30
+ "report_category_access_problem": "Access / download problem",
31
+ "report_category_installation_problem": "Installation problem",
32
+ "report_category_other": "Other",
33
+ "report_category_question": "Question",
34
+ "report_message": "Message (required)",
35
+ "report_problem": "Report a problem",
36
+ "report_related_build": "Related build (optional)",
37
+ "report_select_build_placeholder": "General question / not build-specific",
38
+ "report_sent": "Sent — only the studio can see this.",
39
+ "report_submit": "Send to studio",
27
40
  "resend_code": "Resend code",
28
- "revoked": "Your build access has been revoked",
41
+ "revoked": "Your access to this build has been revoked.",
42
+ "revoked_contact_link": "If you think this is a mistake, let them know.",
43
+ "revoked_hint": "This is usually done by the studio running the campaign.",
44
+ "select_category_placeholder": "Select…",
29
45
  "send_code": "Send verification code",
30
46
  "sending_code": "Sending code…",
47
+ "sending_report": "Sending…",
31
48
  "sign": "Sign",
32
49
  "signing": "Signing…",
33
50
  "verify": "Verify",
package/locales/es.json CHANGED
@@ -24,10 +24,27 @@
24
24
  "enter_code": "Ingresa el código de 6 dígitos que te enviamos por correo",
25
25
  "nda_title": "Firma el NDA para desbloquear la build",
26
26
  "no_builds": "Todavía no hay builds disponibles",
27
+ "report_another": "Enviar otro reporte",
28
+ "report_attach_screenshot": "Adjuntar una captura de pantalla (opcional)",
29
+ "report_category": "¿De qué se trata?",
30
+ "report_category_access_problem": "Problema de acceso / descarga",
31
+ "report_category_installation_problem": "Problema de instalación",
32
+ "report_category_other": "Otro",
33
+ "report_category_question": "Pregunta",
34
+ "report_message": "Mensaje (obligatorio)",
35
+ "report_problem": "Reportar un problema",
36
+ "report_related_build": "Build relacionada (opcional)",
37
+ "report_select_build_placeholder": "Pregunta general / no específica de una build",
38
+ "report_sent": "Enviado — solo el estudio puede verlo.",
39
+ "report_submit": "Enviar al estudio",
27
40
  "resend_code": "Reenviar código",
28
- "revoked": "Tu acceso a la build ha sido revocado",
41
+ "revoked": "Tu acceso a esta build ha sido revocado.",
42
+ "revoked_contact_link": "Si crees que esto es un error, avísale al estudio.",
43
+ "revoked_hint": "Esto normalmente lo hace el estudio a cargo de la campaña.",
44
+ "select_category_placeholder": "Selecciona…",
29
45
  "send_code": "Enviar código de verificación",
30
46
  "sending_code": "Enviando código…",
47
+ "sending_report": "Enviando…",
31
48
  "sign": "Firmar",
32
49
  "signing": "Firmando…",
33
50
  "verify": "Verificar",
@@ -24,10 +24,27 @@
24
24
  "enter_code": "Digite o código de 6 dígitos que enviamos por e-mail",
25
25
  "nda_title": "Assine o NDA para desbloquear a build",
26
26
  "no_builds": "Nenhuma build disponível ainda",
27
+ "report_another": "Enviar outro relato",
28
+ "report_attach_screenshot": "Anexar uma captura de tela (opcional)",
29
+ "report_category": "Sobre o que é isso?",
30
+ "report_category_access_problem": "Problema de acesso / download",
31
+ "report_category_installation_problem": "Problema de instalação",
32
+ "report_category_other": "Outro",
33
+ "report_category_question": "Dúvida",
34
+ "report_message": "Mensagem (obrigatória)",
35
+ "report_problem": "Relatar um problema",
36
+ "report_related_build": "Build relacionada (opcional)",
37
+ "report_select_build_placeholder": "Dúvida geral / não específica de uma build",
38
+ "report_sent": "Enviado — só o estúdio vê isso.",
39
+ "report_submit": "Enviar para o estúdio",
27
40
  "resend_code": "Reenviar código",
28
- "revoked": "Seu acesso à build foi revogado",
41
+ "revoked": "Seu acesso a esta build foi revogado.",
42
+ "revoked_contact_link": "Se você acha que isso é um engano, avise o estúdio.",
43
+ "revoked_hint": "Isso geralmente é feito pelo estúdio responsável pela campanha.",
44
+ "select_category_placeholder": "Selecione…",
29
45
  "send_code": "Enviar código de verificação",
30
46
  "sending_code": "Enviando código…",
47
+ "sending_report": "Enviando…",
31
48
  "sign": "Assinar",
32
49
  "signing": "Assinando…",
33
50
  "verify": "Verificar",
package/locales/ro.json CHANGED
@@ -24,10 +24,27 @@
24
24
  "enter_code": "Introdu codul din 6 cifre pe care ți l-am trimis pe e-mail",
25
25
  "nda_title": "Semnează NDA-ul pentru a debloca build-ul",
26
26
  "no_builds": "Niciun build disponibil momentan",
27
+ "report_another": "Trimite un alt raport",
28
+ "report_attach_screenshot": "Atașează o captură de ecran (opțional)",
29
+ "report_category": "Despre ce este vorba?",
30
+ "report_category_access_problem": "Problemă de acces / descărcare",
31
+ "report_category_installation_problem": "Problemă de instalare",
32
+ "report_category_other": "Altceva",
33
+ "report_category_question": "Întrebare",
34
+ "report_message": "Mesaj (obligatoriu)",
35
+ "report_problem": "Raportează o problemă",
36
+ "report_related_build": "Build asociat (opțional)",
37
+ "report_select_build_placeholder": "Întrebare generală / fără legătură cu un build anume",
38
+ "report_sent": "Trimis — doar studioul vede asta.",
39
+ "report_submit": "Trimite studioului",
27
40
  "resend_code": "Retrimite codul",
28
- "revoked": "Accesul tău la build a fost revocat",
41
+ "revoked": "Accesul tău la acest build a fost revocat.",
42
+ "revoked_contact_link": "Dacă crezi că e o greșeală, anunță studioul.",
43
+ "revoked_hint": "Acest lucru este de obicei făcut de studioul care rulează campania.",
44
+ "select_category_placeholder": "Selectează…",
29
45
  "send_code": "Trimite codul de verificare",
30
46
  "sending_code": "Se trimite codul…",
47
+ "sending_report": "Se trimite…",
31
48
  "sign": "Semnează",
32
49
  "signing": "Se semnează…",
33
50
  "verify": "Verifică",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mundogamernetwork/shared-ui",
3
- "version": "1.16.23",
3
+ "version": "1.16.24",
4
4
  "description": "Mundo Gamer Network - Shared UI Layer (Nuxt 3)",
5
5
  "type": "module",
6
6
  "main": "./nuxt.config.ts",
@@ -530,6 +530,7 @@ export interface PlaytestBuildAsset {
530
530
  original_filename: string
531
531
  mime_type: string | null
532
532
  size_bytes: number | null
533
+ notes: string | null
533
534
  status: 'pending' | 'ready' | 'failed'
534
535
  }
535
536
 
@@ -556,3 +557,40 @@ export const fetchBuildDownloadUrl = (grantId: number, assetId: number) =>
556
557
  httpService.get<{ data: { url: string; expires_at: string } }>(
557
558
  `/playtest/build-access/${grantId}/builds/${assetId}/download`,
558
559
  )
560
+
561
+ // ---------------------------------------------------------------------------
562
+ // Build problem reports — a private, one-way tester -> studio channel, never
563
+ // a public wall (this whole feature exists to keep an unreleased build
564
+ // confidential). Works at ANY grant status, including pending_nda — a
565
+ // tester stuck in the sign flow itself (e.g. never got the OTP email) still
566
+ // needs a way to reach the studio.
567
+ // ---------------------------------------------------------------------------
568
+
569
+ export type BuildProblemReportCategory = 'question' | 'installation_problem' | 'access_problem' | 'other'
570
+
571
+ export interface StoreBuildProblemReportPayload {
572
+ category: BuildProblemReportCategory
573
+ message: string
574
+ playtest_build_asset_id?: number
575
+ media_url?: string
576
+ }
577
+
578
+ export interface PlaytestBuildProblemReport {
579
+ id: number
580
+ playtest_campaign_id: number
581
+ playtest_build_grant_id: number
582
+ playtest_build_asset_id: number | null
583
+ category: BuildProblemReportCategory
584
+ message: string
585
+ media_url: string | null
586
+ status: 'open' | 'in_progress' | 'resolved'
587
+ studio_response: string | null
588
+ responded_at: string | null
589
+ created_at: string
590
+ }
591
+
592
+ export const storeBuildProblemReport = (grantId: number, payload: StoreBuildProblemReportPayload) =>
593
+ httpService.post<{ data: PlaytestBuildProblemReport }>(
594
+ `/playtest/build-access/${grantId}/problem-reports`,
595
+ payload,
596
+ )