@mundogamernetwork/shared-ui 1.8.7 → 1.8.8

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.
@@ -0,0 +1,76 @@
1
+ <template>
2
+ <client-only>
3
+ <div ref="mapContainer" class="mk-map-container">
4
+ <l-map v-if="mapReady" :zoom="0.5" :center="[0, 0]" :use-global-leaflet="false">
5
+ <l-tile-layer url="https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png" layer-type="base"
6
+ attribution="OpenStreetMap" />
7
+
8
+ <l-circle-marker v-for="(country, index) in countries" :key="index"
9
+ :lat-lng="[country.latitude, country.longitude]"
10
+ :radius="calculateRadius(country.percent)" color="#FDB215" fill-opacity="0.6">
11
+ <l-tooltip :content="`${country.name}: ${country.percent}%`" />
12
+ </l-circle-marker>
13
+ </l-map>
14
+ </div>
15
+ </client-only>
16
+ </template>
17
+
18
+ <script setup lang="ts">
19
+ // Ported from agency-frontend/components/MapCountries.vue (the old
20
+ // casting-details page's audience map) — same lib/tile source/marker
21
+ // styling, adapted to the media-kit's audience_geo field names
22
+ // (name/percent instead of localized_name/percentage).
23
+ import 'leaflet/dist/leaflet.css';
24
+ import { shallowRef, ref, onMounted } from 'vue';
25
+
26
+ defineProps<{
27
+ countries: {
28
+ latitude: number;
29
+ longitude: number;
30
+ percent: number;
31
+ name: string;
32
+ }[];
33
+ }>();
34
+
35
+ const mapContainer = ref<HTMLElement | null>(null);
36
+ const mapReady = ref(false);
37
+ const LMap = shallowRef();
38
+ const LTileLayer = shallowRef();
39
+ const LCircleMarker = shallowRef();
40
+ const LTooltip = shallowRef();
41
+
42
+ const calculateRadius = (percent: number) => {
43
+ return Math.max(2, percent * 0.3);
44
+ };
45
+
46
+ if (import.meta.client) {
47
+ const { LMap: LMapComponent, LTileLayer: LTileLayerComponent, LCircleMarker: LCircleMarkerComponent, LTooltip: LTooltipComponent } = await import('@vue-leaflet/vue-leaflet');
48
+ LMap.value = LMapComponent;
49
+ LTileLayer.value = LTileLayerComponent;
50
+ LCircleMarker.value = LCircleMarkerComponent;
51
+ LTooltip.value = LTooltipComponent;
52
+ }
53
+
54
+ onMounted(() => {
55
+ mapReady.value = true;
56
+ });
57
+ </script>
58
+
59
+ <style scoped>
60
+ .mk-map-container {
61
+ height: 220px;
62
+ width: 100%;
63
+ position: relative;
64
+ }
65
+
66
+ :deep(.leaflet-container) {
67
+ height: 100% !important;
68
+ width: 100% !important;
69
+ background: #0a0a0f;
70
+ }
71
+
72
+ :deep(.leaflet-bottom) {
73
+ bottom: 0;
74
+ display: none !important;
75
+ }
76
+ </style>
package/nuxt.config.ts CHANGED
@@ -61,6 +61,13 @@ try {
61
61
  }
62
62
 
