@mundogamernetwork/shared-ui 1.16.22 → 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.
- package/components/indie-wall/MediaKitWallBlock.vue +1 -1
- package/components/playtest/build-access/BuildAccessPanel.vue +219 -0
- package/components/playtest/build-access/BuildDownload.vue +141 -0
- package/components/playtest/build-access/NdaSign.vue +221 -0
- package/components/playtest/build-access/ReportProblemForm.vue +143 -0
- package/components/playtest/concept-poll/FiveSecondTestVote.vue +1 -1
- package/components/playtest/lqa/FileIssueForm.vue +9 -1
- package/composables/usePlaytestBuildAccess.ts +263 -0
- package/locales/de.json +150 -0
- package/locales/en.json +150 -0
- package/locales/es.json +150 -0
- package/locales/pt-BR.json +150 -0
- package/locales/ro.json +150 -0
- package/package.json +1 -1
- package/pages/key-campaigns/redeem-key-approved.vue +51 -0
- package/pages/mural/[slug]/index.vue +148 -60
- package/services/esignatureService.ts +56 -0
- package/services/playtestTesterService.ts +91 -0
- package/pages/mural/[slug]/pixel/[pixelId].vue +0 -460
- package/pages/wall/[slug]/pixel/[pixelId].vue +0 -12
|
@@ -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>
|
|
@@ -70,7 +70,7 @@ function submit() {
|
|
|
70
70
|
<template>
|
|
71
71
|
<div class="five-sec-vote">
|
|
72
72
|
<div v-if="phase === 'intro'" class="five-sec-vote__intro">
|
|
73
|
-
<p>{{ $t("playtest.concept_poll.five_second_intro", `You'll see the image(s) for ${seconds} seconds, then answer from memory
|
|
73
|
+
<p>{{ $t("playtest.concept_poll.five_second_intro", `You'll see the image(s) for ${seconds} seconds, then answer from memory.`, { seconds }) }}</p>
|
|
74
74
|
<button type="button" class="btn primary" @click="start">
|
|
75
75
|
{{ $t("playtest.concept_poll.five_second_start", "Start") }}
|
|
76
76
|
</button>
|
|
@@ -38,6 +38,7 @@ const canSubmit = computed(() =>
|
|
|
38
38
|
!!issueType.value &&
|
|
39
39
|
!!severity.value &&
|
|
40
40
|
description.value.trim().length > 0 &&
|
|
41
|
+
!!mediaUrl.value &&
|
|
41
42
|
!props.submitting
|
|
42
43
|
);
|
|
43
44
|
|
|
@@ -116,10 +117,11 @@ function submit() {
|
|
|
116
117
|
</div>
|
|
117
118
|
|
|
118
119
|
<div class="file-issue-form__row">
|
|
119
|
-
<label class="file-issue-form__label">{{ $t("playtest.lqa.attach_screenshot", "Attach screenshot/video (
|
|
120
|
+
<label class="file-issue-form__label">{{ $t("playtest.lqa.attach_screenshot", "Attach screenshot/video (required)") }}</label>
|
|
120
121
|
<input type="file" accept="image/*,video/mp4,video/quicktime,video/webm" @change="onFileChange">
|
|
121
122
|
<span v-if="uploading" class="file-issue-form__uploading">{{ $t("playtest.lqa.uploading", "Uploading…") }}</span>
|
|
122
123
|
<span v-else-if="mediaUrl" class="file-issue-form__uploaded">{{ $t("playtest.lqa.uploaded", "Attached") }}</span>
|
|
124
|
+
<p v-if="!mediaUrl && !uploading" class="file-issue-form__hint">{{ $t("playtest.lqa.evidence_hint", "A screenshot or clip of the actual build is required so the studio can verify the issue.") }}</p>
|
|
123
125
|
</div>
|
|
124
126
|
|
|
125
127
|
<button type="button" class="btn primary file-issue-form__submit" :disabled="!canSubmit" @click="submit">
|
|
@@ -174,6 +176,12 @@ function submit() {
|
|
|
174
176
|
color: var(--primary, #D297FF);
|
|
175
177
|
}
|
|
176
178
|
|
|
179
|
+
&__hint {
|
|
180
|
+
margin: 6px 0 0;
|
|
181
|
+
font-size: 11px;
|
|
182
|
+
color: var(--secondary-info-fg, #888);
|
|
183
|
+
}
|
|
184
|
+
|
|
177
185
|
&__submit {
|
|
178
186
|
margin-top: 4px;
|
|
179
187
|
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
2
|
+
import {
|
|
3
|
+
requestBuildAccess,
|
|
4
|
+
initiateBuildNda,
|
|
5
|
+
fetchBuildAssets,
|
|
6
|
+
fetchBuildDownloadUrl,
|
|
7
|
+
storeBuildProblemReport,
|
|
8
|
+
uploadPlaytestMedia,
|
|
9
|
+
type PlaytestBuildGrant,
|
|
10
|
+
type PlaytestBuildAsset,
|
|
11
|
+
type StoreBuildProblemReportPayload,
|
|
12
|
+
} from "../services/playtestTesterService";
|
|
13
|
+
import {
|
|
14
|
+
fetchSigningSession,
|
|
15
|
+
requestSigningOtp,
|
|
16
|
+
verifySigningOtp,
|
|
17
|
+
acceptSigningConsent,
|
|
18
|
+
submitSignature,
|
|
19
|
+
type EsignatureEnvelope,
|
|
20
|
+
type EsignatureSignerState,
|
|
21
|
+
} from "../services/esignatureService";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Build-access + NDA-sign state for a single campaign. Mirrors
|
|
25
|
+
* usePlaytestAccessGrant's shape (loading/submitting/genericError refs), but
|
|
26
|
+
* one level deeper: this owns BOTH the Playtest-specific grant/asset state
|
|
27
|
+
* AND drives the generic ESignature sign flow through a sign_token once the
|
|
28
|
+
* NDA envelope exists — a tester never talks to /esignature/* directly.
|
|
29
|
+
*/
|
|
30
|
+
export function usePlaytestBuildAccess(campaignId: number) {
|
|
31
|
+
const loading = ref(false);
|
|
32
|
+
const submitting = ref(false);
|
|
33
|
+
const genericError = ref<string | null>(null);
|
|
34
|
+
|
|
35
|
+
const grant = ref<PlaytestBuildGrant | null>(null);
|
|
36
|
+
const assets = ref<PlaytestBuildAsset[]>([]);
|
|
37
|
+
const signToken = ref<string | null>(null);
|
|
38
|
+
const envelope = ref<EsignatureEnvelope | null>(null);
|
|
39
|
+
const signer = ref<EsignatureSignerState | null>(null);
|
|
40
|
+
// True once an OTP has been sent for the current attempt — same
|
|
41
|
+
// UI-responsiveness reasoning as usePlaytestAccessGrant.waiverInitiated.
|
|
42
|
+
const otpInitiated = ref(false);
|
|
43
|
+
|
|
44
|
+
function resetErrors() {
|
|
45
|
+
genericError.value = null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function errorMessage(e: any, fallback: string): string {
|
|
49
|
+
return e?.response?.data?.message ?? e?.message ?? fallback;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Loads the ready build list — call this on mount regardless of grant status, so the panel can decide whether to show anything at all. */
|
|
53
|
+
async function loadAssets() {
|
|
54
|
+
loading.value = true;
|
|
55
|
+
try {
|
|
56
|
+
const res = await fetchBuildAssets(campaignId);
|
|
57
|
+
assets.value = res.data.data;
|
|
58
|
+
return assets.value;
|
|
59
|
+
} finally {
|
|
60
|
+
loading.value = false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Idempotent — safe to call every time the panel mounts. No-ops (server-side) into returning the existing grant if one is already there. */
|
|
65
|
+
async function requestAccess() {
|
|
66
|
+
resetErrors();
|
|
67
|
+
submitting.value = true;
|
|
68
|
+
try {
|
|
69
|
+
const res = await requestBuildAccess(campaignId);
|
|
70
|
+
grant.value = res.data.data;
|
|
71
|
+
return grant.value;
|
|
72
|
+
} catch (e: any) {
|
|
73
|
+
genericError.value = errorMessage(e, "Could not request build access. Please try again.");
|
|
74
|
+
return null;
|
|
75
|
+
} finally {
|
|
76
|
+
submitting.value = false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function initiateNda() {
|
|
81
|
+
if (!grant.value) return null;
|
|
82
|
+
resetErrors();
|
|
83
|
+
submitting.value = true;
|
|
84
|
+
try {
|
|
85
|
+
const res = await initiateBuildNda(grant.value.id);
|
|
86
|
+
signToken.value = res.data.data.sign_token;
|
|
87
|
+
if (signToken.value) await loadSigningSession();
|
|
88
|
+
return signToken.value;
|
|
89
|
+
} catch (e: any) {
|
|
90
|
+
genericError.value = errorMessage(e, "Could not start the NDA signing process.");
|
|
91
|
+
return null;
|
|
92
|
+
} finally {
|
|
93
|
+
submitting.value = false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The single entry point the UI calls for both "send code" (first time,
|
|
99
|
+
* no envelope exists yet — creates one first) and "resend code" (envelope
|
|
100
|
+
* already exists) — one click either way, the component never needs to
|
|
101
|
+
* know which case it is.
|
|
102
|
+
*/
|
|
103
|
+
async function sendCode() {
|
|
104
|
+
if (!signToken.value) {
|
|
105
|
+
const token = await initiateNda();
|
|
106
|
+
if (!token) return false;
|
|
107
|
+
}
|
|
108
|
+
return sendOtp();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function loadSigningSession() {
|
|
112
|
+
if (!signToken.value) return;
|
|
113
|
+
const res = await fetchSigningSession(signToken.value);
|
|
114
|
+
envelope.value = res.data.data.envelope;
|
|
115
|
+
signer.value = res.data.data.signer;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function sendOtp() {
|
|
119
|
+
if (!signToken.value) return false;
|
|
120
|
+
resetErrors();
|
|
121
|
+
submitting.value = true;
|
|
122
|
+
try {
|
|
123
|
+
await requestSigningOtp(signToken.value);
|
|
124
|
+
otpInitiated.value = true;
|
|
125
|
+
return true;
|
|
126
|
+
} catch (e: any) {
|
|
127
|
+
genericError.value = errorMessage(e, "Could not send the verification code. Please try again.");
|
|
128
|
+
return false;
|
|
129
|
+
} finally {
|
|
130
|
+
submitting.value = false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function verifyOtp(otpCode: string) {
|
|
135
|
+
if (!signToken.value) return false;
|
|
136
|
+
resetErrors();
|
|
137
|
+
submitting.value = true;
|
|
138
|
+
try {
|
|
139
|
+
await verifySigningOtp(signToken.value, otpCode);
|
|
140
|
+
await loadSigningSession();
|
|
141
|
+
return true;
|
|
142
|
+
} catch (e: any) {
|
|
143
|
+
genericError.value = errorMessage(e, "Invalid or expired code.");
|
|
144
|
+
return false;
|
|
145
|
+
} finally {
|
|
146
|
+
submitting.value = false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function acceptConsent() {
|
|
151
|
+
if (!signToken.value) return false;
|
|
152
|
+
resetErrors();
|
|
153
|
+
submitting.value = true;
|
|
154
|
+
try {
|
|
155
|
+
await acceptSigningConsent(signToken.value);
|
|
156
|
+
await loadSigningSession();
|
|
157
|
+
return true;
|
|
158
|
+
} catch (e: any) {
|
|
159
|
+
genericError.value = errorMessage(e, "Could not accept the consent. Please try again.");
|
|
160
|
+
return false;
|
|
161
|
+
} finally {
|
|
162
|
+
submitting.value = false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Signs, then re-fetches the grant — the PlaytestBuildGrantEnvelopeObserver flips it to nda_signed the instant the envelope completes. */
|
|
167
|
+
async function sign() {
|
|
168
|
+
if (!signToken.value) return false;
|
|
169
|
+
resetErrors();
|
|
170
|
+
submitting.value = true;
|
|
171
|
+
try {
|
|
172
|
+
await submitSignature(signToken.value);
|
|
173
|
+
await loadSigningSession();
|
|
174
|
+
await requestAccess(); // re-fetch grant, now nda_signed
|
|
175
|
+
return true;
|
|
176
|
+
} catch (e: any) {
|
|
177
|
+
genericError.value = errorMessage(e, "Could not submit the signature. Please try again.");
|
|
178
|
+
return false;
|
|
179
|
+
} finally {
|
|
180
|
+
submitting.value = false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Mints a fresh URL every call — never reuse a previously returned one. */
|
|
185
|
+
async function getDownloadUrl(assetId: number) {
|
|
186
|
+
if (!grant.value) return null;
|
|
187
|
+
resetErrors();
|
|
188
|
+
submitting.value = true;
|
|
189
|
+
try {
|
|
190
|
+
const res = await fetchBuildDownloadUrl(grant.value.id, assetId);
|
|
191
|
+
return res.data.data.url;
|
|
192
|
+
} catch (e: any) {
|
|
193
|
+
genericError.value = errorMessage(e, "Could not generate a download link. Please try again.");
|
|
194
|
+
return null;
|
|
195
|
+
} finally {
|
|
196
|
+
submitting.value = false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
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
|
+
|
|
235
|
+
return {
|
|
236
|
+
loading,
|
|
237
|
+
submitting,
|
|
238
|
+
genericError,
|
|
239
|
+
grant,
|
|
240
|
+
assets,
|
|
241
|
+
envelope,
|
|
242
|
+
signer,
|
|
243
|
+
otpInitiated,
|
|
244
|
+
loadAssets,
|
|
245
|
+
requestAccess,
|
|
246
|
+
initiateNda,
|
|
247
|
+
sendCode,
|
|
248
|
+
sendOtp,
|
|
249
|
+
verifyOtp,
|
|
250
|
+
acceptConsent,
|
|
251
|
+
sign,
|
|
252
|
+
getDownloadUrl,
|
|
253
|
+
resetErrors,
|
|
254
|
+
reportSubmitting,
|
|
255
|
+
reportUploading,
|
|
256
|
+
reportSuccess,
|
|
257
|
+
reportError,
|
|
258
|
+
submitProblemReport,
|
|
259
|
+
uploadReportMedia,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export type PlaytestBuildAccessComposable = ReturnType<typeof usePlaytestBuildAccess>;
|
package/locales/de.json
CHANGED
|
@@ -1,4 +1,153 @@
|
|
|
1
1
|
{
|
|
2
|
+
"playtest": {
|
|
3
|
+
"access_grant": {
|
|
4
|
+
"copied": "Kopiert",
|
|
5
|
+
"copy": "Kopieren",
|
|
6
|
+
"enter_code": "Gib den 6-stelligen Code ein, den wir dir per E-Mail geschickt haben",
|
|
7
|
+
"open_link": "Link öffnen",
|
|
8
|
+
"resend_code": "Code erneut senden",
|
|
9
|
+
"reveal": "Anzeigen",
|
|
10
|
+
"send_code": "Bestätigungscode senden",
|
|
11
|
+
"sending_code": "Code wird gesendet…",
|
|
12
|
+
"verify": "Bestätigen",
|
|
13
|
+
"verifying": "Wird bestätigt…",
|
|
14
|
+
"waiver_title": "Unterschreibe den Haftungsausschluss, um deinen Spielzugang freizuschalten",
|
|
15
|
+
"your_key": "Dein Spielschlüssel",
|
|
16
|
+
"your_link": "Dein Download-Link"
|
|
17
|
+
},
|
|
18
|
+
"build_access": {
|
|
19
|
+
"accept_consent": "Akzeptieren und fortfahren",
|
|
20
|
+
"accepting": "Wird gesendet…",
|
|
21
|
+
"builds_title": "Verfügbare Builds",
|
|
22
|
+
"consent_checkbox": "Ich habe die obige Vereinbarung gelesen und verstanden",
|
|
23
|
+
"download": "Herunterladen",
|
|
24
|
+
"enter_code": "Gib den 6-stelligen Code ein, den wir dir per E-Mail geschickt haben",
|
|
25
|
+
"nda_title": "Unterschreibe die NDA, um die Build freizuschalten",
|
|
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",
|
|
40
|
+
"resend_code": "Code erneut senden",
|
|
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…",
|
|
45
|
+
"send_code": "Bestätigungscode senden",
|
|
46
|
+
"sending_code": "Code wird gesendet…",
|
|
47
|
+
"sending_report": "Wird gesendet…",
|
|
48
|
+
"sign": "Unterschreiben",
|
|
49
|
+
"signing": "Wird unterschrieben…",
|
|
50
|
+
"verify": "Bestätigen",
|
|
51
|
+
"verifying": "Wird bestätigt…",
|
|
52
|
+
"view_pdf": "NDA-Dokument ansehen"
|
|
53
|
+
},
|
|
54
|
+
"accessibility": {
|
|
55
|
+
"notes_required": "Notizen (erforderlich bei Nichtbestehen)",
|
|
56
|
+
"uses_accommodation": "Ich bin persönlich auf diese Hilfestellung angewiesen"
|
|
57
|
+
},
|
|
58
|
+
"baseline": {
|
|
59
|
+
"feedback_optional": "Dein Feedback (optional)",
|
|
60
|
+
"feedback_required": "Dein Feedback (erforderlich)"
|
|
61
|
+
},
|
|
62
|
+
"brief": {
|
|
63
|
+
"ineligible_generic": "Du erfüllst derzeit nicht die Voraussetzungen für diesen Playtest.",
|
|
64
|
+
"not_found": "Diese Playtest-Kampagne konnte nicht gefunden werden.",
|
|
65
|
+
"paid_on_acceptance": "Wird ausgezahlt, sobald das Studio deine Antwort akzeptiert, nicht beim Einreichen.",
|
|
66
|
+
"reward": "Belohnung"
|
|
67
|
+
},
|
|
68
|
+
"compliance": {
|
|
69
|
+
"attach_evidence": "Nachweis anhängen (Screenshot/Video)",
|
|
70
|
+
"notes_required": "Notizen (erforderlich bei Nichtbestehen)",
|
|
71
|
+
"uploaded": "Angehängt",
|
|
72
|
+
"uploading": "Wird hochgeladen…"
|
|
73
|
+
},
|
|
74
|
+
"concept_poll": {
|
|
75
|
+
"emoji_label": "Wähle deine Reaktion",
|
|
76
|
+
"five_second_intro": "Du siehst das/die Bild(er) {seconds} Sekunden lang und antwortest danach aus dem Gedächtnis.",
|
|
77
|
+
"five_second_start": "Start",
|
|
78
|
+
"ranked_hint": "Ordne diese von deiner bevorzugtesten zur am wenigsten bevorzugten (nutze die Pfeile).",
|
|
79
|
+
"reason_label": "Warum hast du das gewählt? (erforderlich)",
|
|
80
|
+
"reason_min_length": "Bitte gib mindestens 3 Zeichen ein.",
|
|
81
|
+
"recall_label": "Woran erinnerst du dich? (erforderlich)",
|
|
82
|
+
"stars_label": "Deine Bewertung",
|
|
83
|
+
"submit_vote": "Stimme abgeben"
|
|
84
|
+
},
|
|
85
|
+
"flow": {
|
|
86
|
+
"criteria_completed": "Kriterien abgeschlossen",
|
|
87
|
+
"submit_checklist": "Checkliste absenden",
|
|
88
|
+
"submit_response": "Antwort absenden"
|
|
89
|
+
},
|
|
90
|
+
"lqa": {
|
|
91
|
+
"actual_text": "Tatsächlicher Text",
|
|
92
|
+
"attach_screenshot": "Screenshot/Video anhängen (erforderlich)",
|
|
93
|
+
"description": "Beschreibung (erforderlich)",
|
|
94
|
+
"evidence_hint": "Ein Screenshot oder Videoclip des tatsächlichen Builds ist erforderlich, damit das Studio das Problem überprüfen kann.",
|
|
95
|
+
"expected_text": "Erwarteter Text",
|
|
96
|
+
"file_issue": "Problem melden",
|
|
97
|
+
"filed_issues": "In dieser Sitzung gemeldete Probleme",
|
|
98
|
+
"issue_type": "Problemtyp",
|
|
99
|
+
"location": "Ort (Bildschirm/Szene, optional)",
|
|
100
|
+
"no_issues_yet": "Noch keine Probleme gemeldet.",
|
|
101
|
+
"responses_progress": "Bisherige Antworten",
|
|
102
|
+
"select_locale": "Sprache / Build im Fokus",
|
|
103
|
+
"select_locale_placeholder": "Sprache auswählen…",
|
|
104
|
+
"select_severity_placeholder": "Auswählen…",
|
|
105
|
+
"select_type_placeholder": "Auswählen…",
|
|
106
|
+
"severity": "Schweregrad",
|
|
107
|
+
"uploaded": "Angehängt",
|
|
108
|
+
"uploading": "Wird hochgeladen…"
|
|
109
|
+
},
|
|
110
|
+
"moderated": {
|
|
111
|
+
"cancel_session": "Abbrechen",
|
|
112
|
+
"claim_slot": "Diesen Termin reservieren",
|
|
113
|
+
"confirm_attendance": "Teilnahme bestätigen",
|
|
114
|
+
"confirmed": "bestätigt",
|
|
115
|
+
"join_call": "Anruf beitreten",
|
|
116
|
+
"link_pending": "Der Anruflink erscheint hier kurz vor der Sitzung.",
|
|
117
|
+
"no_session": "Keine Sitzung ausgewählt.",
|
|
118
|
+
"session_title": "Moderierte Sitzung",
|
|
119
|
+
"you_confirmed": "Du hast deine Teilnahme bestätigt"
|
|
120
|
+
},
|
|
121
|
+
"pending": {
|
|
122
|
+
"browse_more": "Weitere Playtests durchsuchen",
|
|
123
|
+
"ineligible_message": "Seit deinem Start hat sich etwas geändert — die Anforderungen dieser Kampagne werden nicht mehr erfüllt.",
|
|
124
|
+
"ineligible_title": "Du erfüllst die Voraussetzungen für diesen Playtest nicht mehr",
|
|
125
|
+
"message": "Danke fürs Testen! Das Studio wird deine Antwort prüfen — du wirst bezahlt, wenn sie akzeptiert wird.",
|
|
126
|
+
"title": "Antwort gesendet"
|
|
127
|
+
},
|
|
128
|
+
"video_capture": {
|
|
129
|
+
"continue_anyway": "Trotzdem fortfahren",
|
|
130
|
+
"hint": "Zeichne deinen Bildschirm auf, während du spielst. Du kannst dabei Notizen mit Zeitstempel hinzufügen.",
|
|
131
|
+
"mark": "Markieren",
|
|
132
|
+
"marker_save_failed": "Antwort gesendet, aber {v} Notiz(en) konnten nicht gespeichert werden.",
|
|
133
|
+
"processing_hint": "Deine Aufnahme wurde hochgeladen und wird verarbeitet. Sende ab, wenn du bereit bist — wir bestätigen im Hintergrund, dass sie fertig ist.",
|
|
134
|
+
"recording": "Aufnahme läuft…",
|
|
135
|
+
"retry_markers": "Notizen erneut speichern",
|
|
136
|
+
"retrying": "Wird erneut versucht…",
|
|
137
|
+
"review_hint": "Aufnahme erfasst ({v}). Lade sie hoch, um deine Antwort zu senden.",
|
|
138
|
+
"start": "Aufnahme starten",
|
|
139
|
+
"stop": "Aufnahme stoppen",
|
|
140
|
+
"submit": "Antwort absenden",
|
|
141
|
+
"submitting": "Wird gesendet…",
|
|
142
|
+
"upload": "Aufnahme hochladen",
|
|
143
|
+
"uploading": "Wird hochgeladen… {v}%"
|
|
144
|
+
},
|
|
145
|
+
"video_player": {
|
|
146
|
+
"open_direct": "Aufnahme öffnen",
|
|
147
|
+
"playback_error": "Dieser Browser kann die Aufnahme nicht direkt abspielen — versuche es mit Safari oder öffne den Datei-Link unten.",
|
|
148
|
+
"processing": "Die Aufnahme wird noch verarbeitet — schau in Kürze noch einmal vorbei."
|
|
149
|
+
}
|
|
150
|
+
},
|
|
2
151
|
"keys": {
|
|
3
152
|
"notif_prompt_title": "Benachrichtigungen für neue Key-Kampagnen erhalten?",
|
|
4
153
|
"notif_prompt_desc": "Wir benachrichtigen Sie per E-Mail und Push, wenn eine neue Key-Kampagne startet.",
|
|
@@ -210,6 +359,7 @@
|
|
|
210
359
|
"your_key": "Dein Schlüssel",
|
|
211
360
|
"copy": "Kopieren",
|
|
212
361
|
"copied": "Kopiert!",
|
|
362
|
+
"extra_codes": "Diese Anfrage wurde für {count} Keys genehmigt — hier sind alle.",
|
|
213
363
|
"redeem_on_platform": "Löse diesen Schlüssel auf der entsprechenden Plattform ein.",
|
|
214
364
|
"revealing": "Enthülle...",
|
|
215
365
|
"status": {
|