@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
|
@@ -47,7 +47,7 @@ const wallUrl = computed(() => {
|
|
|
47
47
|
|
|
48
48
|
const detailPixelPageUrl = computed(() => {
|
|
49
49
|
if (!wall.value || !detailPixel.value) return '#'
|
|
50
|
-
return
|
|
50
|
+
return `${wallUrl.value}?pixel=${detailPixel.value.id}`
|
|
51
51
|
})
|
|
52
52
|
|
|
53
53
|
function onTapEmpty(coord: { x: number; y: number }) {
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Single orchestrator for a campaign's NDA-gated build access — mounted once
|
|
3
|
+
// per campaign a tester is eligible for, mirroring AccessGrantPanel.vue's
|
|
4
|
+
// role for the (unrelated, accept-time) key/link grant. Renders nothing if
|
|
5
|
+
// the campaign has no ready build asset at all, so campaigns that don't use
|
|
6
|
+
// this feature (concept polls, etc.) show no empty panel.
|
|
7
|
+
import { onMounted, computed, ref } from "vue";
|
|
8
|
+
import { usePlaytestBuildAccess } from "../../../composables/usePlaytestBuildAccess";
|
|
9
|
+
import NdaSign from "./NdaSign.vue";
|
|
10
|
+
import BuildDownload from "./BuildDownload.vue";
|
|
11
|
+
import ReportProblemForm from "./ReportProblemForm.vue";
|
|
12
|
+
import type { StoreBuildProblemReportPayload } from "../../../services/playtestTesterService";
|
|
13
|
+
|
|
14
|
+
const props = defineProps<{
|
|
15
|
+
campaignId: number;
|
|
16
|
+
}>();
|
|
17
|
+
|
|
18
|
+
const state = usePlaytestBuildAccess(props.campaignId);
|
|
19
|
+
|
|
20
|
+
const hasAnyBuild = computed(() => state.assets.value.length > 0);
|
|
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
|
+
|
|
29
|
+
onMounted(async () => {
|
|
30
|
+
await state.loadAssets();
|
|
31
|
+
if (hasAnyBuild.value) {
|
|
32
|
+
await state.requestAccess();
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
async function onDownload(assetId: number) {
|
|
37
|
+
const url = await state.getDownloadUrl(assetId);
|
|
38
|
+
if (url) window.open(url, "_blank", "noopener");
|
|
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
|
+
}
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<template>
|
|
59
|
+
<div v-if="state.loading.value" class="build-access-panel build-access-panel--loading">
|
|
60
|
+
<div class="build-access-panel__skeleton" />
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div v-else-if="hasAnyBuild" class="build-access-panel">
|
|
64
|
+
<NdaSign
|
|
65
|
+
v-if="state.grant.value?.status === 'pending_nda'"
|
|
66
|
+
:envelope="state.envelope.value"
|
|
67
|
+
:signer="state.signer.value"
|
|
68
|
+
:submitting="state.submitting.value"
|
|
69
|
+
:otp-initiated="state.otpInitiated.value"
|
|
70
|
+
:error="state.genericError.value"
|
|
71
|
+
@send-code="state.sendCode"
|
|
72
|
+
@verify="state.verifyOtp"
|
|
73
|
+
@accept-consent="state.acceptConsent"
|
|
74
|
+
@sign="state.sign"
|
|
75
|
+
/>
|
|
76
|
+
|
|
77
|
+
<BuildDownload
|
|
78
|
+
v-else-if="state.grant.value?.status === 'nda_signed'"
|
|
79
|
+
:assets="state.assets.value"
|
|
80
|
+
:submitting="state.submitting.value"
|
|
81
|
+
:error="state.genericError.value"
|
|
82
|
+
@download="onDownload"
|
|
83
|
+
/>
|
|
84
|
+
|
|
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>
|
|
129
|
+
</div>
|
|
130
|
+
</template>
|
|
131
|
+
|
|
132
|
+
<style scoped lang="scss">
|
|
133
|
+
.build-access-panel {
|
|
134
|
+
padding: 16px;
|
|
135
|
+
background: var(--bg-app-badge);
|
|
136
|
+
border: 1px solid var(--button-secondary-default-bg);
|
|
137
|
+
|
|
138
|
+
&--loading {
|
|
139
|
+
padding: 0;
|
|
140
|
+
border: none;
|
|
141
|
+
background: none;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
&__skeleton {
|
|
145
|
+
width: 100%;
|
|
146
|
+
height: 64px;
|
|
147
|
+
background: var(--bg-app-badge);
|
|
148
|
+
animation: build-access-panel-pulse 1.4s ease-in-out infinite;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
&__revoked {
|
|
152
|
+
display: flex;
|
|
153
|
+
flex-direction: column;
|
|
154
|
+
gap: 6px;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
&__revoked-title {
|
|
158
|
+
font-size: 13px;
|
|
159
|
+
color: var(--danger, #e53935);
|
|
160
|
+
margin: 0;
|
|
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
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
@keyframes build-access-panel-pulse {
|
|
216
|
+
0%, 100% { opacity: 1; }
|
|
217
|
+
50% { opacity: 0.4; }
|
|
218
|
+
}
|
|
219
|
+
</style>
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Lists a campaign's ready build assets, each with a Download button that
|
|
3
|
+
// calls the download endpoint FRESH every click — the returned URL is a
|
|
4
|
+
// 5-minute presigned S3 link and is never cached/reused, unlike
|
|
5
|
+
// AccessGrantReveal's masked-value-already-in-hand pattern (that value
|
|
6
|
+
// never expires; this one deliberately does).
|
|
7
|
+
import type { PlaytestBuildAsset } from "../../../services/playtestTesterService";
|
|
8
|
+
|
|
9
|
+
defineProps<{
|
|
10
|
+
assets: PlaytestBuildAsset[];
|
|
11
|
+
submitting: boolean;
|
|
12
|
+
error?: string | null;
|
|
13
|
+
}>();
|
|
14
|
+
|
|
15
|
+
const emit = defineEmits<{
|
|
16
|
+
(e: "download", assetId: number): void;
|
|
17
|
+
}>();
|
|
18
|
+
|
|
19
|
+
function formatSize(bytes: number | null): string {
|
|
20
|
+
if (!bytes) return "";
|
|
21
|
+
const mb = bytes / (1024 * 1024);
|
|
22
|
+
return mb >= 1024 ? `${(mb / 1024).toFixed(1)} GB` : `${mb.toFixed(1)} MB`;
|
|
23
|
+
}
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<div class="build-download">
|
|
28
|
+
<h3 class="build-download__title">
|
|
29
|
+
{{ $t("playtest.build_access.builds_title", "Available builds") }}
|
|
30
|
+
</h3>
|
|
31
|
+
|
|
32
|
+
<p v-if="error" class="build-download__error">{{ error }}</p>
|
|
33
|
+
|
|
34
|
+
<div v-if="!assets.length" class="build-download__empty">
|
|
35
|
+
{{ $t("playtest.build_access.no_builds", "No build has been uploaded yet.") }}
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div v-else class="build-download__list">
|
|
39
|
+
<div v-for="asset in assets" :key="asset.id" class="build-download__row">
|
|
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>
|
|
48
|
+
</div>
|
|
49
|
+
<button
|
|
50
|
+
type="button"
|
|
51
|
+
class="btn primary"
|
|
52
|
+
:disabled="submitting"
|
|
53
|
+
@click="emit('download', asset.id)"
|
|
54
|
+
>
|
|
55
|
+
{{ $t("playtest.build_access.download", "Download") }}
|
|
56
|
+
</button>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</template>
|
|
61
|
+
|
|
62
|
+
<style scoped lang="scss">
|
|
63
|
+
.build-download {
|
|
64
|
+
display: flex;
|
|
65
|
+
flex-direction: column;
|
|
66
|
+
gap: 10px;
|
|
67
|
+
|
|
68
|
+
&__title {
|
|
69
|
+
font-size: 15px;
|
|
70
|
+
font-weight: 700;
|
|
71
|
+
color: var(--card-article-title, #fff);
|
|
72
|
+
margin: 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
&__empty {
|
|
76
|
+
font-size: 13px;
|
|
77
|
+
color: var(--secondary-info-fg, #888);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
&__error {
|
|
81
|
+
font-size: 12px;
|
|
82
|
+
color: var(--danger, #e53935);
|
|
83
|
+
margin: 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
&__list {
|
|
87
|
+
display: flex;
|
|
88
|
+
flex-direction: column;
|
|
89
|
+
gap: 8px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
&__row {
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
justify-content: space-between;
|
|
96
|
+
gap: 12px;
|
|
97
|
+
padding: 10px 12px;
|
|
98
|
+
background: var(--bg-app-badge, #1a1a1a);
|
|
99
|
+
border: 1px solid var(--bg-app-badge);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
&__main {
|
|
103
|
+
display: flex;
|
|
104
|
+
flex-direction: column;
|
|
105
|
+
gap: 4px;
|
|
106
|
+
flex: 1;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
&__info {
|
|
110
|
+
display: flex;
|
|
111
|
+
flex-wrap: wrap;
|
|
112
|
+
align-items: baseline;
|
|
113
|
+
gap: 8px;
|
|
114
|
+
font-size: 13px;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
&__notes {
|
|
118
|
+
white-space: pre-wrap;
|
|
119
|
+
font-size: 11px;
|
|
120
|
+
color: var(--secondary-info-fg, #888);
|
|
121
|
+
margin: 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
&__platform {
|
|
125
|
+
text-transform: uppercase;
|
|
126
|
+
font-size: 10px;
|
|
127
|
+
font-weight: 700;
|
|
128
|
+
color: var(--primary, #d297ff);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
&__filename {
|
|
132
|
+
color: var(--card-article-title, #fff);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
&__version,
|
|
136
|
+
&__size {
|
|
137
|
+
color: var(--secondary-info-fg, #888);
|
|
138
|
+
font-size: 11px;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
</style>
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// NDA review + sign — document (text or embedded PDF) -> OTP -> consent ->
|
|
3
|
+
// sign. Purely presentational, mirrors AccessGrantWaiver.vue's role:
|
|
4
|
+
// BuildAccessPanel.vue owns the actual usePlaytestBuildAccess() calls. Four
|
|
5
|
+
// steps instead of AccessGrantWaiver's two because a real NDA needs an
|
|
6
|
+
// explicit clickwrap consent step separate from "I have the right code" —
|
|
7
|
+
// the old liability-waiver flow this replaces skipped that.
|
|
8
|
+
import { ref, computed } from "vue";
|
|
9
|
+
import type { EsignatureEnvelope, EsignatureSignerState } from "../../../services/esignatureService";
|
|
10
|
+
|
|
11
|
+
const props = defineProps<{
|
|
12
|
+
envelope: EsignatureEnvelope | null;
|
|
13
|
+
signer: EsignatureSignerState | null;
|
|
14
|
+
submitting: boolean;
|
|
15
|
+
otpInitiated: boolean;
|
|
16
|
+
error?: string | null;
|
|
17
|
+
}>();
|
|
18
|
+
|
|
19
|
+
const emit = defineEmits<{
|
|
20
|
+
(e: "send-code"): void;
|
|
21
|
+
(e: "verify", otpCode: string): void;
|
|
22
|
+
(e: "accept-consent"): void;
|
|
23
|
+
(e: "sign"): void;
|
|
24
|
+
}>();
|
|
25
|
+
|
|
26
|
+
const otpCode = ref("");
|
|
27
|
+
const consentChecked = ref(false);
|
|
28
|
+
|
|
29
|
+
const step = computed<"otp" | "consent" | "sign">(() => {
|
|
30
|
+
if (!props.signer?.otp_verified) return "otp";
|
|
31
|
+
if (props.signer.status !== "consented" && props.signer.status !== "signed") return "consent";
|
|
32
|
+
return "sign";
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
function submitCode() {
|
|
36
|
+
if (!otpCode.value) return;
|
|
37
|
+
emit("verify", otpCode.value);
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<div class="nda-sign">
|
|
43
|
+
<h3 class="nda-sign__title">
|
|
44
|
+
{{ $t("playtest.build_access.nda_title", "Sign the NDA to access this build") }}
|
|
45
|
+
</h3>
|
|
46
|
+
|
|
47
|
+
<div class="nda-sign__document">
|
|
48
|
+
<div v-if="envelope?.content_type === 'html'" class="nda-sign__document-html" v-html="envelope.document_html" />
|
|
49
|
+
<div v-else-if="envelope?.content_type === 'file' && envelope.document_file_preview_url" class="nda-sign__document-file">
|
|
50
|
+
<a :href="envelope.document_file_preview_url" target="_blank" rel="noopener">
|
|
51
|
+
{{ $t("playtest.build_access.view_pdf", "View the NDA document (PDF)") }} ↗
|
|
52
|
+
</a>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<p v-if="error" class="nda-sign__error">{{ error }}</p>
|
|
57
|
+
|
|
58
|
+
<!-- Step 1: OTP -->
|
|
59
|
+
<div v-if="step === 'otp'" class="nda-sign__step">
|
|
60
|
+
<div v-if="!otpInitiated" class="nda-sign__actions">
|
|
61
|
+
<button type="button" class="btn primary" :disabled="submitting" @click="emit('send-code')">
|
|
62
|
+
{{ submitting
|
|
63
|
+
? $t("playtest.build_access.sending_code", "Sending code…")
|
|
64
|
+
: $t("playtest.build_access.send_code", "Send verification code") }}
|
|
65
|
+
</button>
|
|
66
|
+
</div>
|
|
67
|
+
<div v-else class="nda-sign__otp">
|
|
68
|
+
<label class="nda-sign__label" for="nda-otp">
|
|
69
|
+
{{ $t("playtest.build_access.enter_code", "Enter the 6-digit code we emailed you") }}
|
|
70
|
+
</label>
|
|
71
|
+
<div class="nda-sign__otp-row">
|
|
72
|
+
<input
|
|
73
|
+
id="nda-otp"
|
|
74
|
+
v-model="otpCode"
|
|
75
|
+
type="text"
|
|
76
|
+
inputmode="numeric"
|
|
77
|
+
maxlength="6"
|
|
78
|
+
class="nda-sign__otp-input"
|
|
79
|
+
:disabled="submitting"
|
|
80
|
+
@keyup.enter="submitCode"
|
|
81
|
+
/>
|
|
82
|
+
<button type="button" class="btn primary" :disabled="submitting || otpCode.length === 0" @click="submitCode">
|
|
83
|
+
{{ submitting ? $t("playtest.build_access.verifying", "Verifying…") : $t("playtest.build_access.verify", "Verify") }}
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
<button type="button" class="nda-sign__resend" :disabled="submitting" @click="emit('send-code')">
|
|
87
|
+
{{ $t("playtest.build_access.resend_code", "Resend code") }}
|
|
88
|
+
</button>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<!-- Step 2: Consent -->
|
|
93
|
+
<div v-else-if="step === 'consent'" class="nda-sign__step">
|
|
94
|
+
<label class="nda-sign__consent-row">
|
|
95
|
+
<input type="checkbox" v-model="consentChecked" :disabled="submitting" />
|
|
96
|
+
{{ $t("playtest.build_access.consent_checkbox", "I have read and agree to the terms of this NDA") }}
|
|
97
|
+
</label>
|
|
98
|
+
<button
|
|
99
|
+
type="button"
|
|
100
|
+
class="btn primary"
|
|
101
|
+
:disabled="submitting || !consentChecked"
|
|
102
|
+
@click="emit('accept-consent')"
|
|
103
|
+
>
|
|
104
|
+
{{ submitting ? $t("playtest.build_access.accepting", "Accepting…") : $t("playtest.build_access.accept_consent", "Accept") }}
|
|
105
|
+
</button>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<!-- Step 3: Sign -->
|
|
109
|
+
<div v-else class="nda-sign__step">
|
|
110
|
+
<button type="button" class="btn primary" :disabled="submitting" @click="emit('sign')">
|
|
111
|
+
{{ submitting ? $t("playtest.build_access.signing", "Signing…") : $t("playtest.build_access.sign", "Sign NDA") }}
|
|
112
|
+
</button>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
</template>
|
|
116
|
+
|
|
117
|
+
<style scoped lang="scss">
|
|
118
|
+
.nda-sign {
|
|
119
|
+
display: flex;
|
|
120
|
+
flex-direction: column;
|
|
121
|
+
gap: 14px;
|
|
122
|
+
|
|
123
|
+
&__title {
|
|
124
|
+
font-size: 15px;
|
|
125
|
+
font-weight: 700;
|
|
126
|
+
color: var(--card-article-title, #fff);
|
|
127
|
+
margin: 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
&__document {
|
|
131
|
+
max-height: 260px;
|
|
132
|
+
overflow-y: auto;
|
|
133
|
+
padding: 12px;
|
|
134
|
+
background: var(--bg-app-badge, #1a1a1a);
|
|
135
|
+
border: 1px solid var(--bg-app-badge);
|
|
136
|
+
font-size: 12px;
|
|
137
|
+
color: var(--secondary-info-fg, #ccc);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
&__document-html {
|
|
141
|
+
:deep(p) { margin: 0 0 8px; }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
&__document-file a {
|
|
145
|
+
color: var(--primary, #d297ff);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
&__error {
|
|
149
|
+
font-size: 12px;
|
|
150
|
+
color: var(--danger, #e53935);
|
|
151
|
+
margin: 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
&__step {
|
|
155
|
+
display: flex;
|
|
156
|
+
flex-direction: column;
|
|
157
|
+
gap: 10px;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
&__actions {
|
|
161
|
+
display: flex;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
&__otp {
|
|
165
|
+
display: flex;
|
|
166
|
+
flex-direction: column;
|
|
167
|
+
gap: 8px;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
&__label {
|
|
171
|
+
font-size: 12px;
|
|
172
|
+
color: var(--secondary-info-fg, #aaa);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
&__otp-row {
|
|
176
|
+
display: flex;
|
|
177
|
+
align-items: center;
|
|
178
|
+
gap: 8px;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
&__otp-input {
|
|
182
|
+
width: 140px;
|
|
183
|
+
height: 44px;
|
|
184
|
+
padding: 0 12px;
|
|
185
|
+
background-color: transparent;
|
|
186
|
+
border: 1px solid var(--bg-app-badge);
|
|
187
|
+
color: var(--card-article-title, #fff);
|
|
188
|
+
font-size: 18px;
|
|
189
|
+
letter-spacing: 4px;
|
|
190
|
+
text-align: center;
|
|
191
|
+
|
|
192
|
+
&:focus {
|
|
193
|
+
border-color: var(--primary, #d297ff);
|
|
194
|
+
outline: 0;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
&__resend {
|
|
199
|
+
align-self: flex-start;
|
|
200
|
+
background: none;
|
|
201
|
+
border: none;
|
|
202
|
+
padding: 0;
|
|
203
|
+
font-size: 12px;
|
|
204
|
+
color: var(--primary, #d297ff);
|
|
205
|
+
cursor: pointer;
|
|
206
|
+
|
|
207
|
+
&:disabled {
|
|
208
|
+
opacity: 0.45;
|
|
209
|
+
cursor: not-allowed;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
&__consent-row {
|
|
214
|
+
display: flex;
|
|
215
|
+
align-items: center;
|
|
216
|
+
gap: 8px;
|
|
217
|
+
font-size: 13px;
|
|
218
|
+
color: var(--secondary-info-fg, #ccc);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
</style>
|