Files
frontend/app/composables/useAuth.ts
T

121 lines
3.8 KiB
TypeScript

import type {
ChangePasswordRequest,
CurrentUserResponse, EmailRequest, NotificationListResponse,
OkResponse,
OtpRequestResponse, PasswordLoginRequest, RegisterCitizenRequest,
RegisterCitizenResponse, RewardsResponse,
TokenResponse, VerifyOtpRequest, UpdateProfileRequest
} from "~/types/api";
export const useAuth = () => {
const route = useRoute()
const nuxtApp = useNuxtApp()
const token = useCookie<string | null>('auth_token', {
maxAge: 60 * 60 * 12,
sameSite: 'lax',
path: '/',
watch: true
})
const tokenState = useState<string | null>('auth_token_state', () => token.value ?? null)
type UserContext = CurrentUserResponse['user']
const user = useState<UserContext | null>('auth_user', () => null)
const isSyncingUser = useState<boolean>('auth_syncing', () => false)
const isAuthenticated = computed(() => !!(tokenState.value || token.value))
watch(() => token.value, (newVal) => {
tokenState.value = newVal || null
})
const syncUser = async (redirectOnSuccess = false) => {
const tokenVal = tokenState.value || token.value
if (!tokenVal || isSyncingUser.value) return
isSyncingUser.value = true
try {
const res = await fetchCurrentUser()
if (res.ok) {
user.value = res.user
if (res.user.must_change_password && route.path !== '/auth/change-password') {
await nuxtApp.runWithContext(() => navigateTo('/auth/change-password'))
} else if (redirectOnSuccess && route.path.startsWith('/auth')) {
await nuxtApp.runWithContext(() => navigateTo('/dashboard'))
}
}
} catch (error) {
console.error('[Auth] Sync failed, logging out:', error)
logout()
} finally {
isSyncingUser.value = false
}
}
const handleLoginSuccess = async (jwtToken: string) => {
token.value = jwtToken
tokenState.value = jwtToken
await syncUser(true)
}
const logout = () => {
token.value = null
tokenState.value = null
user.value = null
nuxtApp.runWithContext(() => navigateTo('/auth/login'))
}
const register = (body: RegisterCitizenRequest) =>
useApiFetch<RegisterCitizenResponse>('/api/v1/auth/register', { method: 'POST', body })
const requestOtp = (body: EmailRequest) =>
useApiFetch<OtpRequestResponse>('/api/v1/auth/otp/request', { method: 'POST', body })
const verifyOtp = (body: VerifyOtpRequest) =>
useApiFetch<TokenResponse>('/api/v1/auth/otp/verify', { method: 'POST', body })
const loginWithPassword = (body: PasswordLoginRequest) =>
useApiFetch<TokenResponse>('/api/v1/auth/login/password', { method: 'POST', body })
const changePassword = (body: ChangePasswordRequest) =>
useApiFetch<OkResponse>('/api/v1/auth/change-password', { method: 'POST', body })
const updateProfile = (body: UpdateProfileRequest) =>
useApiFetch<CurrentUserResponse>('/api/v1/me', { method: 'PATCH', body })
const fetchCurrentUser = () =>
useApiFetch<CurrentUserResponse>('/api/v1/me', { method: 'GET' })
const fetchRewards = (query?: { page?: number; per_page?: number }) =>
useApiFetch<RewardsResponse>('/api/v1/me/rewards', { method: 'GET', query })
const fetchNotifications = (query?: { page?: number; per_page?: number }) =>
useApiFetch<NotificationListResponse>('/api/v1/notifications', { method: 'GET', query })
const markNotificationRead = (id: number) =>
useApiFetch<OkResponse>(`/api/v1/notifications/${id}/read`, { method: 'PATCH' })
return {
user,
token,
tokenState,
isSyncingUser,
isAuthenticated,
handleLoginSuccess,
syncUser,
logout,
register,
requestOtp,
verifyOtp,
loginWithPassword,
changePassword,
updateProfile,
fetchCurrentUser,
fetchRewards,
fetchNotifications,
markNotificationRead,
}
}