feat: stabilize admin dashboard, refactor group management, implement PWA, and fix routing
This commit is contained in:
+113
-1
@@ -3,6 +3,66 @@
|
||||
<h1 class="text-2xl font-bold mb-6">Настройки</h1>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Profile Settings -->
|
||||
<div class="bg-slate-800 border border-slate-700 rounded-xl p-6">
|
||||
<h2 class="text-lg font-semibold mb-4 flex items-center gap-2">
|
||||
<span>👤</span> Личный профиль
|
||||
</h2>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<img :src="profileForm.avatar" class="w-16 h-16 rounded-full bg-slate-700 object-cover border-2 border-blue-500/30" />
|
||||
<div class="flex-1">
|
||||
<label class="text-xs text-slate-500 uppercase font-bold">Ссылка на аватар</label>
|
||||
<input v-model="profileForm.avatar" class="w-full mt-1 bg-slate-900 border border-slate-700 rounded px-3 py-1.5 text-sm text-slate-200 outline-none focus:border-blue-500" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<div>
|
||||
<label class="text-xs text-slate-500 uppercase font-bold">Имя (Display Name)</label>
|
||||
<input v-model="profileForm.displayName" class="w-full mt-1 bg-slate-900 border border-slate-700 rounded px-3 py-1.5 text-sm text-slate-200 outline-none focus:border-blue-500" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-xs text-slate-500 uppercase font-bold">О себе (Bio)</label>
|
||||
<textarea v-model="profileForm.bio" rows="3" class="w-full mt-1 bg-slate-900 border border-slate-700 rounded px-3 py-1.5 text-sm text-slate-200 outline-none focus:border-blue-500 resize-none"></textarea>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@click="saveProfile"
|
||||
:disabled="saving"
|
||||
class="w-full py-2 bg-blue-600 hover:bg-blue-500 text-white rounded-lg font-medium transition disabled:opacity-50"
|
||||
>
|
||||
{{ saving ? 'Сохранение...' : 'Сохранить изменения' }}
|
||||
</button>
|
||||
<p v-if="saveMessage" :class="saveMessage.type === 'error' ? 'text-red-400' : 'text-emerald-400'" class="text-center text-xs mt-2">
|
||||
{{ saveMessage.text }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- My Groups -->
|
||||
<div class="bg-slate-800 border border-slate-700 rounded-xl p-6">
|
||||
<h2 class="text-lg font-semibold mb-4 flex items-center gap-2">
|
||||
<span>👥</span> Мои Группы
|
||||
</h2>
|
||||
<div v-if="userGroups.length === 0" class="text-slate-500 text-sm py-4">
|
||||
Вы не состоите ни в одной группе.
|
||||
</div>
|
||||
<div v-else class="space-y-3">
|
||||
<div v-for="g in userGroups" :key="g.id" class="flex items-center justify-between p-3 bg-slate-900/50 rounded-lg border border-slate-700">
|
||||
<div>
|
||||
<div class="font-medium text-slate-200">{{ g.name }}</div>
|
||||
<div class="text-xs text-slate-500">{{ g.description }}</div>
|
||||
</div>
|
||||
<span :class="g.membershipRole === 'admin' ? 'text-purple-400 bg-purple-900/20' : 'text-blue-400 bg-blue-900/20'" class="text-[10px] uppercase font-bold px-2 py-0.5 rounded border border-current/20">
|
||||
{{ g.membershipRole }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Push Notifications -->
|
||||
<div class="bg-slate-800 border border-slate-700 rounded-xl p-6">
|
||||
<h2 class="text-lg font-semibold mb-4 flex items-center gap-2">
|
||||
@@ -72,10 +132,59 @@ definePageMeta({
|
||||
middleware: 'auth'
|
||||
})
|
||||
|
||||
const { user } = useAuth()
|
||||
const { isSupported, subscription, requestPermission, subscribe: pushSubscribe } = usePush()
|
||||
const { achievements, loadAchievements } = useAchievements()
|
||||
|
||||
const permissionGranted = ref(false)
|
||||
const saving = ref(false)
|
||||
const saveMessage = ref(null)
|
||||
const userGroups = ref([])
|
||||
|
||||
const profileForm = reactive({
|
||||
displayName: '',
|
||||
username: '',
|
||||
bio: '',
|
||||
avatar: ''
|
||||
})
|
||||
|
||||
const loadProfile = async () => {
|
||||
try {
|
||||
const data = await $fetch(`/api/users/${user.value.id}/profile`, {
|
||||
headers: { Authorization: `Bearer ${useAuth().token.value}` }
|
||||
})
|
||||
const groupsData = await $fetch('/api/user/groups', {
|
||||
headers: { Authorization: `Bearer ${useAuth().token.value}` }
|
||||
})
|
||||
userGroups.value = groupsData.groups || []
|
||||
|
||||
profileForm.displayName = data.profile.displayName || ''
|
||||
profileForm.username = data.profile.user.username || ''
|
||||
profileForm.bio = data.profile.bio || ''
|
||||
profileForm.avatar = data.profile.avatar || ''
|
||||
} catch (e) {
|
||||
console.error('Failed to load profile', e)
|
||||
}
|
||||
}
|
||||
|
||||
const saveProfile = async () => {
|
||||
saving.value = true
|
||||
saveMessage.value = null
|
||||
try {
|
||||
await $fetch('/api/user/profile', {
|
||||
method: 'PUT',
|
||||
body: profileForm,
|
||||
headers: { Authorization: `Bearer ${useAuth().token.value}` }
|
||||
})
|
||||
saveMessage.value = { type: 'success', text: 'Профиль обновлен успешно!' }
|
||||
// Update local user data if needed
|
||||
user.value.username = profileForm.username
|
||||
} catch (e) {
|
||||
saveMessage.value = { type: 'error', text: e.data?.message || 'Ошибка при сохранении' }
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const enablePush = async () => {
|
||||
const granted = await requestPermission()
|
||||
@@ -91,6 +200,9 @@ const subscribe = async () => {
|
||||
|
||||
onMounted(async () => {
|
||||
permissionGranted.value = Notification.permission === 'granted'
|
||||
await loadAchievements()
|
||||
await Promise.all([
|
||||
loadAchievements(),
|
||||
loadProfile()
|
||||
])
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user