@mundogamernetwork/shared-ui 1.17.7 → 1.17.10

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.
@@ -42,11 +42,12 @@ const initialized = computed(() => {
42
42
  <MGIcon v-else-if="!signedIn" icon="profile" />
43
43
  <img
44
44
  v-else
45
- :src="user?.avatar_url"
45
+ :src="user?.avatar_url || '/imgs/default-avatar.png'"
46
46
  :width="props.size"
47
47
  :height="props.size"
48
48
  class="mg-avatar-img"
49
49
  alt=""
50
+ @error="(e: Event) => { const t = e.target as HTMLImageElement; if (t) { t.onerror = null; t.src = '/imgs/default-avatar.png' } }"
50
51
  />
51
52
  <template #fallback>
52
53
  <div
@@ -109,7 +109,14 @@ onMounted(() => {
109
109
  @click.prevent="show(false, !showUsers)"
110
110
  >
111
111
  <MGIcon v-if="!signedIn" icon="profile" />
112
- <img v-else :src="user?.avatar_url" width="32" height="32" class="rounded-circle" />
112
+ <img
113
+ v-else
114
+ :src="user?.avatar_url || '/imgs/default-avatar.png'"
115
+ width="32"
116
+ height="32"
117
+ class="rounded-circle"
118
+ @error="(e: Event) => { const t = e.target as HTMLImageElement; if (t) { t.onerror = null; t.src = '/imgs/default-avatar.png' } }"
119
+ />
113
120
  </a>
114
121
  </li>
115
122
 
@@ -122,7 +122,14 @@ const handleToggleOnline = () => {
122
122
  <div v-if="!user?.avatar_url" class="no-avatar-wrapper">
123
123
  <MGIcon icon="profile" />
124
124
  </div>
125
- <img v-else :src="user.avatar_url" width="40" height="40" class="avatar-img" />
125
+ <img
126
+ v-else
127
+ :src="user.avatar_url"
128
+ width="40"
129
+ height="40"
130
+ class="avatar-img"
131
+ @error="(e: Event) => { const t = e.target as HTMLImageElement; if (t) { t.onerror = null; t.src = '/imgs/default-avatar.png' } }"
132
+ />
126
133
  </div>
127
134
 
128
135
  <!--
@@ -40,23 +40,23 @@ function close() {
40
40
  {{ $t("header.login.btn_register") }}
41
41
  </a>
42
42
  <Teleport to="body">
43
- <div v-if="open" class="modal">
44
- <div class="modal-backdrop" @click="close()"></div>
45
- <div class="pop-up">
46
- <div class="title">
43
+ <div v-if="open" class="mg-login-modal">
44
+ <div class="mg-login-modal-backdrop" @click="close()"></div>
45
+ <div class="mg-login-modal-popup">
46
+ <div class="mg-login-modal-title">
47
47
  {{ $t("more.login_modal.title") }}
48
48
  </div>
49
- <div class="subtitle">
49
+ <div class="mg-login-modal-subtitle">
50
50
  {{ $t("more.login_modal.subtitle") }}
51
51
  </div>
52
- <div class="buttons">
53
- <a v-if="props.loginUrl" class="btn1" :href="props.loginUrl">
52
+ <div class="mg-login-modal-buttons">
53
+ <a v-if="props.loginUrl" class="mg-login-modal-btn1" :href="props.loginUrl">
54
54
  {{ $t("more.login_modal.btn_login") }}
55
55
  </a>
56
- <a v-if="!signedIn" class="btn1" :href="registerUrl">
56
+ <a v-if="!signedIn" class="mg-login-modal-btn1" :href="registerUrl">
57
57
  {{ $t("more.login_modal.btn_1") }}
58
58
  </a>
59
- <a class="btn2" @click="close()">
59
+ <a class="mg-login-modal-btn2" @click="close()">
60
60
  {{ $t("more.login_modal.btn_2") }}
61
61
  </a>
62
62
  </div>
@@ -67,100 +67,109 @@ function close() {
67
67
  </template>
68
68
 
69
69
  <style lang="scss">
70
+ // #mgLoginModal only wraps the inline "Register" link below — the modal
71
+ // itself is <Teleport to="body">'d out of this element entirely, so it can
72
+ // never be reached by a `#mgLoginModal .foo` selector. It previously used
73
+ // bare, unscoped names (.modal, .pop-up, .title, .buttons, .btn1/2) that
74
+ // silently collided with Bootstrap's own (and other) same-named classes —
75
+ // Bootstrap's `.modal { display: none; z-index: 1055 }` always won since the
76
+ // teleported node is no longer a descendant of #mgLoginModal, so the intended
77
+ // `#mgLoginModal .modal` override never matched it. Every teleported class is
78
+ // now uniquely prefixed and styled independently of #mgLoginModal.
70
79
  #mgLoginModal {
71
80
  a {
72
81
  cursor: pointer;
73
82
  margin-left: 5px;
74
83
  }
84
+ }
85
+
86
+ .mg-login-modal {
87
+ position: fixed;
88
+ display: block;
89
+ width: 100%;
90
+ height: 100vh;
91
+ z-index: 99999;
92
+ top: 0;
93
+ left: 0;
75
94
 
76
- .modal {
95
+ .mg-login-modal-backdrop {
77
96
  position: fixed;
78
- display: block;
79
97
  width: 100%;
98
+ background-color: rgba(0, 0, 0, 0.5);
80
99
  height: 100vh;
81
- z-index: 99999;
82
- top: 0;
100
+ z-index: 1;
83
101
  left: 0;
102
+ top: 0;
103
+ }
84
104
 
85
- .modal-backdrop {
86
- position: fixed;
87
- width: 100%;
88
- background-color: rgba(0, 0, 0, 0.5);
89
- height: 100vh;
90
- z-index: 1;
91
- left: 0;
92
- top: 0;
105
+ .mg-login-modal-popup {
106
+ max-height: 90vh;
107
+ width: 320px;
108
+ padding: 24px 32px;
109
+ background-color: var(--sidebar-bg);
110
+ border: 0;
111
+ display: flex;
112
+ flex-direction: column;
113
+ gap: 16px;
114
+ justify-content: center;
115
+ align-items: center;
116
+ text-align: center;
117
+ margin: auto;
118
+ margin-top: 15%;
119
+ position: relative;
120
+ z-index: 999;
121
+
122
+ .mg-login-modal-title {
123
+ font-size: 18px;
124
+ font-weight: 600;
125
+ line-height: 26px;
126
+ color: var(--active);
127
+ }
128
+
129
+ .mg-login-modal-subtitle {
130
+ font-size: 14px;
131
+ font-weight: 400;
132
+ line-height: 20px;
133
+ color: var(--active);
93
134
  }
94
135
 
95
- .pop-up {
96
- max-height: 90vh;
97
- width: 320px;
98
- padding: 24px 32px;
99
- background-color: var(--sidebar-bg);
100
- border: 0;
136
+ .mg-login-modal-buttons {
101
137
  display: flex;
102
138
  flex-direction: column;
103
- gap: 16px;
104
- justify-content: center;
105
- align-items: center;
106
- text-align: center;
107
- margin: auto;
108
- margin-top: 15%;
109
- position: relative;
110
- z-index: 999;
111
-
112
- .title {
113
- font-size: 18px;
114
- font-weight: 600;
115
- line-height: 26px;
116
- color: var(--active);
117
- }
139
+ gap: 8px;
140
+ width: 100%;
118
141
 
119
- .subtitle {
120
- font-size: 14px;
121
- font-weight: 400;
122
- line-height: 20px;
142
+ .mg-login-modal-btn1 {
143
+ display: flex;
144
+ width: 100%;
145
+ height: 44px;
146
+ padding: 0 12px;
147
+ justify-content: center;
148
+ align-items: center;
149
+ font-size: 16px;
123
150
  color: var(--active);
151
+ background-color: var(--sidebar-bg);
152
+ cursor: pointer;
153
+ border: 1px solid var(--chip-text);
124
154
  }
125
155
 
126
- .buttons {
156
+ .mg-login-modal-btn2 {
127
157
  display: flex;
128
- flex-direction: column;
129
- gap: 8px;
130
158
  width: 100%;
131
-
132
- .btn1 {
133
- display: flex;
134
- width: 100%;
135
- height: 44px;
136
- padding: 0 12px;
137
- justify-content: center;
138
- align-items: center;
139
- font-size: 16px;
140
- color: var(--active);
141
- background-color: var(--sidebar-bg);
142
- cursor: pointer;
143
- border: 1px solid var(--chip-text);
144
- }
145
-
146
- .btn2 {
147
- display: flex;
148
- width: 100%;
149
- height: 44px;
150
- padding: 0 12px;
151
- justify-content: center;
152
- align-items: center;
153
- border: 0;
154
- color: var(--chip-text);
155
- cursor: pointer;
156
- background-color: var(--search-bar-bg);
157
- }
159
+ height: 44px;
160
+ padding: 0 12px;
161
+ justify-content: center;
162
+ align-items: center;
163
+ border: 0;
164
+ color: var(--chip-text);
165
+ cursor: pointer;
166
+ background-color: var(--search-bar-bg);
158
167
  }
159
168
  }
160
169
  }
161
170
 
162
171
  @media screen and (max-width: 768px) {
163
- .modal .pop-up {
172
+ .mg-login-modal-popup {
164
173
  width: 100%;
165
174
  margin-top: 50%;
166
175
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mundogamernetwork/shared-ui",
3
- "version": "1.17.7",
3
+ "version": "1.17.10",
4
4
  "description": "Mundo Gamer Network - Shared UI Layer (Nuxt 3)",
5
5
  "type": "module",
6
6
  "main": "./nuxt.config.ts",
@@ -14,13 +14,17 @@ import {
14
14
  } from "../../services/campaignService"
15
15
  import { fetchDetailGame, fetchDetailGameImage } from "../../services/gamesService"
16
16
  import { formatEmbargoDateTime } from "../../utils/embargo"
17
+ import { useLoginStore } from "../../stores/login"
17
18
 
18
19
  // Injected by each front — provide in app.vue / a plugin
19
20
  const mgPlatform = inject<string>("mgPlatform", "MGTV")
20
- const loginUrl = inject<string>("loginUrl", "/")
21
21
  const upgradeRoute = inject<string>("upgradeRoute", "/")
22
22
  // The consuming front should provide the authenticated user as { id, ... } or null
23
23
  const currentUser = inject<any>("currentUser", null)
24
+ const loginStore = useLoginStore()
25
+ // The auth store actually defaults `user` to `{}` (not null) even when signed
26
+ // out, so a raw truthiness check on it is always true — `.id` is the real signal.
27
+ const isSignedIn = () => !!unref(currentUser)?.id
24
28
 
25
29
  const { $i18n } = useNuxtApp()
26
30
  const locale = $i18n.locale
@@ -212,8 +216,9 @@ const following = ref(!!campaign.value?.is_following)
212
216
  const followBusy = ref(false)
213
217
 
214
218
  async function toggleFollow() {
215
- if (!currentUser) {
216
- return navigateTo(`${loginUrl}?redirect_to=${encodeURIComponent(route.fullPath)}`, { external: true })
219
+ if (!isSignedIn()) {
220
+ loginStore.showLoginModalComponent = true
221
+ return
217
222
  }
218
223
  if (!campaign.value?.id || followBusy.value) return
219
224
 
@@ -601,14 +606,11 @@ const tierLabel = computed(() => {
601
606
  // Auth guard for CTA actions
602
607
  // ------------------------------------------------------------------
603
608
  function requireAuth(targetPath: string) {
604
- if (unref(currentUser)) {
609
+ if (isSignedIn()) {
605
610
  navigateTo(targetPath)
606
611
  return
607
612
  }
608
- const returnTo = typeof window !== "undefined"
609
- ? `${window.location.origin}${route.fullPath}`
610
- : targetPath
611
- navigateTo(`${loginUrl}?redirect_to=${encodeURIComponent(returnTo)}`, { external: true })
613
+ loginStore.showLoginModalComponent = true
612
614
  }
613
615
 
614
616
  // ------------------------------------------------------------------
@@ -622,7 +624,7 @@ const showUsersModal = ref(false)
622
624
  onMounted(async () => {
623
625
  updateCountdown()
624
626
 
625
- if (unref(currentUser) && campaign.value?.id) {
627
+ if (isSignedIn() && campaign.value?.id) {
626
628
  try {
627
629
  const response = await fetchMyRequests({ "filter[key_id]": campaign.value.id })
628
630
  const requests = response?.data?.data
@@ -470,7 +470,9 @@ function truthyFlag(value: unknown) {
470
470
  }
471
471
 
472
472
  function hasCurrentUser() {
473
- return !!unref(currentUser)
473
+ // `currentUser` defaults to `{}` (not null) in the auth store even when
474
+ // signed out, so a raw truthiness check on it is always true.
475
+ return !!unref(currentUser)?.id
474
476
  }
475
477
 
476
478
  function requestLogin() {
@@ -14,13 +14,19 @@ import {
14
14
  isEmbargoActive,
15
15
  type EmbargoDisplay,
16
16
  } from "../../utils/embargo"
17
+ import { useLoginStore } from "../../stores/login"
17
18
 
18
19
  const route = useRoute()
19
20
  const { $i18n } = useNuxtApp()
20
21
  const locale = $i18n.locale
22
+ const loginStore = useLoginStore()
21
23
 
22
24
  const currentUser = inject<any>("currentUser", null)
23
25
  const loginUrl = inject<string>("loginUrl", "/")
26
+ // `currentUser` defaults to `{}` (never null) in the auth store even when
27
+ // signed out, so a raw truthiness check on it is always true. `.id` is the
28
+ // same "really signed in" signal the store's own getters/persisted state use.
29
+ const isSignedIn = () => !!unref(currentUser)?.id
24
30
 
25
31
  const requestId = Number(route.query.requestId)
26
32
  const keyItemsParam = route.query.keyItems as string | undefined
@@ -74,9 +80,8 @@ const redeemUrl = computed(() => {
74
80
  })
75
81
 
76
82
  onMounted(async () => {
77
- if (!unref(currentUser)) {
78
- const returnTo = typeof window !== "undefined" ? window.location.href : ""
79
- navigateTo(`${loginUrl}?redirect_to=${encodeURIComponent(returnTo)}`, { external: true })
83
+ if (!isSignedIn()) {
84
+ loginStore.showLoginModalComponent = true
80
85
  return
81
86
  }
82
87
  if (!requestId) {
@@ -13,13 +13,19 @@ import {
13
13
  buildReLoginUrl,
14
14
  } from "../../services/campaignService"
15
15
  import { formatEmbargoDateTime } from "../../utils/embargo"
16
+ import { useLoginStore } from "../../stores/login"
16
17
 
17
18
  const route = useRoute()
18
19
  const { $i18n } = useNuxtApp()
19
20
  const locale = $i18n.locale
21
+ const loginStore = useLoginStore()
20
22
 
21
23
  const currentUser = inject<any>("currentUser", null)
22
24
  const loginUrl = inject<string>("loginUrl", "/")
25
+ // `currentUser` defaults to `{}` (never null) in the auth store even when
26
+ // signed out, so a raw truthiness check on it is always true. `.id` is the
27
+ // same "really signed in" signal the store's own getters/persisted state use.
28
+ const isSignedIn = () => !!unref(currentUser)?.id
23
29
 
24
30
  const keyId = Number(route.query.keyId)
25
31
 
@@ -70,9 +76,8 @@ function clampQuantity() {
70
76
  }
71
77
 
72
78
  onMounted(async () => {
73
- if (!unref(currentUser)) {
74
- const returnTo = typeof window !== "undefined" ? window.location.href : ""
75
- navigateTo(`${loginUrl}?redirect_to=${encodeURIComponent(returnTo)}`, { external: true })
79
+ if (!isSignedIn()) {
80
+ loginStore.showLoginModalComponent = true
76
81
  return
77
82
  }
78
83
  try {
@@ -118,7 +123,7 @@ onMounted(async () => {
118
123
  watch(
119
124
  () => unref(currentUser),
120
125
  (user, previous) => {
121
- if (previous && !user) {
126
+ if (previous?.id && !user?.id) {
122
127
  sessionExpired.value = true
123
128
  requestError.value = $i18n.t("keys.campaigns.error_session_expired")
124
129
  }