@mundogamernetwork/shared-ui 1.13.1 → 1.13.3

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.
@@ -72,8 +72,12 @@ onMounted(async () => {
72
72
  try {
73
73
  const response = await fetchGamificationSummary();
74
74
  summary.value = response?.data?.data ?? response?.data ?? null;
75
- } catch {
76
- // Never let a gamification hiccup break the header it sits in.
75
+ } catch (error) {
76
+ // Never let a gamification hiccup break the header it sits in — but say
77
+ // so. Swallowing this silently made a deployed bar that renders nothing
78
+ // indistinguishable from a bar that was never deployed, and cost hours
79
+ // of bundle forensics to tell apart.
80
+ console.warn("[MgXpLevelBar] gamification dashboard unavailable", error);
77
81
  summary.value = null;
78
82
  } finally {
79
83
  loaded.value = true;
@@ -82,13 +86,24 @@ onMounted(async () => {
82
86
  </script>
83
87
 
84
88
  <template>
89
+ <!--
90
+ The root renders unconditionally so the server and the client agree on
91
+ the node at hydration time. A v-if here produced a comment on the server
92
+ and an <a> on the client, and Vue bails out of hydrating a subtree whose
93
+ node type disagrees — leaving the server markup in place and never
94
+ mounting the component, so onMounted never ran and the bar was silently
95
+ dead. The skeleton is what both sides render before the fetch resolves.
96
+ -->
85
97
  <a
86
- v-if="loaded && level"
87
98
  :href="destination"
88
99
  class="mg-xp-bar"
89
- :class="{ 'mg-xp-bar--compact': props.compact }"
100
+ :class="{
101
+ 'mg-xp-bar--compact': props.compact,
102
+ 'mg-xp-bar--empty': loaded && !level,
103
+ }"
90
104
  @click="emit('navigate')"
91
105
  >
106
+ <template v-if="level">
92
107
  <div class="mg-xp-bar__top">
93
108
  <span class="mg-xp-bar__badge">{{ level.current_level }}</span>
94
109
 
@@ -107,6 +122,17 @@ onMounted(async () => {
107
122
  <div class="mg-xp-bar__track">
108
123
  <div class="mg-xp-bar__fill" :style="{ width: percent + '%' }" />
109
124
  </div>
125
+ </template>
126
+
127
+ <template v-else>
128
+ <div class="mg-xp-bar__top">
129
+ <span class="mg-xp-bar__badge mg-xp-bar__skeleton" />
130
+ <div class="mg-xp-bar__meta">
131
+ <span class="mg-xp-bar__skeleton mg-xp-bar__skeleton--line" />
132
+ </div>
133
+ </div>
134
+ <div class="mg-xp-bar__track" />
135
+ </template>
110
136
  </a>
111
137
  </template>
112
138
 
@@ -228,6 +254,22 @@ onMounted(async () => {
228
254
  transition: width 0.3s ease;
229
255
  }
230
256
 
257
+ // Hidden only after the fetch resolves with nothing — a client-side change
258
+ // that happens well after hydration, so it cannot cause a mismatch.
259
+ &--empty {
260
+ display: none;
261
+ }
262
+
263
+ &__skeleton {
264
+ background: rgba(128, 128, 128, 0.25);
265
+ border-color: transparent;
266
+
267
+ &--line {
268
+ height: 9px;
269
+ width: 70%;
270
+ }
271
+ }
272
+
231
273
  &--compact {
232
274
  padding: 0.35rem 0.5rem;
233
275
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mundogamernetwork/shared-ui",
3
- "version": "1.13.1",
3
+ "version": "1.13.3",
4
4
  "description": "Mundo Gamer Network - Shared UI Layer (Nuxt 3)",
5
5
  "type": "module",
6
6
  "main": "./nuxt.config.ts",