@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,355 @@
1
+ <script setup lang="ts">
2
+ import { storeToRefs } from "pinia";
3
+
4
+ definePageMeta({ layout: "default" });
5
+
6
+ const institutionalStore = useInstitutionalStore();
7
+ const { roadmap } = storeToRefs(institutionalStore);
8
+
9
+ const config = useRuntimeConfig();
10
+ const systemId = config.public.mgSharedUi?.systemId || import.meta.env.VITE_SYSTEM_ID;
11
+
12
+ const activeQuarter = ref<string | null>(null);
13
+ const activeStatus = ref<string | null>(null);
14
+ const activeTopic = ref<number | null>(null);
15
+
16
+ const statusLabels: Record<number, string> = {
17
+ 1: "development",
18
+ 2: "planning",
19
+ 3: "complete",
20
+ };
21
+
22
+ const statusColors: Record<string, string> = {
23
+ planning: "#f0ad4e",
24
+ development: "#5bc0de",
25
+ complete: "#5cb85c",
26
+ };
27
+
28
+ async function fetchRoadmapData() {
29
+ try {
30
+ await institutionalStore.fetchRoadmap({
31
+ filter: {
32
+ status: 1,
33
+ platform_id: systemId,
34
+ },
35
+ sort: "start_date",
36
+ order: "asc",
37
+ per_page: "all",
38
+ });
39
+ } catch {
40
+ // silent
41
+ }
42
+ }
43
+
44
+ const groupedByQuarter = computed(() => {
45
+ const data = roadmap.value?.data?.data;
46
+ if (!data || !Array.isArray(data)) return {};
47
+
48
+ const groups: Record<string, any[]> = {};
49
+ data.forEach((item: any) => {
50
+ const date = item.start_date ? new Date(item.start_date) : null;
51
+ const quarter = date ? `Q${Math.ceil((date.getMonth() + 1) / 3)} ${date.getFullYear()}` : "Unknown";
52
+ if (!groups[quarter]) groups[quarter] = [];
53
+ groups[quarter].push(item);
54
+ });
55
+ return groups;
56
+ });
57
+
58
+ const quarters = computed(() => Object.keys(groupedByQuarter.value));
59
+
60
+ const activeQuarterItems = computed(() => {
61
+ if (!activeQuarter.value) return [];
62
+ return groupedByQuarter.value[activeQuarter.value] || [];
63
+ });
64
+
65
+ const groupedByStatus = computed(() => {
66
+ const groups: Record<string, any[]> = {};
67
+ activeQuarterItems.value.forEach((item: any) => {
68
+ const status = statusLabels[item.status_id] || "planning";
69
+ if (!groups[status]) groups[status] = [];
70
+ groups[status].push(item);
71
+ });
72
+ return groups;
73
+ });
74
+
75
+ const statuses = computed(() => Object.keys(groupedByStatus.value));
76
+
77
+ const activeStatusItems = computed(() => {
78
+ if (!activeStatus.value) return [];
79
+ return groupedByStatus.value[activeStatus.value] || [];
80
+ });
81
+
82
+ function selectQuarter(q: string) {
83
+ activeQuarter.value = activeQuarter.value === q ? null : q;
84
+ activeStatus.value = null;
85
+ activeTopic.value = null;
86
+ }
87
+
88
+ function selectStatus(s: string) {
89
+ activeStatus.value = activeStatus.value === s ? null : s;
90
+ activeTopic.value = null;
91
+ }
92
+
93
+ function selectTopic(id: number) {
94
+ activeTopic.value = activeTopic.value === id ? null : id;
95
+ }
96
+
97
+ onMounted(() => {
98
+ fetchRoadmapData();
99
+ });
100
+ </script>
101
+
102
+ <template>
103
+ <div id="roadmap" class="container roadmap">
104
+ <section>
105
+ <div class="header">
106
+ <div>
107
+ <h1 class="title-4">{{ $t("more.roadmap.title") }}</h1>
108
+ <p>{{ $t("more.roadmap.subtitle") }}</p>
109
+ </div>
110
+ </div>
111
+ <div class="container">
112
+ <div class="row">
113
+ <div class="col-8 offset-2">
114
+ <div v-if="roadmap.status === 'pending'" class="loading">
115
+ <p>{{ $t("components.loading") }}</p>
116
+ </div>
117
+
118
+ <div v-else-if="quarters.length > 0">
119
+ <div class="quarters">
120
+ <button
121
+ v-for="q in quarters"
122
+ :key="q"
123
+ class="quarter-btn"
124
+ :class="{ active: activeQuarter === q }"
125
+ @click="selectQuarter(q)"
126
+ >
127
+ {{ q }}
128
+ </button>
129
+ </div>
130
+
131
+ <div v-if="activeQuarter && statuses.length > 0" class="statuses">
132
+ <button
133
+ v-for="s in statuses"
134
+ :key="s"
135
+ class="status-btn"
136
+ :class="{ active: activeStatus === s }"
137
+ :style="{ borderColor: statusColors[s] }"
138
+ @click="selectStatus(s)"
139
+ >
140
+ {{ $t(`more.roadmap.status_${s}`, s) }}
141
+ <span class="count">{{ groupedByStatus[s]?.length || 0 }}</span>
142
+ </button>
143
+ </div>
144
+
145
+ <div v-if="activeStatus && activeStatusItems.length > 0" class="topics">
146
+ <div
147
+ v-for="topic in activeStatusItems"
148
+ :key="topic.id"
149
+ class="topic-card"
150
+ >
151
+ <button
152
+ class="topic-header"
153
+ @click="selectTopic(topic.id)"
154
+ >
155
+ <span>{{ topic.localized_title || topic.title }}</span>
156
+ <MGIcon icon="chevron-down" :class="{ rotate: activeTopic === topic.id }" />
157
+ </button>
158
+
159
+ <div v-if="activeTopic === topic.id" class="topic-detail">
160
+ <p v-if="topic.localized_description">{{ topic.localized_description }}</p>
161
+
162
+ <div v-if="topic.stages?.length" class="stages">
163
+ <div
164
+ v-for="stage in topic.stages"
165
+ :key="stage.id"
166
+ class="stage"
167
+ >
168
+ <div class="stage-header">
169
+ <span>{{ stage.localized_title || stage.title }}</span>
170
+ <span class="stage-progress">{{ stage.progress || 0 }}%</span>
171
+ </div>
172
+ <div class="progress-bar">
173
+ <div
174
+ class="progress-fill"
175
+ :style="{
176
+ width: `${stage.progress || 0}%`,
177
+ backgroundColor: statusColors[activeStatus] || '#5bc0de',
178
+ }"
179
+ ></div>
180
+ </div>
181
+ </div>
182
+ </div>
183
+ </div>
184
+ </div>
185
+ </div>
186
+ </div>
187
+
188
+ <div v-else class="no-data">
189
+ {{ $t("more.roadmap.no_data") }}
190
+ </div>
191
+ </div>
192
+ </div>
193
+ </div>
194
+ </section>
195
+ </div>
196
+ </template>
197
+
198
+ <style lang="scss" scoped>
199
+ .roadmap {
200
+ color: var(--inactive);
201
+ font-size: 0.875rem;
202
+
203
+ .header {
204
+ position: relative;
205
+ height: 200px;
206
+ display: flex;
207
+ justify-content: center;
208
+ align-items: center;
209
+ margin-bottom: 2rem;
210
+
211
+ & > div {
212
+ width: 480px;
213
+ text-align: center;
214
+ }
215
+
216
+ &:before {
217
+ content: " ";
218
+ display: block;
219
+ position: absolute;
220
+ background: url("@/assets/images/bg-roadmap.png") no-repeat center center / cover;
221
+ width: 100%;
222
+ height: 100%;
223
+ top: 50%;
224
+ transform: translateY(-50%);
225
+ z-index: -1;
226
+ }
227
+ }
228
+
229
+ .quarters {
230
+ display: flex;
231
+ flex-wrap: wrap;
232
+ gap: 8px;
233
+ margin-bottom: 24px;
234
+
235
+ .quarter-btn {
236
+ padding: 8px 16px;
237
+ border: 1px solid var(--search-bar-border-color);
238
+ background: transparent;
239
+ color: var(--inactive);
240
+ cursor: pointer;
241
+ font-size: 14px;
242
+
243
+ &.active {
244
+ background: var(--chip-text);
245
+ color: var(--chip-background-2);
246
+ border-color: var(--chip-text);
247
+ }
248
+ }
249
+ }
250
+
251
+ .statuses {
252
+ display: flex;
253
+ gap: 12px;
254
+ margin-bottom: 24px;
255
+
256
+ .status-btn {
257
+ padding: 8px 16px;
258
+ border: 2px solid;
259
+ background: transparent;
260
+ color: var(--card-cover-title);
261
+ cursor: pointer;
262
+ font-size: 14px;
263
+ display: flex;
264
+ align-items: center;
265
+ gap: 8px;
266
+
267
+ &.active {
268
+ opacity: 1;
269
+ }
270
+
271
+ .count {
272
+ font-size: 12px;
273
+ opacity: 0.7;
274
+ }
275
+ }
276
+ }
277
+
278
+ .topics {
279
+ display: flex;
280
+ flex-direction: column;
281
+ gap: 12px;
282
+ }
283
+
284
+ .topic-card {
285
+ background-color: var(--chip-background-2);
286
+
287
+ .topic-header {
288
+ width: 100%;
289
+ padding: 16px;
290
+ display: flex;
291
+ justify-content: space-between;
292
+ align-items: center;
293
+ background: transparent;
294
+ border: none;
295
+ color: var(--card-cover-title);
296
+ font-size: 14px;
297
+ font-weight: 600;
298
+ cursor: pointer;
299
+
300
+ .rotate {
301
+ transform: rotate(180deg);
302
+ transition: 0.3s;
303
+ }
304
+ }
305
+
306
+ .topic-detail {
307
+ padding: 0 16px 16px;
308
+
309
+ p {
310
+ font-size: 14px;
311
+ line-height: 20px;
312
+ margin-bottom: 16px;
313
+ }
314
+ }
315
+ }
316
+
317
+ .stages {
318
+ display: flex;
319
+ flex-direction: column;
320
+ gap: 12px;
321
+ }
322
+
323
+ .stage {
324
+ &-header {
325
+ display: flex;
326
+ justify-content: space-between;
327
+ font-size: 13px;
328
+ margin-bottom: 4px;
329
+ }
330
+
331
+ &-progress {
332
+ font-size: 12px;
333
+ color: var(--secondary-info-fg);
334
+ }
335
+ }
336
+
337
+ .progress-bar {
338
+ height: 6px;
339
+ background: var(--body-bg);
340
+ border-radius: 3px;
341
+ overflow: hidden;
342
+
343
+ .progress-fill {
344
+ height: 100%;
345
+ transition: width 0.3s;
346
+ }
347
+ }
348
+
349
+ .no-data {
350
+ text-align: center;
351
+ padding: 32px;
352
+ color: var(--secondary-info-fg);
353
+ }
354
+ }
355
+ </style>
@@ -0,0 +1,199 @@
1
+ <script setup lang="ts">
2
+ import { storeToRefs } from "pinia";
3
+
4
+ definePageMeta({ layout: "default" });
5
+
6
+ const institutionalStore = useInstitutionalStore();
7
+ const { status, generalStatus } = storeToRefs(institutionalStore);
8
+
9
+ async function fetchStatusData() {
10
+ try {
11
+ await Promise.all([
12
+ institutionalStore.fetchStatus({
13
+ filter: { status: 1 },
14
+ sort: "name",
15
+ order: "asc",
16
+ per_page: "all",
17
+ }),
18
+ institutionalStore.fetchGeneralStatus({
19
+ filter: { status: 1 },
20
+ }),
21
+ ]);
22
+ } catch {
23
+ // silent
24
+ }
25
+ }
26
+
27
+ const statusColorMap: Record<string, string> = {
28
+ green: "#5cb85c",
29
+ yellow: "#f0ad4e",
30
+ orange: "#ff8c00",
31
+ red: "#d9534f",
32
+ };
33
+
34
+ function getStatusColor(color: string): string {
35
+ return statusColorMap[color?.toLowerCase()] || color || "#5cb85c";
36
+ }
37
+
38
+ onMounted(() => {
39
+ fetchStatusData();
40
+ });
41
+ </script>
42
+
43
+ <template>
44
+ <div id="status" class="container status-page">
45
+ <section>
46
+ <div class="header">
47
+ <div>
48
+ <h1 class="title-4">{{ $t("more.status.title") }}</h1>
49
+ <p>{{ $t("more.status.subtitle") }}</p>
50
+ </div>
51
+ </div>
52
+ <div class="container">
53
+ <div class="row">
54
+ <div class="col-8 offset-2">
55
+ <div
56
+ v-if="generalStatus?.data?.data"
57
+ class="general-alert"
58
+ :style="{ backgroundColor: getStatusColor(generalStatus.data.data.color) }"
59
+ >
60
+ <span>{{ generalStatus.data.data.localized_message || generalStatus.data.data.message }}</span>
61
+ </div>
62
+
63
+ <div v-if="status.status === 'pending'" class="loading">
64
+ <p>{{ $t("components.loading") }}</p>
65
+ </div>
66
+
67
+ <div v-else-if="status?.data?.data && status.data.data.length > 0" class="status-list">
68
+ <div
69
+ v-for="item in status.data.data"
70
+ :key="item.id"
71
+ class="status-item"
72
+ >
73
+ <div class="status-item__icon">
74
+ <div
75
+ class="status-dot"
76
+ :style="{ backgroundColor: getStatusColor(item.status_color) }"
77
+ ></div>
78
+ </div>
79
+ <img
80
+ v-if="item.avatar_url || item.icon_url"
81
+ :src="item.avatar_url || item.icon_url"
82
+ :alt="item.name"
83
+ class="status-item__avatar"
84
+ />
85
+ <div class="status-item__info">
86
+ <h3>{{ item.localized_name || item.name }}</h3>
87
+ <p v-if="item.localized_description">{{ item.localized_description }}</p>
88
+ </div>
89
+ <span class="status-item__label" :style="{ color: getStatusColor(item.status_color) }">
90
+ {{ item.status_label || $t("more.status.operational") }}
91
+ </span>
92
+ </div>
93
+ </div>
94
+
95
+ <div v-else class="no-data">
96
+ {{ $t("more.status.no_data") }}
97
+ </div>
98
+ </div>
99
+ </div>
100
+ </div>
101
+ </section>
102
+ </div>
103
+ </template>
104
+
105
+ <style lang="scss" scoped>
106
+ .status-page {
107
+ color: var(--inactive);
108
+ font-size: 0.875rem;
109
+
110
+ .header {
111
+ position: relative;
112
+ height: 200px;
113
+ display: flex;
114
+ justify-content: center;
115
+ align-items: center;
116
+ margin-bottom: 2rem;
117
+
118
+ & > div {
119
+ width: 480px;
120
+ text-align: center;
121
+ }
122
+
123
+ &:before {
124
+ content: " ";
125
+ display: block;
126
+ position: absolute;
127
+ background: url("@/assets/images/bg-status.png") no-repeat center center / cover;
128
+ width: 100%;
129
+ height: 100%;
130
+ top: 50%;
131
+ transform: translateY(-50%);
132
+ z-index: -1;
133
+ }
134
+ }
135
+
136
+ .general-alert {
137
+ padding: 16px;
138
+ margin-bottom: 24px;
139
+ text-align: center;
140
+ color: #fff;
141
+ font-size: 14px;
142
+ font-weight: 600;
143
+ }
144
+
145
+ .status-list {
146
+ display: flex;
147
+ flex-direction: column;
148
+ gap: 8px;
149
+ }
150
+
151
+ .status-item {
152
+ display: flex;
153
+ align-items: center;
154
+ gap: 12px;
155
+ padding: 16px;
156
+ background-color: var(--chip-background-2);
157
+
158
+ &__icon {
159
+ .status-dot {
160
+ width: 12px;
161
+ height: 12px;
162
+ border-radius: 50%;
163
+ }
164
+ }
165
+
166
+ &__avatar {
167
+ width: 32px;
168
+ height: 32px;
169
+ object-fit: contain;
170
+ }
171
+
172
+ &__info {
173
+ flex: 1;
174
+
175
+ h3 {
176
+ font-size: 14px;
177
+ font-weight: 500;
178
+ color: var(--card-cover-title);
179
+ }
180
+
181
+ p {
182
+ font-size: 12px;
183
+ color: var(--secondary-info-fg);
184
+ }
185
+ }
186
+
187
+ &__label {
188
+ font-size: 12px;
189
+ font-weight: 500;
190
+ }
191
+ }
192
+
193
+ .no-data {
194
+ text-align: center;
195
+ padding: 32px;
196
+ color: var(--secondary-info-fg);
197
+ }
198
+ }
199
+ </style>