changes in api routes, ihave created almost functional settings form

This commit is contained in:
tdv
2025-11-07 18:07:23 +02:00
parent d0cece3001
commit 2ba75d0e87
8 changed files with 219 additions and 13 deletions

View File

@@ -3,10 +3,13 @@ import {
DropdownMenu, DropdownMenuContent,
DropdownMenuTrigger, DropdownMenuSeparator,
DropdownMenuItem, DropdownMenuLabel
} from '@/components/ui/dropdown-menu'
import { cn } from '@/lib/utils'
import { Settings } from 'lucide-vue-next'
import { RouterLink, useRoute } from 'vue-router'
} from '@/components/ui/dropdown-menu';
import { cn } from '@/lib/utils';
import { Settings } from 'lucide-vue-next';
import { RouterLink, useRoute } from 'vue-router';
import { api } from '@/lib/api';
import { onMounted, ref } from 'vue';
import type { Users } from '@/lib/interfaces';
const { customComponent } = defineProps<{ customComponent?: any }>()
@@ -24,6 +27,17 @@ function navLinkClass(prefix: string) {
isActive(prefix) ? 'text-primary' : 'text-muted-foreground hover:text-primary'
)
}
const username = ref<string | null>(null)
onMounted(async () => {
try {
const { data } = await api.get<Users>('/users/profile')
username.value = data?.username ?? null
} catch {
// 401s are already handled by interceptor; keep silent on others
}
})
</script>
<template>
@@ -60,7 +74,7 @@ function navLinkClass(prefix: string) {
</button>
</DropdownMenuTrigger>
<DropdownMenuContent class="w-48">
<DropdownMenuLabel>Admin</DropdownMenuLabel>
<DropdownMenuLabel>{{ username }}</DropdownMenuLabel>
<DropdownMenuSeparator />
<RouterLink to="/settings">
<DropdownMenuItem>Settings</DropdownMenuItem>

View File

@@ -3,6 +3,86 @@ import { Card, CardContent, CardHeader, CardTitle, CardFooter } from '@/componen
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
import { Sun, Moon } from 'lucide-vue-next'
import { useColorMode } from '@vueuse/core';
import { computed, onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import type { Users } from '@/lib/interfaces';
import { api } from '@/lib/api';
const mode = useColorMode()
const modeLabel = computed(() => {
if (mode.value === 'auto') return 'System'
return mode.value === 'dark' ? 'Dark' : 'Light'
})
type ChangePasswordDto = {
userId?: number
oldPassword: string
newPassword: string
}
const router = useRouter()
// ---- State ----
const user = ref<Users | null>(null)
const loadingProfile = ref(false)
const oldPassword = ref('')
const newPassword = ref('')
const submitting = ref(false)
const errorMsg = ref<string | null>(null)
const successMsg = ref<string | null>(null)
onMounted(async () => {
loadingProfile.value = true
try {
const { data } = await api.get<Users>('/users/profile')
user.value = data
} catch (err: any) {
// 401 is handled by interceptor; still surface generic error if needed
errorMsg.value = err?.response?.data?.error || 'Failed to load profile.'
} finally {
loadingProfile.value = false
}
})
async function submitChangePassword() {
errorMsg.value = null
successMsg.value = null
if (!user.value) {
errorMsg.value = 'User profile not loaded.'
return
}
if (!newPassword.value) {
errorMsg.value = 'New password is required.'
return
}
const payload: ChangePasswordDto = {
userId: user.value.id, // optional in DTO, but we provide it
oldPassword: oldPassword.value,
newPassword: newPassword.value,
}
submitting.value = true
try {
await api.post('/auth/change_password', payload)
successMsg.value = 'Password changed successfully.'
// Clear inputs
oldPassword.value = ''
newPassword.value = ''
} catch (err: any) {
errorMsg.value =
err?.response?.data?.error ||
err?.response?.data?.message ||
'Failed to change password.'
} finally {
submitting.value = false
}
}
function goBack() {
router.back()
}
</script>
<template>
<div class="w-full h-full flex items-center justify-center px-4">
@@ -12,24 +92,54 @@ import { Label } from '@/components/ui/label'
Settings
</CardTitle>
</CardHeader>
<Button variant="secondary" @click="goBack">Back</Button>
<CardContent>
<div class="grid gap-4 py-4">
<form class="grid gap-4 py-4" @submit.prevent="submitChangePassword">
<div class="grid grid-cols-4 items-center gap-4">
<Label for="username" class="text-right">Username</Label>
<Input id="username" class="col-span-3" />
<Input id="username" class="col-span-3"
:value="user?.username || (loadingProfile ? 'Loading…' : '')" disabled readonly />
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="current_password" class="text-right">Current password</Label>
<Input id="current_password" class="col-span-3" type="password" />
<Input id="current_password" class="col-span-3" type="password" v-model="oldPassword"
autocomplete="current-password" />
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="new_password" class="text-right">New password</Label>
<Input id="new_password" class="col-span-3" type="password" />
<Input id="new_password" class="col-span-3" type="password" v-model="newPassword"
autocomplete="new-password" required />
</div>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label class="text-right">Theme</Label>
<div class="col-span-3 flex items-center gap-3">
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button variant="outline" class="relative w-32 justify-start">
<Moon
class="h-[1.1rem] w-[1.1rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Sun
class="absolute h-[1.1rem] w-[1.1rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span class="ml-6 truncate">{{ modeLabel }}</span>
<span class="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem @click="mode = 'light'">Light</DropdownMenuItem>
<DropdownMenuItem @click="mode = 'dark'">Dark</DropdownMenuItem>
<DropdownMenuItem @click="mode = 'auto'">System</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
<div v-if="errorMsg" class="text-sm text-destructive">{{ errorMsg }}</div>
<div v-if="successMsg" class="text-sm text-green-600">{{ successMsg }}</div>
</form>
</CardContent>
<CardFooter>
<Button type="submit">Save changes</Button>
<Button type="submit" :disabled="submitting">
{{ submitting ? 'Saving…' : 'Save changes' }}
</Button>
</CardFooter>
</Card>
</div>