@mundogamernetwork/shared-ui 1.3.3 → 1.3.7
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/assets/kit/kit-base.css +17 -0
- package/components/MediaKit/MgRateCardBundleEditor.vue +131 -1
- package/components/MediaKit/MgRateCardEditor.vue +133 -0
- package/components/keys/KeyCampaignDashboardCard.vue +1 -1
- package/locales/de.json +2 -0
- package/locales/en.json +2 -0
- package/locales/pt-BR.json +3 -1
- package/locales/ro.json +3 -1
- package/package.json +1 -1
- package/pages/key-campaigns/index.vue +47 -11
- package/pages/presskit/game/[slug].vue +26 -1
package/assets/kit/kit-base.css
CHANGED
|
@@ -34,6 +34,23 @@ body:has(.kit-press) {
|
|
|
34
34
|
color: var(--kit-muted, #8b92a0);
|
|
35
35
|
font-size: 1rem;
|
|
36
36
|
}
|
|
37
|
+
.kit-state--not-found {
|
|
38
|
+
min-height: 100vh;
|
|
39
|
+
justify-content: flex-start;
|
|
40
|
+
padding-top: 15vh;
|
|
41
|
+
width: 100%;
|
|
42
|
+
}
|
|
43
|
+
.kit-state__desc {
|
|
44
|
+
max-width: 420px;
|
|
45
|
+
text-align: center;
|
|
46
|
+
margin: -8px 0 4px;
|
|
47
|
+
font-size: 0.9rem;
|
|
48
|
+
line-height: 1.5;
|
|
49
|
+
}
|
|
50
|
+
.kit-more--not-found {
|
|
51
|
+
width: 100%;
|
|
52
|
+
margin-top: 40px;
|
|
53
|
+
}
|
|
37
54
|
.kit-state h2 {
|
|
38
55
|
font-size: 1.5rem;
|
|
39
56
|
color: var(--kit-text, #f4f6fa);
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
|
|
12
12
|
<!-- Bundle Form -->
|
|
13
13
|
<div v-if="showForm" class="bundle-form">
|
|
14
|
+
<div class="visibility-note">
|
|
15
|
+
<MGIcon icon="info" />
|
|
16
|
+
<span>{{ $t("media_kit.bundles.visibility_note") }}</span>
|
|
17
|
+
</div>
|
|
14
18
|
<div class="form-group">
|
|
15
19
|
<label>{{ $t("media_kit.bundles.bundle_name") }}</label>
|
|
16
20
|
<input v-model="form.name" type="text" :placeholder="$t('media_kit.bundles.name_placeholder')" />
|
|
@@ -105,6 +109,20 @@
|
|
|
105
109
|
</div>
|
|
106
110
|
</div>
|
|
107
111
|
|
|
112
|
+
<label class="visibility-checkbox" :class="{ disabled: !isSubscriber }">
|
|
113
|
+
<input
|
|
114
|
+
type="checkbox"
|
|
115
|
+
:checked="showPublicly"
|
|
116
|
+
:disabled="!isSubscriber"
|
|
117
|
+
@change="onToggleVisibility(($event.target as HTMLInputElement).checked)"
|
|
118
|
+
/>
|
|
119
|
+
<span>{{ $t("media_kit.bundles.show_publicly") }}</span>
|
|
120
|
+
<button v-if="!isSubscriber" type="button" class="upgrade-link" @click.prevent="emit('paywall', 'rates')">
|
|
121
|
+
<MGIcon icon="lock" />
|
|
122
|
+
{{ $t("media_kit.editor.upgrade_to_show") }}
|
|
123
|
+
</button>
|
|
124
|
+
</label>
|
|
125
|
+
|
|
108
126
|
<div class="form-actions">
|
|
109
127
|
<button class="btn-cancel" @click="cancelForm">{{ $t("media_kit.editor.cancel") }}</button>
|
|
110
128
|
<button class="btn-save" @click="saveBundle" :disabled="saving || form.rate_card_ids.length < 2">
|
|
@@ -186,19 +204,32 @@ const props = withDefaults(
|
|
|
186
204
|
availableRateCards: RateCard[];
|
|
187
205
|
influencerProInfoId: number;
|
|
188
206
|
isEnabled?: boolean;
|
|
207
|
+
/** Whether the account has an active paid subscription — gates public visibility only, not creation. */
|
|
208
|
+
isSubscriber?: boolean;
|
|
209
|
+
/** Current value of the account-wide "show bundles publicly" setting. */
|
|
210
|
+
showPublicly?: boolean;
|
|
189
211
|
}>(),
|
|
190
212
|
{
|
|
191
213
|
isEnabled: true,
|
|
214
|
+
isSubscriber: false,
|
|
215
|
+
showPublicly: true,
|
|
192
216
|
}
|
|
193
217
|
);
|
|
194
218
|
|
|
195
219
|
const emit = defineEmits<{
|
|
196
220
|
(e: "refresh"): void;
|
|
197
221
|
(e: "error", message: string): void;
|
|
198
|
-
/**
|
|
222
|
+
/** Fired when a non-subscriber clicks the upgrade CTA next to the disabled checkbox, or when isEnabled is false at save time. */
|
|
199
223
|
(e: "paywall", feature: string): void;
|
|
224
|
+
/** Fired when the "show publicly" checkbox changes (only reachable while isSubscriber). */
|
|
225
|
+
(e: "toggle-visibility", value: boolean): void;
|
|
200
226
|
}>();
|
|
201
227
|
|
|
228
|
+
function onToggleVisibility(value: boolean) {
|
|
229
|
+
if (!props.isSubscriber) return;
|
|
230
|
+
emit('toggle-visibility', value);
|
|
231
|
+
}
|
|
232
|
+
|
|
202
233
|
const showForm = ref(false);
|
|
203
234
|
const editing = ref<number | null>(null);
|
|
204
235
|
const saving = ref(false);
|
|
@@ -216,6 +247,53 @@ const defaultForm = {
|
|
|
216
247
|
|
|
217
248
|
const form = ref({ ...defaultForm, quantities: {} });
|
|
218
249
|
|
|
250
|
+
// --- Draft persistence (new-item only) --------------------------------------
|
|
251
|
+
// Guards against losing a half-filled form on accidental navigation/refresh.
|
|
252
|
+
// Scoped per profile so switching accounts in the same browser can't leak a
|
|
253
|
+
// draft between them. Only the "new item" flow is persisted — editing an
|
|
254
|
+
// existing bundle already has server data as the source of truth.
|
|
255
|
+
const DRAFT_KEY = computed(() => `mgn_bundle_draft_${props.influencerProInfoId}`);
|
|
256
|
+
|
|
257
|
+
function loadDraft() {
|
|
258
|
+
if (typeof window === 'undefined') return null;
|
|
259
|
+
try {
|
|
260
|
+
const raw = window.localStorage.getItem(DRAFT_KEY.value);
|
|
261
|
+
return raw ? JSON.parse(raw) : null;
|
|
262
|
+
} catch {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function saveDraft() {
|
|
268
|
+
if (typeof window === 'undefined' || editing.value) return;
|
|
269
|
+
try {
|
|
270
|
+
window.localStorage.setItem(DRAFT_KEY.value, JSON.stringify(form.value));
|
|
271
|
+
} catch {
|
|
272
|
+
// storage unavailable (private browsing, quota) — draft just won't persist
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function clearDraft() {
|
|
277
|
+
if (typeof window === 'undefined') return;
|
|
278
|
+
try {
|
|
279
|
+
window.localStorage.removeItem(DRAFT_KEY.value);
|
|
280
|
+
} catch {
|
|
281
|
+
// ignore
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
watch(form, () => {
|
|
286
|
+
if (!editing.value) saveDraft();
|
|
287
|
+
}, { deep: true });
|
|
288
|
+
|
|
289
|
+
onMounted(() => {
|
|
290
|
+
const draft = loadDraft();
|
|
291
|
+
if (draft) {
|
|
292
|
+
form.value = { ...defaultForm, quantities: {}, ...draft };
|
|
293
|
+
showForm.value = true;
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
219
297
|
function qtyFor(id: number): number {
|
|
220
298
|
return form.value.quantities[id] || 1;
|
|
221
299
|
}
|
|
@@ -254,6 +332,7 @@ function cancelForm() {
|
|
|
254
332
|
showForm.value = false;
|
|
255
333
|
editing.value = null;
|
|
256
334
|
form.value = { ...defaultForm, rate_card_ids: [], quantities: {} };
|
|
335
|
+
clearDraft();
|
|
257
336
|
}
|
|
258
337
|
|
|
259
338
|
function editBundle(bundle: Bundle) {
|
|
@@ -353,7 +432,58 @@ async function removeBundle(id: number) {
|
|
|
353
432
|
margin: -8px 0 0;
|
|
354
433
|
}
|
|
355
434
|
|
|
435
|
+
.visibility-checkbox {
|
|
436
|
+
display: flex;
|
|
437
|
+
align-items: center;
|
|
438
|
+
gap: 8px;
|
|
439
|
+
font-size: 13px;
|
|
440
|
+
color: #E4E6EB;
|
|
441
|
+
cursor: pointer;
|
|
442
|
+
|
|
443
|
+
input[type="checkbox"] {
|
|
444
|
+
width: 16px;
|
|
445
|
+
height: 16px;
|
|
446
|
+
cursor: pointer;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
&.disabled {
|
|
450
|
+
cursor: not-allowed;
|
|
451
|
+
color: #7A7D89;
|
|
452
|
+
|
|
453
|
+
input[type="checkbox"] { cursor: not-allowed; }
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.upgrade-link {
|
|
457
|
+
display: inline-flex;
|
|
458
|
+
align-items: center;
|
|
459
|
+
gap: 4px;
|
|
460
|
+
margin-left: 4px;
|
|
461
|
+
padding: 3px 8px;
|
|
462
|
+
background: none;
|
|
463
|
+
border: 1px solid #FDB215;
|
|
464
|
+
color: #FDB215;
|
|
465
|
+
font-size: 12px;
|
|
466
|
+
cursor: pointer;
|
|
467
|
+
|
|
468
|
+
&:hover { background: rgba(253, 178, 21, 0.1); }
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
356
472
|
.bundle-form {
|
|
473
|
+
.visibility-note {
|
|
474
|
+
display: flex;
|
|
475
|
+
align-items: flex-start;
|
|
476
|
+
gap: 8px;
|
|
477
|
+
padding: 10px 12px;
|
|
478
|
+
background: rgba(255, 255, 255, 0.04);
|
|
479
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
480
|
+
border-radius: 4px;
|
|
481
|
+
font-size: 13px;
|
|
482
|
+
color: #B7BAC4;
|
|
483
|
+
|
|
484
|
+
svg { flex-shrink: 0; margin-top: 2px; }
|
|
485
|
+
}
|
|
486
|
+
|
|
357
487
|
background: #1E1E22;
|
|
358
488
|
padding: 20px;
|
|
359
489
|
display: flex;
|
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
10
|
<div v-if="showForm" class="rate-card-form">
|
|
11
|
+
<div class="visibility-note">
|
|
12
|
+
<MGIcon icon="info" />
|
|
13
|
+
<span>{{ $t("media_kit.editor.visibility_note") }}</span>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
11
16
|
<div class="form-row">
|
|
12
17
|
<div class="form-group">
|
|
13
18
|
<label>{{ $t("media_kit.editor.content_format") }}</label>
|
|
@@ -79,6 +84,20 @@
|
|
|
79
84
|
<textarea v-model="form.description" rows="3"
|
|
80
85
|
:placeholder="$t('media_kit.editor.description_placeholder')"></textarea>
|
|
81
86
|
</div>
|
|
87
|
+
<label class="visibility-checkbox" :class="{ disabled: !isSubscriber }">
|
|
88
|
+
<input
|
|
89
|
+
type="checkbox"
|
|
90
|
+
:checked="showPublicly"
|
|
91
|
+
:disabled="!isSubscriber"
|
|
92
|
+
@change="onToggleVisibility(($event.target as HTMLInputElement).checked)"
|
|
93
|
+
/>
|
|
94
|
+
<span>{{ $t("media_kit.editor.show_publicly") }}</span>
|
|
95
|
+
<button v-if="!isSubscriber" type="button" class="upgrade-link" @click.prevent="emit('paywall', 'rates')">
|
|
96
|
+
<MGIcon icon="lock" />
|
|
97
|
+
{{ $t("media_kit.editor.upgrade_to_show") }}
|
|
98
|
+
</button>
|
|
99
|
+
</label>
|
|
100
|
+
|
|
82
101
|
<div class="form-actions">
|
|
83
102
|
<button class="btn-cancel" @click="cancelForm">{{ $t("media_kit.editor.cancel") }}</button>
|
|
84
103
|
<button class="btn-save" @click="saveRateCard" :disabled="saving">
|
|
@@ -143,19 +162,34 @@ const props = withDefaults(
|
|
|
143
162
|
commissionPercent?: number;
|
|
144
163
|
/** Current plan name, used in the fee label. */
|
|
145
164
|
planName?: string;
|
|
165
|
+
/** Whether the account has an active paid subscription — gates public visibility only, not creation. */
|
|
166
|
+
isSubscriber?: boolean;
|
|
167
|
+
/** Current value of the account-wide "show rate cards publicly" setting. */
|
|
168
|
+
showPublicly?: boolean;
|
|
146
169
|
}>(),
|
|
147
170
|
{
|
|
148
171
|
isEnabled: true,
|
|
149
172
|
commissionPercent: 20,
|
|
150
173
|
planName: "Free",
|
|
174
|
+
isSubscriber: false,
|
|
175
|
+
showPublicly: true,
|
|
151
176
|
}
|
|
152
177
|
);
|
|
153
178
|
|
|
154
179
|
const emit = defineEmits<{
|
|
155
180
|
(e: "refresh"): void;
|
|
156
181
|
(e: "error", message: string): void;
|
|
182
|
+
/** Fired when the "show publicly" checkbox changes (only reachable while isSubscriber). */
|
|
183
|
+
(e: "toggle-visibility", value: boolean): void;
|
|
184
|
+
/** Fired when a non-subscriber clicks the upgrade CTA next to the disabled checkbox. */
|
|
185
|
+
(e: "paywall", feature: string): void;
|
|
157
186
|
}>();
|
|
158
187
|
|
|
188
|
+
function onToggleVisibility(value: boolean) {
|
|
189
|
+
if (!props.isSubscriber) return;
|
|
190
|
+
emit('toggle-visibility', value);
|
|
191
|
+
}
|
|
192
|
+
|
|
159
193
|
const showForm = ref(false);
|
|
160
194
|
const editing = ref<number | null>(null);
|
|
161
195
|
const saving = ref(false);
|
|
@@ -173,10 +207,58 @@ const defaultForm = {
|
|
|
173
207
|
|
|
174
208
|
const form = ref({ ...defaultForm });
|
|
175
209
|
|
|
210
|
+
// --- Draft persistence (new-item only) --------------------------------------
|
|
211
|
+
// Guards against losing a half-filled form on accidental navigation/refresh.
|
|
212
|
+
// Scoped per profile so switching accounts in the same browser can't leak a
|
|
213
|
+
// draft between them. Only the "new item" flow is persisted — editing an
|
|
214
|
+
// existing card already has server data as the source of truth.
|
|
215
|
+
const DRAFT_KEY = computed(() => `mgn_rate_card_draft_${props.influencerProInfoId}`);
|
|
216
|
+
|
|
217
|
+
function loadDraft() {
|
|
218
|
+
if (typeof window === 'undefined') return null;
|
|
219
|
+
try {
|
|
220
|
+
const raw = window.localStorage.getItem(DRAFT_KEY.value);
|
|
221
|
+
return raw ? JSON.parse(raw) : null;
|
|
222
|
+
} catch {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function saveDraft() {
|
|
228
|
+
if (typeof window === 'undefined' || editing.value) return;
|
|
229
|
+
try {
|
|
230
|
+
window.localStorage.setItem(DRAFT_KEY.value, JSON.stringify(form.value));
|
|
231
|
+
} catch {
|
|
232
|
+
// storage unavailable (private browsing, quota) — draft just won't persist
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function clearDraft() {
|
|
237
|
+
if (typeof window === 'undefined') return;
|
|
238
|
+
try {
|
|
239
|
+
window.localStorage.removeItem(DRAFT_KEY.value);
|
|
240
|
+
} catch {
|
|
241
|
+
// ignore
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
watch(form, () => {
|
|
246
|
+
if (!editing.value) saveDraft();
|
|
247
|
+
}, { deep: true });
|
|
248
|
+
|
|
249
|
+
onMounted(() => {
|
|
250
|
+
const draft = loadDraft();
|
|
251
|
+
if (draft) {
|
|
252
|
+
form.value = { ...defaultForm, ...draft };
|
|
253
|
+
showForm.value = true;
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
176
257
|
const cancelForm = () => {
|
|
177
258
|
showForm.value = false;
|
|
178
259
|
editing.value = null;
|
|
179
260
|
form.value = { ...defaultForm };
|
|
261
|
+
clearDraft();
|
|
180
262
|
};
|
|
181
263
|
|
|
182
264
|
const editCard = (card: RateCard) => {
|
|
@@ -284,6 +366,57 @@ const netDisplay = computed(() => {
|
|
|
284
366
|
flex-direction: column;
|
|
285
367
|
gap: 16px;
|
|
286
368
|
|
|
369
|
+
.visibility-note {
|
|
370
|
+
display: flex;
|
|
371
|
+
align-items: flex-start;
|
|
372
|
+
gap: 8px;
|
|
373
|
+
padding: 10px 12px;
|
|
374
|
+
background: rgba(255, 255, 255, 0.04);
|
|
375
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
376
|
+
border-radius: 4px;
|
|
377
|
+
font-size: 13px;
|
|
378
|
+
color: #B7BAC4;
|
|
379
|
+
|
|
380
|
+
svg { flex-shrink: 0; margin-top: 2px; }
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.visibility-checkbox {
|
|
384
|
+
display: flex;
|
|
385
|
+
align-items: center;
|
|
386
|
+
gap: 8px;
|
|
387
|
+
font-size: 13px;
|
|
388
|
+
color: #E4E6EB;
|
|
389
|
+
cursor: pointer;
|
|
390
|
+
|
|
391
|
+
input[type="checkbox"] {
|
|
392
|
+
width: 16px;
|
|
393
|
+
height: 16px;
|
|
394
|
+
cursor: pointer;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
&.disabled {
|
|
398
|
+
cursor: not-allowed;
|
|
399
|
+
color: #7A7D89;
|
|
400
|
+
|
|
401
|
+
input[type="checkbox"] { cursor: not-allowed; }
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.upgrade-link {
|
|
405
|
+
display: inline-flex;
|
|
406
|
+
align-items: center;
|
|
407
|
+
gap: 4px;
|
|
408
|
+
margin-left: 4px;
|
|
409
|
+
padding: 3px 8px;
|
|
410
|
+
background: none;
|
|
411
|
+
border: 1px solid #FDB215;
|
|
412
|
+
color: #FDB215;
|
|
413
|
+
font-size: 12px;
|
|
414
|
+
cursor: pointer;
|
|
415
|
+
|
|
416
|
+
&:hover { background: rgba(253, 178, 21, 0.1); }
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
287
420
|
.mgn-fee-note {
|
|
288
421
|
display: flex;
|
|
289
422
|
flex-direction: column;
|
|
@@ -120,7 +120,7 @@ const detailRoute = computed(() =>
|
|
|
120
120
|
</div>
|
|
121
121
|
|
|
122
122
|
<!-- Tier-locked badge -->
|
|
123
|
-
<div v-else-if="card.is_tier_locked && card.access_tier !== 'free'" class="kc-card__exclusive-badge">
|
|
123
|
+
<div v-else-if="card.is_tier_locked && card.access_tier !== 'free' && !userCanAccess" class="kc-card__exclusive-badge">
|
|
124
124
|
<i class="fa-solid fa-crown" /> {{ cardTierLabel(card.access_tier) }}
|
|
125
125
|
</div>
|
|
126
126
|
|
package/locales/de.json
CHANGED
package/locales/en.json
CHANGED
package/locales/pt-BR.json
CHANGED
|
@@ -45,6 +45,8 @@
|
|
|
45
45
|
"upcoming": "Coming soon",
|
|
46
46
|
"back": "Back",
|
|
47
47
|
"search_placeholder": "Search campaigns...",
|
|
48
|
+
"sort_recent": "Mais recentes",
|
|
49
|
+
"sort_popular": "Mais populares",
|
|
48
50
|
"seo": {
|
|
49
51
|
"title_generic": "Key Campaigns"
|
|
50
52
|
},
|
|
@@ -133,7 +135,7 @@
|
|
|
133
135
|
"rp_label": "Redeemed by",
|
|
134
136
|
"who_redeem": "Who redeemed",
|
|
135
137
|
"created_by": "Published by",
|
|
136
|
-
"no_data": "Esta campanha ainda
|
|
138
|
+
"no_data": "Esta campanha ainda não foi associada a um jogo no sistema",
|
|
137
139
|
"title": "Campanha",
|
|
138
140
|
"about": "Sobre o jogo",
|
|
139
141
|
"screenshot_alt": "Captura de tela do jogo",
|
package/locales/ro.json
CHANGED
|
@@ -45,6 +45,8 @@
|
|
|
45
45
|
"upcoming": "Coming soon",
|
|
46
46
|
"back": "Back",
|
|
47
47
|
"search_placeholder": "Search campaigns...",
|
|
48
|
+
"sort_recent": "Cele mai recente",
|
|
49
|
+
"sort_popular": "Cele mai populare",
|
|
48
50
|
"seo": {
|
|
49
51
|
"title_generic": "Key Campaigns"
|
|
50
52
|
},
|
|
@@ -133,7 +135,7 @@
|
|
|
133
135
|
"rp_label": "Redeemed by",
|
|
134
136
|
"who_redeem": "Who redeemed",
|
|
135
137
|
"created_by": "Published by",
|
|
136
|
-
"no_data": "
|
|
138
|
+
"no_data": "Această campanie nu este încă asociată cu un joc în sistem",
|
|
137
139
|
"title": "Campanie",
|
|
138
140
|
"about": "Despre joc",
|
|
139
141
|
"screenshot_alt": "Captură de ecran a jocului",
|
package/package.json
CHANGED
|
@@ -75,14 +75,24 @@
|
|
|
75
75
|
</template>
|
|
76
76
|
</div>
|
|
77
77
|
<div class="bar">
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
<
|
|
85
|
-
</
|
|
78
|
+
<select
|
|
79
|
+
class="platform-select sort-mode-select"
|
|
80
|
+
:value="sortMode"
|
|
81
|
+
@change="setSortMode(($event.target as HTMLSelectElement).value as 'recent' | 'popular')"
|
|
82
|
+
>
|
|
83
|
+
<option value="recent">{{ $t("keys.campaigns.sort_recent") }}</option>
|
|
84
|
+
<option value="popular">{{ $t("keys.campaigns.sort_popular") }}</option>
|
|
85
|
+
</select>
|
|
86
|
+
<div class="search-box">
|
|
87
|
+
<input
|
|
88
|
+
type="text"
|
|
89
|
+
v-model="searchTerm"
|
|
90
|
+
:placeholder="$t('keys.campaigns.search_placeholder')"
|
|
91
|
+
/>
|
|
92
|
+
<button type="button">
|
|
93
|
+
<MGIcon icon="search" />
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
86
96
|
</div>
|
|
87
97
|
</div>
|
|
88
98
|
|
|
@@ -134,7 +144,7 @@
|
|
|
134
144
|
</div>
|
|
135
145
|
|
|
136
146
|
<div
|
|
137
|
-
v-else-if="card.is_tier_locked && card.access_tier !== 'free'"
|
|
147
|
+
v-else-if="card.is_tier_locked && card.access_tier !== 'free' && !card.user_can_access"
|
|
138
148
|
class="exclusive-badge"
|
|
139
149
|
>
|
|
140
150
|
<i class="fa-solid fa-crown" /> {{ cardTierLabel(card.access_tier) }}
|
|
@@ -405,6 +415,7 @@ const platformList = ref<Array<{ id: number; name: string; slug: string }>>([])
|
|
|
405
415
|
const activeFilter = ref<number | null>(null)
|
|
406
416
|
const activePlatformFilter = ref<number | null>(null)
|
|
407
417
|
const redeemedStatusFilter = ref<number | null>(null)
|
|
418
|
+
const sortMode = ref<"recent" | "popular">("recent")
|
|
408
419
|
|
|
409
420
|
// ------------------------------------------------------------------
|
|
410
421
|
// Data
|
|
@@ -513,7 +524,10 @@ async function loadFeaturedCampaigns() {
|
|
|
513
524
|
try {
|
|
514
525
|
const response = await campaignsStore.fetchFeaturedCampaigns(mgPlatform)
|
|
515
526
|
sliderImages.value = (response.data || [])
|
|
516
|
-
|
|
527
|
+
// Featured/highlight alone isn't enough — a campaign that's ended or
|
|
528
|
+
// sold out but still flagged featured must not show in the hero
|
|
529
|
+
// carousel. is_open already accounts for status/dates/available_count.
|
|
530
|
+
.filter((item: any) => (truthyFlag(item.featured) || truthyFlag(item.highlight)) && truthyFlag(item.is_open))
|
|
517
531
|
.map((item: any) => ({
|
|
518
532
|
id: item.id,
|
|
519
533
|
cover: item.game?.url_banner_src || item.cover_src,
|
|
@@ -542,6 +556,7 @@ async function loadCampaigns(page = 1) {
|
|
|
542
556
|
per_page: perPage.value,
|
|
543
557
|
sort: "id",
|
|
544
558
|
order: "desc",
|
|
559
|
+
sort_mode: sortMode.value,
|
|
545
560
|
}
|
|
546
561
|
|
|
547
562
|
if (activeFilter.value !== null) params["filter[type_id]"] = activeFilter.value
|
|
@@ -682,6 +697,14 @@ function setPlatformFilter(platformId: number | null) {
|
|
|
682
697
|
loadCampaigns(1)
|
|
683
698
|
}
|
|
684
699
|
|
|
700
|
+
function setSortMode(mode: "recent" | "popular") {
|
|
701
|
+
if (sortMode.value === mode) return
|
|
702
|
+
sortMode.value = mode
|
|
703
|
+
currentPage.value = 1
|
|
704
|
+
hasMoreItems.value = true
|
|
705
|
+
loadCampaigns(1)
|
|
706
|
+
}
|
|
707
|
+
|
|
685
708
|
const loadMoreItems = async () => {
|
|
686
709
|
if (!hasMoreItems.value || loading.value || loadingMore.value) return
|
|
687
710
|
await loadCampaigns(currentPage.value + 1)
|
|
@@ -837,7 +860,7 @@ onUnmounted(() => {
|
|
|
837
860
|
|
|
838
861
|
.keys-badge {
|
|
839
862
|
position: absolute;
|
|
840
|
-
|
|
863
|
+
top: 8px;
|
|
841
864
|
right: 8px;
|
|
842
865
|
background: var(--key-accent-dark, var(--key-accent-bg, rgba(107, 21, 172, 0.9)));
|
|
843
866
|
color: #fff;
|
|
@@ -1092,6 +1115,18 @@ onUnmounted(() => {
|
|
|
1092
1115
|
}
|
|
1093
1116
|
|
|
1094
1117
|
.bar {
|
|
1118
|
+
position: relative;
|
|
1119
|
+
display: flex;
|
|
1120
|
+
align-items: center;
|
|
1121
|
+
gap: 8px;
|
|
1122
|
+
|
|
1123
|
+
.sort-mode-select {
|
|
1124
|
+
flex-shrink: 0;
|
|
1125
|
+
height: 44px;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
.search-box {
|
|
1095
1130
|
position: relative;
|
|
1096
1131
|
display: flex;
|
|
1097
1132
|
width: 339px;
|
|
@@ -1270,6 +1305,7 @@ onUnmounted(() => {
|
|
|
1270
1305
|
flex-direction: column;
|
|
1271
1306
|
gap: 12px;
|
|
1272
1307
|
.bar { width: 100%; }
|
|
1308
|
+
.search-box { flex: 1; width: auto; }
|
|
1273
1309
|
}
|
|
1274
1310
|
.cards {
|
|
1275
1311
|
flex-direction: column;
|
|
@@ -205,8 +205,33 @@ useSeoMeta({
|
|
|
205
205
|
<template>
|
|
206
206
|
<div class="kit-press" :data-tpl="tpl" :data-layout="layout">
|
|
207
207
|
<div v-if="loading" class="kit-state">{{ $t('kit.loading') }}</div>
|
|
208
|
-
<div v-else-if="error || !kit" class="kit-state">
|
|
208
|
+
<div v-else-if="error || !kit" class="kit-state kit-state--not-found">
|
|
209
209
|
<h2>{{ $t('kit.press.not_found') }}</h2>
|
|
210
|
+
<p class="kit-state__desc">{{ $t('kit.press.not_found_desc') }}</p>
|
|
211
|
+
<a :href="`${agencyBase}/presskit`" target="_blank" rel="noopener" class="kit-footer__cta">
|
|
212
|
+
{{ $t('kit.press.create_yours_cta') }}
|
|
213
|
+
</a>
|
|
214
|
+
|
|
215
|
+
<section v-if="moreKits.length" class="kit-more kit-more--not-found">
|
|
216
|
+
<h2 class="kit-more__title">{{ $t('kit.press.more_games') }}</h2>
|
|
217
|
+
<div class="kit-more__track">
|
|
218
|
+
<a
|
|
219
|
+
v-for="k in moreKits"
|
|
220
|
+
:key="k.slug"
|
|
221
|
+
:href="`/presskit/game/${k.slug}`"
|
|
222
|
+
class="kit-more__card"
|
|
223
|
+
>
|
|
224
|
+
<div
|
|
225
|
+
class="kit-more__cover"
|
|
226
|
+
:style="k.cover_image_url ? { backgroundImage: `url(${k.cover_image_url})` } : {}"
|
|
227
|
+
>
|
|
228
|
+
<div v-if="!k.cover_image_url" class="kit-more__cover-fallback">{{ k.name?.charAt(0) }}</div>
|
|
229
|
+
</div>
|
|
230
|
+
<div class="kit-more__name">{{ k.name }}</div>
|
|
231
|
+
<div v-if="k.developer" class="kit-more__dev">{{ k.developer }}</div>
|
|
232
|
+
</a>
|
|
233
|
+
</div>
|
|
234
|
+
</section>
|
|
210
235
|
</div>
|
|
211
236
|
|
|
212
237
|
<template v-else>
|