124 lines
3.5 KiB
Vue
124 lines
3.5 KiB
Vue
<template>
|
|
<div class="flex flex-col items-center space-y-4">
|
|
<!-- Avatar -->
|
|
<div class="relative">
|
|
<div
|
|
class="w-24 h-24 rounded-full overflow-hidden border-4 border-base-300 shadow-lg"
|
|
:class="{ 'animate-pulse bg-base-200': loading }"
|
|
>
|
|
<div
|
|
v-if="!loading && avatarSvg"
|
|
v-html="avatarSvg"
|
|
class="w-full h-full"
|
|
/>
|
|
<div
|
|
v-else-if="!loading"
|
|
class="w-full h-full bg-gradient-to-br from-primary to-primary/70 flex items-center justify-center text-primary-content text-2xl font-bold"
|
|
>
|
|
{{ initials }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Change avatar button -->
|
|
<button
|
|
@click="regenerateAvatar"
|
|
:disabled="loading"
|
|
class="absolute -bottom-1 -right-1 w-8 h-8 bg-primary hover:bg-primary/80 disabled:bg-base-300 text-primary-content rounded-full flex items-center justify-center shadow-lg transition-colors"
|
|
:title="$t('profile.regenerate_avatar')"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- User name -->
|
|
<div class="text-center">
|
|
<p class="font-semibold text-base-content">{{ displayName }}</p>
|
|
<p class="text-sm text-base-content/60" v-if="userId">ID: {{ userId }}</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
const props = defineProps({
|
|
userId: String,
|
|
firstName: String,
|
|
lastName: String,
|
|
avatarId: String,
|
|
editable: {
|
|
type: Boolean,
|
|
default: true
|
|
}
|
|
})
|
|
|
|
const emit = defineEmits(['avatar-changed'])
|
|
|
|
const loading = ref(false)
|
|
const avatarSvg = ref('')
|
|
|
|
// Computed properties
|
|
const displayName = computed(() => {
|
|
const first = props.firstName || ''
|
|
const last = props.lastName || ''
|
|
return `${first} ${last}`.trim() || 'User'
|
|
})
|
|
|
|
const initials = computed(() => {
|
|
const first = props.firstName?.charAt(0) || ''
|
|
const last = props.lastName?.charAt(0) || ''
|
|
return (first + last).toUpperCase() || '?'
|
|
})
|
|
|
|
// Generate avatar via DiceBear API
|
|
const generateAvatar = async (seed) => {
|
|
if (!seed) return
|
|
|
|
try {
|
|
loading.value = true
|
|
|
|
// Use DiceBear API to generate SVG avatar
|
|
const response = await fetch(`https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(seed)}&backgroundColor=b6e3f4,c0aede,d1d4f9`)
|
|
|
|
if (response.ok) {
|
|
avatarSvg.value = await response.text()
|
|
} else {
|
|
console.error('Failed to generate avatar:', response.status)
|
|
}
|
|
} catch (error) {
|
|
console.error('Error generating avatar:', error)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
// Generate new random avatar ID
|
|
const regenerateAvatar = async () => {
|
|
if (!props.editable || loading.value) return
|
|
|
|
const newAvatarId = Math.random().toString(36).substring(2, 15)
|
|
|
|
// Update avatar locally first
|
|
await generateAvatar(newAvatarId)
|
|
|
|
// Notify parent about avatar change
|
|
emit('avatar-changed', newAvatarId)
|
|
}
|
|
|
|
// Watch avatarId changes
|
|
watch(() => props.avatarId, (newAvatarId) => {
|
|
if (newAvatarId) {
|
|
generateAvatar(newAvatarId)
|
|
}
|
|
}, { immediate: true })
|
|
|
|
// If no avatarId, generate deterministic one based on userId
|
|
onMounted(async () => {
|
|
if (!props.avatarId && props.userId) {
|
|
// Build deterministic ID from userId
|
|
const fallbackSeed = props.userId
|
|
await generateAvatar(fallbackSeed)
|
|
}
|
|
})
|
|
</script>
|