@mundogamernetwork/shared-ui 1.0.0

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.
Files changed (87) hide show
  1. package/README.md +283 -0
  2. package/components/PressKit/AssetGallery.vue +349 -0
  3. package/components/PressKit/Awards.vue +100 -0
  4. package/components/PressKit/Credits.vue +78 -0
  5. package/components/PressKit/FactSheet.vue +204 -0
  6. package/components/PressKit/Hero.vue +143 -0
  7. package/components/PressKit/Quotes.vue +80 -0
  8. package/components/PressKit/VideoPlayer.vue +134 -0
  9. package/components/checkout/MgCartItemList.vue +214 -0
  10. package/components/checkout/MgCartSummary.vue +204 -0
  11. package/components/checkout/MgCheckoutSidebar.vue +230 -0
  12. package/components/checkout/MgGuestEmailForm.vue +97 -0
  13. package/components/checkout/MgPaymentMethodSelector.vue +162 -0
  14. package/components/checkout/MgPixQRCode.vue +222 -0
  15. package/components/indie-wall/IndieWallLeaderboard.vue +208 -0
  16. package/components/indie-wall/MuralCanvas.vue +481 -0
  17. package/components/indie-wall/StepBlock.vue +314 -0
  18. package/components/indie-wall/StepCustomize.vue +530 -0
  19. package/components/indie-wall/StepGoal.vue +169 -0
  20. package/components/indie-wall/StepPackage.vue +145 -0
  21. package/components/indie-wall/StepPay.vue +209 -0
  22. package/components/indie-wall/SupportStepper.vue +372 -0
  23. package/components/invoices/MgInvoiceDownload.vue +50 -0
  24. package/components/pricing/MgBillingToggle.vue +74 -0
  25. package/components/pricing/MgPricingCard.vue +245 -0
  26. package/components/ui/Header/MgMessageCard.vue +147 -0
  27. package/components/ui/Header/MgMessageModal.vue +414 -0
  28. package/components/ui/Header/MgNotificationCard.vue +200 -0
  29. package/components/ui/Header/MgNotificationsModal.vue +125 -0
  30. package/components/ui/MgAnnouncementBanner.vue +147 -0
  31. package/components/ui/MgBanners.vue +23 -0
  32. package/components/ui/MgHeaderComponent.vue +283 -0
  33. package/components/ui/MgHeaderUIConfig.vue +225 -0
  34. package/components/ui/MgHeaderUIUser.vue +301 -0
  35. package/components/ui/MgLoginModal.vue +156 -0
  36. package/components/ui/MgPromotionBanner.vue +185 -0
  37. package/composables/useLogout.ts +42 -0
  38. package/composables/useMgCheckout.ts +287 -0
  39. package/composables/useMgUserNotifications.ts +122 -0
  40. package/composables/usePaymentMethods.ts +75 -0
  41. package/composables/useSubscription.ts +163 -0
  42. package/middleware/auth.global.ts +40 -0
  43. package/nuxt.config.ts +31 -0
  44. package/package.json +40 -0
  45. package/pages/[slug]/index.vue +112 -0
  46. package/pages/about.vue +133 -0
  47. package/pages/blog.vue +430 -0
  48. package/pages/careers.vue +329 -0
  49. package/pages/contact.vue +339 -0
  50. package/pages/faq.vue +317 -0
  51. package/pages/health-check.vue +20 -0
  52. package/pages/icons.vue +58 -0
  53. package/pages/magazine/[slug].vue +209 -0
  54. package/pages/magazine/index.vue +267 -0
  55. package/pages/media-kit/[slug].vue +625 -0
  56. package/pages/mural/[slug].vue +1058 -0
  57. package/pages/partners.vue +290 -0
  58. package/pages/press.vue +237 -0
  59. package/pages/presskit/[slug].vue +191 -0
  60. package/pages/roadmap.vue +355 -0
  61. package/pages/status.vue +199 -0
  62. package/pages/team.vue +266 -0
  63. package/pages/wall/[slug].vue +11 -0
  64. package/plugins/auth.client.ts +17 -0
  65. package/plugins/echo.client.ts +132 -0
  66. package/services/authService.ts +95 -0
  67. package/services/chatService.ts +53 -0
  68. package/services/contactService.ts +35 -0
  69. package/services/documentService.ts +16 -0
  70. package/services/httpService.ts +95 -0
  71. package/services/indieWallService.ts +174 -0
  72. package/services/institutionalService.ts +248 -0
  73. package/services/mediaKitService.ts +51 -0
  74. package/services/notificationsService.ts +20 -0
  75. package/services/pressKitService.ts +55 -0
  76. package/stores/announcement.ts +129 -0
  77. package/stores/auth.ts +86 -0
  78. package/stores/chat.ts +150 -0
  79. package/stores/contact.ts +28 -0
  80. package/stores/document.ts +27 -0
  81. package/stores/index.ts +34 -0
  82. package/stores/institutional.ts +231 -0
  83. package/stores/login.ts +27 -0
  84. package/stores/notifications.ts +133 -0
  85. package/stores/promotion.ts +154 -0
  86. package/types/index.ts +135 -0
  87. package/utils/serialize.ts +29 -0
