Enhance user profile management and Gravatar integration
- Added email field to user profile API and frontend components for better user identification. - Implemented PATCH /map/api/me endpoint to update user email, enhancing user experience. - Introduced useGravatarUrl composable for generating Gravatar URLs based on user email. - Updated profile and layout components to display user avatars using Gravatar, improving visual consistency. - Enhanced development documentation to guide testing of navbar and profile features.
This commit is contained in:
@@ -19,30 +19,39 @@
|
||||
</NuxtLink>
|
||||
<div
|
||||
v-if="me"
|
||||
class="hidden md:flex items-center gap-2 shrink-0"
|
||||
class="hidden md:flex items-center gap-2 shrink-0 min-h-9"
|
||||
>
|
||||
<div
|
||||
class="tooltip tooltip-bottom shrink-0"
|
||||
:data-tip="live ? 'Connected to live updates' : 'Disconnected'"
|
||||
>
|
||||
<div class="flex items-center shrink-0 min-h-9">
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 text-xs text-base-content/70"
|
||||
class="inline-flex items-center gap-1.5 text-xs text-base-content/70 leading-none"
|
||||
:class="live ? 'text-success' : 'text-base-content/50'"
|
||||
>
|
||||
<span
|
||||
class="size-2 rounded-full"
|
||||
class="size-2 rounded-full shrink-0"
|
||||
:class="live ? 'bg-success animate-pulse' : 'bg-base-content/40'"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{{ live ? 'Live' : 'Offline' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="dropdown dropdown-end">
|
||||
<div class="dropdown dropdown-end flex items-center">
|
||||
<details ref="userDropdownRef" class="dropdown group">
|
||||
<summary class="btn btn-ghost btn-sm gap-2 cursor-pointer list-none [&::-webkit-details-marker]:hidden">
|
||||
<div class="avatar placeholder">
|
||||
<div class="bg-primary text-primary-content rounded-full w-8">
|
||||
<span class="text-sm font-medium">{{ (me.username || '?')[0].toUpperCase() }}</span>
|
||||
<summary class="btn btn-ghost btn-sm gap-2 flex items-center min-h-9 h-9 cursor-pointer list-none [&::-webkit-details-marker]:hidden">
|
||||
<div class="avatar">
|
||||
<div class="rounded-full w-8 h-8 overflow-hidden flex items-center justify-center">
|
||||
<img
|
||||
v-if="me.email && !gravatarErrorDesktop"
|
||||
:src="useGravatarUrl(me.email, 32)"
|
||||
alt=""
|
||||
class="w-full h-full object-cover"
|
||||
@error="gravatarErrorDesktop = true"
|
||||
>
|
||||
<div
|
||||
v-else
|
||||
class="bg-primary text-primary-content rounded-full w-8 h-8 flex items-center justify-center"
|
||||
>
|
||||
<span class="text-sm font-medium">{{ (me.username || '?')[0].toUpperCase() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="max-w-[8rem] truncate font-medium">{{ me.username }}</span>
|
||||
@@ -119,9 +128,21 @@
|
||||
<aside class="bg-base-200/95 backdrop-blur-xl min-h-full w-72 p-4 flex flex-col">
|
||||
<!-- Mobile: user + live when logged in -->
|
||||
<div v-if="!isLogin && me" class="flex items-center gap-3 pb-4 mb-2 border-b border-base-300/50">
|
||||
<div class="avatar placeholder">
|
||||
<div class="bg-primary text-primary-content rounded-full w-10">
|
||||
<span class="text-sm font-medium">{{ (me.username || '?')[0].toUpperCase() }}</span>
|
||||
<div class="avatar">
|
||||
<div class="rounded-full w-10 h-10 overflow-hidden flex items-center justify-center">
|
||||
<img
|
||||
v-if="me.email && !gravatarErrorDrawer"
|
||||
:src="useGravatarUrl(me.email, 40)"
|
||||
alt=""
|
||||
class="w-full h-full object-cover"
|
||||
@error="gravatarErrorDrawer = true"
|
||||
>
|
||||
<div
|
||||
v-else
|
||||
class="bg-primary text-primary-content rounded-full w-10 h-10 flex items-center justify-center"
|
||||
>
|
||||
<span class="text-sm font-medium">{{ (me.username || '?')[0].toUpperCase() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
@@ -185,6 +206,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { MeResponse } from '~/types/api'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const THEME_KEY = 'hnh-map-theme'
|
||||
@@ -202,7 +225,9 @@ function getInitialDark(): boolean {
|
||||
const title = ref('HnH Map')
|
||||
const dark = ref(false)
|
||||
const live = ref(false)
|
||||
const me = ref<{ username?: string; auths?: string[] } | null>(null)
|
||||
const me = useState<MeResponse | null>('me', () => null)
|
||||
const gravatarErrorDesktop = ref(false)
|
||||
const gravatarErrorDrawer = ref(false)
|
||||
const userDropdownRef = ref<HTMLDetailsElement | null>(null)
|
||||
const drawerCheckboxRef = ref<HTMLInputElement | null>(null)
|
||||
|
||||
@@ -248,6 +273,14 @@ watch(
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => me.value?.email,
|
||||
() => {
|
||||
gravatarErrorDesktop.value = false
|
||||
gravatarErrorDrawer.value = false
|
||||
}
|
||||
)
|
||||
|
||||
function onThemeToggle() {
|
||||
dark.value = !dark.value
|
||||
applyTheme()
|
||||
|
||||
Reference in New Issue
Block a user