63
63
  export default defineNuxtConfig({
64
+ // Nuxt/c12 merges extended-layer config via defu (arrays concatenate), so
65
+ // this reaches every consuming app automatically — matches the storesDirs
66
+ // pattern below. @vue-leaflet/vue-leaflet ships untranspiled ESM that
67
+ // breaks Nuxt's build without this, needed by MediaKit/MapCountries.vue.
68
+ build: {
69
+ transpile: ['@vue-leaflet/vue-leaflet'],
70
+ },
64
71
  components: [
65
72
  {
66
73
  path: `${packageRoot}/components`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mundogamernetwork/shared-ui",
3
- "version": "1.8.7",
3
+ "version": "1.8.8",
4
4
  "description": "Mundo Gamer Network - Shared UI Layer (Nuxt 3)",
5
5
  "type": "module",
6
6
  "main": "./nuxt.config.ts",
@@ -30,9 +30,11 @@
30
30
  "peerDependencies": {
31
31
  "@pinia-plugin-persistedstate/nuxt": ">=1.0.0",
32
32
  "@pinia/nuxt": ">=0.5.0",
33
+ "@vue-leaflet/vue-leaflet": ">=0.10.0",
33
34
  "axios": ">=1.0.0",
34
35
  "hls.js": ">=1.5.0",
35
36
  "laravel-echo": ">=1.15.0",
37
+ "leaflet": ">=1.9.0",
36
38
  "nuxt": ">=3.13.0",
37
39
  "pinia": ">=2.1.0",
38
40
  "pusher-js": ">=8.0.0",
@@ -41,9 +43,12 @@
41
43
  "devDependencies": {
42
44
  "@pinia-plugin-persistedstate/nuxt": "^1.2.1",
43
45
  "@pinia/nuxt": "^0.5.5",
46
+ "@types/leaflet": "^1.9.18",
47
+ "@vue-leaflet/vue-leaflet": "^0.10.1",
44
48
  "axios": "^1.18.1",
45
49
  "hls.js": "^1.6.16",
46
50
  "laravel-echo": "^2.3.7",
51
+ "leaflet": "^1.9.4",
47
52
  "nuxt": "^3.15.4",
48
53
  "pinia": "^2.3.1",
49
54
  "pusher-js": "^8.5.0",
@@ -3,6 +3,7 @@ import { fetchPublicMediaKit, trackMediaKitEvent } from '../../services/mediaKit
3
3
  import { sendMediaKitProposal } from '../../services/mediaKitProposalService';
4
4
  import MediaKitWallBlock from '../../components/indie-wall/MediaKitWallBlock.vue';
5
5
  import MgMediaKitStore from '../../components/MediaKit/MgMediaKitStore.vue';
6
+ import MapCountries from '../../components/MediaKit/MapCountries.vue';
6
7
 
7
8
  const route = useRoute();
8
9
  const slug = computed(() => route.params.slug as string);
@@ -78,10 +79,32 @@ const websiteUrl = computed<string | null>(() => streamer.value?.website ?? null
78
79
 
79
80
  // ── D2 ecosystem data ──
80
81
  const selectedGames = computed<string[]>(() => kit.value?.selected_games ?? []);
82
+ const languages = computed<any[]>(() => kit.value?.languages ?? []);
81
83
  const audienceGeo = computed<any[]>(() => kit.value?.audience_geo ?? []);
84
+ // A handful of countries.latitude/longitude rows are historically unset —
85
+ // the list still renders those (name/percent don't need coordinates), the
86
+ // map just skips them.
87
+ const audienceGeoMappable = computed(() => audienceGeo.value.filter((c: any) => c.latitude != null && c.longitude != null));
82
88
  const platformsPlayed = computed<any[]>(() => kit.value?.platforms_played ?? []);
83
89
  const academyMentor = computed(() => kit.value?.academy_mentor ?? null);
84
90
  const communityStats = computed(() => kit.value?.community ?? null);
91
+ // Business signals collected by the /trails-form onboarding wizard, exposed
92
+ // via PublicMediaKitController::getBusinessSignals() (2026-07-23 audit).
93
+ const businessSignals = computed<Record<string, any> | null>(() => kit.value?.business_signals ?? null);
94
+ const hasAvailabilitySection = computed(() =>
95
+ !!businessSignals.value && (
96
+ !!businessSignals.value.contact_type
97
+ || !!businessSignals.value.availability_hour
98
+ || !!businessSignals.value.availability_days
99
+ ),
100
+ );
101
+ const hasSelfReportedMetrics = computed(() =>
102
+ !!businessSignals.value && (
103
+ !!businessSignals.value.avg_views_per_content
104
+ || !!businessSignals.value.avg_monthly_views
105
+ || !!businessSignals.value.audience_profile
106
+ ),
107
+ );
85
108
  const tvChannelLogin = computed<string | null>(() => streamer.value?.tv_channel_login ?? null);
86
109
  const tvSiteUrl = import.meta.env.VITE_TV_SITE_URL || 'https://mundogamer.tv';
87
110
  const tvChannelUrl = computed(() => tvChannelLogin.value
@@ -288,6 +311,10 @@ function onDownload() {
288
311
  <div class="mk-badges">
289
312
  <span v-if="streamer.platform" class="mk-badge plat">{{ streamer.platform }}</span>
290
313
  <span v-if="streamer.tier && streamer.tier !== 'free'" class="mk-badge tier">{{ String(streamer.tier).toUpperCase() }}</span>
314
+ <span v-if="businessSignals?.available_for_campaigns === true" class="mk-badge available">{{ $t('kit.media.available_for_campaigns') }}</span>
315
+ <span v-else-if="businessSignals?.available_for_campaigns === false" class="mk-badge unavailable">{{ $t('kit.media.unavailable_for_campaigns') }}</span>
316
+ <span v-if="businessSignals?.accept_proposals === false" class="mk-badge muted">{{ $t('kit.media.not_accepting_proposals') }}</span>
317
+ <span v-if="businessSignals?.participated_in_campaigns" class="mk-badge muted">{{ $t('kit.media.past_campaigns') }}</span>
291
318
  </div>
292
319
 
293
320
  <div v-if="showShortlistBar" class="mk-internal-bar no-print">
@@ -347,7 +374,10 @@ function onDownload() {
347
374
 
348
375
  <!-- ── TAB: PERFIL ── -->
349
376
  <div class="mk-tabpanel" :class="{ active: activeTab === 0 }" data-print-tab="profile">
350
- <p v-if="streamer.bio" class="mk-bio">{{ streamer.bio }}</p>
377
+ <section v-if="streamer.bio" class="kit-block" data-sec="about">
378
+ <h2>{{ $t('kit.media.about_title', { name: streamer.name }) }}</h2>
379
+ <p class="mk-bio">{{ streamer.bio }}</p>
380
+ </section>
351
381
 
352
382
  <section v-if="academyMentor" class="kit-block" data-sec="academy">
353
383
  <h2>{{ $t('kit.media.academy_mentor_title') }}</h2>
@@ -365,7 +395,10 @@ function onDownload() {
365
395
  <div class="mk-platforms">
366
396
  <a v-for="p in platforms" :key="p.platform" class="mk-platform" :href="p.url || undefined" target="_blank" rel="noopener" @click="track('click')">
367
397
  <div class="pl-head">
368
- <span class="pl-name">{{ PLATFORM_LABEL[p.platform] || p.platform }}</span>
398
+ <span class="pl-name-group">
399
+ <i v-if="PLATFORM_ICON[p.platform]" class="fa-brands pl-icon" :class="PLATFORM_ICON[p.platform]" />
400
+ <span class="pl-name">{{ PLATFORM_LABEL[p.platform] || p.platform }}</span>
401
+ </span>
369
402
  <span v-if="p.verified" class="pl-verified" :title="$t('kit.media.verified')">✓</span>
370
403
  </div>
371
404
  <div class="pl-followers">{{ fmt(p.followers) }}</div>
@@ -377,8 +410,27 @@ function onDownload() {
377
410
  </div>
378
411
  </section>
379
412
 
413
+ <section v-if="hasSelfReportedMetrics" class="kit-block" data-sec="self-reported-metrics">
414
+ <h2>{{ $t('kit.media.self_reported_metrics') }}</h2>
415
+ <div class="mk-metrics">
416
+ <div v-if="businessSignals.avg_views_per_content" class="mk-metric">
417
+ <span class="val">{{ businessSignals.avg_views_per_content }}</span>
418
+ <span class="lab">{{ $t('kit.media.avg_views_per_content') }}</span>
419
+ </div>
420
+ <div v-if="businessSignals.avg_monthly_views" class="mk-metric">
421
+ <span class="val">{{ businessSignals.avg_monthly_views }}</span>
422
+ <span class="lab">{{ $t('kit.media.avg_monthly_views') }}</span>
423
+ </div>
424
+ <div v-if="businessSignals.audience_profile" class="mk-metric">
425
+ <span class="val val-sm">{{ businessSignals.audience_profile }}</span>
426
+ <span class="lab">{{ $t('kit.media.audience_profile') }}</span>
427
+ </div>
428
+ </div>
429
+ </section>
430
+
380
431
  <section v-if="audienceGeo.length" class="kit-block" data-sec="geo">
381
432
  <h2>{{ $t('kit.media.audience_geo') }}</h2>
433
+ <MapCountries v-if="audienceGeoMappable.length" :countries="audienceGeoMappable" class="mk-geo-map no-print" />
382
434
  <div class="mk-geo">
383
435
  <div v-for="c in audienceGeo" :key="c.isocode" class="mk-bar">
384
436
  <div class="top">
@@ -426,6 +478,30 @@ function onDownload() {
426
478
  </div>
427
479
  </section>
428
480
 
481
+ <!-- Languages the creator works in — was on the old casting-details
482
+ page, dropped when it was consolidated into this shared builder. -->
483
+ <section v-if="languages.length" class="kit-block" data-sec="languages">
484
+ <h2>{{ $t('kit.media.languages') }}</h2>
485
+ <div class="mk-tags">
486
+ <span v-for="l in languages" :key="l.id" class="mk-tag">{{ l.name }}</span>
487
+ </div>
488
+ </section>
489
+
490
+ <section v-if="hasAvailabilitySection" class="kit-block" data-sec="availability-preference">
491
+ <h2>{{ $t('kit.media.availability_title') }}</h2>
492
+ <div class="mk-cols2">
493
+ <div v-if="businessSignals.contact_type" class="kit-card mk-pad">
494
+ <h3>{{ $t('kit.media.contact_type') }}</h3>
495
+ <span class="mk-tag">{{ businessSignals.contact_type }}</span>
496
+ </div>
497
+ <div v-if="businessSignals.availability_hour || businessSignals.availability_days" class="kit-card mk-pad">
498
+ <h3>{{ $t('kit.media.best_time') }}</h3>
499
+ <span v-if="businessSignals.availability_hour" class="mk-tag">{{ businessSignals.availability_hour }}</span>
500
+ <span v-if="businessSignals.availability_days" class="mk-tag">{{ businessSignals.availability_days }}</span>
501
+ </div>
502
+ </div>
503
+ </section>
504
+
429
505
  <section v-if="platformsPlayed.length" class="kit-block" data-sec="platforms-played">
430
506
  <h2>{{ $t('kit.media.platforms_played') }}</h2>
431
507
  <div class="mk-platforms-played">
@@ -687,6 +763,9 @@ function onDownload() {
687
763
 
688
764
  &.plat { background: var(--kit-surface-2); border: 1px solid var(--kit-line); }
689
765
  &.tier { background: var(--kit-accent); color: var(--kit-accent-ink); }
766
+ &.available { background: rgba(74, 222, 128, .12); color: #4ade80; border: 1px solid rgba(74, 222, 128, .3); }
767
+ &.unavailable { background: rgba(239, 68, 68, .12); color: #ef4444; border: 1px solid rgba(239, 68, 68, .3); }
768
+ &.muted { background: rgba(139, 146, 160, .1); color: var(--kit-muted); border: 1px solid rgba(139, 146, 160, .2); }
690
769
  }
691
770
 
692
771
  .mk-internal-bar {
@@ -846,6 +925,8 @@ function onDownload() {
846
925
 
847
926
  &:hover { border-color: var(--kit-accent); }
848
927
  .pl-head { display: flex; align-items: center; justify-content: space-between; }
928
+ .pl-name-group { display: flex; align-items: center; gap: 6px; }
929
+ .pl-icon { font-size: 13px; color: var(--kit-accent); }
849
930
  .pl-name { font-size: 12px; text-transform: uppercase; letter-spacing: 1px; color: var(--kit-muted); }
850
931
  .pl-verified { color: #27ae60; font-weight: 700; }
851
932
  .pl-followers { font-family: 'Rajdhani', sans-serif; font-weight: 700; font-size: 24px; }
@@ -853,6 +934,7 @@ function onDownload() {
853
934
  }
854
935
 
855
936
  /* Geo */
937
+ .mk-geo-map { margin-bottom: 16px; border: 1px solid var(--kit-line); }
856
938
  .mk-geo { display: flex; flex-direction: column; gap: 10px; }
857
939
  .mk-flag { width: 18px; height: 13px; object-fit: cover; margin-right: 8px; vertical-align: middle; }
858
940
 
@@ -981,7 +1063,7 @@ function onDownload() {
981
1063
  .mk-metric {
982
1064
  background: var(--kit-surface); border: 1px solid var(--kit-line); padding: 20px 16px; text-align: center; position: relative; overflow: hidden;
983
1065
  &::before { content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 2px; background: var(--kit-accent); }
984
- .val { font-family: 'Rajdhani', sans-serif; font-weight: 700; font-size: 30px; line-height: 1; &.accent { color: var(--kit-accent); } }
1066
+ .val { font-family: 'Rajdhani', sans-serif; font-weight: 700; font-size: 30px; line-height: 1; &.accent { color: var(--kit-accent); } &.val-sm { font-size: 16px; } }
985
1067
  .lab { font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: var(--kit-muted); margin-top: 8px; }
986
1068
  }
987
1069
  .mk-period { color: var(--kit-muted); font-size: 13px; margin: -8px 0 16px; }