1037 lines
31 KiB
TypeScript
1037 lines
31 KiB
TypeScript
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
|
import axios from '../../axios';
|
|
import { toastError } from '../../lib/toastNotification';
|
|
|
|
// =====================
|
|
// Типы
|
|
// =====================
|
|
|
|
export interface Solution {
|
|
id: number;
|
|
missionId: number;
|
|
language: string;
|
|
languageVersion: string;
|
|
sourceCode: string;
|
|
status: string;
|
|
time: string;
|
|
testerState: string;
|
|
testerErrorCode: string;
|
|
testerMessage: string;
|
|
currentTest: number;
|
|
amountOfTests: number;
|
|
}
|
|
|
|
export interface Submission {
|
|
id: number;
|
|
userId: number;
|
|
solution: Solution;
|
|
contestId: number;
|
|
contestName: string;
|
|
sourceType: string;
|
|
}
|
|
|
|
export interface Mission {
|
|
id: number;
|
|
authorId: number;
|
|
name: string;
|
|
difficulty: number;
|
|
tags: string[];
|
|
timeLimitMilliseconds: number;
|
|
memoryLimitBytes: number;
|
|
statements: string;
|
|
}
|
|
|
|
export interface Member {
|
|
userId: number;
|
|
username: string;
|
|
role: string;
|
|
}
|
|
|
|
export interface Group {
|
|
groupId: number;
|
|
groupName: string;
|
|
}
|
|
|
|
export interface Contest {
|
|
id: number;
|
|
name: string;
|
|
description?: string;
|
|
scheduleType: 'AlwaysOpen' | 'FixedWindow' | 'RollingWindow';
|
|
visibility: 'Public' | 'GroupPrivate';
|
|
startsAt?: string;
|
|
endsAt?: string;
|
|
attemptDurationMinutes?: number;
|
|
maxAttempts?: number;
|
|
allowEarlyFinish?: boolean;
|
|
groupId?: number;
|
|
groupName?: string;
|
|
missions?: Mission[];
|
|
articles?: any[];
|
|
members?: Member[];
|
|
}
|
|
|
|
export interface Attempt {
|
|
attemptId: number;
|
|
contestId: number;
|
|
startedAt: string;
|
|
expiresAt: string;
|
|
finished: boolean;
|
|
submissions?: Submission[];
|
|
results?: any[];
|
|
}
|
|
|
|
interface ContestsResponse {
|
|
hasNextPage: boolean;
|
|
contests: Contest[];
|
|
}
|
|
|
|
interface MembersPage {
|
|
members: Member[];
|
|
hasNextPage: boolean;
|
|
}
|
|
|
|
export interface CreateContestBody {
|
|
name: string;
|
|
description?: string;
|
|
scheduleType: 'AlwaysOpen' | 'FixedWindow' | 'RollingWindow';
|
|
visibility: 'Public' | 'GroupPrivate';
|
|
startsAt?: string;
|
|
endsAt?: string;
|
|
attemptDurationMinutes?: number;
|
|
maxAttempts?: number;
|
|
allowEarlyFinish?: boolean;
|
|
groupId?: number;
|
|
groupName?: string;
|
|
missionIds?: number[];
|
|
articleIds?: number[];
|
|
}
|
|
|
|
// =====================
|
|
// Состояние
|
|
// =====================
|
|
|
|
type Status = 'idle' | 'loading' | 'successful' | 'failed';
|
|
|
|
interface ContestsState {
|
|
fetchContests: {
|
|
contests: Contest[];
|
|
hasNextPage: boolean;
|
|
status: Status;
|
|
error?: string;
|
|
};
|
|
fetchContestById: {
|
|
contest: Contest;
|
|
status: Status;
|
|
error?: string;
|
|
};
|
|
createContest: {
|
|
contest: Contest;
|
|
status: Status;
|
|
error?: string;
|
|
};
|
|
fetchMySubmissions: {
|
|
submissions: Submission[];
|
|
status: Status;
|
|
error?: string;
|
|
};
|
|
updateContest: {
|
|
contest: Contest;
|
|
status: Status;
|
|
error?: string;
|
|
};
|
|
deleteContest: {
|
|
status: Status;
|
|
error?: string;
|
|
};
|
|
fetchMyContests: {
|
|
contests: Contest[];
|
|
status: Status;
|
|
error?: string;
|
|
};
|
|
fetchRegisteredContests: {
|
|
contests: Contest[];
|
|
hasNextPage: boolean;
|
|
status: Status;
|
|
error?: string;
|
|
};
|
|
|
|
// NEW:
|
|
fetchContestMembers: {
|
|
members: Member[];
|
|
hasNextPage: boolean;
|
|
status: Status;
|
|
error?: string;
|
|
};
|
|
addOrUpdateMember: {
|
|
status: Status;
|
|
error?: string;
|
|
};
|
|
deleteContestMember: {
|
|
status: Status;
|
|
error?: string;
|
|
};
|
|
|
|
startAttempt: {
|
|
attempt?: Attempt;
|
|
status: Status;
|
|
error?: string;
|
|
};
|
|
fetchMyAttemptsInContest: {
|
|
attempts: Attempt[];
|
|
status: Status;
|
|
error?: string;
|
|
};
|
|
fetchMyAllAttempts: {
|
|
attempts: Attempt[];
|
|
status: Status;
|
|
error?: string;
|
|
};
|
|
fetchMyActiveAttempt: {
|
|
attempt?: Attempt | null;
|
|
status: Status;
|
|
error?: string;
|
|
};
|
|
|
|
checkRegistration: {
|
|
registered: boolean;
|
|
status: Status;
|
|
error?: string;
|
|
};
|
|
|
|
fetchUpcomingEligible: {
|
|
contests: Contest[];
|
|
status: Status;
|
|
error?: string;
|
|
};
|
|
|
|
fetchParticipating: {
|
|
contests: Contest[];
|
|
hasNextPage: boolean;
|
|
status: Status;
|
|
error?: string;
|
|
};
|
|
}
|
|
|
|
const emptyContest: Contest = {
|
|
id: 0,
|
|
name: '',
|
|
description: '',
|
|
scheduleType: 'AlwaysOpen',
|
|
visibility: 'Public',
|
|
startsAt: '',
|
|
endsAt: '',
|
|
attemptDurationMinutes: 0,
|
|
maxAttempts: 0,
|
|
allowEarlyFinish: false,
|
|
missions: [],
|
|
articles: [],
|
|
members: [],
|
|
};
|
|
|
|
const initialState: ContestsState = {
|
|
fetchContests: { contests: [], hasNextPage: false, status: 'idle' },
|
|
fetchContestById: { contest: emptyContest, status: 'idle' },
|
|
createContest: { contest: emptyContest, status: 'idle' },
|
|
fetchMySubmissions: { submissions: [], status: 'idle' },
|
|
updateContest: { contest: emptyContest, status: 'idle' },
|
|
deleteContest: { status: 'idle' },
|
|
fetchMyContests: { contests: [], status: 'idle' },
|
|
fetchRegisteredContests: {
|
|
contests: [],
|
|
hasNextPage: false,
|
|
status: 'idle',
|
|
},
|
|
|
|
fetchContestMembers: { members: [], hasNextPage: false, status: 'idle' },
|
|
addOrUpdateMember: { status: 'idle' },
|
|
deleteContestMember: { status: 'idle' },
|
|
|
|
startAttempt: { status: 'idle' },
|
|
fetchMyAttemptsInContest: { attempts: [], status: 'idle' },
|
|
fetchMyAllAttempts: { attempts: [], status: 'idle' },
|
|
fetchMyActiveAttempt: { attempt: null, status: 'idle' },
|
|
|
|
checkRegistration: { registered: false, status: 'idle' },
|
|
fetchUpcomingEligible: { contests: [], status: 'idle' },
|
|
fetchParticipating: {
|
|
contests: [],
|
|
hasNextPage: false,
|
|
status: 'idle',
|
|
error: undefined,
|
|
},
|
|
};
|
|
|
|
// =====================
|
|
// Async Thunks
|
|
// =====================
|
|
|
|
// Existing ----------------------------
|
|
|
|
export const fetchParticipatingContests = createAsyncThunk(
|
|
'contests/fetchParticipating',
|
|
async (
|
|
params: { page?: number; pageSize?: number } = {},
|
|
{ rejectWithValue },
|
|
) => {
|
|
try {
|
|
const { page = 0, pageSize = 100 } = params;
|
|
const response = await axios.get<ContestsResponse>(
|
|
'/contests/participating',
|
|
{ params: { page, pageSize } },
|
|
);
|
|
return response.data;
|
|
} catch (err: any) {
|
|
return rejectWithValue(err.response?.data);
|
|
}
|
|
},
|
|
);
|
|
|
|
export const fetchMySubmissions = createAsyncThunk(
|
|
'contests/fetchMySubmissions',
|
|
async (contestId: number, { rejectWithValue }) => {
|
|
try {
|
|
const response = await axios.get<Submission[]>(
|
|
`/contests/${contestId}/submissions/my`,
|
|
);
|
|
return response.data;
|
|
} catch (err: any) {
|
|
return rejectWithValue(err.response?.data);
|
|
}
|
|
},
|
|
);
|
|
|
|
export const fetchContests = createAsyncThunk(
|
|
'contests/fetchAll',
|
|
async (
|
|
params: {
|
|
page?: number;
|
|
pageSize?: number;
|
|
groupId?: number | null;
|
|
} = {},
|
|
{ rejectWithValue },
|
|
) => {
|
|
try {
|
|
const { page = 0, pageSize = 100, groupId } = params;
|
|
const response = await axios.get<ContestsResponse>('/contests', {
|
|
params: { page, pageSize, groupId },
|
|
});
|
|
return response.data;
|
|
} catch (err: any) {
|
|
return rejectWithValue(err.response?.data);
|
|
}
|
|
},
|
|
);
|
|
|
|
export const fetchContestById = createAsyncThunk(
|
|
'contests/fetchById',
|
|
async (id: number, { rejectWithValue }) => {
|
|
try {
|
|
const response = await axios.get<Contest>(`/contests/${id}`);
|
|
return response.data;
|
|
} catch (err: any) {
|
|
return rejectWithValue(err.response?.data);
|
|
}
|
|
},
|
|
);
|
|
|
|
export const createContest = createAsyncThunk(
|
|
'contests/create',
|
|
async (contestData: CreateContestBody, { rejectWithValue }) => {
|
|
try {
|
|
const response = await axios.post<Contest>(
|
|
'/contests',
|
|
contestData,
|
|
);
|
|
return response.data;
|
|
} catch (err: any) {
|
|
return rejectWithValue(err.response?.data);
|
|
}
|
|
},
|
|
);
|
|
|
|
export const updateContest = createAsyncThunk(
|
|
'contests/update',
|
|
async (
|
|
{
|
|
contestId,
|
|
...contestData
|
|
}: { contestId: number } & CreateContestBody,
|
|
{ rejectWithValue },
|
|
) => {
|
|
try {
|
|
const response = await axios.put<Contest>(
|
|
`/contests/${contestId}`,
|
|
contestData,
|
|
);
|
|
return response.data;
|
|
} catch (err: any) {
|
|
return rejectWithValue(err.response?.data);
|
|
}
|
|
},
|
|
);
|
|
|
|
export const deleteContest = createAsyncThunk(
|
|
'contests/delete',
|
|
async (contestId: number, { rejectWithValue }) => {
|
|
try {
|
|
await axios.delete(`/contests/${contestId}`);
|
|
return contestId;
|
|
} catch (err: any) {
|
|
return rejectWithValue(err.response?.data);
|
|
}
|
|
},
|
|
);
|
|
|
|
export const fetchMyContests = createAsyncThunk(
|
|
'contests/fetchMyContests',
|
|
async (_, { rejectWithValue }) => {
|
|
try {
|
|
const response = await axios.get<Contest[]>('/contests/my');
|
|
return response.data;
|
|
} catch (err: any) {
|
|
return rejectWithValue(err.response?.data);
|
|
}
|
|
},
|
|
);
|
|
|
|
export const fetchRegisteredContests = createAsyncThunk(
|
|
'contests/fetchRegisteredContests',
|
|
async (
|
|
params: { page?: number; pageSize?: number } = {},
|
|
{ rejectWithValue },
|
|
) => {
|
|
try {
|
|
const { page = 0, pageSize = 100 } = params;
|
|
const response = await axios.get<ContestsResponse>(
|
|
'/contests/registered',
|
|
{ params: { page, pageSize } },
|
|
);
|
|
return response.data;
|
|
} catch (err: any) {
|
|
return rejectWithValue(err.response?.data);
|
|
}
|
|
},
|
|
);
|
|
|
|
// NEW -----------------------------------
|
|
|
|
// Add or update member
|
|
export const addOrUpdateContestMember = createAsyncThunk(
|
|
'contests/addOrUpdateMember',
|
|
async (
|
|
{
|
|
contestId,
|
|
member,
|
|
}: { contestId: number; member: { userId: number; role: string } },
|
|
{ rejectWithValue },
|
|
) => {
|
|
try {
|
|
const response = await axios.post<Member[]>(
|
|
`/contests/${contestId}/members`,
|
|
member,
|
|
);
|
|
return { contestId, members: response.data };
|
|
} catch (err: any) {
|
|
return rejectWithValue(err.response?.data);
|
|
}
|
|
},
|
|
);
|
|
|
|
// Delete member
|
|
export const deleteContestMember = createAsyncThunk(
|
|
'contests/deleteContestMember',
|
|
async (
|
|
{ contestId, memberId }: { contestId: number; memberId: number },
|
|
{ rejectWithValue },
|
|
) => {
|
|
try {
|
|
await axios.delete(`/contests/${contestId}/members/${memberId}`);
|
|
return { contestId, memberId };
|
|
} catch (err: any) {
|
|
return rejectWithValue(err.response?.data);
|
|
}
|
|
},
|
|
);
|
|
|
|
// Start attempt
|
|
export const startContestAttempt = createAsyncThunk(
|
|
'contests/startContestAttempt',
|
|
async (contestId: number, { rejectWithValue }) => {
|
|
try {
|
|
const response = await axios.post<Attempt>(
|
|
`/contests/${contestId}/attempts`,
|
|
);
|
|
return response.data;
|
|
} catch (err: any) {
|
|
return rejectWithValue(err.response?.data);
|
|
}
|
|
},
|
|
);
|
|
|
|
// My attempts in contest
|
|
export const fetchMyAttemptsInContest = createAsyncThunk(
|
|
'contests/fetchMyAttemptsInContest',
|
|
async (contestId: number, { rejectWithValue }) => {
|
|
try {
|
|
const response = await axios.get<Attempt[]>(
|
|
`/contests/${contestId}/attempts/my`,
|
|
);
|
|
return response.data;
|
|
} catch (err: any) {
|
|
return rejectWithValue(err.response?.data);
|
|
}
|
|
},
|
|
);
|
|
|
|
// Members with pagination
|
|
export const fetchContestMembers = createAsyncThunk(
|
|
'contests/fetchContestMembers',
|
|
async (
|
|
{
|
|
contestId,
|
|
page = 0,
|
|
pageSize = 25,
|
|
}: { contestId: number; page?: number; pageSize?: number },
|
|
{ rejectWithValue },
|
|
) => {
|
|
try {
|
|
const response = await axios.get<MembersPage>(
|
|
`/contests/${contestId}/members`,
|
|
{ params: { page, pageSize } },
|
|
);
|
|
return { contestId, ...response.data };
|
|
} catch (err: any) {
|
|
return rejectWithValue(err.response?.data);
|
|
}
|
|
},
|
|
);
|
|
|
|
// Check registration
|
|
export const checkContestRegistration = createAsyncThunk(
|
|
'contests/checkRegistration',
|
|
async (contestId: number, { rejectWithValue }) => {
|
|
try {
|
|
const response = await axios.get<{ registered: boolean }>(
|
|
`/contests/${contestId}/registered`,
|
|
);
|
|
return { contestId, registered: response.data.registered };
|
|
} catch (err: any) {
|
|
return rejectWithValue(err.response?.data);
|
|
}
|
|
},
|
|
);
|
|
|
|
// Upcoming eligible contests
|
|
export const fetchUpcomingEligibleContests = createAsyncThunk(
|
|
'contests/fetchUpcomingEligible',
|
|
async (_, { rejectWithValue }) => {
|
|
try {
|
|
const response = await axios.get<Contest[]>(
|
|
'/contests/upcoming/eligible',
|
|
);
|
|
return response.data;
|
|
} catch (err: any) {
|
|
return rejectWithValue(err.response?.data);
|
|
}
|
|
},
|
|
);
|
|
|
|
// All my attempts
|
|
export const fetchMyAllAttempts = createAsyncThunk(
|
|
'contests/fetchMyAllAttempts',
|
|
async (_, { rejectWithValue }) => {
|
|
try {
|
|
const response = await axios.get<Attempt[]>(
|
|
'/contests/attempts/my',
|
|
);
|
|
return response.data;
|
|
} catch (err: any) {
|
|
return rejectWithValue(err.response?.data);
|
|
}
|
|
},
|
|
);
|
|
|
|
// Active attempt
|
|
export const fetchMyActiveAttempt = createAsyncThunk(
|
|
'contests/fetchMyActiveAttempt',
|
|
async (contestId: number, { rejectWithValue }) => {
|
|
try {
|
|
const response = await axios.get<Attempt | null>(
|
|
`/contests/${contestId}/attempts/my/active`,
|
|
);
|
|
return { contestId, attempt: response.data };
|
|
} catch (err: any) {
|
|
return rejectWithValue(err.response?.data);
|
|
}
|
|
},
|
|
);
|
|
|
|
// =====================
|
|
// Slice
|
|
// =====================
|
|
|
|
const contestsSlice = createSlice({
|
|
name: 'contests',
|
|
initialState,
|
|
reducers: {
|
|
setContestStatus: (
|
|
state,
|
|
action: PayloadAction<{ key: keyof ContestsState; status: Status }>,
|
|
) => {
|
|
const { key, status } = action.payload;
|
|
if (state[key]) {
|
|
(state[key] as any).status = status;
|
|
}
|
|
},
|
|
},
|
|
extraReducers: (builder) => {
|
|
// ——— YOUR EXISTING HANDLERS (unchanged) ———
|
|
|
|
builder.addCase(fetchMySubmissions.pending, (state) => {
|
|
state.fetchMySubmissions.status = 'loading';
|
|
state.fetchMySubmissions.error = undefined;
|
|
});
|
|
builder.addCase(
|
|
fetchMySubmissions.fulfilled,
|
|
(state, action: PayloadAction<Submission[]>) => {
|
|
state.fetchMySubmissions.status = 'successful';
|
|
state.fetchMySubmissions.submissions = action.payload;
|
|
},
|
|
);
|
|
builder.addCase(fetchMySubmissions.rejected, (state, action: any) => {
|
|
state.fetchMySubmissions.status = 'failed';
|
|
|
|
const errors = action.payload.errors as Record<string, string[]>;
|
|
Object.values(errors).forEach((messages) => {
|
|
messages.forEach((msg) => {
|
|
toastError(msg);
|
|
});
|
|
});
|
|
});
|
|
|
|
builder.addCase(fetchContests.pending, (state) => {
|
|
state.fetchContests.status = 'loading';
|
|
});
|
|
builder.addCase(
|
|
fetchContests.fulfilled,
|
|
(state, action: PayloadAction<ContestsResponse>) => {
|
|
state.fetchContests.status = 'successful';
|
|
state.fetchContests.contests = action.payload.contests;
|
|
state.fetchContests.hasNextPage = action.payload.hasNextPage;
|
|
},
|
|
);
|
|
builder.addCase(fetchContests.rejected, (state, action: any) => {
|
|
state.fetchContests.status = 'failed';
|
|
|
|
const errors = action.payload.errors as Record<string, string[]>;
|
|
Object.values(errors).forEach((messages) => {
|
|
messages.forEach((msg) => {
|
|
toastError(msg);
|
|
});
|
|
});
|
|
});
|
|
|
|
builder.addCase(fetchContestById.pending, (state) => {
|
|
state.fetchContestById.status = 'loading';
|
|
});
|
|
builder.addCase(
|
|
fetchContestById.fulfilled,
|
|
(state, action: PayloadAction<Contest>) => {
|
|
state.fetchContestById.status = 'successful';
|
|
state.fetchContestById.contest = action.payload;
|
|
},
|
|
);
|
|
builder.addCase(fetchContestById.rejected, (state, action: any) => {
|
|
state.fetchContestById.status = 'failed';
|
|
|
|
const errors = action.payload.errors as Record<string, string[]>;
|
|
Object.values(errors).forEach((messages) => {
|
|
messages.forEach((msg) => {
|
|
toastError(msg);
|
|
});
|
|
});
|
|
});
|
|
|
|
builder.addCase(createContest.pending, (state) => {
|
|
state.createContest.status = 'loading';
|
|
});
|
|
builder.addCase(
|
|
createContest.fulfilled,
|
|
(state, action: PayloadAction<Contest>) => {
|
|
state.createContest.status = 'successful';
|
|
state.createContest.contest = action.payload;
|
|
},
|
|
);
|
|
builder.addCase(createContest.rejected, (state, action: any) => {
|
|
state.createContest.status = 'failed';
|
|
|
|
const errors = action.payload.errors as Record<string, string[]>;
|
|
Object.values(errors).forEach((messages) => {
|
|
messages.forEach((msg) => {
|
|
toastError(msg);
|
|
});
|
|
});
|
|
});
|
|
|
|
builder.addCase(updateContest.pending, (state) => {
|
|
state.updateContest.status = 'loading';
|
|
});
|
|
builder.addCase(
|
|
updateContest.fulfilled,
|
|
(state, action: PayloadAction<Contest>) => {
|
|
state.updateContest.status = 'successful';
|
|
state.updateContest.contest = action.payload;
|
|
},
|
|
);
|
|
builder.addCase(updateContest.rejected, (state, action: any) => {
|
|
state.updateContest.status = 'failed';
|
|
|
|
const errors = action.payload.errors as Record<string, string[]>;
|
|
Object.values(errors).forEach((messages) => {
|
|
messages.forEach((msg) => {
|
|
toastError(msg);
|
|
});
|
|
});
|
|
});
|
|
|
|
builder.addCase(deleteContest.pending, (state) => {
|
|
state.deleteContest.status = 'loading';
|
|
});
|
|
builder.addCase(
|
|
deleteContest.fulfilled,
|
|
(state, action: PayloadAction<number>) => {
|
|
state.deleteContest.status = 'successful';
|
|
state.fetchContests.contests =
|
|
state.fetchContests.contests.filter(
|
|
(c) => c.id !== action.payload,
|
|
);
|
|
state.fetchMyContests.contests =
|
|
state.fetchMyContests.contests.filter(
|
|
(c) => c.id !== action.payload,
|
|
);
|
|
},
|
|
);
|
|
builder.addCase(deleteContest.rejected, (state, action: any) => {
|
|
state.deleteContest.status = 'failed';
|
|
|
|
const errors = action.payload.errors as Record<string, string[]>;
|
|
Object.values(errors).forEach((messages) => {
|
|
messages.forEach((msg) => {
|
|
toastError(msg);
|
|
});
|
|
});
|
|
});
|
|
|
|
builder.addCase(fetchMyContests.pending, (state) => {
|
|
state.fetchMyContests.status = 'loading';
|
|
});
|
|
builder.addCase(
|
|
fetchMyContests.fulfilled,
|
|
(state, action: PayloadAction<Contest[]>) => {
|
|
state.fetchMyContests.status = 'successful';
|
|
state.fetchMyContests.contests = action.payload;
|
|
},
|
|
);
|
|
builder.addCase(fetchMyContests.rejected, (state, action: any) => {
|
|
state.fetchMyContests.status = 'failed';
|
|
|
|
const errors = action.payload.errors as Record<string, string[]>;
|
|
Object.values(errors).forEach((messages) => {
|
|
messages.forEach((msg) => {
|
|
toastError(msg);
|
|
});
|
|
});
|
|
});
|
|
|
|
builder.addCase(fetchRegisteredContests.pending, (state) => {
|
|
state.fetchRegisteredContests.status = 'loading';
|
|
});
|
|
builder.addCase(
|
|
fetchRegisteredContests.fulfilled,
|
|
(state, action: PayloadAction<ContestsResponse>) => {
|
|
state.fetchRegisteredContests.status = 'successful';
|
|
state.fetchRegisteredContests.contests =
|
|
action.payload.contests;
|
|
state.fetchRegisteredContests.hasNextPage =
|
|
action.payload.hasNextPage;
|
|
},
|
|
);
|
|
builder.addCase(
|
|
fetchRegisteredContests.rejected,
|
|
(state, action: any) => {
|
|
state.fetchRegisteredContests.status = 'failed';
|
|
const errors = action.payload.errors as Record<
|
|
string,
|
|
string[]
|
|
>;
|
|
Object.values(errors).forEach((messages) => {
|
|
messages.forEach((msg) => {
|
|
toastError(msg);
|
|
});
|
|
});
|
|
},
|
|
);
|
|
|
|
// NEW HANDLERS
|
|
|
|
builder.addCase(fetchContestMembers.pending, (state) => {
|
|
state.fetchContestMembers.status = 'loading';
|
|
});
|
|
builder.addCase(
|
|
fetchContestMembers.fulfilled,
|
|
(
|
|
state,
|
|
action: PayloadAction<{
|
|
contestId: number;
|
|
members: Member[];
|
|
hasNextPage: boolean;
|
|
}>,
|
|
) => {
|
|
state.fetchContestMembers.status = 'successful';
|
|
state.fetchContestMembers.members = action.payload.members;
|
|
state.fetchContestMembers.hasNextPage =
|
|
action.payload.hasNextPage;
|
|
},
|
|
);
|
|
builder.addCase(fetchContestMembers.rejected, (state, action: any) => {
|
|
state.fetchContestMembers.status = 'failed';
|
|
|
|
const errors = action.payload.errors as Record<string, string[]>;
|
|
Object.values(errors).forEach((messages) => {
|
|
messages.forEach((msg) => {
|
|
toastError(msg);
|
|
});
|
|
});
|
|
});
|
|
|
|
builder.addCase(addOrUpdateContestMember.pending, (state) => {
|
|
state.addOrUpdateMember.status = 'loading';
|
|
});
|
|
builder.addCase(addOrUpdateContestMember.fulfilled, (state) => {
|
|
state.addOrUpdateMember.status = 'successful';
|
|
});
|
|
builder.addCase(
|
|
addOrUpdateContestMember.rejected,
|
|
(state, action: any) => {
|
|
state.addOrUpdateMember.status = 'failed';
|
|
const errors = action.payload.errors as Record<
|
|
string,
|
|
string[]
|
|
>;
|
|
Object.values(errors).forEach((messages) => {
|
|
messages.forEach((msg) => {
|
|
toastError(msg);
|
|
});
|
|
});
|
|
},
|
|
);
|
|
|
|
builder.addCase(deleteContestMember.pending, (state) => {
|
|
state.deleteContestMember.status = 'loading';
|
|
});
|
|
builder.addCase(deleteContestMember.fulfilled, (state) => {
|
|
state.deleteContestMember.status = 'successful';
|
|
});
|
|
builder.addCase(deleteContestMember.rejected, (state, action: any) => {
|
|
state.deleteContestMember.status = 'failed';
|
|
|
|
const errors = action.payload.errors as Record<string, string[]>;
|
|
Object.values(errors).forEach((messages) => {
|
|
messages.forEach((msg) => {
|
|
toastError(msg);
|
|
});
|
|
});
|
|
});
|
|
|
|
builder.addCase(startContestAttempt.pending, (state) => {
|
|
state.startAttempt.status = 'loading';
|
|
});
|
|
builder.addCase(
|
|
startContestAttempt.fulfilled,
|
|
(state, action: PayloadAction<Attempt>) => {
|
|
state.startAttempt.status = 'successful';
|
|
state.startAttempt.attempt = action.payload;
|
|
},
|
|
);
|
|
builder.addCase(startContestAttempt.rejected, (state, action: any) => {
|
|
state.startAttempt.status = 'failed';
|
|
|
|
const errors = action.payload.errors as Record<string, string[]>;
|
|
Object.values(errors).forEach((messages) => {
|
|
messages.forEach((msg) => {
|
|
toastError(msg);
|
|
});
|
|
});
|
|
});
|
|
|
|
builder.addCase(fetchMyAttemptsInContest.pending, (state) => {
|
|
state.fetchMyAttemptsInContest.status = 'loading';
|
|
});
|
|
builder.addCase(
|
|
fetchMyAttemptsInContest.fulfilled,
|
|
(state, action: PayloadAction<Attempt[]>) => {
|
|
state.fetchMyAttemptsInContest.status = 'successful';
|
|
state.fetchMyAttemptsInContest.attempts = action.payload;
|
|
},
|
|
);
|
|
builder.addCase(
|
|
fetchMyAttemptsInContest.rejected,
|
|
(state, action: any) => {
|
|
state.fetchMyAttemptsInContest.status = 'failed';
|
|
const errors = action.payload.errors as Record<
|
|
string,
|
|
string[]
|
|
>;
|
|
Object.values(errors).forEach((messages) => {
|
|
messages.forEach((msg) => {
|
|
toastError(msg);
|
|
});
|
|
});
|
|
},
|
|
);
|
|
|
|
builder.addCase(fetchMyAllAttempts.pending, (state) => {
|
|
state.fetchMyAllAttempts.status = 'loading';
|
|
});
|
|
builder.addCase(
|
|
fetchMyAllAttempts.fulfilled,
|
|
(state, action: PayloadAction<Attempt[]>) => {
|
|
state.fetchMyAllAttempts.status = 'successful';
|
|
state.fetchMyAllAttempts.attempts = action.payload;
|
|
},
|
|
);
|
|
builder.addCase(fetchMyAllAttempts.rejected, (state, action: any) => {
|
|
state.fetchMyAllAttempts.status = 'failed';
|
|
|
|
const errors = action.payload.errors as Record<string, string[]>;
|
|
Object.values(errors).forEach((messages) => {
|
|
messages.forEach((msg) => {
|
|
toastError(msg);
|
|
});
|
|
});
|
|
});
|
|
|
|
builder.addCase(fetchMyActiveAttempt.pending, (state) => {
|
|
state.fetchMyActiveAttempt.status = 'loading';
|
|
});
|
|
builder.addCase(
|
|
fetchMyActiveAttempt.fulfilled,
|
|
(
|
|
state,
|
|
action: PayloadAction<{
|
|
contestId: number;
|
|
attempt: Attempt | null;
|
|
}>,
|
|
) => {
|
|
state.fetchMyActiveAttempt.status = 'successful';
|
|
state.fetchMyActiveAttempt.attempt = action.payload.attempt;
|
|
},
|
|
);
|
|
builder.addCase(fetchMyActiveAttempt.rejected, (state, action: any) => {
|
|
state.fetchMyActiveAttempt.status = 'failed';
|
|
|
|
const errors = action.payload.errors as Record<string, string[]>;
|
|
Object.values(errors).forEach((messages) => {
|
|
messages.forEach((msg) => {
|
|
toastError(msg);
|
|
});
|
|
});
|
|
});
|
|
|
|
builder.addCase(checkContestRegistration.pending, (state) => {
|
|
state.checkRegistration.status = 'loading';
|
|
});
|
|
builder.addCase(
|
|
checkContestRegistration.fulfilled,
|
|
(
|
|
state,
|
|
action: PayloadAction<{
|
|
contestId: number;
|
|
registered: boolean;
|
|
}>,
|
|
) => {
|
|
state.checkRegistration.status = 'successful';
|
|
state.checkRegistration.registered = action.payload.registered;
|
|
},
|
|
);
|
|
builder.addCase(
|
|
checkContestRegistration.rejected,
|
|
(state, action: any) => {
|
|
state.checkRegistration.status = 'failed';
|
|
const errors = action.payload.errors as Record<
|
|
string,
|
|
string[]
|
|
>;
|
|
Object.values(errors).forEach((messages) => {
|
|
messages.forEach((msg) => {
|
|
toastError(msg);
|
|
});
|
|
});
|
|
},
|
|
);
|
|
|
|
builder.addCase(fetchUpcomingEligibleContests.pending, (state) => {
|
|
state.fetchUpcomingEligible.status = 'loading';
|
|
});
|
|
builder.addCase(
|
|
fetchUpcomingEligibleContests.fulfilled,
|
|
(state, action: PayloadAction<Contest[]>) => {
|
|
state.fetchUpcomingEligible.status = 'successful';
|
|
state.fetchUpcomingEligible.contests = action.payload;
|
|
},
|
|
);
|
|
builder.addCase(
|
|
fetchUpcomingEligibleContests.rejected,
|
|
(state, action: any) => {
|
|
state.fetchUpcomingEligible.status = 'failed';
|
|
const errors = action.payload.errors as Record<
|
|
string,
|
|
string[]
|
|
>;
|
|
Object.values(errors).forEach((messages) => {
|
|
messages.forEach((msg) => {
|
|
toastError(msg);
|
|
});
|
|
});
|
|
},
|
|
);
|
|
|
|
builder.addCase(fetchParticipatingContests.pending, (state) => {
|
|
state.fetchParticipating.status = 'loading';
|
|
});
|
|
|
|
builder.addCase(
|
|
fetchParticipatingContests.fulfilled,
|
|
(state, action: PayloadAction<ContestsResponse>) => {
|
|
state.fetchParticipating.status = 'successful';
|
|
state.fetchParticipating.contests = action.payload.contests;
|
|
state.fetchParticipating.hasNextPage =
|
|
action.payload.hasNextPage;
|
|
},
|
|
);
|
|
|
|
builder.addCase(
|
|
fetchParticipatingContests.rejected,
|
|
(state, action: any) => {
|
|
state.fetchParticipating.status = 'failed';
|
|
const errors = action.payload.errors as Record<
|
|
string,
|
|
string[]
|
|
>;
|
|
Object.values(errors).forEach((messages) => {
|
|
messages.forEach((msg) => {
|
|
toastError(msg);
|
|
});
|
|
});
|
|
},
|
|
);
|
|
},
|
|
});
|
|
|
|
// =====================
|
|
// Экспорты
|
|
// =====================
|
|
|
|
export const { setContestStatus } = contestsSlice.actions;
|
|
export const contestsReducer = contestsSlice.reducer;
|