my contests

This commit is contained in:
Виталий Лавшонок
2025-11-06 00:41:01 +03:00
parent 4a65aa4b53
commit dc6df1480e
7 changed files with 354 additions and 121 deletions

View File

@@ -70,33 +70,70 @@ export interface CreateContestBody {
type Status = 'idle' | 'loading' | 'successful' | 'failed';
interface ContestsState {
contests: Contest[];
selectedContest: Contest | null;
hasNextPage: boolean;
statuses: {
fetchList: Status;
fetchById: Status;
create: Status;
fetchContests: {
contests: Contest[];
hasNextPage: boolean;
status: Status;
error: string | null;
};
fetchContestById: {
contest: Contest | null;
status: Status;
error: string | null;
};
createContest: {
contest: Contest | null;
status: Status;
error: string | null;
};
fetchMyContests: {
contests: Contest[];
status: Status;
error: string | null;
};
fetchRegisteredContests: {
contests: Contest[];
hasNextPage: boolean;
status: Status;
error: string | null;
};
error: string | null;
}
const initialState: ContestsState = {
contests: [],
selectedContest: null,
hasNextPage: false,
statuses: {
fetchList: 'idle',
fetchById: 'idle',
create: 'idle',
fetchContests: {
contests: [],
hasNextPage: false,
status: 'idle',
error: null,
},
fetchContestById: {
contest: null,
status: 'idle',
error: null,
},
createContest: {
contest: null,
status: 'idle',
error: null,
},
fetchMyContests: {
contests: [],
status: 'idle',
error: null,
},
fetchRegisteredContests: {
contests: [],
hasNextPage: false,
status: 'idle',
error: null,
},
error: null,
};
// =====================
// Async Thunks
// =====================
// Все контесты
export const fetchContests = createAsyncThunk(
'contests/fetchAll',
async (
@@ -121,6 +158,7 @@ export const fetchContests = createAsyncThunk(
},
);
// Контест по ID
export const fetchContestById = createAsyncThunk(
'contests/fetchById',
async (id: number, { rejectWithValue }) => {
@@ -135,6 +173,7 @@ export const fetchContestById = createAsyncThunk(
},
);
// Создание контеста
export const createContest = createAsyncThunk(
'contests/create',
async (contestData: CreateContestBody, { rejectWithValue }) => {
@@ -152,6 +191,45 @@ export const createContest = createAsyncThunk(
},
);
// Контесты, созданные мной
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?.message || 'Failed to fetch my contests',
);
}
},
);
// Контесты, где я зарегистрирован
export const fetchRegisteredContests = createAsyncThunk(
'contests/fetchRegisteredContests',
async (
params: { page?: number; pageSize?: number } = {},
{ rejectWithValue },
) => {
try {
const { page = 0, pageSize = 10 } = params;
const response = await axios.get<ContestsResponse>(
'/contests/registered',
{ params: { page, pageSize } },
);
return response.data;
} catch (err: any) {
return rejectWithValue(
err.response?.data?.message ||
'Failed to fetch registered contests',
);
}
},
);
// =====================
// Slice
// =====================
@@ -161,77 +239,100 @@ const contestsSlice = createSlice({
initialState,
reducers: {
clearSelectedContest: (state) => {
state.selectedContest = null;
},
setContestStatus: (
state,
action: PayloadAction<{
key: keyof ContestsState['statuses'];
status: Status;
}>,
) => {
state.statuses[action.payload.key] = action.payload.status;
state.fetchContestById.contest = null;
},
},
extraReducers: (builder) => {
// fetchContests
builder.addCase(fetchContests.pending, (state) => {
state.statuses.fetchList = 'loading';
state.error = null;
state.fetchContests.status = 'loading';
state.fetchContests.error = null;
});
builder.addCase(
fetchContests.fulfilled,
(state, action: PayloadAction<ContestsResponse>) => {
state.statuses.fetchList = 'successful';
state.contests = action.payload.contests;
state.hasNextPage = action.payload.hasNextPage;
},
);
builder.addCase(
fetchContests.rejected,
(state, action: PayloadAction<any>) => {
state.statuses.fetchList = 'failed';
state.error = action.payload;
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';
state.fetchContests.error = action.payload;
});
// fetchContestById
builder.addCase(fetchContestById.pending, (state) => {
state.statuses.fetchById = 'loading';
state.error = null;
state.fetchContestById.status = 'loading';
state.fetchContestById.error = null;
});
builder.addCase(
fetchContestById.fulfilled,
(state, action: PayloadAction<Contest>) => {
state.statuses.fetchById = 'successful';
state.selectedContest = action.payload;
},
);
builder.addCase(
fetchContestById.rejected,
(state, action: PayloadAction<any>) => {
state.statuses.fetchById = 'failed';
state.error = action.payload;
state.fetchContestById.status = 'successful';
state.fetchContestById.contest = action.payload;
},
);
builder.addCase(fetchContestById.rejected, (state, action: any) => {
state.fetchContestById.status = 'failed';
state.fetchContestById.error = action.payload;
});
// createContest
builder.addCase(createContest.pending, (state) => {
state.statuses.create = 'loading';
state.error = null;
state.createContest.status = 'loading';
state.createContest.error = null;
});
builder.addCase(
createContest.fulfilled,
(state, action: PayloadAction<Contest>) => {
state.statuses.create = 'successful';
state.contests.unshift(action.payload);
state.createContest.status = 'successful';
state.createContest.contest = action.payload;
},
);
builder.addCase(createContest.rejected, (state, action: any) => {
state.createContest.status = 'failed';
state.createContest.error = action.payload;
});
// fetchMyContests
// fetchMyContests
builder.addCase(fetchMyContests.pending, (state) => {
state.fetchMyContests.status = 'loading';
state.fetchMyContests.error = null;
});
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';
state.fetchMyContests.error = action.payload;
});
// fetchRegisteredContests
builder.addCase(fetchRegisteredContests.pending, (state) => {
state.fetchRegisteredContests.status = 'loading';
state.fetchRegisteredContests.error = null;
});
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(
createContest.rejected,
(state, action: PayloadAction<any>) => {
state.statuses.create = 'failed';
state.error = action.payload;
fetchRegisteredContests.rejected,
(state, action: any) => {
state.fetchRegisteredContests.status = 'failed';
state.fetchRegisteredContests.error = action.payload;
},
);
},
@@ -241,5 +342,5 @@ const contestsSlice = createSlice({
// Экспорты
// =====================
export const { clearSelectedContest, setContestStatus } = contestsSlice.actions;
export const { clearSelectedContest } = contestsSlice.actions;
export const contestsReducer = contestsSlice.reducer;