Replace composable with Pinia store for persistent auth state
This commit is contained in:
@@ -93,30 +93,32 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { User, ChevronUp } from 'lucide-vue-next'
|
import { User, ChevronUp } from 'lucide-vue-next'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const authStore = useAuthStore()
|
||||||
const currentYear = new Date().getFullYear()
|
const currentYear = new Date().getFullYear()
|
||||||
const isMemberMenuOpen = ref(false)
|
const isMemberMenuOpen = ref(false)
|
||||||
|
|
||||||
// Use global auth state
|
// Reactive auth state from store
|
||||||
const { isLoggedIn, isAdmin, logout: authLogout, checkAuth } = useAuth()
|
const isLoggedIn = computed(() => authStore.isLoggedIn)
|
||||||
|
const isAdmin = computed(() => authStore.isAdmin)
|
||||||
|
|
||||||
const toggleMemberMenu = () => {
|
const toggleMemberMenu = () => {
|
||||||
isMemberMenuOpen.value = !isMemberMenuOpen.value
|
isMemberMenuOpen.value = !isMemberMenuOpen.value
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
await authLogout()
|
await authStore.logout()
|
||||||
isMemberMenuOpen.value = false
|
isMemberMenuOpen.value = false
|
||||||
router.push('/')
|
router.push('/')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check auth status on mount
|
// Check auth status on mount
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
checkAuth()
|
authStore.checkAuth()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Close menu when clicking outside
|
// Close menu when clicking outside
|
||||||
|
|||||||
@@ -396,13 +396,15 @@ import { useRoute } from 'vue-router'
|
|||||||
import { Menu, X, ChevronDown } from 'lucide-vue-next'
|
import { Menu, X, ChevronDown } from 'lucide-vue-next'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const authStore = useAuthStore()
|
||||||
const isMobileMenuOpen = ref(false)
|
const isMobileMenuOpen = ref(false)
|
||||||
const mobileSubmenu = ref(null)
|
const mobileSubmenu = ref(null)
|
||||||
const mannschaften = ref([])
|
const mannschaften = ref([])
|
||||||
const hasGalleryImages = ref(false)
|
const hasGalleryImages = ref(false)
|
||||||
|
|
||||||
// Use global auth state
|
// Reactive auth state from store
|
||||||
const { isLoggedIn, userRole, isAdmin, checkAuth } = useAuth()
|
const isLoggedIn = computed(() => authStore.isLoggedIn)
|
||||||
|
const isAdmin = computed(() => authStore.isAdmin)
|
||||||
|
|
||||||
// Automatisches Setzen des Submenus basierend auf der Route
|
// Automatisches Setzen des Submenus basierend auf der Route
|
||||||
const currentSubmenu = computed(() => {
|
const currentSubmenu = computed(() => {
|
||||||
@@ -486,7 +488,7 @@ const checkGalleryImages = async () => {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadMannschaften()
|
loadMannschaften()
|
||||||
checkGalleryImages()
|
checkGalleryImages()
|
||||||
checkAuth()
|
authStore.checkAuth()
|
||||||
})
|
})
|
||||||
|
|
||||||
const toggleSubmenu = (menu) => {
|
const toggleSubmenu = (menu) => {
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
import { ref, computed } from 'vue'
|
|
||||||
|
|
||||||
// Global state (shared across all components)
|
|
||||||
const isLoggedIn = ref(false)
|
|
||||||
const user = ref(null)
|
|
||||||
const userRole = ref(null)
|
|
||||||
|
|
||||||
export const useAuth = () => {
|
|
||||||
const isAdmin = computed(() => {
|
|
||||||
return userRole.value === 'admin' || userRole.value === 'vorstand'
|
|
||||||
})
|
|
||||||
|
|
||||||
const checkAuth = async () => {
|
|
||||||
try {
|
|
||||||
const response = await $fetch('/api/auth/status')
|
|
||||||
isLoggedIn.value = response.isLoggedIn
|
|
||||||
user.value = response.user
|
|
||||||
userRole.value = response.role
|
|
||||||
return response
|
|
||||||
} catch (error) {
|
|
||||||
isLoggedIn.value = false
|
|
||||||
user.value = null
|
|
||||||
userRole.value = null
|
|
||||||
return { isLoggedIn: false }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const logout = async () => {
|
|
||||||
try {
|
|
||||||
await $fetch('/api/auth/logout', { method: 'POST' })
|
|
||||||
isLoggedIn.value = false
|
|
||||||
user.value = null
|
|
||||||
userRole.value = null
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Logout fehlgeschlagen:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
isLoggedIn,
|
|
||||||
user,
|
|
||||||
userRole,
|
|
||||||
isAdmin,
|
|
||||||
checkAuth,
|
|
||||||
logout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
devtools: { enabled: true },
|
devtools: { enabled: true },
|
||||||
|
|
||||||
modules: ['@nuxtjs/tailwindcss'],
|
modules: ['@nuxtjs/tailwindcss', '@pinia/nuxt'],
|
||||||
|
|
||||||
nitro: {
|
nitro: {
|
||||||
preset: 'node-server',
|
preset: 'node-server',
|
||||||
|
|||||||
47
package-lock.json
generated
47
package-lock.json
generated
@@ -9,10 +9,12 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@pinia/nuxt": "^0.11.2",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"nodemailer": "^7.0.9",
|
"nodemailer": "^7.0.9",
|
||||||
"nuxt": "^4.1.3",
|
"nuxt": "^4.1.3",
|
||||||
|
"pinia": "^3.0.3",
|
||||||
"vue": "^3.5.22"
|
"vue": "^3.5.22"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -2530,6 +2532,21 @@
|
|||||||
"url": "https://opencollective.com/parcel"
|
"url": "https://opencollective.com/parcel"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@pinia/nuxt": {
|
||||||
|
"version": "0.11.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@pinia/nuxt/-/nuxt-0.11.2.tgz",
|
||||||
|
"integrity": "sha512-CgvSWpbktxxWBV7ModhAcsExsQZqpPq6vMYEe9DexmmY6959ev8ukL4iFhr/qov2Nb9cQAWd7niFDnaWkN+FHg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@nuxt/kit": "^3.9.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/posva"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"pinia": "^3.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@pkgjs/parseargs": {
|
"node_modules/@pkgjs/parseargs": {
|
||||||
"version": "0.11.0",
|
"version": "0.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||||
@@ -8123,6 +8140,36 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pinia": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/devtools-api": "^7.7.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/posva"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": ">=4.4.4",
|
||||||
|
"vue": "^2.7.0 || ^3.5.11"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pinia/node_modules/@vue/devtools-api": {
|
||||||
|
"version": "7.7.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.7.tgz",
|
||||||
|
"integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/devtools-kit": "^7.7.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pirates": {
|
"node_modules/pirates": {
|
||||||
"version": "4.0.7",
|
"version": "4.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
|
||||||
|
|||||||
@@ -13,10 +13,12 @@
|
|||||||
"postinstall": "nuxt prepare"
|
"postinstall": "nuxt prepare"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@pinia/nuxt": "^0.11.2",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"nodemailer": "^7.0.9",
|
"nodemailer": "^7.0.9",
|
||||||
"nuxt": "^4.1.3",
|
"nuxt": "^4.1.3",
|
||||||
|
"pinia": "^3.0.3",
|
||||||
"vue": "^3.5.22"
|
"vue": "^3.5.22"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ import { useRouter } from 'vue-router'
|
|||||||
import { AlertCircle, Check, Loader2, Lock } from 'lucide-vue-next'
|
import { AlertCircle, Check, Loader2, Lock } from 'lucide-vue-next'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { checkAuth } = useAuth()
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
email: '',
|
email: '',
|
||||||
@@ -118,20 +118,11 @@ const handleLogin = async () => {
|
|||||||
successMessage.value = ''
|
successMessage.value = ''
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await $fetch('/api/auth/login', {
|
const response = await authStore.login(formData.value.email, formData.value.password)
|
||||||
method: 'POST',
|
|
||||||
body: {
|
|
||||||
email: formData.value.email,
|
|
||||||
password: formData.value.password
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
successMessage.value = 'Anmeldung erfolgreich! Sie werden weitergeleitet...'
|
successMessage.value = 'Anmeldung erfolgreich! Sie werden weitergeleitet...'
|
||||||
|
|
||||||
// Update global auth state
|
|
||||||
await checkAuth()
|
|
||||||
|
|
||||||
// Redirect based on role
|
// Redirect based on role
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (response.user.role === 'admin' || response.user.role === 'vorstand') {
|
if (response.user.role === 'admin' || response.user.role === 'vorstand') {
|
||||||
|
|||||||
@@ -40,5 +40,19 @@
|
|||||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJlbWFpbCI6ImFkbWluQGhhcmhlaW1lcnRjLmRlIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNzYxMDQ4NTgwLCJleHAiOjE3NjE2NTMzODB9._ZEOavin4scWHMI9ofk3MBNdx98K7Q_JossQYxUX4rQ",
|
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJlbWFpbCI6ImFkbWluQGhhcmhlaW1lcnRjLmRlIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNzYxMDQ4NTgwLCJleHAiOjE3NjE2NTMzODB9._ZEOavin4scWHMI9ofk3MBNdx98K7Q_JossQYxUX4rQ",
|
||||||
"createdAt": "2025-10-21T12:09:40.103Z",
|
"createdAt": "2025-10-21T12:09:40.103Z",
|
||||||
"expiresAt": "2025-10-28T12:09:40.103Z"
|
"expiresAt": "2025-10-28T12:09:40.103Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1761049028598",
|
||||||
|
"userId": "1",
|
||||||
|
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJlbWFpbCI6ImFkbWluQGhhcmhlaW1lcnRjLmRlIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNzYxMDQ5MDI4LCJleHAiOjE3NjE2NTM4Mjh9.FsC7CJpxVYVJ824vEPcZol4lKsCKU2I7-L644yeZuMw",
|
||||||
|
"createdAt": "2025-10-21T12:17:08.598Z",
|
||||||
|
"expiresAt": "2025-10-28T12:17:08.598Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1761049044973",
|
||||||
|
"userId": "1",
|
||||||
|
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJlbWFpbCI6ImFkbWluQGhhcmhlaW1lcnRjLmRlIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNzYxMDQ5MDQ0LCJleHAiOjE3NjE2NTM4NDR9.fhaWO80YN8qKy9zBCK5nauGWBT29hmr3KxAK_Vlab4E",
|
||||||
|
"createdAt": "2025-10-21T12:17:24.973Z",
|
||||||
|
"expiresAt": "2025-10-28T12:17:24.973Z"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -8,6 +8,6 @@
|
|||||||
"phone": "",
|
"phone": "",
|
||||||
"active": true,
|
"active": true,
|
||||||
"created": "2025-10-21T00:00:00.000Z",
|
"created": "2025-10-21T00:00:00.000Z",
|
||||||
"lastLogin": "2025-10-21T12:09:40.103Z"
|
"lastLogin": "2025-10-21T12:17:24.973Z"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
58
stores/auth.js
Normal file
58
stores/auth.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export const useAuthStore = defineStore('auth', {
|
||||||
|
state: () => ({
|
||||||
|
isLoggedIn: false,
|
||||||
|
user: null,
|
||||||
|
role: null
|
||||||
|
}),
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
isAdmin: (state) => {
|
||||||
|
return state.role === 'admin' || state.role === 'vorstand'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
async checkAuth() {
|
||||||
|
try {
|
||||||
|
const response = await $fetch('/api/auth/status')
|
||||||
|
this.isLoggedIn = response.isLoggedIn
|
||||||
|
this.user = response.user
|
||||||
|
this.role = response.role
|
||||||
|
return response
|
||||||
|
} catch (error) {
|
||||||
|
this.isLoggedIn = false
|
||||||
|
this.user = null
|
||||||
|
this.role = null
|
||||||
|
return { isLoggedIn: false }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async login(email, password) {
|
||||||
|
const response = await $fetch('/api/auth/login', {
|
||||||
|
method: 'POST',
|
||||||
|
body: { email, password }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
await this.checkAuth()
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
},
|
||||||
|
|
||||||
|
async logout() {
|
||||||
|
try {
|
||||||
|
await $fetch('/api/auth/logout', { method: 'POST' })
|
||||||
|
this.isLoggedIn = false
|
||||||
|
this.user = null
|
||||||
|
this.role = null
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Logout fehlgeschlagen:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
Reference in New Issue
Block a user