This commit is contained in:
Виталий Лавшонок
2025-11-04 22:45:03 +03:00
parent 42da6684ba
commit 994954c817
11 changed files with 197 additions and 109 deletions

View File

@@ -1,25 +1,36 @@
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import axios from '../../axios';
// Типы данных
// 🔹 Функция для декодирования JWT
function decodeJwt(token: string) {
const [, payload] = token.split('.');
const json = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));
return JSON.parse(decodeURIComponent(escape(json)));
}
// 🔹 Типы данных
interface AuthState {
jwt: string | null;
refreshToken: string | null;
username: string | null;
email: string | null; // <-- добавили email
id: string | null;
status: 'idle' | 'loading' | 'successful' | 'failed';
error: string | null;
}
// Инициализация состояния
// 🔹 Инициализация состояния
const initialState: AuthState = {
jwt: null,
refreshToken: null,
username: null,
email: null, // <-- добавили email
id: null,
status: 'idle',
error: null,
};
// AsyncThunk: Регистрация
// 🔹 AsyncThunk: Регистрация
export const registerUser = createAsyncThunk(
'auth/register',
async (
@@ -45,7 +56,7 @@ export const registerUser = createAsyncThunk(
},
);
// AsyncThunk: Логин
// 🔹 AsyncThunk: Логин
export const loginUser = createAsyncThunk(
'auth/login',
async (
@@ -66,7 +77,7 @@ export const loginUser = createAsyncThunk(
},
);
// AsyncThunk: Обновление токена
// 🔹 AsyncThunk: Обновление токена
export const refreshToken = createAsyncThunk(
'auth/refresh',
async ({ refreshToken }: { refreshToken: string }, { rejectWithValue }) => {
@@ -74,7 +85,7 @@ export const refreshToken = createAsyncThunk(
const response = await axios.post('/authentication/refresh', {
refreshToken,
});
return response.data; // { username }
return response.data; // { jwt, refreshToken }
} catch (err: any) {
return rejectWithValue(
err.response?.data?.message || 'Refresh token failed',
@@ -83,7 +94,7 @@ export const refreshToken = createAsyncThunk(
},
);
// AsyncThunk: Получение информации о пользователе
// 🔹 AsyncThunk: Получение информации о пользователе
export const fetchWhoAmI = createAsyncThunk(
'auth/whoami',
async (_, { rejectWithValue }) => {
@@ -98,10 +109,10 @@ export const fetchWhoAmI = createAsyncThunk(
},
);
// AsyncThunk: Загрузка токенов из localStorage
// 🔹 AsyncThunk: Загрузка токенов из localStorage
export const loadTokensFromLocalStorage = createAsyncThunk(
'auth/loadTokens',
async (_, {}) => {
async () => {
const jwt = localStorage.getItem('jwt');
const refreshToken = localStorage.getItem('refreshToken');
@@ -114,7 +125,7 @@ export const loadTokensFromLocalStorage = createAsyncThunk(
},
);
// Slice
// 🔹 Slice
const authSlice = createSlice({
name: 'auth',
initialState,
@@ -123,6 +134,8 @@ const authSlice = createSlice({
state.jwt = null;
state.refreshToken = null;
state.username = null;
state.email = null; // <-- очистка email
state.id = null;
state.status = 'idle';
state.error = null;
localStorage.removeItem('jwt');
@@ -136,118 +149,145 @@ const authSlice = createSlice({
state.status = 'loading';
state.error = null;
});
builder.addCase(
registerUser.fulfilled,
(
state,
action: PayloadAction<{ jwt: string; refreshToken: string }>,
) => {
state.status = 'successful';
state.jwt = action.payload.jwt;
state.refreshToken = action.payload.refreshToken;
axios.defaults.headers.common[
'Authorization'
] = `Bearer ${action.payload.jwt}`;
localStorage.setItem('jwt', action.payload.jwt);
localStorage.setItem(
'refreshToken',
action.payload.refreshToken,
);
},
);
builder.addCase(
registerUser.rejected,
(state, action: PayloadAction<any>) => {
state.status = 'failed';
state.error = action.payload;
},
);
builder.addCase(registerUser.fulfilled, (state, action) => {
state.status = 'successful';
state.jwt = action.payload.jwt;
state.refreshToken = action.payload.refreshToken;
// 🔸 Декодируем JWT
const decoded = decodeJwt(action.payload.jwt);
state.username =
decoded[
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name'
] || null;
state.email =
decoded[
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'
] || null;
state.id =
decoded[
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier'
] || null;
axios.defaults.headers.common[
'Authorization'
] = `Bearer ${action.payload.jwt}`;
localStorage.setItem('jwt', action.payload.jwt);
localStorage.setItem('refreshToken', action.payload.refreshToken);
});
builder.addCase(registerUser.rejected, (state, action) => {
state.status = 'failed';
state.error = action.payload as string;
});
// Логин
builder.addCase(loginUser.pending, (state) => {
state.status = 'loading';
state.error = null;
});
builder.addCase(
loginUser.fulfilled,
(
state,
action: PayloadAction<{ jwt: string; refreshToken: string }>,
) => {
state.status = 'successful';
state.jwt = action.payload.jwt;
state.refreshToken = action.payload.refreshToken;
axios.defaults.headers.common[
'Authorization'
] = `Bearer ${action.payload.jwt}`;
localStorage.setItem('jwt', action.payload.jwt);
localStorage.setItem(
'refreshToken',
action.payload.refreshToken,
);
},
);
builder.addCase(
loginUser.rejected,
(state, action: PayloadAction<any>) => {
state.status = 'failed';
state.error = action.payload;
},
);
builder.addCase(loginUser.fulfilled, (state, action) => {
state.status = 'successful';
state.jwt = action.payload.jwt;
state.refreshToken = action.payload.refreshToken;
// 🔸 Декодируем JWT
const decoded = decodeJwt(action.payload.jwt);
state.username =
decoded[
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name'
] || null;
state.email =
decoded[
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'
] || null;
state.id =
decoded[
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier'
] || null;
axios.defaults.headers.common[
'Authorization'
] = `Bearer ${action.payload.jwt}`;
localStorage.setItem('jwt', action.payload.jwt);
localStorage.setItem('refreshToken', action.payload.refreshToken);
});
builder.addCase(loginUser.rejected, (state, action) => {
state.status = 'failed';
state.error = action.payload as string;
});
// Обновление токена
builder.addCase(refreshToken.pending, (state) => {
state.status = 'loading';
state.error = null;
});
builder.addCase(
refreshToken.fulfilled,
(state, action: PayloadAction<{ username: string }>) => {
state.status = 'successful';
state.username = action.payload.username;
},
);
builder.addCase(
refreshToken.rejected,
(state, action: PayloadAction<any>) => {
state.status = 'failed';
state.error = action.payload;
},
);
builder.addCase(refreshToken.fulfilled, (state, action) => {
state.status = 'successful';
state.jwt = action.payload.jwt;
state.refreshToken = action.payload.refreshToken;
// 🔸 Декодируем JWT
const decoded = decodeJwt(action.payload.jwt);
state.username =
decoded[
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name'
] || null;
state.email =
decoded[
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'
] || null;
state.id =
decoded[
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier'
] || null;
axios.defaults.headers.common[
'Authorization'
] = `Bearer ${action.payload.jwt}`;
localStorage.setItem('jwt', action.payload.jwt);
localStorage.setItem('refreshToken', action.payload.refreshToken);
});
builder.addCase(refreshToken.rejected, (state, action) => {
state.status = 'failed';
state.error = action.payload as string;
});
// Получение информации о пользователе
builder.addCase(fetchWhoAmI.pending, (state) => {
state.status = 'loading';
state.error = null;
});
builder.addCase(
fetchWhoAmI.fulfilled,
(state, action: PayloadAction<{ username: string }>) => {
state.status = 'successful';
state.username = action.payload.username;
},
);
builder.addCase(
fetchWhoAmI.rejected,
(state, action: PayloadAction<any>) => {
state.status = 'failed';
state.error = action.payload;
},
);
builder.addCase(fetchWhoAmI.fulfilled, (state, action) => {
state.status = 'successful';
state.username = action.payload.username;
});
builder.addCase(fetchWhoAmI.rejected, (state, action) => {
state.status = 'failed';
state.error = action.payload as string;
});
// Загрузка токенов из localStorage
builder.addCase(
loadTokensFromLocalStorage.fulfilled,
(
state,
action: PayloadAction<{
jwt: string | null;
refreshToken: string | null;
}>,
) => {
(state, action) => {
state.jwt = action.payload.jwt;
state.refreshToken = action.payload.refreshToken;
if (action.payload.jwt) {
const decoded = decodeJwt(action.payload.jwt);
state.username =
decoded[
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name'
] || null;
state.email =
decoded[
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'
] || null;
state.id =
decoded[
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier'
] || null;
axios.defaults.headers.common[
'Authorization'
] = `Bearer ${action.payload.jwt}`;

View File

@@ -6,9 +6,16 @@ import axios from '../../axios';
// =====================
export interface Mission {
missionId: number;
id: number;
authorId: number;
name: string;
sortOrder: number;
difficulty: number;
tags: string[];
createdAt: string;
updatedAt: string;
timeLimitMilliseconds: number;
memoryLimitBytes: number;
statements: null;
}
export interface Member {