@mundogamernetwork/shared-ui 1.8.2 → 1.8.4
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/keys/KeyCampaignDashboardCard.vue +14 -0
- package/components/keys/KeyCampaignsCarousel.vue +13 -95
- package/components/ui/MgHeaderUIUser.vue +1 -1
- package/components/ui/MgWalletPlanBadge.vue +1 -1
- package/package.json +1 -1
- package/pages/key-campaign/[slug].vue +8 -2
- package/pages/key-campaigns/[slug].vue +16 -4
- package/pages/key-campaigns/index.vue +18 -6
- package/pages/media-kit/[slug].vue +643 -610
- package/pages/presskit/game/[slug].vue +8 -2
- package/public/imgs/default-avatar.png +0 -0
- package/services/campaignService.ts +11 -8
- package/services/mediaKitProposalService.ts +35 -0
- package/stores/shortlist.ts +113 -0
|
@@ -89,6 +89,12 @@ const allowDownload = computed(() => kit.value?.allow_asset_download !== false);
|
|
|
89
89
|
const hasDownloadableAssets = computed(() => assets.value.length > 0);
|
|
90
90
|
const canDownload = computed(() => allowDownload.value && hasDownloadableAssets.value);
|
|
91
91
|
const zipDownloadUrl = computed(() => `${apiBase}/public/press-kits/${slug}/download${previewToken ? `?preview_token=${previewToken}` : ''}`);
|
|
92
|
+
const interviewMailto = computed(() => {
|
|
93
|
+
const email = kit.value?.press_contact_email;
|
|
94
|
+
if (!email) return '';
|
|
95
|
+
const subject = `${t('kit.press.request_interview')} — ${kit.value?.name || ''}`;
|
|
96
|
+
return `mailto:${email}?subject=${encodeURIComponent(subject)}`;
|
|
97
|
+
});
|
|
92
98
|
|
|
93
99
|
const keyPoolsVisible = ref(false);
|
|
94
100
|
function onPoolsReady(count: number) {
|
|
@@ -343,7 +349,7 @@ useHead(() => ({
|
|
|
343
349
|
offered when the kit is actually linked to a game with open pools, same
|
|
344
350
|
gate as the section itself, so this never links to an empty/broken state. -->
|
|
345
351
|
<a v-if="kit.game_id" class="kit-btn kit-btn-ghost" href="#review-key" @click.prevent="scrollToSection('review-key')">{{ $t('kit.press.request_review_key') }}</a>
|
|
346
|
-
<a class="kit-btn kit-btn-ghost" :href="
|
|
352
|
+
<a v-if="interviewMailto" class="kit-btn kit-btn-ghost" :href="interviewMailto" @click="track('contact')">{{ $t('kit.press.request_interview') }}</a>
|
|
347
353
|
</div>
|
|
348
354
|
</div>
|
|
349
355
|
</div>
|
|
@@ -613,7 +619,7 @@ useHead(() => ({
|
|
|
613
619
|
</section>
|
|
614
620
|
|
|
615
621
|
<footer class="kit-footer">
|
|
616
|
-
<p v-if="!kit.
|
|
622
|
+
<p v-if="!kit.no_branding" class="kit-footer__text">{{ $t('kit.press.made_with') }} <strong>Mundo Gamer Agency</strong></p>
|
|
617
623
|
<a :href="kit.create_kit_url || `${agencyBase}/presskit`" target="_blank" rel="noopener" class="kit-footer__cta">{{ $t('kit.press.create_yours') }}</a>
|
|
618
624
|
</footer>
|
|
619
625
|
</template>
|
|
Binary file
|
|
@@ -422,16 +422,19 @@ export const revealKey = (requestId: number) => {
|
|
|
422
422
|
}
|
|
423
423
|
|
|
424
424
|
/**
|
|
425
|
-
* Explicit opt-in to key campaign alerts —
|
|
426
|
-
* KeyBrowser.vue
|
|
425
|
+
* Explicit opt-in to key campaign alerts — canonical, cross-product
|
|
426
|
+
* preferences endpoint (same one KeyBrowser.vue, network-accounts, and
|
|
427
|
+
* mobile use). No mg_network_system_id, so the opt-in is global.
|
|
427
428
|
*/
|
|
428
429
|
export const enableKeyCampaignNotifications = () => {
|
|
429
|
-
return authClient.
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
430
|
+
return authClient.post("/account-management/notification-preferences", {
|
|
431
|
+
preferences: [{
|
|
432
|
+
category: "key_campaigns",
|
|
433
|
+
enabled: true,
|
|
434
|
+
channel_email: true,
|
|
435
|
+
channel_push: true,
|
|
436
|
+
channel_in_app: true,
|
|
437
|
+
}],
|
|
435
438
|
})
|
|
436
439
|
}
|
|
437
440
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { getHttpService } from './httpService';
|
|
2
|
+
|
|
3
|
+
export interface ProposalDeliverableInput {
|
|
4
|
+
title: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface SendMediaKitProposalPayload {
|
|
9
|
+
receiverInfluencerProId: number;
|
|
10
|
+
senderUserId: number;
|
|
11
|
+
title: string;
|
|
12
|
+
proposedAmount: number;
|
|
13
|
+
currency?: string;
|
|
14
|
+
deliverables: ProposalDeliverableInput[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Submits a proposal built from the public Media Kit's rate-card picker.
|
|
19
|
+
* Reuses the existing agency Proposal system (sender_user_id required — caller
|
|
20
|
+
* must already be authenticated) so no new payment/checkout path is introduced.
|
|
21
|
+
*/
|
|
22
|
+
export async function sendMediaKitProposal(payload: SendMediaKitProposalPayload) {
|
|
23
|
+
return getHttpService().post('/proposals', {
|
|
24
|
+
sender_user_id: payload.senderUserId,
|
|
25
|
+
receiver_influencer_pro_id: payload.receiverInfluencerProId,
|
|
26
|
+
title: payload.title,
|
|
27
|
+
proposed_amount: payload.proposedAmount,
|
|
28
|
+
currency: payload.currency || 'BRL',
|
|
29
|
+
proposal_deliverables: payload.deliverables.map((d, i) => ({
|
|
30
|
+
title: d.title,
|
|
31
|
+
description: d.description,
|
|
32
|
+
sort_order: i,
|
|
33
|
+
})),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { defineStore } from "pinia";
|
|
2
|
+
|
|
3
|
+
export interface RateCard {
|
|
4
|
+
id: number | string;
|
|
5
|
+
name?: string;
|
|
6
|
+
platform?: string;
|
|
7
|
+
type?: string;
|
|
8
|
+
price?: number | string | null;
|
|
9
|
+
currency?: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ShortlistItem {
|
|
14
|
+
id: number | string;
|
|
15
|
+
name: string;
|
|
16
|
+
slug?: string;
|
|
17
|
+
image?: string | null;
|
|
18
|
+
rate_cards?: RateCard[];
|
|
19
|
+
selected_actions?: (number | string)[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const STORAGE_KEY = "agency_casting_shortlist";
|
|
23
|
+
|
|
24
|
+
function loadInitial(): ShortlistItem[] {
|
|
25
|
+
if (typeof window === "undefined") return [];
|
|
26
|
+
try {
|
|
27
|
+
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
28
|
+
return raw ? (JSON.parse(raw) as ShortlistItem[]) : [];
|
|
29
|
+
} catch {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const useShortlistStore = defineStore({
|
|
35
|
+
id: "shortlist-store",
|
|
36
|
+
state: () => ({
|
|
37
|
+
items: [] as ShortlistItem[],
|
|
38
|
+
hydrated: false,
|
|
39
|
+
}),
|
|
40
|
+
getters: {
|
|
41
|
+
count: (state) => state.items.length,
|
|
42
|
+
ids: (state) => state.items.map((i) => i.id),
|
|
43
|
+
estimatedBudget: (state): number => {
|
|
44
|
+
return state.items.reduce((total, item) => {
|
|
45
|
+
const selected = item.selected_actions || [];
|
|
46
|
+
const cards = item.rate_cards || [];
|
|
47
|
+
if (!selected.length) return total;
|
|
48
|
+
const sum = cards
|
|
49
|
+
.filter((c) => selected.some((s) => String(s) === String(c.id)))
|
|
50
|
+
.reduce((s, c) => s + (parseFloat(String(c.price || 0)) || 0), 0);
|
|
51
|
+
return total + sum;
|
|
52
|
+
}, 0);
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
actions: {
|
|
56
|
+
hydrate() {
|
|
57
|
+
if (this.hydrated) return;
|
|
58
|
+
this.items = loadInitial();
|
|
59
|
+
this.hydrated = true;
|
|
60
|
+
},
|
|
61
|
+
persist() {
|
|
62
|
+
if (typeof window === "undefined") return;
|
|
63
|
+
try {
|
|
64
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(this.items));
|
|
65
|
+
} catch {
|
|
66
|
+
/* ignore quota errors */
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
has(id: number | string): boolean {
|
|
70
|
+
return this.items.some((i) => String(i.id) === String(id));
|
|
71
|
+
},
|
|
72
|
+
add(item: ShortlistItem) {
|
|
73
|
+
if (this.has(item.id)) return;
|
|
74
|
+
this.items.push({ ...item, selected_actions: item.selected_actions || [] });
|
|
75
|
+
this.persist();
|
|
76
|
+
},
|
|
77
|
+
remove(id: number | string) {
|
|
78
|
+
this.items = this.items.filter((i) => String(i.id) !== String(id));
|
|
79
|
+
this.persist();
|
|
80
|
+
},
|
|
81
|
+
toggle(item: ShortlistItem) {
|
|
82
|
+
this.has(item.id) ? this.remove(item.id) : this.add(item);
|
|
83
|
+
},
|
|
84
|
+
updateRateCards(influencerId: number | string, rateCards: RateCard[]) {
|
|
85
|
+
const item = this.items.find((i) => String(i.id) === String(influencerId));
|
|
86
|
+
if (item) {
|
|
87
|
+
item.rate_cards = rateCards;
|
|
88
|
+
this.persist();
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
toggleAction(influencerId: number | string, rateCardId: number | string) {
|
|
92
|
+
const item = this.items.find((i) => String(i.id) === String(influencerId));
|
|
93
|
+
if (!item) return;
|
|
94
|
+
const selected = item.selected_actions || [];
|
|
95
|
+
const idx = selected.findIndex((s) => String(s) === String(rateCardId));
|
|
96
|
+
if (idx === -1) {
|
|
97
|
+
item.selected_actions = [...selected, rateCardId];
|
|
98
|
+
} else {
|
|
99
|
+
item.selected_actions = selected.filter((s) => String(s) !== String(rateCardId));
|
|
100
|
+
}
|
|
101
|
+
this.persist();
|
|
102
|
+
},
|
|
103
|
+
hasAction(influencerId: number | string, rateCardId: number | string): boolean {
|
|
104
|
+
const item = this.items.find((i) => String(i.id) === String(influencerId));
|
|
105
|
+
if (!item) return false;
|
|
106
|
+
return (item.selected_actions || []).some((s) => String(s) === String(rateCardId));
|
|
107
|
+
},
|
|
108
|
+
clear() {
|
|
109
|
+
this.items = [];
|
|
110
|
+
this.persist();
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
});
|