@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,134 @@
1
+ <script setup lang="ts">
2
+ interface Video {
3
+ id: number;
4
+ title: string;
5
+ url: string;
6
+ type?: string;
7
+ is_primary?: boolean;
8
+ sort_order?: number;
9
+ }
10
+
11
+ interface Props {
12
+ videos: Video[];
13
+ }
14
+
15
+ const props = defineProps<Props>();
16
+
17
+ const activeVideo = ref<Video | null>(null);
18
+
19
+ onMounted(() => {
20
+ const primary = props.videos.find(v => v.is_primary);
21
+ activeVideo.value = primary || props.videos[0] || null;
22
+ });
23
+
24
+ function getEmbedUrl(url: string) {
25
+ const ytMatch = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([\w-]+)/);
26
+ if (ytMatch) return `https://www.youtube.com/embed/${ytMatch[1]}`;
27
+ const vimeoMatch = url.match(/vimeo\.com\/(\d+)/);
28
+ if (vimeoMatch) return `https://player.vimeo.com/video/${vimeoMatch[1]}`;
29
+ return url;
30
+ }
31
+
32
+ function formatType(type?: string) {
33
+ if (!type) return '';
34
+ return type.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
35
+ }
36
+ </script>
37
+
38
+ <template>
39
+ <div v-if="videos.length" class="video-player">
40
+ <h3 class="section-title">{{ $t('press_kit.videos') }}</h3>
41
+
42
+ <div v-if="activeVideo" class="video-embed">
43
+ <iframe
44
+ :src="getEmbedUrl(activeVideo.url)"
45
+ frameborder="0"
46
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
47
+ allowfullscreen
48
+ />
49
+ </div>
50
+
51
+ <div v-if="videos.length > 1" class="video-list">
52
+ <div
53
+ v-for="video in videos"
54
+ :key="video.id"
55
+ :class="['video-item', { active: activeVideo?.id === video.id }]"
56
+ @click="activeVideo = video"
57
+ >
58
+ <MGIcon icon="play-circle" />
59
+ <div class="video-meta">
60
+ <span class="video-title">{{ video.title }}</span>
61
+ <span v-if="video.type" class="video-type">{{ formatType(video.type) }}</span>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </template>
67
+
68
+ <style lang="scss" scoped>
69
+ .video-player {
70
+ .section-title {
71
+ font-size: 1.2rem;
72
+ font-weight: 700;
73
+ color: #fff;
74
+ margin-bottom: 16px;
75
+ }
76
+
77
+ .video-embed {
78
+ position: relative;
79
+ width: 100%;
80
+ aspect-ratio: 16 / 9;
81
+ overflow: hidden;
82
+ background: #000;
83
+ margin-bottom: 16px;
84
+
85
+ iframe {
86
+ width: 100%;
87
+ height: 100%;
88
+ }
89
+ }
90
+
91
+ .video-list {
92
+ display: flex;
93
+ flex-direction: column;
94
+ gap: 8px;
95
+ }
96
+
97
+ .video-item {
98
+ display: flex;
99
+ align-items: center;
100
+ gap: 12px;
101
+ padding: 12px 16px;
102
+ background: #191B20;
103
+ border: 1px solid #1e2028;
104
+ cursor: pointer;
105
+ transition: background 0.2s;
106
+ color: #aaa;
107
+
108
+ &:hover {
109
+ background: #22252b;
110
+ }
111
+
112
+ &.active {
113
+ background: #22252b;
114
+ border-left: 3px solid var(--color-primary, #FDB215);
115
+ color: #fff;
116
+ }
117
+ }
118
+
119
+ .video-meta {
120
+ display: flex;
121
+ flex-direction: column;
122
+ }
123
+
124
+ .video-title {
125
+ font-size: 0.9rem;
126
+ font-weight: 500;
127
+ }
128
+
129
+ .video-type {
130
+ font-size: 0.75rem;
131
+ color: #888;
132
+ }
133
+ }
134
+ </style>
@@ -0,0 +1,214 @@
1
+ <script setup lang="ts">
2
+ import type { CartItem } from "../../composables/useMgCheckout";
3
+
4
+ defineProps<{
5
+ items: CartItem[];
6
+ loading?: boolean;
7
+ editable?: boolean;
8
+ currencySymbol?: string;
9
+ }>();
10
+
11
+ const emit = defineEmits<{
12
+ "add-item": [id: number, type: string];
13
+ "remove-item": [id: number, type: string];
14
+ "clear-all": [];
15
+ }>();
16
+ </script>
17
+
18
+ <template>
19
+ <div class="mg-cart-items">
20
+ <div class="mg-cart-items__header">
21
+ <span class="col-item">{{ $t?.("checkout.cart.item") ?? "Item" }}</span>
22
+ <span class="col-qty">{{ $t?.("checkout.cart.qty") ?? "Qty" }}</span>
23
+ <span class="col-value">{{ $t?.("checkout.cart.value") ?? "Value" }}</span>
24
+ </div>
25
+
26
+ <div v-if="loading" class="mg-cart-items__loading">
27
+ <div v-for="i in 2" :key="i" class="skeleton-row">
28
+ <div class="skeleton-block" style="width: 60%; height: 14px"></div>
29
+ <div class="skeleton-block" style="width: 20%; height: 14px"></div>
30
+ </div>
31
+ </div>
32
+
33
+ <div v-else-if="items.length === 0" class="mg-cart-items__empty">
34
+ <p>{{ $t?.("checkout.cart.empty") ?? "Your cart is empty" }}</p>
35
+ </div>
36
+
37
+ <template v-else>
38
+ <div
39
+ v-for="item in items"
40
+ :key="`${item.item_type}-${item.item_id}`"
41
+ class="mg-cart-item"
42
+ >
43
+ <div class="col-item">
44
+ <img
45
+ v-if="item.item?.image_url"
46
+ :src="item.item.image_url"
47
+ :alt="item.item?.name"
48
+ class="mg-cart-item__img"
49
+ />
50
+ <span class="mg-cart-item__name">{{ item.item?.name ?? "Product" }}</span>
51
+ </div>
52
+
53
+ <div class="col-qty">
54
+ <template v-if="editable">
55
+ <button class="qty-btn" @click="emit('remove-item', item.item_id, item.item_type)">-</button>
56
+ <span class="qty-value">{{ item.quantity }}</span>
57
+ <button class="qty-btn" @click="emit('add-item', item.item_id, item.item_type)">+</button>
58
+ </template>
59
+ <span v-else class="qty-value">{{ item.quantity }}</span>
60
+ </div>
61
+
62
+ <div class="col-value">
63
+ <span v-if="item.discount > 0" class="original-price">
64
+ {{ currencySymbol ?? "$" }}{{ (item.price * item.quantity).toFixed(2) }}
65
+ </span>
66
+ <span class="final-price">
67
+ {{ currencySymbol ?? "$" }}{{ (item.price_with_discount * item.quantity).toFixed(2) }}
68
+ </span>
69
+ </div>
70
+ </div>
71
+
72
+ <div v-if="editable" class="mg-cart-items__footer">
73
+ <button class="clear-btn" @click="emit('clear-all')">
74
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
75
+ <polyline points="3 6 5 6 21 6" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
76
+ </svg>
77
+ {{ $t?.("checkout.cart.clear") ?? "Clear all" }}
78
+ </button>
79
+ </div>
80
+ </template>
81
+ </div>
82
+ </template>
83
+
84
+ <style lang="scss" scoped>
85
+ .mg-cart-items {
86
+ width: 100%;
87
+
88
+ &__header {
89
+ display: flex;
90
+ padding: 8px 0;
91
+ border-bottom: 1px solid var(--inactive, #e5e7eb);
92
+ margin-bottom: 8px;
93
+
94
+ span {
95
+ font-size: 11px;
96
+ font-weight: 600;
97
+ text-transform: uppercase;
98
+ color: var(--inactive, #6b7280);
99
+ letter-spacing: 0.5px;
100
+ }
101
+ }
102
+
103
+ &__loading {
104
+ padding: 16px 0;
105
+
106
+ .skeleton-row {
107
+ display: flex;
108
+ gap: 16px;
109
+ margin-bottom: 12px;
110
+ }
111
+ }
112
+
113
+ &__empty {
114
+ padding: 24px 0;
115
+ text-align: center;
116
+ color: var(--inactive, #6b7280);
117
+ font-size: 14px;
118
+ }
119
+
120
+ &__footer {
121
+ padding-top: 8px;
122
+ border-top: 1px solid var(--inactive, #e5e7eb);
123
+ }
124
+ }
125
+
126
+ .col-item { flex: 5; display: flex; align-items: center; gap: 8px; }
127
+ .col-qty { flex: 3; display: flex; align-items: center; justify-content: center; gap: 6px; }
128
+ .col-value { flex: 4; display: flex; flex-direction: column; align-items: flex-end; }
129
+
130
+ .mg-cart-item {
131
+ display: flex;
132
+ align-items: center;
133
+ padding: 10px 0;
134
+ border-bottom: 1px solid var(--inactive, #f3f4f6);
135
+
136
+ &__img {
137
+ width: 36px;
138
+ height: 36px;
139
+ object-fit: contain;
140
+ border-radius: 4px;
141
+ }
142
+
143
+ &__name {
144
+ font-size: 13px;
145
+ color: var(--active, #111827);
146
+ line-height: 1.3;
147
+ }
148
+ }
149
+
150
+ .qty-btn {
151
+ width: 24px;
152
+ height: 24px;
153
+ display: flex;
154
+ align-items: center;
155
+ justify-content: center;
156
+ border: 1px solid var(--inactive, #d1d5db);
157
+ border-radius: 4px;
158
+ background: transparent;
159
+ color: var(--active, #111827);
160
+ cursor: pointer;
161
+ font-size: 14px;
162
+ font-weight: 600;
163
+
164
+ &:hover {
165
+ border-color: #4f46e5;
166
+ color: #4f46e5;
167
+ }
168
+ }
169
+
170
+ .qty-value {
171
+ font-size: 14px;
172
+ font-weight: 500;
173
+ color: var(--active, #111827);
174
+ min-width: 20px;
175
+ text-align: center;
176
+ }
177
+
178
+ .original-price {
179
+ font-size: 11px;
180
+ color: var(--inactive, #9ca3af);
181
+ text-decoration: line-through;
182
+ }
183
+
184
+ .final-price {
185
+ font-size: 14px;
186
+ font-weight: 600;
187
+ color: var(--active, #111827);
188
+ }
189
+
190
+ .clear-btn {
191
+ display: flex;
192
+ align-items: center;
193
+ gap: 6px;
194
+ background: none;
195
+ border: none;
196
+ color: #ee3831;
197
+ font-size: 12px;
198
+ cursor: pointer;
199
+ padding: 4px 0;
200
+
201
+ &:hover { opacity: 0.8; }
202
+ }
203
+
204
+ .skeleton-block {
205
+ background: var(--inactive, #e5e7eb);
206
+ border-radius: 4px;
207
+ animation: pulse 1.5s ease-in-out infinite;
208
+ }
209
+
210
+ @keyframes pulse {
211
+ 0%, 100% { opacity: 1; }
212
+ 50% { opacity: 0.5; }
213
+ }
214
+ </style>
@@ -0,0 +1,204 @@
1
+ <script setup lang="ts">
2
+ import { ref } from "vue";
3
+
4
+ const props = defineProps<{
5
+ total: number | string;
6
+ originalTotal?: number | string;
7
+ hasDiscount?: boolean;
8
+ currencySymbol?: string;
9
+ couponInfo?: { name: string; discount: number; code: string } | null;
10
+ couponLoading?: boolean;
11
+ couponError?: string | null;
12
+ disabled?: boolean;
13
+ }>();
14
+
15
+ const emit = defineEmits<{
16
+ "apply-coupon": [code: string];
17
+ "remove-coupon": [];
18
+ }>();
19
+
20
+ const couponInput = ref("");
21
+
22
+ function handleApply() {
23
+ if (couponInput.value.trim()) {
24
+ emit("apply-coupon", couponInput.value.trim());
25
+ }
26
+ }
27
+ </script>
28
+
29
+ <template>
30
+ <div class="mg-cart-summary">
31
+ <!-- Coupon Input -->
32
+ <div class="mg-cart-summary__coupon">
33
+ <div v-if="couponInfo" class="coupon-applied">
34
+ <span class="coupon-badge">
35
+ {{ couponInfo.code }} (-{{ couponInfo.discount }}%)
36
+ </span>
37
+ <button class="coupon-remove" @click="emit('remove-coupon')">
38
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
39
+ <line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" />
40
+ </svg>
41
+ </button>
42
+ </div>
43
+ <div v-else class="coupon-form">
44
+ <input
45
+ v-model="couponInput"
46
+ type="text"
47
+ :placeholder="$t?.('checkout.coupon.placeholder') ?? 'Discount code'"
48
+ :disabled="disabled || couponLoading"
49
+ class="coupon-input"
50
+ @keyup.enter="handleApply"
51
+ />
52
+ <button
53
+ class="coupon-btn"
54
+ :disabled="!couponInput.trim() || disabled || couponLoading"
55
+ @click="handleApply"
56
+ >
57
+ <span v-if="couponLoading" class="spinner"></span>
58
+ <template v-else>{{ $t?.("checkout.coupon.apply") ?? "Apply" }}</template>
59
+ </button>
60
+ </div>
61
+ <small v-if="couponError" class="coupon-error">{{ couponError }}</small>
62
+ </div>
63
+
64
+ <!-- Totals -->
65
+ <div class="mg-cart-summary__totals">
66
+ <div v-if="hasDiscount" class="total-row">
67
+ <span>{{ $t?.("checkout.summary.original") ?? "Subtotal" }}</span>
68
+ <span class="original-total">{{ currencySymbol ?? "$" }}{{ Number(originalTotal).toFixed(2) }}</span>
69
+ </div>
70
+ <div v-if="hasDiscount" class="total-row discount-row">
71
+ <span>{{ $t?.("checkout.summary.discount") ?? "Discount" }}</span>
72
+ <span class="discount-value">-{{ currencySymbol ?? "$" }}{{ (Number(originalTotal) - Number(total)).toFixed(2) }}</span>
73
+ </div>
74
+ <div class="total-row total-row--final">
75
+ <span>{{ $t?.("checkout.summary.total") ?? "Total" }}</span>
76
+ <span class="final-total">{{ currencySymbol ?? "$" }}{{ Number(total).toFixed(2) }}</span>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </template>
81
+
82
+ <style lang="scss" scoped>
83
+ .mg-cart-summary {
84
+ width: 100%;
85
+
86
+ &__coupon {
87
+ margin-bottom: 16px;
88
+ }
89
+
90
+ &__totals {
91
+ border-top: 1px solid var(--inactive, #e5e7eb);
92
+ padding-top: 12px;
93
+ }
94
+ }
95
+
96
+ .coupon-form {
97
+ display: flex;
98
+ gap: 8px;
99
+ }
100
+
101
+ .coupon-input {
102
+ flex: 1;
103
+ padding: 8px 12px;
104
+ border: 1px solid var(--inactive, #d1d5db);
105
+ border-radius: 6px;
106
+ font-size: 13px;
107
+ background: transparent;
108
+ color: var(--active, #111827);
109
+
110
+ &::placeholder { color: var(--inactive, #9ca3af); }
111
+ &:focus { outline: none; border-color: #4f46e5; }
112
+ }
113
+
114
+ .coupon-btn {
115
+ padding: 8px 16px;
116
+ border: none;
117
+ border-radius: 6px;
118
+ background: #4f46e5;
119
+ color: #fff;
120
+ font-size: 13px;
121
+ font-weight: 500;
122
+ cursor: pointer;
123
+ white-space: nowrap;
124
+
125
+ &:disabled { opacity: 0.5; cursor: not-allowed; }
126
+ &:hover:not(:disabled) { background: #4338ca; }
127
+ }
128
+
129
+ .coupon-applied {
130
+ display: flex;
131
+ align-items: center;
132
+ gap: 8px;
133
+ }
134
+
135
+ .coupon-badge {
136
+ display: inline-flex;
137
+ padding: 4px 10px;
138
+ border-radius: 4px;
139
+ background: rgba(79, 70, 229, 0.1);
140
+ color: #4f46e5;
141
+ font-size: 12px;
142
+ font-weight: 600;
143
+ }
144
+
145
+ .coupon-remove {
146
+ background: none;
147
+ border: none;
148
+ color: var(--inactive, #6b7280);
149
+ cursor: pointer;
150
+ padding: 2px;
151
+
152
+ &:hover { color: #ee3831; }
153
+ }
154
+
155
+ .coupon-error {
156
+ display: block;
157
+ color: #ee3831;
158
+ font-size: 12px;
159
+ margin-top: 4px;
160
+ }
161
+
162
+ .total-row {
163
+ display: flex;
164
+ justify-content: space-between;
165
+ align-items: center;
166
+ padding: 6px 0;
167
+ font-size: 13px;
168
+ color: var(--inactive, #6b7280);
169
+
170
+ &--final {
171
+ padding-top: 10px;
172
+ margin-top: 4px;
173
+ border-top: 1px solid var(--inactive, #e5e7eb);
174
+ font-size: 16px;
175
+ font-weight: 700;
176
+ color: var(--active, #111827);
177
+ }
178
+ }
179
+
180
+ .original-total {
181
+ text-decoration: line-through;
182
+ }
183
+
184
+ .discount-value {
185
+ color: #22c55e;
186
+ font-weight: 500;
187
+ }
188
+
189
+ .final-total {
190
+ color: var(--active, #111827);
191
+ }
192
+
193
+ .spinner {
194
+ width: 14px;
195
+ height: 14px;
196
+ border: 2px solid rgba(255, 255, 255, 0.3);
197
+ border-top-color: #fff;
198
+ border-radius: 50%;
199
+ animation: spin 0.6s linear infinite;
200
+ display: inline-block;
201
+ }
202
+
203
+ @keyframes spin { to { transform: rotate(360deg); } }
204
+ </style>