refactor: Transition to authentication system and remove identity management

- Replace identity management with a new authentication system across the application.
- Update App.vue to integrate LoginDialog and remove PasswordDialog.
- Modify Navbar.vue to handle user authentication state and logout functionality.
- Enhance Home.vue to display user information upon login.
- Implement routing changes in index.ts to enforce authentication requirements for protected routes.
This commit is contained in:
padreug 2025-07-29 23:10:31 +02:00
parent 5ceb12ca3b
commit be4ab13b32
11 changed files with 1065 additions and 96 deletions

View file

@ -0,0 +1,237 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { User } from 'lucide-vue-next'
import { auth } from '@/composables/useAuth'
import { toast } from 'vue-sonner'
interface Props {
isOpen: boolean
}
interface Emits {
(e: 'update:isOpen', value: boolean): void
(e: 'success'): void
}
defineProps<Props>()
const emit = defineEmits<Emits>()
// Form states
const activeTab = ref('login')
const isLoading = ref(false)
const error = ref('')
// Login form
const loginForm = ref({
username: '',
password: ''
})
// Register form
const registerForm = ref({
username: '',
email: '',
password: '',
password_repeat: ''
})
const canLogin = computed(() => {
return loginForm.value.username.trim() && loginForm.value.password.trim()
})
const canRegister = computed(() => {
const { username, password, password_repeat } = registerForm.value
return username.trim() && password.trim() && password === password_repeat && password.length >= 6
})
async function handleLogin() {
if (!canLogin.value) return
try {
isLoading.value = true
error.value = ''
await auth.login({
username: loginForm.value.username,
password: loginForm.value.password
})
toast.success('Login successful!')
emit('success')
handleClose()
} catch (err) {
error.value = err instanceof Error ? err.message : 'Login failed'
toast.error('Login failed. Please check your credentials.')
} finally {
isLoading.value = false
}
}
async function handleRegister() {
if (!canRegister.value) return
try {
isLoading.value = true
error.value = ''
await auth.register({
username: registerForm.value.username,
email: registerForm.value.email || undefined,
password: registerForm.value.password,
password_repeat: registerForm.value.password_repeat
})
toast.success('Registration successful!')
emit('success')
handleClose()
} catch (err) {
error.value = err instanceof Error ? err.message : 'Registration failed'
toast.error('Registration failed. Please try again.')
} finally {
isLoading.value = false
}
}
function handleClose() {
emit('update:isOpen', false)
// Reset forms
loginForm.value = { username: '', password: '' }
registerForm.value = { username: '', email: '', password: '', password_repeat: '' }
error.value = ''
isLoading.value = false
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Enter') {
if (activeTab.value === 'login') {
handleLogin()
} else {
handleRegister()
}
}
}
</script>
<template>
<Dialog :open="isOpen" @update:open="handleClose">
<DialogContent class="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle class="flex items-center gap-2">
<User class="w-5 h-5" />
Authentication
</DialogTitle>
<DialogDescription>
Login to your account or create a new one
</DialogDescription>
</DialogHeader>
<Tabs v-model="activeTab" class="w-full">
<TabsList class="grid w-full grid-cols-2">
<TabsTrigger value="login">Login</TabsTrigger>
<TabsTrigger value="register">Register</TabsTrigger>
</TabsList>
<TabsContent value="login" class="space-y-4">
<div class="space-y-4">
<div class="space-y-2">
<Label for="login-username">Username or Email</Label>
<Input
id="login-username"
v-model="loginForm.username"
placeholder="Enter your username or email"
:disabled="isLoading"
@keydown="handleKeydown"
/>
</div>
<div class="space-y-2">
<Label for="login-password">Password</Label>
<Input
id="login-password"
type="password"
v-model="loginForm.password"
placeholder="Enter your password"
:disabled="isLoading"
@keydown="handleKeydown"
/>
</div>
<p v-if="error && activeTab === 'login'" class="text-sm text-destructive">
{{ error }}
</p>
<Button
@click="handleLogin"
:disabled="isLoading || !canLogin"
class="w-full"
>
<span v-if="isLoading" class="animate-spin mr-2"></span>
Login
</Button>
</div>
</TabsContent>
<TabsContent value="register" class="space-y-4">
<div class="space-y-4">
<div class="space-y-2">
<Label for="register-username">Username</Label>
<Input
id="register-username"
v-model="registerForm.username"
placeholder="Choose a username"
:disabled="isLoading"
@keydown="handleKeydown"
/>
</div>
<div class="space-y-2">
<Label for="register-email">Email (optional)</Label>
<Input
id="register-email"
type="email"
v-model="registerForm.email"
placeholder="Enter your email"
:disabled="isLoading"
@keydown="handleKeydown"
/>
</div>
<div class="space-y-2">
<Label for="register-password">Password</Label>
<Input
id="register-password"
type="password"
v-model="registerForm.password"
placeholder="Choose a password"
:disabled="isLoading"
@keydown="handleKeydown"
/>
</div>
<div class="space-y-2">
<Label for="register-password-repeat">Confirm Password</Label>
<Input
id="register-password-repeat"
type="password"
v-model="registerForm.password_repeat"
placeholder="Confirm your password"
:disabled="isLoading"
@keydown="handleKeydown"
/>
</div>
<p v-if="error && activeTab === 'register'" class="text-sm text-destructive">
{{ error }}
</p>
<Button
@click="handleRegister"
:disabled="isLoading || !canRegister"
class="w-full"
>
<span v-if="isLoading" class="animate-spin mr-2"></span>
Register
</Button>
</div>
</TabsContent>
</Tabs>
</DialogContent>
</Dialog>
</template>

View file

@ -0,0 +1,70 @@
<script setup lang="ts">
import { computed } from 'vue'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { User, LogOut, Settings } from 'lucide-vue-next'
import { auth } from '@/composables/useAuth'
import { toast } from 'vue-sonner'
const userDisplay = computed(() => auth.userDisplay.value)
async function handleLogout() {
try {
await auth.logout()
toast.success('Logged out successfully')
} catch (error) {
toast.error('Failed to logout')
}
}
</script>
<template>
<div v-if="userDisplay" class="space-y-4">
<Card>
<CardHeader>
<CardTitle class="flex items-center gap-2">
<User class="w-5 h-5" />
User Profile
</CardTitle>
<CardDescription>
Your account information
</CardDescription>
</CardHeader>
<CardContent class="space-y-4">
<div class="grid gap-4">
<div class="flex items-center justify-between">
<span class="text-sm font-medium">Name:</span>
<span class="text-sm">{{ userDisplay.name }}</span>
</div>
<div v-if="userDisplay.username" class="flex items-center justify-between">
<span class="text-sm font-medium">Username:</span>
<span class="text-sm">{{ userDisplay.username }}</span>
</div>
<div v-if="userDisplay.email" class="flex items-center justify-between">
<span class="text-sm font-medium">Email:</span>
<span class="text-sm">{{ userDisplay.email }}</span>
</div>
<div class="flex items-center justify-between">
<span class="text-sm font-medium">User ID:</span>
<Badge variant="secondary" class="text-xs">{{ userDisplay.shortId }}</Badge>
</div>
</div>
<div class="flex gap-2 pt-4">
<Button variant="outline" size="sm" class="flex-1">
<Settings class="w-4 h-4 mr-2" />
Settings
</Button>
<Button variant="destructive" size="sm" @click="handleLogout" class="flex-1">
<LogOut class="w-4 h-4 mr-2" />
Logout
</Button>
</div>
</CardContent>
</Card>
</div>
</template>