Add import/export
This commit is contained in:
parent
02ecca7598
commit
667c490537
9 changed files with 2261 additions and 54 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import { useState, useCallback, useRef } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useAuthenticatedFetch } from './useAuthenticatedFetch';
|
||||
import type { Quiz, QuizSource, SavedQuiz, QuizListItem, GameConfig } from '../types';
|
||||
import type { Quiz, QuizSource, SavedQuiz, QuizListItem, GameConfig, ExportedQuiz, QuizExportFile } from '../types';
|
||||
|
||||
interface UseQuizLibraryReturn {
|
||||
quizzes: QuizListItem[];
|
||||
|
|
@ -9,6 +9,8 @@ interface UseQuizLibraryReturn {
|
|||
loadingQuizId: string | null;
|
||||
deletingQuizId: string | null;
|
||||
saving: boolean;
|
||||
exporting: boolean;
|
||||
importing: boolean;
|
||||
error: string | null;
|
||||
fetchQuizzes: () => Promise<void>;
|
||||
loadQuiz: (id: string) => Promise<SavedQuiz>;
|
||||
|
|
@ -16,6 +18,9 @@ interface UseQuizLibraryReturn {
|
|||
updateQuiz: (id: string, quiz: Quiz) => Promise<void>;
|
||||
updateQuizConfig: (id: string, config: GameConfig) => Promise<void>;
|
||||
deleteQuiz: (id: string) => Promise<void>;
|
||||
exportQuizzes: (quizIds: string[]) => Promise<void>;
|
||||
importQuizzes: (quizzes: ExportedQuiz[]) => Promise<void>;
|
||||
parseImportFile: (file: File) => Promise<QuizExportFile>;
|
||||
retry: () => Promise<void>;
|
||||
clearError: () => void;
|
||||
}
|
||||
|
|
@ -27,6 +32,8 @@ export const useQuizLibrary = (): UseQuizLibraryReturn => {
|
|||
const [loadingQuizId, setLoadingQuizId] = useState<string | null>(null);
|
||||
const [deletingQuizId, setDeletingQuizId] = useState<string | null>(null);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [exporting, setExporting] = useState(false);
|
||||
const [importing, setImporting] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const lastOperationRef = useRef<(() => Promise<void>) | null>(null);
|
||||
const savingRef = useRef(false);
|
||||
|
|
@ -165,6 +172,7 @@ export const useQuizLibrary = (): UseQuizLibraryReturn => {
|
|||
}
|
||||
throw err;
|
||||
} finally {
|
||||
savingRef.current = false;
|
||||
setSaving(false);
|
||||
}
|
||||
}, [authFetch]);
|
||||
|
|
@ -260,6 +268,102 @@ export const useQuizLibrary = (): UseQuizLibraryReturn => {
|
|||
}
|
||||
}, [authFetch]);
|
||||
|
||||
const exportQuizzes = useCallback(async (quizIds: string[]): Promise<void> => {
|
||||
if (quizIds.length === 0) {
|
||||
toast.error('No quizzes selected');
|
||||
return;
|
||||
}
|
||||
|
||||
setExporting(true);
|
||||
try {
|
||||
const quizzesToExport: ExportedQuiz[] = [];
|
||||
|
||||
for (const id of quizIds) {
|
||||
const response = await authFetch(`/api/quizzes/${id}`);
|
||||
if (!response.ok) continue;
|
||||
|
||||
const data = await response.json();
|
||||
quizzesToExport.push({
|
||||
title: data.title,
|
||||
source: data.source,
|
||||
aiTopic: data.aiTopic,
|
||||
config: data.gameConfig,
|
||||
questions: data.questions.map((q: { id: string; text: string; timeLimit: number; options: { text: string; isCorrect: boolean; shape: string; color: string; reason?: string }[] }) => ({
|
||||
id: q.id,
|
||||
text: q.text,
|
||||
timeLimit: q.timeLimit,
|
||||
options: q.options,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
const exportData: QuizExportFile = {
|
||||
version: 1,
|
||||
exportedAt: new Date().toISOString(),
|
||||
quizzes: quizzesToExport,
|
||||
};
|
||||
|
||||
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `kaboot-quizzes-${new Date().toISOString().split('T')[0]}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
toast.success(`Exported ${quizzesToExport.length} quiz${quizzesToExport.length !== 1 ? 'zes' : ''}`);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Failed to export quizzes';
|
||||
toast.error(message);
|
||||
throw err;
|
||||
} finally {
|
||||
setExporting(false);
|
||||
}
|
||||
}, [authFetch]);
|
||||
|
||||
const parseImportFile = useCallback(async (file: File): Promise<QuizExportFile> => {
|
||||
const text = await file.text();
|
||||
const data = JSON.parse(text);
|
||||
|
||||
if (!data.version || !data.quizzes || !Array.isArray(data.quizzes)) {
|
||||
throw new Error('Invalid export file format');
|
||||
}
|
||||
|
||||
return data as QuizExportFile;
|
||||
}, []);
|
||||
|
||||
const importQuizzes = useCallback(async (quizzesToImport: ExportedQuiz[]): Promise<void> => {
|
||||
if (quizzesToImport.length === 0) {
|
||||
toast.error('No quizzes selected');
|
||||
return;
|
||||
}
|
||||
|
||||
setImporting(true);
|
||||
let successCount = 0;
|
||||
|
||||
try {
|
||||
for (const quiz of quizzesToImport) {
|
||||
await saveQuiz(
|
||||
{ title: quiz.title, questions: quiz.questions, config: quiz.config },
|
||||
quiz.source,
|
||||
quiz.aiTopic
|
||||
);
|
||||
successCount++;
|
||||
}
|
||||
|
||||
toast.success(`Imported ${successCount} quiz${successCount !== 1 ? 'zes' : ''}`);
|
||||
await fetchQuizzes();
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Failed to import quizzes';
|
||||
toast.error(`Imported ${successCount} of ${quizzesToImport.length}. ${message}`);
|
||||
throw err;
|
||||
} finally {
|
||||
setImporting(false);
|
||||
}
|
||||
}, [saveQuiz, fetchQuizzes]);
|
||||
|
||||
const retry = useCallback(async () => {
|
||||
if (lastOperationRef.current) {
|
||||
await lastOperationRef.current();
|
||||
|
|
@ -276,6 +380,8 @@ export const useQuizLibrary = (): UseQuizLibraryReturn => {
|
|||
loadingQuizId,
|
||||
deletingQuizId,
|
||||
saving,
|
||||
exporting,
|
||||
importing,
|
||||
error,
|
||||
fetchQuizzes,
|
||||
loadQuiz,
|
||||
|
|
@ -283,6 +389,9 @@ export const useQuizLibrary = (): UseQuizLibraryReturn => {
|
|||
updateQuiz,
|
||||
updateQuizConfig,
|
||||
deleteQuiz,
|
||||
exportQuizzes,
|
||||
importQuizzes,
|
||||
parseImportFile,
|
||||
retry,
|
||||
clearError,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue