feat: Admin Area Challenges: List, Import/Export and Add/Edit Modal 1.01

This commit is contained in:
OpenVelo Agent
2026-07-22 21:26:23 +00:00
parent 241a966000
commit 2ee84bac27
5 changed files with 383 additions and 90 deletions
@@ -230,6 +230,60 @@ export interface ChallengeStagedFileUi {
uploading?: boolean;
}
export interface StageFileOutcome {
accepted: { file: File; placeholder: ChallengeStagedFileUi }[];
rejected: { file: File; reason: string }[];
}
export function partitionFilesBySize(
files: File[],
globalFileLimit: number,
): StageFileOutcome {
const accepted: StageFileOutcome['accepted'] = [];
const rejected: StageFileOutcome['rejected'] = [];
for (const file of files) {
if (file.size > globalFileLimit) {
rejected.push({
file,
reason: `Failed to upload '${file.name}': File exceeds the global size limit (${file.size} > ${globalFileLimit})`,
});
} else {
accepted.push({
file,
placeholder: {
kind: 'staged',
token: undefined,
originalFilename: file.name,
mimeType: file.type || 'application/octet-stream',
sizeBytes: file.size,
uploading: true,
},
});
}
}
return { accepted, rejected };
}
export function firstErrorTab(
errors: ChallengeFormErrors,
): 'general' | 'connection' | 'files' | null {
if (
errors.name ||
errors.descriptionMd ||
errors.categoryId ||
errors.initialPoints ||
errors.minimumPoints ||
errors.decaySolves ||
errors.flag
) {
return 'general';
}
if (errors.protocol || errors.port || errors.ipAddress) {
return 'connection';
}
return null;
}
export function buildInitialFiles(detail?: AdminChallengeDetail | null): ChallengeStagedFileUi[] {
if (!detail) return [];
return detail.files.map((f: AdminChallengeFile) => ({