Qassure-frontend/src/store/authSlice.ts

180 lines
5.5 KiB
TypeScript

import { createSlice, createAsyncThunk, type PayloadAction } from '@reduxjs/toolkit';
import { authService, type LoginRequest, type LoginResponse, type LoginError, type GeneralError, type Permission } from '@/services/auth-service';
interface User {
id: string;
email: string;
first_name: string;
last_name: string;
}
interface AuthState {
user: User | null;
tenantId: string | null;
// tenant: Tenant | null;
roles: string[];
permissions: Permission[];
accessToken: string | null;
refreshToken: string | null;
tokenType: string | null;
expiresIn: number | null;
expiresAt: string | null;
isAuthenticated: boolean;
isLoading: boolean;
error: string | null;
}
const initialState: AuthState = {
user: null,
tenantId: null,
// tenant: null,
roles: [],
permissions: [],
accessToken: null,
refreshToken: null,
tokenType: null,
expiresIn: null,
expiresAt: null,
isAuthenticated: false,
isLoading: false,
error: null,
};
// Async thunk for login
export const loginAsync = createAsyncThunk<
{ data: LoginResponse['data']; message?: string },
LoginRequest,
{ rejectValue: LoginError }
>('auth/login', async (credentials, { rejectWithValue }) => {
try {
const response = await authService.login(credentials);
if (response.success) {
return { data: response.data, message: response.message };
}
return rejectWithValue(response as unknown as LoginError);
} catch (error: any) {
if (error.response?.data) {
return rejectWithValue(error.response.data as LoginError);
}
return rejectWithValue({
success: false,
error: {
code: 'UNKNOWN_ERROR',
message: error.message || 'An unexpected error occurred',
},
} as GeneralError);
}
});
// Async thunk for logout
export const logoutAsync = createAsyncThunk<{ message?: string }, void, { rejectValue: { message?: string } }>('auth/logout', async (_, { rejectWithValue }) => {
try {
const response = await authService.logout();
return { message: response.message };
} catch (error: any) {
// Even if API call fails, we should still logout locally
const errorData = error?.response?.data || { message: 'Logout failed' };
return rejectWithValue({ message: errorData.message });
}
});
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
logout: (state) => {
state.user = null;
state.tenantId = null;
// state.tenant = null;
state.roles = [];
state.permissions = [];
state.accessToken = null;
state.refreshToken = null;
state.tokenType = null;
state.expiresIn = null;
state.expiresAt = null;
state.isAuthenticated = false;
state.error = null;
},
clearError: (state) => {
state.error = null;
},
},
extraReducers: (builder) => {
builder
.addCase(loginAsync.pending, (state) => {
state.isLoading = true;
state.error = null;
})
.addCase(loginAsync.fulfilled, (state, action: PayloadAction<{ data: LoginResponse['data']; message?: string }>) => {
state.isLoading = false;
state.user = action.payload.data.user;
state.tenantId = action.payload.data.tenant_id;
// state.tenant = action.payload.data.tenant;
state.roles = action.payload.data.roles;
state.permissions = action.payload.data.permissions || [];
state.accessToken = action.payload.data.access_token;
state.refreshToken = action.payload.data.refresh_token;
state.tokenType = action.payload.data.token_type;
state.expiresIn = action.payload.data.expires_in;
state.expiresAt = action.payload.data.expires_at;
state.isAuthenticated = true;
state.error = null;
})
.addCase(loginAsync.rejected, (state, action) => {
state.isLoading = false;
state.isAuthenticated = false;
if (action.payload) {
const error = action.payload;
if ('error' in error && typeof error.error === 'object' && 'message' in error.error) {
// General error
state.error = error.error.message;
} else {
// Validation error or other
state.error = 'Validation failed';
}
} else {
state.error = action.error.message || 'Login failed';
}
})
.addCase(logoutAsync.pending, (state) => {
state.isLoading = true;
})
.addCase(logoutAsync.fulfilled, (state) => {
// Reset to initial state
state.user = null;
state.tenantId = null;
// state.tenant = null;
state.roles = [];
state.permissions = [];
state.accessToken = null;
state.refreshToken = null;
state.tokenType = null;
state.expiresIn = null;
state.expiresAt = null;
state.isAuthenticated = false;
state.isLoading = false;
state.error = null;
})
.addCase(logoutAsync.rejected, (state) => {
// Even if API call fails, clear local state
state.user = null;
state.tenantId = null;
// state.tenant = null;
state.roles = [];
state.permissions = [];
state.accessToken = null;
state.refreshToken = null;
state.tokenType = null;
state.expiresIn = null;
state.expiresAt = null;
state.isAuthenticated = false;
state.isLoading = false;
state.error = null;
});
},
});
export const { logout, clearError } = authSlice.actions;
export default authSlice.reducer;