Add sharing

This commit is contained in:
Joey Yakimowich-Payne 2026-01-16 08:49:21 -07:00
commit 8a11275849
No known key found for this signature in database
GPG key ID: 6BFE655FA5ABD1E1
16 changed files with 1996 additions and 10 deletions

View file

@ -8,6 +8,7 @@ interface UseQuizLibraryReturn {
loading: boolean;
loadingQuizId: string | null;
deletingQuizId: string | null;
sharingQuizId: string | null;
saving: boolean;
exporting: boolean;
importing: boolean;
@ -18,6 +19,8 @@ interface UseQuizLibraryReturn {
updateQuiz: (id: string, quiz: Quiz) => Promise<void>;
updateQuizConfig: (id: string, config: GameConfig) => Promise<void>;
deleteQuiz: (id: string) => Promise<void>;
shareQuiz: (id: string) => Promise<string>;
unshareQuiz: (id: string) => Promise<void>;
exportQuizzes: (quizIds: string[]) => Promise<void>;
importQuizzes: (quizzes: ExportedQuiz[]) => Promise<void>;
parseImportFile: (file: File) => Promise<QuizExportFile>;
@ -31,6 +34,7 @@ export const useQuizLibrary = (): UseQuizLibraryReturn => {
const [loading, setLoading] = useState(false);
const [loadingQuizId, setLoadingQuizId] = useState<string | null>(null);
const [deletingQuizId, setDeletingQuizId] = useState<string | null>(null);
const [sharingQuizId, setSharingQuizId] = useState<string | null>(null);
const [saving, setSaving] = useState(false);
const [exporting, setExporting] = useState(false);
const [importing, setImporting] = useState(false);
@ -267,6 +271,70 @@ export const useQuizLibrary = (): UseQuizLibraryReturn => {
setDeletingQuizId(null);
}
}, [authFetch]);
const shareQuiz = useCallback(async (id: string): Promise<string> => {
setSharingQuizId(id);
setError(null);
try {
const response = await authFetch(`/api/quizzes/${id}/share`, {
method: 'POST',
});
if (!response.ok) {
const errorText = response.status === 404
? 'Quiz not found.'
: 'Failed to share quiz.';
throw new Error(errorText);
}
const data = await response.json();
setQuizzes(prev => prev.map(q =>
q.id === id ? { ...q, shareToken: data.shareToken, isShared: true } : q
));
toast.success('Quiz shared! Link copied to clipboard.');
return data.shareToken;
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to share quiz';
if (!message.includes('redirecting')) {
toast.error(message);
}
throw err;
} finally {
setSharingQuizId(null);
}
}, [authFetch]);
const unshareQuiz = useCallback(async (id: string): Promise<void> => {
setSharingQuizId(id);
setError(null);
try {
const response = await authFetch(`/api/quizzes/${id}/share`, {
method: 'DELETE',
});
if (!response.ok) {
const errorText = response.status === 404
? 'Quiz not found.'
: 'Failed to stop sharing quiz.';
throw new Error(errorText);
}
setQuizzes(prev => prev.map(q =>
q.id === id ? { ...q, shareToken: undefined, isShared: false } : q
));
toast.success('Quiz is no longer shared');
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to stop sharing quiz';
if (!message.includes('redirecting')) {
toast.error(message);
}
throw err;
} finally {
setSharingQuizId(null);
}
}, [authFetch]);
const exportQuizzes = useCallback(async (quizIds: string[]): Promise<void> => {
if (quizIds.length === 0) {
@ -379,6 +447,7 @@ export const useQuizLibrary = (): UseQuizLibraryReturn => {
loading,
loadingQuizId,
deletingQuizId,
sharingQuizId,
saving,
exporting,
importing,
@ -389,6 +458,8 @@ export const useQuizLibrary = (): UseQuizLibraryReturn => {
updateQuiz,
updateQuizConfig,
deleteQuiz,
shareQuiz,
unshareQuiz,
exportQuizzes,
importQuizzes,
parseImportFile,