@@ -0,0 +1,125 @@
1
+ <template>
2
+ <section id="mg-notifications-modal" ref="modalRef">
3
+ <div class="read-all--button-wrapper">
4
+ <a class="d-flex align-items-center read-all--button" @click="handleClickAll">
5
+ {{ $t("notification_component.mark_read") }}
6
+ <MGIcon icon="chevron-right" />
7
+ </a>
8
+ </div>
9
+ <div class="cards" @scroll="handleScroll">
10
+ <MgNotificationCard
11
+ v-for="(notification, index) in notifications"
12
+ :key="index"
13
+ :notification="notification"
14
+ @close="closeModal()"
15
+ />
16
+ </div>
17
+ </section>
18
+ </template>
19
+
20
+ <script lang="ts" setup>
21
+ import { useDebounceFn } from "@vueuse/core";
22
+
23
+ const notificationsStore = useNotificationsStore();
24
+ const notifications = computed(() => notificationsStore.notifications);
25
+
26
+ const handleClickAll = () => {
27
+ notificationsStore.markAllAsRead();
28
+ };
29
+
30
+ const emit = defineEmits(["toggle-visible"]);
31
+ const modalRef = ref<HTMLElement | null>(null);
32
+
33
+ const handleScroll = useDebounceFn((event: Event) => {
34
+ const element = event.target as HTMLElement;
35
+ const bottomReached = element.scrollHeight - element.scrollTop <= element.clientHeight + 1;
36
+
37
+ if (bottomReached) {
38
+ const total = notificationsStore.totalNotifications;
39
+ const currentPage = notificationsStore.currentPage;
40
+
41
+ if (notifications.value.length < total) {
42
+ notificationsStore.fetchNotifications(Number(currentPage + 1));
43
+ }
44
+ }
45
+ }, 300);
46
+
47
+ const closeModal = () => {
48
+ emit("toggle-visible");
49
+ };
50
+
51
+ const handleClickOutside = (event: MouseEvent) => {
52
+ if (modalRef.value && !modalRef.value.contains(event.target as Node)) {
53
+ closeModal();
54
+ }
55
+ };
56
+
57
+ onMounted(() => {
58
+ document.addEventListener("click", handleClickOutside);
59
+ });
60
+
61
+ onUnmounted(() => {
62
+ document.removeEventListener("click", handleClickOutside);
63
+ });
64
+ </script>
65
+
66
+ <style lang="scss" scoped>
67
+ #mg-notifications-modal {
68
+ position: absolute;
69
+ width: 400px;
70
+ background-color: var(--card-article-bg);
71
+ z-index: 100;
72
+ max-height: 750px;
73
+ top: 60px;
74
+ right: 160px;
75
+ overflow: auto;
76
+ border: 1px solid var(--card-article-border);
77
+
78
+ &::-webkit-scrollbar {
79
+ width: 6px;
80
+ }
81
+
82
+ &::-webkit-scrollbar-thumb {
83
+ background: #808080;
84
+ border-radius: 0px;
85
+ }
86
+
87
+ &::-webkit-scrollbar-thumb:hover {
88
+ background: #23c0e9;
89
+ }
90
+
91
+ &::-webkit-scrollbar-track {
92
+ background: #272930;
93
+ }
94
+ }
95
+
96
+ .cards {
97
+ display: flex;
98
+ flex-direction: column;
99
+ align-items: flex-start;
100
+ align-self: stretch;
101
+ }
102
+
103
+ .read-all--button-wrapper {
104
+ padding: 24px;
105
+ font-size: 14px;
106
+ font-weight: 400;
107
+ line-height: 20px;
108
+ border-bottom: 1px solid var(--body-bg);
109
+
110
+ .read-all--button {
111
+ transition: all 0.1s ease-in;
112
+ color: var(--sidebar-menus-active-bg);
113
+ cursor: pointer;
114
+ }
115
+ }
116
+
117
+ @media screen and (max-width: 576px) {
118
+ #mg-notifications-modal {
119
+ min-width: 300px;
120
+ width: 100%;
121
+ top: 60px;
122
+ left: 30px;
123
+ }
124
+ }
125
+ </style>
@@ -0,0 +1,147 @@
1
+ <script setup lang="ts">
2
+ const announcementStore = useAnnouncementStore();
3
+ const { t } = useI18n();
4
+
5
+ onMounted(async () => {
6
+ await announcementStore.fetchBanner();
7
+ });
8
+
9
+ const { banner, isVisible } = storeToRefs(announcementStore);
10
+
11
+ // The API already resolves the correct translation for the current locale,
12
+ // so banner.message is always a plain string ready to display.
13
+ const resolvedMessage = computed(() => banner.value?.message ?? '');
14
+
15
+ const resolvedLinkText = computed(() => {
16
+ if (!banner.value?.link_url) return '';
17
+ if (banner.value.link_text) return banner.value.link_text;
18
+ return t('announcementBanner.learnMore', 'Learn more');
19
+ });
20
+
21
+ // Inline colors override the type-based CSS vars when set
22
+ const bannerStyle = computed(() => {
23
+ if (!banner.value) return {};
24
+ const style: Record<string, string> = {};
25
+ if (banner.value.bg_color) style.backgroundColor = banner.value.bg_color;
26
+ if (banner.value.text_color) style.color = banner.value.text_color;
27
+ return style;
28
+ });
29
+
30
+ function dismiss() {
31
+ announcementStore.dismiss();
32
+ }
33
+ </script>
34
+
35
+ <template>
36
+ <Transition name="mg-announcement-slide">
37
+ <div
38
+ v-if="isVisible && banner && resolvedMessage"
39
+ class="mg-announcement-banner"
40
+ :class="`mg-announcement-banner--${banner.type}`"
41
+ :style="bannerStyle"
42
+ >
43
+ <span class="mg-announcement-banner__message">{{ resolvedMessage }}</span>
44
+ <a
45
+ v-if="banner.link_url"
46
+ :href="banner.link_url"
47
+ class="mg-announcement-banner__link"
48
+ target="_blank"
49
+ rel="noopener noreferrer"
50
+ >{{ resolvedLinkText }}</a>
51
+ <button
52
+ class="mg-announcement-banner__close"
53
+ :aria-label="t('announcementBanner.close', 'Close')"
54
+ @click="dismiss"
55
+ >&times;</button>
56
+ </div>
57
+ </Transition>
58
+ </template>
59
+
60
+ <style lang="scss" scoped>
61
+ .mg-announcement-banner {
62
+ display: flex;
63
+ align-items: center;
64
+ justify-content: center;
65
+ gap: 0.75rem;
66
+ padding: 0.5rem 3rem 0.5rem 1rem;
67
+ width: 100%;
68
+ font-size: 0.875rem;
69
+ font-weight: 500;
70
+ position: relative;
71
+ z-index: 100;
72
+
73
+ &--info {
74
+ background: var(--announcement-info-bg, #1a6ea8);
75
+ color: var(--announcement-info-fg, #ffffff);
76
+ }
77
+
78
+ &--warning {
79
+ background: var(--announcement-warning-bg, #92400e);
80
+ color: var(--announcement-warning-fg, #ffffff);
81
+ }
82
+
83
+ &--success {
84
+ background: var(--announcement-success-bg, #166534);
85
+ color: var(--announcement-success-fg, #ffffff);
86
+ }
87
+
88
+ &--danger {
89
+ background: var(--announcement-danger-bg, #991b1b);
90
+ color: var(--announcement-danger-fg, #ffffff);
91
+ }
92
+
93
+ &__message {
94
+ flex: 0 1 auto;
95
+ text-align: center;
96
+ }
97
+
98
+ &__link {
99
+ flex-shrink: 0;
100
+ font-weight: 700;
101
+ text-decoration: underline;
102
+ color: inherit;
103
+ white-space: nowrap;
104
+
105
+ &:hover {
106
+ opacity: 0.85;
107
+ }
108
+ }
109
+
110
+ &__close {
111
+ position: absolute;
112
+ right: 0.75rem;
113
+ top: 50%;
114
+ transform: translateY(-50%);
115
+ background: transparent;
116
+ border: none;
117
+ color: inherit;
118
+ font-size: 1.25rem;
119
+ line-height: 1;
120
+ cursor: pointer;
121
+ padding: 0 0.25rem;
122
+ opacity: 0.8;
123
+
124
+ &:hover {
125
+ opacity: 1;
126
+ }
127
+ }
128
+ }
129
+
130
+ .mg-announcement-slide-enter-active,
131
+ .mg-announcement-slide-leave-active {
132
+ transition: max-height 0.25s ease, opacity 0.25s ease;
133
+ overflow: hidden;
134
+ }
135
+
136
+ .mg-announcement-slide-enter-from,
137
+ .mg-announcement-slide-leave-to {
138
+ max-height: 0;
139
+ opacity: 0;
140
+ }
141
+
142
+ .mg-announcement-slide-enter-to,
143
+ .mg-announcement-slide-leave-from {
144
+ max-height: 60px;
145
+ opacity: 1;
146
+ }
147
+ </style>
@@ -0,0 +1,23 @@
1
+ <script setup lang="ts">
2
+ const announcementStore = useAnnouncementStore();
3
+ const promotionStore = usePromotionStore();
4
+
5
+ // Fetch both concurrently on mount — the loaded guard in each store
6
+ // prevents double-fetching if the sub-components were also mounted.
7
+ onMounted(async () => {
8
+ await Promise.all([
9
+ announcementStore.fetchBanner(),
10
+ promotionStore.fetchBanner(),
11
+ ]);
12
+ });
13
+
14
+ const { isVisible: announcementVisible } = storeToRefs(announcementStore);
15
+ </script>
16
+
17
+ <template>
18
+ <!-- At most one banner is shown at a time.
19
+ Announcement takes priority; promotion only renders when there is no
20
+ active/visible announcement. -->
21
+ <MgAnnouncementBanner />
22
+ <MgPromotionBanner v-if="!announcementVisible" />
23
+ </template>
@@ -0,0 +1,283 @@
1
+ <script setup lang="ts">
2
+ import { ref } from "vue";
3
+ import { storeToRefs } from "pinia";
4
+
5
+ const authStore = useAuthStore();
6
+ const indexStore = useIndexStore();
7
+ const notificationsStore = useNotificationsStore();
8
+
9
+ const { user, signedIn } = storeToRefs(authStore);
10
+ const { notificationCount } = storeToRefs(notificationsStore);
11
+
12
+ const runtimeConfig = useRuntimeConfig();
13
+ const features = runtimeConfig.public.mgSharedUi?.features ?? {};
14
+
15
+ const showUIConfig = ref(false);
16
+ const showUsers = ref(false);
17
+ const showNotifications = ref(false);
18
+ const showMessages = ref(false);
19
+
20
+ const colorMode = useColorMode();
21
+
22
+ function show(uiConfig: boolean, uiUsers: boolean) {
23
+ showUIConfig.value = uiConfig;
24
+ showUsers.value = uiUsers;
25
+ showNotifications.value = false;
26
+ showMessages.value = false;
27
+ }
28
+
29
+ function toggleNotifications() {
30
+ showNotifications.value = !showNotifications.value;
31
+ showMessages.value = false;
32
+ showUIConfig.value = false;
33
+ showUsers.value = false;
34
+ }
35
+
36
+ function toggleMessages() {
37
+ showMessages.value = !showMessages.value;
38
+ showNotifications.value = false;
39
+ showUIConfig.value = false;
40
+ showUsers.value = false;
41
+ }
42
+
43
+ function toggleSidebar() {
44
+ indexStore.toggleSidebar();
45
+ }
46
+
47
+ const logoURL = ref("/imgs/light-big-logo.png");
48
+
49
+ const updateLogo = () => {
50
+ logoURL.value =
51
+ colorMode.preference === "light" ? "/imgs/light-big-logo.png" : "/imgs/dark-big-logo.png";
52
+ };
53
+
54
+ watch(colorMode, updateLogo);
55
+ onMounted(() => {
56
+ updateLogo();
57
+ if (signedIn.value && features.notifications) {
58
+ notificationsStore.fetchNotifications();
59
+ }
60
+ });
61
+ </script>
62
+
63
+ <template>
64
+ <header class="mg-navbar navbar navbar-expand-lg">
65
+ <button type="button" class="sidebar-toggler" @click="toggleSidebar()">
66
+ <MGIcon icon="menu" />
67
+ </button>
68
+
69
+ <div class="search-bar-container">
70
+ <slot name="search">
71
+ <search-bar-component v-if="features.search" class="mx-auto searchBar" />
72
+ </slot>
73
+
74
+ <div class="mx-auto mobile-logo">
75
+ <slot name="logo">
76
+ <img :src="logoURL" width="138" height="29" alt="Mundo Gamer logo" />
77
+ </slot>
78
+ </div>
79
+ </div>
80
+
81
+ <ul class="user-bar">
82
+ <li v-if="signedIn && features.notifications" class="menu-item" @click="toggleNotifications">
83
+ <a href="#" @click.prevent :class="{ active: showNotifications }">
84
+ <MGIcon icon="notification" />
85
+ <span v-if="notificationCount > 0" class="badge">{{ notificationCount }}</span>
86
+ </a>
87
+ </li>
88
+
89
+ <li v-if="signedIn && features.chat" class="menu-item" @click="toggleMessages">
90
+ <a href="#" @click.prevent :class="{ active: showMessages }">
91
+ <MGIcon icon="message" />
92
+ </a>
93
+ </li>
94
+
95
+ <li class="menu-item">
96
+ <a href="#" :class="[{ active: showUIConfig }]" @click.prevent="show(!showUIConfig, false)">
97
+ <MGIcon icon="gear" />
98
+ </a>
99
+ </li>
100
+
101
+ <li
102
+ class="menu-item bordered-menu"
103
+ :class="{ 'rounded-circle': signedIn }"
104
+ :style="showUsers ? 'background: var(--header-bordered-menu-active-bg); color: black;' : ''"
105
+ >
106
+ <a
107
+ href="#"
108
+ :style="showUsers ? 'color: black;' : ''"
109
+ @click.prevent="show(false, !showUsers)"
110
+ >
111
+ <MGIcon v-if="!signedIn" icon="profile" />
112
+ <img v-else :src="user?.avatar_url" width="32" height="32" class="rounded-circle" />
113
+ </a>
114
+ </li>
115
+
116
+ <transition name="fade">
117
+ <MgHeaderUIConfig
118
+ v-if="showUIConfig"
119
+ class="header_ui_config"
120
+ @toggle-visible="showUIConfig = false"
121
+ />
122
+ </transition>
123
+
124
+ <transition name="fade">
125
+ <MgHeaderUIUser
126
+ v-if="showUsers"
127
+ class="header_ui_users"
128
+ @toggle-visible="showUsers = false"
129
+ />
130
+ </transition>
131
+
132
+ <transition name="fade">
133
+ <MgNotificationsModal
134
+ v-if="showNotifications"
135
+ @toggle-visible="showNotifications = false"
136
+ />
137
+ </transition>
138
+
139
+ <transition name="fade">
140
+ <MgMessageModal
141
+ v-if="showMessages"
142
+ @toggle-visible="showMessages = false"
143
+ />
144
+ </transition>
145
+ </ul>
146
+ </header>
147
+ </template>
148
+
149
+ <style lang="scss" scoped>
150
+ .search-bar-container {
151
+ display: flex;
152
+ flex: 1;
153
+ }
154
+ .mobile-logo {
155
+ display: none;
156
+ }
157
+ .header_ui_config {
158
+ right: 70px;
159
+ }
160
+ .header_ui_users {
161
+ margin-right: 24px;
162
+ top: 60px;
163
+ }
164
+ .mg-navbar {
165
+ align-items: center;
166
+ padding: 0.75rem 1rem;
167
+ transition: background 0.3s ease;
168
+ width: 100%;
169
+ background: var(--sidebar-bg);
170
+
171
+ .sidebar-toggler {
172
+ border: 0;
173
+ outline: 0;
174
+ background: transparent;
175
+ display: none;
176
+ }
177
+
178
+ .badge {
179
+ position: absolute;
180
+ top: -4px;
181
+ right: -4px;
182
+ background: var(--highlight-color, #23c0e9);
183
+ color: #fff;
184
+ border-radius: 50%;
185
+ font-size: 0.625rem;
186
+ min-width: 16px;
187
+ height: 16px;
188
+ display: flex;
189
+ align-items: center;
190
+ justify-content: center;
191
+ padding: 0 4px;
192
+ }
193
+
194
+ .user-bar {
195
+ display: flex;
196
+ margin: 0;
197
+ padding: 0;
198
+ list-style: none;
199
+
200
+ .menu-item {
201
+ display: flex;
202
+ justify-content: center;
203
+ align-items: center;
204
+ height: 32px;
205
+ width: 32px;
206
+ position: relative;
207
+
208
+ &.bordered-menu {
209
+ border: 1px solid var(--header-bordered-menu-border-color);
210
+ transition: 0.15s ease-in-out;
211
+
212
+ a {
213
+ display: flex;
214
+ justify-content: center;
215
+ align-items: center;
216
+ }
217
+ }
218
+
219
+ & + .menu-item {
220
+ margin-left: 1rem;
221
+ }
222
+
223
+ a {
224
+ height: inherit;
225
+ width: inherit;
226
+ color: var(--header-fg);
227
+ text-decoration: none;
228
+ transition: 0.15s ease-in-out;
229
+
230
+ &:hover,
231
+ &.active {
232
+ background-color: var(--header-active-fg);
233
+ color: var(--sidebar-bg);
234
+ }
235
+ }
236
+
237
+ .icon {
238
+ font-size: 2rem;
239
+ }
240
+ }
241
+ }
242
+ }
243
+
244
+ @media screen and (max-width: 576px) {
245
+ .search-bar-container {
246
+ flex-direction: row-reverse;
247
+ }
248
+ .mg-navbar {
249
+ width: 100%;
250
+ height: 60px;
251
+
252
+ .sidebar-toggler {
253
+ color: var(--header-fg);
254
+ display: block;
255
+ .icon {
256
+ font-size: 1.713rem;
257
+ }
258
+ }
259
+
260
+ .mobile-logo {
261
+ display: flex;
262
+ justify-content: center;
263
+ align-items: center;
264
+ flex-grow: 1;
265
+ padding-top: 0.438rem;
266
+ }
267
+
268
+ .user-bar {
269
+ position: absolute;
270
+ left: -100vw;
271
+ justify-content: space-around;
272
+ align-items: center;
273
+ height: 64px;
274
+ width: 71vw;
275
+ padding: 0 1rem;
276
+ background: var(--header-mobile-bg);
277
+ transition: 0.2s ease-in;
278
+ will-change: left;
279
+ z-index: 10;
280
+ }
281
+ }
282
+ }
283
+ </style>