add contest thunck and remove google button
This commit is contained in:
@@ -5,4 +5,5 @@ import Edit from './edit.svg';
|
||||
import UserAdd from './user-profile-add.svg';
|
||||
import ChevroneDown from './chevron-down.svg';
|
||||
|
||||
|
||||
export { Book, Edit, EyeClosed, EyeOpen, UserAdd, ChevroneDown };
|
||||
|
||||
@@ -5,10 +5,6 @@ import axios from '../../axios';
|
||||
// Типы
|
||||
// =====================
|
||||
|
||||
// =====================
|
||||
// Типы для посылок
|
||||
// =====================
|
||||
|
||||
export interface Solution {
|
||||
id: number;
|
||||
missionId: number;
|
||||
@@ -73,11 +69,25 @@ export interface Contest {
|
||||
members?: Member[];
|
||||
}
|
||||
|
||||
export interface Attempt {
|
||||
attemptId: number;
|
||||
contestId: number;
|
||||
startedAt: string;
|
||||
expiresAt: string;
|
||||
finished: boolean;
|
||||
results?: any[];
|
||||
}
|
||||
|
||||
interface ContestsResponse {
|
||||
hasNextPage: boolean;
|
||||
contests: Contest[];
|
||||
}
|
||||
|
||||
interface MembersPage {
|
||||
members: Member[];
|
||||
hasNextPage: boolean;
|
||||
}
|
||||
|
||||
export interface CreateContestBody {
|
||||
name: string;
|
||||
description?: string;
|
||||
@@ -142,106 +152,106 @@ interface ContestsState {
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
const initialState: ContestsState = {
|
||||
fetchContests: {
|
||||
contests: [],
|
||||
hasNextPage: false,
|
||||
status: 'idle',
|
||||
error: undefined,
|
||||
},
|
||||
fetchContestById: {
|
||||
contest: {
|
||||
id: 0,
|
||||
name: '',
|
||||
description: '',
|
||||
scheduleType: 'AlwaysOpen',
|
||||
visibility: 'Public',
|
||||
startsAt: '',
|
||||
endsAt: '',
|
||||
attemptDurationMinutes: 0,
|
||||
maxAttempts: 0,
|
||||
allowEarlyFinish: false,
|
||||
groupId: undefined,
|
||||
groupName: undefined,
|
||||
missions: [],
|
||||
articles: [],
|
||||
members: [],
|
||||
},
|
||||
status: 'idle',
|
||||
error: undefined,
|
||||
},
|
||||
fetchMySubmissions: {
|
||||
submissions: [],
|
||||
status: 'idle',
|
||||
error: undefined,
|
||||
},
|
||||
const emptyContest: Contest = {
|
||||
id: 0,
|
||||
name: '',
|
||||
description: '',
|
||||
scheduleType: 'AlwaysOpen',
|
||||
visibility: 'Public',
|
||||
startsAt: '',
|
||||
endsAt: '',
|
||||
attemptDurationMinutes: 0,
|
||||
maxAttempts: 0,
|
||||
allowEarlyFinish: false,
|
||||
missions: [],
|
||||
articles: [],
|
||||
members: [],
|
||||
};
|
||||
|
||||
createContest: {
|
||||
contest: {
|
||||
id: 0,
|
||||
name: '',
|
||||
description: '',
|
||||
scheduleType: 'AlwaysOpen',
|
||||
visibility: 'Public',
|
||||
startsAt: '',
|
||||
endsAt: '',
|
||||
attemptDurationMinutes: 0,
|
||||
maxAttempts: 0,
|
||||
allowEarlyFinish: false,
|
||||
groupId: undefined,
|
||||
groupName: undefined,
|
||||
missions: [],
|
||||
articles: [],
|
||||
members: [],
|
||||
},
|
||||
status: 'idle',
|
||||
error: undefined,
|
||||
},
|
||||
updateContest: {
|
||||
contest: {
|
||||
id: 0,
|
||||
name: '',
|
||||
description: '',
|
||||
scheduleType: 'AlwaysOpen',
|
||||
visibility: 'Public',
|
||||
startsAt: '',
|
||||
endsAt: '',
|
||||
attemptDurationMinutes: 0,
|
||||
maxAttempts: 0,
|
||||
allowEarlyFinish: false,
|
||||
groupId: undefined,
|
||||
groupName: undefined,
|
||||
missions: [],
|
||||
articles: [],
|
||||
members: [],
|
||||
},
|
||||
status: 'idle',
|
||||
error: undefined,
|
||||
},
|
||||
deleteContest: {
|
||||
status: 'idle',
|
||||
error: undefined,
|
||||
},
|
||||
fetchMyContests: {
|
||||
contests: [],
|
||||
status: 'idle',
|
||||
error: undefined,
|
||||
},
|
||||
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',
|
||||
error: undefined,
|
||||
},
|
||||
|
||||
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' },
|
||||
};
|
||||
|
||||
// =====================
|
||||
// Async Thunks
|
||||
// =====================
|
||||
|
||||
// Мои посылки в контесте
|
||||
// Existing ----------------------------
|
||||
|
||||
export const fetchMySubmissions = createAsyncThunk(
|
||||
'contests/fetchMySubmissions',
|
||||
async (contestId: number, { rejectWithValue }) => {
|
||||
@@ -258,7 +268,6 @@ export const fetchMySubmissions = createAsyncThunk(
|
||||
},
|
||||
);
|
||||
|
||||
// Все контесты
|
||||
export const fetchContests = createAsyncThunk(
|
||||
'contests/fetchAll',
|
||||
async (
|
||||
@@ -283,7 +292,6 @@ export const fetchContests = createAsyncThunk(
|
||||
},
|
||||
);
|
||||
|
||||
// Контест по ID
|
||||
export const fetchContestById = createAsyncThunk(
|
||||
'contests/fetchById',
|
||||
async (id: number, { rejectWithValue }) => {
|
||||
@@ -298,7 +306,6 @@ export const fetchContestById = createAsyncThunk(
|
||||
},
|
||||
);
|
||||
|
||||
// Создание контеста
|
||||
export const createContest = createAsyncThunk(
|
||||
'contests/create',
|
||||
async (contestData: CreateContestBody, { rejectWithValue }) => {
|
||||
@@ -316,7 +323,6 @@ export const createContest = createAsyncThunk(
|
||||
},
|
||||
);
|
||||
|
||||
// 🆕 Обновление контеста
|
||||
export const updateContest = createAsyncThunk(
|
||||
'contests/update',
|
||||
async (
|
||||
@@ -340,7 +346,6 @@ export const updateContest = createAsyncThunk(
|
||||
},
|
||||
);
|
||||
|
||||
// 🆕 Удаление контеста
|
||||
export const deleteContest = createAsyncThunk(
|
||||
'contests/delete',
|
||||
async (contestId: number, { rejectWithValue }) => {
|
||||
@@ -355,7 +360,6 @@ export const deleteContest = createAsyncThunk(
|
||||
},
|
||||
);
|
||||
|
||||
// Контесты, созданные мной
|
||||
export const fetchMyContests = createAsyncThunk(
|
||||
'contests/fetchMyContests',
|
||||
async (_, { rejectWithValue }) => {
|
||||
@@ -370,7 +374,6 @@ export const fetchMyContests = createAsyncThunk(
|
||||
},
|
||||
);
|
||||
|
||||
// Контесты, где я зарегистрирован
|
||||
export const fetchRegisteredContests = createAsyncThunk(
|
||||
'contests/fetchRegisteredContests',
|
||||
async (
|
||||
@@ -393,6 +396,182 @@ export const fetchRegisteredContests = createAsyncThunk(
|
||||
},
|
||||
);
|
||||
|
||||
// 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?.message ||
|
||||
'Failed to add or update contest member',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// 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?.message ||
|
||||
'Failed to delete contest member',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// 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?.message ||
|
||||
'Failed to start contest attempt',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// 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?.message || 'Failed to fetch my attempts',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// 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?.message ||
|
||||
'Failed to fetch contest members',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// 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?.message || 'Failed to check registration',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// 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?.message ||
|
||||
'Failed to fetch upcoming eligible contests',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// 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?.message || 'Failed to fetch my attempts',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// 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?.message || 'Failed to fetch active attempt',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// =====================
|
||||
// Slice
|
||||
// =====================
|
||||
@@ -401,7 +580,6 @@ const contestsSlice = createSlice({
|
||||
name: 'contests',
|
||||
initialState,
|
||||
reducers: {
|
||||
// 🆕 Сброс статусов
|
||||
setContestStatus: (
|
||||
state,
|
||||
action: PayloadAction<{ key: keyof ContestsState; status: Status }>,
|
||||
@@ -413,7 +591,8 @@ const contestsSlice = createSlice({
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
// 🆕 fetchMySubmissions
|
||||
// ——— YOUR EXISTING HANDLERS (unchanged) ———
|
||||
|
||||
builder.addCase(fetchMySubmissions.pending, (state) => {
|
||||
state.fetchMySubmissions.status = 'loading';
|
||||
state.fetchMySubmissions.error = undefined;
|
||||
@@ -430,10 +609,8 @@ const contestsSlice = createSlice({
|
||||
state.fetchMySubmissions.error = action.payload;
|
||||
});
|
||||
|
||||
// fetchContests
|
||||
builder.addCase(fetchContests.pending, (state) => {
|
||||
state.fetchContests.status = 'loading';
|
||||
state.fetchContests.error = undefined;
|
||||
});
|
||||
builder.addCase(
|
||||
fetchContests.fulfilled,
|
||||
@@ -448,10 +625,8 @@ const contestsSlice = createSlice({
|
||||
state.fetchContests.error = action.payload;
|
||||
});
|
||||
|
||||
// fetchContestById
|
||||
builder.addCase(fetchContestById.pending, (state) => {
|
||||
state.fetchContestById.status = 'loading';
|
||||
state.fetchContestById.error = undefined;
|
||||
});
|
||||
builder.addCase(
|
||||
fetchContestById.fulfilled,
|
||||
@@ -465,10 +640,8 @@ const contestsSlice = createSlice({
|
||||
state.fetchContestById.error = action.payload;
|
||||
});
|
||||
|
||||
// createContest
|
||||
builder.addCase(createContest.pending, (state) => {
|
||||
state.createContest.status = 'loading';
|
||||
state.createContest.error = undefined;
|
||||
});
|
||||
builder.addCase(
|
||||
createContest.fulfilled,
|
||||
@@ -482,10 +655,8 @@ const contestsSlice = createSlice({
|
||||
state.createContest.error = action.payload;
|
||||
});
|
||||
|
||||
// 🆕 updateContest
|
||||
builder.addCase(updateContest.pending, (state) => {
|
||||
state.updateContest.status = 'loading';
|
||||
state.updateContest.error = undefined;
|
||||
});
|
||||
builder.addCase(
|
||||
updateContest.fulfilled,
|
||||
@@ -499,16 +670,13 @@ const contestsSlice = createSlice({
|
||||
state.updateContest.error = action.payload;
|
||||
});
|
||||
|
||||
// 🆕 deleteContest
|
||||
builder.addCase(deleteContest.pending, (state) => {
|
||||
state.deleteContest.status = 'loading';
|
||||
state.deleteContest.error = undefined;
|
||||
});
|
||||
builder.addCase(
|
||||
deleteContest.fulfilled,
|
||||
(state, action: PayloadAction<number>) => {
|
||||
state.deleteContest.status = 'successful';
|
||||
// Удалим контест из списков
|
||||
state.fetchContests.contests =
|
||||
state.fetchContests.contests.filter(
|
||||
(c) => c.id !== action.payload,
|
||||
@@ -524,10 +692,8 @@ const contestsSlice = createSlice({
|
||||
state.deleteContest.error = action.payload;
|
||||
});
|
||||
|
||||
// fetchMyContests
|
||||
builder.addCase(fetchMyContests.pending, (state) => {
|
||||
state.fetchMyContests.status = 'loading';
|
||||
state.fetchMyContests.error = undefined;
|
||||
});
|
||||
builder.addCase(
|
||||
fetchMyContests.fulfilled,
|
||||
@@ -541,10 +707,8 @@ const contestsSlice = createSlice({
|
||||
state.fetchMyContests.error = action.payload;
|
||||
});
|
||||
|
||||
// fetchRegisteredContests
|
||||
builder.addCase(fetchRegisteredContests.pending, (state) => {
|
||||
state.fetchRegisteredContests.status = 'loading';
|
||||
state.fetchRegisteredContests.error = undefined;
|
||||
});
|
||||
builder.addCase(
|
||||
fetchRegisteredContests.fulfilled,
|
||||
@@ -563,6 +727,168 @@ const contestsSlice = createSlice({
|
||||
state.fetchRegisteredContests.error = action.payload;
|
||||
},
|
||||
);
|
||||
|
||||
// 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';
|
||||
state.fetchContestMembers.error = action.payload;
|
||||
});
|
||||
|
||||
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';
|
||||
state.addOrUpdateMember.error = action.payload;
|
||||
},
|
||||
);
|
||||
|
||||
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';
|
||||
state.deleteContestMember.error = action.payload;
|
||||
});
|
||||
|
||||
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';
|
||||
state.startAttempt.error = action.payload;
|
||||
});
|
||||
|
||||
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';
|
||||
state.fetchMyAttemptsInContest.error = action.payload;
|
||||
},
|
||||
);
|
||||
|
||||
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';
|
||||
state.fetchMyAllAttempts.error = action.payload;
|
||||
});
|
||||
|
||||
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';
|
||||
state.fetchMyActiveAttempt.error = action.payload;
|
||||
});
|
||||
|
||||
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';
|
||||
state.checkRegistration.error = action.payload;
|
||||
},
|
||||
);
|
||||
|
||||
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';
|
||||
state.fetchUpcomingEligible.error = action.payload;
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -120,15 +120,6 @@ const Login = () => {
|
||||
text={status === 'loading' ? 'Вход...' : 'Вход'}
|
||||
disabled={status === 'loading'}
|
||||
/>
|
||||
<SecondaryButton className="w-full" onClick={() => {}}>
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
src={googleLogo}
|
||||
className="h-[24px] w-[24px] mr-[15px]"
|
||||
/>
|
||||
Вход с Google
|
||||
</div>
|
||||
</SecondaryButton>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center mt-[10px]">
|
||||
|
||||
@@ -237,15 +237,6 @@ const Register = () => {
|
||||
}
|
||||
disabled={status === 'loading'}
|
||||
/>
|
||||
<SecondaryButton className="w-full" onClick={() => {}}>
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
src={googleLogo}
|
||||
className="h-[24px] w-[24px] mr-[15px]"
|
||||
/>
|
||||
Регистрация с Google
|
||||
</div>
|
||||
</SecondaryButton>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center mt-[10px]">
|
||||
|
||||
@@ -12,6 +12,16 @@ import { CreateContestBody } from '../../../redux/slices/contests';
|
||||
import DateRangeInput from '../../../components/input/DateRangeInput';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
function toUtc(localDateTime?: string): string {
|
||||
if (!localDateTime) return '';
|
||||
|
||||
// Создаём дату (она автоматически считается как локальная)
|
||||
const date = new Date(localDateTime);
|
||||
|
||||
// Возвращаем ISO-строку с 'Z' (всегда в UTC)
|
||||
return date.toISOString();
|
||||
}
|
||||
|
||||
interface ModalCreateContestProps {
|
||||
active: boolean;
|
||||
setActive: (value: boolean) => void;
|
||||
@@ -61,7 +71,13 @@ const ModalCreateContest: FC<ModalCreateContestProps> = ({
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
dispatch(createContest(form));
|
||||
dispatch(
|
||||
createContest({
|
||||
...form,
|
||||
endsAt: toUtc(form.endsAt),
|
||||
startsAt: toUtc(form.startsAt),
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -19,35 +19,7 @@ const Filters = () => {
|
||||
|
||||
return (
|
||||
<div className=" h-[50px] mb-[20px] flex gap-[20px] items-center">
|
||||
<SearchInput onChange={() => {}} placeholder="Поиск задачи" />
|
||||
|
||||
<SorterDropDown
|
||||
items={[
|
||||
{
|
||||
value: '1',
|
||||
text: 'Сложность',
|
||||
},
|
||||
{
|
||||
value: '2',
|
||||
text: 'Дата создания',
|
||||
},
|
||||
{
|
||||
value: '3',
|
||||
text: 'ID',
|
||||
},
|
||||
]}
|
||||
onChange={(v) => {
|
||||
v;
|
||||
}}
|
||||
/>
|
||||
|
||||
<FilterDropDown
|
||||
items={items}
|
||||
defaultState={[]}
|
||||
onChange={(values) => {
|
||||
values;
|
||||
}}
|
||||
/>
|
||||
<SearchInput onChange={() => {}} placeholder="Поиск группы" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user