@mundogamernetwork/shared-ui 1.16.12 → 1.16.14
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.
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { storeToRefs } from "pinia";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The account-menu toggle icon in the header top bar (not the dropdown
|
|
6
|
+
* panel itself — see MgHeaderUIUser for that). Every host used to inline
|
|
7
|
+
* this exact 3-line ClientOnly block, and all of them had the same gap:
|
|
8
|
+
* the very first thing painted — the ClientOnly #fallback, shown during
|
|
9
|
+
* SSR and until the client mounts — was a hardcoded guest icon. SSR can
|
|
10
|
+
* never know the real auth state here (the oauth_token cookie isn't
|
|
11
|
+
* readable server-side), so every real user saw themselves rendered as
|
|
12
|
+
* signed-out for a beat on every single page load, before the client's
|
|
13
|
+
* own getUser() call resolved and swapped in the real avatar. Showing a
|
|
14
|
+
* neutral skeleton instead — both in the fallback and while the client's
|
|
15
|
+
* own check is still in flight — means the icon is never confidently
|
|
16
|
+
* wrong, only "still figuring it out."
|
|
17
|
+
*/
|
|
18
|
+
const props = withDefaults(defineProps<{ size?: number }>(), { size: 32 });
|
|
19
|
+
|
|
20
|
+
const authStore = useAuthStore();
|
|
21
|
+
const { user, signedIn } = storeToRefs(authStore);
|
|
22
|
+
|
|
23
|
+
// Not every host's store has adopted `initialized` yet (it's the signal
|
|
24
|
+
// that getUser() has settled, one way or the other — see stores/auth.ts
|
|
25
|
+
// in any already-migrated host for the reference shape). Read it
|
|
26
|
+
// defensively instead of destructuring: a host whose store doesn't expose
|
|
27
|
+
// it treats the state as already known, skipping the skeleton rather than
|
|
28
|
+
// getting stuck showing it forever.
|
|
29
|
+
const initialized = computed(() => {
|
|
30
|
+
const raw = (authStore as unknown as { initialized?: boolean }).initialized;
|
|
31
|
+
return raw === undefined ? true : raw;
|
|
32
|
+
});
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<template>
|
|
36
|
+
<ClientOnly>
|
|
37
|
+
<div
|
|
38
|
+
v-if="!initialized"
|
|
39
|
+
class="mg-avatar-skeleton"
|
|
40
|
+
:style="{ width: `${props.size}px`, height: `${props.size}px` }"
|
|
41
|
+
/>
|
|
42
|
+
<MGIcon v-else-if="!signedIn" icon="profile" />
|
|
43
|
+
<img
|
|
44
|
+
v-else
|
|
45
|
+
:src="user?.avatar_url"
|
|
46
|
+
:width="props.size"
|
|
47
|
+
:height="props.size"
|
|
48
|
+
class="mg-avatar-img"
|
|
49
|
+
/>
|
|
50
|
+
<template #fallback>
|
|
51
|
+
<div
|
|
52
|
+
class="mg-avatar-skeleton"
|
|
53
|
+
:style="{ width: `${props.size}px`, height: `${props.size}px` }"
|
|
54
|
+
/>
|
|
55
|
+
</template>
|
|
56
|
+
</ClientOnly>
|
|
57
|
+
</template>
|
|
58
|
+
|
|
59
|
+
<style scoped lang="scss">
|
|
60
|
+
.mg-avatar-skeleton {
|
|
61
|
+
// No border-radius — MGN is angular by brand decision, no rounded
|
|
62
|
+
// corners anywhere.
|
|
63
|
+
background: linear-gradient(
|
|
64
|
+
90deg,
|
|
65
|
+
var(--skeleton-base, rgba(128, 128, 128, 0.18)) 25%,
|
|
66
|
+
var(--skeleton-shine, rgba(128, 128, 128, 0.32)) 50%,
|
|
67
|
+
var(--skeleton-base, rgba(128, 128, 128, 0.18)) 75%
|
|
68
|
+
);
|
|
69
|
+
background-size: 200% 100%;
|
|
70
|
+
animation: mg-avatar-skeleton-shimmer 1.4s ease-in-out infinite;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@keyframes mg-avatar-skeleton-shimmer {
|
|
74
|
+
0% {
|
|
75
|
+
background-position: 200% 0;
|
|
76
|
+
}
|
|
77
|
+
100% {
|
|
78
|
+
background-position: -200% 0;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.mg-avatar-img {
|
|
83
|
+
display: block;
|
|
84
|
+
}
|
|
85
|
+
</style>
|
|
@@ -125,27 +125,38 @@ const handleToggleOnline = () => {
|
|
|
125
125
|
<img v-else :src="user.avatar_url" width="40" height="40" class="avatar-img" />
|
|
126
126
|
</div>
|
|
127
127
|
|
|
128
|
-
<div v-if="hasStats" class="col p-0 d-flex align-items-center stats">
|
|
129
|
-
<div v-if="hasFollowingCount" class="col-6 p-0">
|
|
130
|
-
<span class="stat-value">{{ user?.following_count ?? 0 }}</span>
|
|
131
|
-
<span class="stat-label">{{ $t("account_menu.following") }}</span>
|
|
132
|
-
</div>
|
|
133
|
-
<div v-if="hasFollowersCount" class="col-6 p-0">
|
|
134
|
-
<span class="stat-value">{{ user?.followers_count ?? 0 }}</span>
|
|
135
|
-
<span class="stat-label">{{ $t("account_menu.followers") }}</span>
|
|
136
|
-
</div>
|
|
137
|
-
</div>
|
|
138
|
-
|
|
139
128
|
<!--
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
129
|
+
Optional. A host with its own identity content to show
|
|
130
|
+
beside the avatar (e.g. jobs-frontend's role + plan, which
|
|
131
|
+
is more useful at a glance than a raw follow count) can
|
|
132
|
+
fill this to fully replace the default stats-or-username
|
|
133
|
+
block below. Hosts that don't fill it get the exact same
|
|
134
|
+
default they always had — this slot changes nothing for
|
|
135
|
+
them.
|
|
145
136
|
-->
|
|
146
|
-
<
|
|
147
|
-
<
|
|
148
|
-
|
|
137
|
+
<slot name="identity" :has-stats="hasStats" :has-following-count="hasFollowingCount" :has-followers-count="hasFollowersCount">
|
|
138
|
+
<div v-if="hasStats" class="col p-0 d-flex align-items-center stats">
|
|
139
|
+
<div v-if="hasFollowingCount" class="col-6 p-0">
|
|
140
|
+
<span class="stat-value">{{ user?.following_count ?? 0 }}</span>
|
|
141
|
+
<span class="stat-label">{{ $t("account_menu.following") }}</span>
|
|
142
|
+
</div>
|
|
143
|
+
<div v-if="hasFollowersCount" class="col-6 p-0">
|
|
144
|
+
<span class="stat-value">{{ user?.followers_count ?? 0 }}</span>
|
|
145
|
+
<span class="stat-label">{{ $t("account_menu.followers") }}</span>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<!--
|
|
150
|
+
No following/followers concept on this host (e.g.
|
|
151
|
+
agency, token, club — nothing plausible found for
|
|
152
|
+
either count) — rather than leave this space reserved
|
|
153
|
+
and blank next to the avatar, show the username here
|
|
154
|
+
instead of in its own row below.
|
|
155
|
+
-->
|
|
156
|
+
<div v-else class="col p-0 d-flex align-items-center">
|
|
157
|
+
<span class="username username--inline">{{ user?.nickname || user?.name }}</span>
|
|
158
|
+
</div>
|
|
159
|
+
</slot>
|
|
149
160
|
</div>
|
|
150
161
|
|
|
151
162
|
<div v-if="hasStats" class="row info-row">
|
|
@@ -156,7 +156,7 @@ onMounted(async () => {
|
|
|
156
156
|
<span class="mg-xp-bar__badge">{{ level.current_level }}</span>
|
|
157
157
|
|
|
158
158
|
<div class="mg-xp-bar__meta">
|
|
159
|
-
<span v-if="
|
|
159
|
+
<span v-if="level.title" class="mg-xp-bar__title">{{ level.title }}</span>
|
|
160
160
|
<span class="mg-xp-bar__xp">{{ level.current_level_xp }} / {{ level.next_level_xp }} XP</span>
|
|
161
161
|
</div>
|
|
162
162
|
|