feat: enhance child edit and view components with improved form handling and validation
All checks were successful
Chore App Build and Push Docker Images / build-and-push (push) Successful in 1m4s
All checks were successful
Chore App Build and Push Docker Images / build-and-push (push) Successful in 1m4s
- Added `requireDirty` prop to `EntityEditForm` for dirty state management. - Updated `ChildEditView` to handle initial data loading and image selection more robustly. - Refactored `ChildView` to remove unused reward dialog logic and prevent API calls in child mode. - Improved type definitions for form fields and initial data in `ChildEditView`. - Enhanced error handling in form submissions across components. - Implemented cross-tab logout synchronization on password reset in the auth store. - Added tests for login and entity edit form functionalities to ensure proper behavior. - Introduced global fetch interceptor for handling unauthorized responses. - Documented password reset flow and its implications on session management.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { isParentAuthenticated, loginUser } from '../auth'
|
||||
import { isParentAuthenticated, isUserLoggedIn, loginUser, initAuthSync } from '../auth'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
// Helper to mock localStorage
|
||||
@@ -30,4 +30,20 @@ describe('auth store - child mode on login', () => {
|
||||
await nextTick() // flush Vue watcher
|
||||
expect(isParentAuthenticated.value).toBe(false)
|
||||
})
|
||||
|
||||
it('logs out on cross-tab storage logout event', async () => {
|
||||
initAuthSync()
|
||||
isUserLoggedIn.value = true
|
||||
isParentAuthenticated.value = true
|
||||
|
||||
const logoutEvent = new StorageEvent('storage', {
|
||||
key: 'authSyncEvent',
|
||||
newValue: JSON.stringify({ type: 'logout', at: Date.now() }),
|
||||
})
|
||||
window.dispatchEvent(logoutEvent)
|
||||
|
||||
await nextTick()
|
||||
expect(isUserLoggedIn.value).toBe(false)
|
||||
expect(isParentAuthenticated.value).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ref, watch } from 'vue'
|
||||
|
||||
const hasLocalStorage =
|
||||
typeof localStorage !== 'undefined' && typeof localStorage.getItem === 'function'
|
||||
const AUTH_SYNC_EVENT_KEY = 'authSyncEvent'
|
||||
|
||||
export const isParentAuthenticated = ref(
|
||||
hasLocalStorage ? localStorage.getItem('isParentAuthenticated') === 'true' : false,
|
||||
@@ -9,6 +10,7 @@ export const isParentAuthenticated = ref(
|
||||
export const isUserLoggedIn = ref(false)
|
||||
export const isAuthReady = ref(false)
|
||||
export const currentUserId = ref('')
|
||||
let authSyncInitialized = false
|
||||
|
||||
watch(isParentAuthenticated, (val) => {
|
||||
if (hasLocalStorage && typeof localStorage.setItem === 'function') {
|
||||
@@ -33,12 +35,42 @@ export function loginUser() {
|
||||
isParentAuthenticated.value = false
|
||||
}
|
||||
|
||||
export function logoutUser() {
|
||||
function applyLoggedOutState() {
|
||||
isUserLoggedIn.value = false
|
||||
currentUserId.value = ''
|
||||
logoutParent()
|
||||
}
|
||||
|
||||
function broadcastLogoutEvent() {
|
||||
if (!hasLocalStorage || typeof localStorage.setItem !== 'function') return
|
||||
localStorage.setItem(AUTH_SYNC_EVENT_KEY, JSON.stringify({ type: 'logout', at: Date.now() }))
|
||||
}
|
||||
|
||||
export function logoutUser() {
|
||||
applyLoggedOutState()
|
||||
broadcastLogoutEvent()
|
||||
}
|
||||
|
||||
export function initAuthSync() {
|
||||
if (authSyncInitialized || typeof window === 'undefined') return
|
||||
authSyncInitialized = true
|
||||
|
||||
window.addEventListener('storage', (event) => {
|
||||
if (event.key !== AUTH_SYNC_EVENT_KEY || !event.newValue) return
|
||||
try {
|
||||
const payload = JSON.parse(event.newValue)
|
||||
if (payload?.type === 'logout') {
|
||||
applyLoggedOutState()
|
||||
if (!window.location.pathname.startsWith('/auth')) {
|
||||
window.location.href = '/auth/login'
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore malformed sync events.
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function checkAuth() {
|
||||
try {
|
||||
const res = await fetch('/api/auth/me', { method: 'GET' })
|
||||
@@ -47,12 +79,10 @@ export async function checkAuth() {
|
||||
currentUserId.value = data.id
|
||||
isUserLoggedIn.value = true
|
||||
} else {
|
||||
isUserLoggedIn.value = false
|
||||
currentUserId.value = ''
|
||||
logoutUser()
|
||||
}
|
||||
} catch {
|
||||
isUserLoggedIn.value = false
|
||||
currentUserId.value = ''
|
||||
logoutUser()
|
||||
}
|
||||
isAuthReady.value = true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user