payload-quiz-plugin 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,434 @@
1
+ import { CollectionConfig, Plugin, Block } from 'payload';
2
+
3
+ /**
4
+ * Translation keys for the quiz plugin
5
+ */
6
+ interface QuizTranslations {
7
+ tests?: {
8
+ title?: string;
9
+ titleSingular?: string;
10
+ titlePlural?: string;
11
+ pageTitle?: string;
12
+ startButton?: string;
13
+ finishButton?: string;
14
+ nextButton?: string;
15
+ previousButton?: string;
16
+ noQuestionsAvailable?: string;
17
+ noQuestionsDescription?: string;
18
+ backToTests?: string;
19
+ questionsLabel?: string;
20
+ minutesLabel?: string;
21
+ passMarkLabel?: string;
22
+ beginTestDescription?: string;
23
+ showingRange?: string;
24
+ showingSingle?: string;
25
+ noResults?: string;
26
+ };
27
+ questions?: {
28
+ selectOne?: string;
29
+ selectMultiple?: string;
30
+ loading?: string;
31
+ };
32
+ results?: {
33
+ congratulations?: string;
34
+ keepPracticing?: string;
35
+ youPassed?: string;
36
+ needToPass?: string;
37
+ correct?: string;
38
+ incorrect?: string;
39
+ unanswered?: string;
40
+ timeTaken?: string;
41
+ tryAgain?: string;
42
+ backToTests?: string;
43
+ reviewAnswers?: string;
44
+ question?: string;
45
+ yourAnswer?: string;
46
+ correctAnswer?: string;
47
+ explanation?: string;
48
+ };
49
+ timer?: {
50
+ timeRemaining?: string;
51
+ };
52
+ modal?: {
53
+ unansweredTitle?: string;
54
+ unansweredDescription?: string;
55
+ continueTest?: string;
56
+ finishAnyway?: string;
57
+ };
58
+ }
59
+ /**
60
+ * i18n configuration for the quiz plugin
61
+ */
62
+ interface QuizI18nConfig {
63
+ /**
64
+ * Enable i18n support. When false, uses defaultLocale translations only.
65
+ * @default true
66
+ */
67
+ enabled?: boolean;
68
+ /**
69
+ * Default locale to use when i18n is disabled or as fallback
70
+ * @default 'en'
71
+ */
72
+ defaultLocale?: string;
73
+ /**
74
+ * Custom translations that override or extend plugin defaults.
75
+ * Keys are locale codes (e.g., 'en', 'de', 'fr')
76
+ */
77
+ translations?: Record<string, QuizTranslations>;
78
+ }
79
+ /**
80
+ * Collection customization options
81
+ */
82
+ interface QuizCollectionOverrides {
83
+ /**
84
+ * Override or extend the Questions collection config
85
+ */
86
+ questions?: Partial<CollectionConfig>;
87
+ /**
88
+ * Override or extend the Tests collection config
89
+ */
90
+ tests?: Partial<CollectionConfig>;
91
+ /**
92
+ * Override or extend the CertificateTypes collection config
93
+ */
94
+ certificateTypes?: Partial<CollectionConfig>;
95
+ }
96
+ /**
97
+ * Admin UI configuration
98
+ */
99
+ interface QuizAdminConfig {
100
+ /**
101
+ * Admin group name for quiz collections
102
+ * @default 'Quiz'
103
+ */
104
+ group?: string;
105
+ }
106
+ /**
107
+ * Main plugin configuration
108
+ */
109
+ interface QuizPluginConfig {
110
+ /**
111
+ * i18n configuration
112
+ */
113
+ i18n?: QuizI18nConfig;
114
+ /**
115
+ * Collection configuration overrides
116
+ */
117
+ collections?: QuizCollectionOverrides;
118
+ /**
119
+ * Admin panel configuration
120
+ */
121
+ admin?: QuizAdminConfig;
122
+ /**
123
+ * Enable or disable specific features
124
+ */
125
+ features?: {
126
+ /**
127
+ * Enable certificate types collection
128
+ * @default true
129
+ */
130
+ certificateTypes?: boolean;
131
+ /**
132
+ * Enable the tests archive block
133
+ * @default true
134
+ */
135
+ testsArchiveBlock?: boolean;
136
+ };
137
+ }
138
+ /**
139
+ * Resolved plugin configuration with all defaults applied
140
+ */
141
+ interface ResolvedQuizPluginConfig {
142
+ i18n: Required<QuizI18nConfig>;
143
+ collections: QuizCollectionOverrides;
144
+ admin: Required<QuizAdminConfig>;
145
+ features: {
146
+ certificateTypes: boolean;
147
+ testsArchiveBlock: boolean;
148
+ };
149
+ }
150
+
151
+ /**
152
+ * Quiz Plugin for Payload CMS
153
+ *
154
+ * Adds quiz/test functionality with:
155
+ * - Questions collection with multiple choice support
156
+ * - Tests collection with configurable settings
157
+ * - Certificate Types collection for organizing questions
158
+ * - Quiz UI components for the frontend
159
+ *
160
+ * @example
161
+ * ```ts
162
+ * import { quizPlugin } from 'payload-quiz-plugin'
163
+ *
164
+ * export default buildConfig({
165
+ * plugins: [
166
+ * quizPlugin({
167
+ * i18n: {
168
+ * enabled: true,
169
+ * translations: {
170
+ * fr: { tests: { title: 'Tests' } }
171
+ * }
172
+ * }
173
+ * })
174
+ * ]
175
+ * })
176
+ * ```
177
+ */
178
+ declare function quizPlugin(pluginConfig?: QuizPluginConfig): Plugin;
179
+ /**
180
+ * Get the resolved plugin configuration
181
+ * Useful for accessing translations and settings in components
182
+ */
183
+ declare function getQuizPluginConfig(pluginConfig?: QuizPluginConfig): ResolvedQuizPluginConfig;
184
+
185
+ declare function createCertificateTypesCollection(config: ResolvedQuizPluginConfig): CollectionConfig;
186
+
187
+ declare function createQuestionsCollection(config: ResolvedQuizPluginConfig): CollectionConfig;
188
+
189
+ declare function createTestsCollection(config: ResolvedQuizPluginConfig): CollectionConfig;
190
+
191
+ /**
192
+ * Quiz Plugin Types
193
+ * These types are standalone and don't depend on generated payload-types
194
+ */
195
+ /**
196
+ * Rich text data structure (Lexical format)
197
+ */
198
+ type RichTextData = {
199
+ root: {
200
+ type: string;
201
+ children: unknown[];
202
+ [key: string]: unknown;
203
+ };
204
+ [key: string]: unknown;
205
+ } | null;
206
+ /**
207
+ * Component for rendering rich text content
208
+ * This allows projects to provide their own RichText component
209
+ */
210
+ type RichTextRenderer = React.ComponentType<{
211
+ data: RichTextData;
212
+ enableGutter?: boolean;
213
+ className?: string;
214
+ }>;
215
+ /**
216
+ * Choice option in a question
217
+ */
218
+ type QuizChoice = {
219
+ id?: string | null;
220
+ text: string;
221
+ isCorrect?: boolean | null;
222
+ explanation?: RichTextData;
223
+ };
224
+ /**
225
+ * Question structure for the quiz
226
+ */
227
+ type QuizQuestion = {
228
+ id: number;
229
+ question: string;
230
+ requiredAnswers?: number | null;
231
+ choices?: QuizChoice[] | null;
232
+ explanation?: RichTextData;
233
+ };
234
+ /**
235
+ * Test configuration
236
+ */
237
+ type QuizTest = {
238
+ id?: number;
239
+ title: string;
240
+ slug?: string | null;
241
+ questionCount?: number | null;
242
+ timeLimit?: number | null;
243
+ passMark?: number | null;
244
+ questionsPerPage?: number | null;
245
+ allowGoBack?: boolean | null;
246
+ requireAllAnswered?: boolean | null;
247
+ };
248
+ /**
249
+ * User's answer to a question
250
+ */
251
+ type UserAnswer = {
252
+ questionId: number;
253
+ selectedChoiceIds: string[];
254
+ };
255
+ /**
256
+ * Quiz state for the reducer
257
+ */
258
+ type QuizState = {
259
+ status: 'loading' | 'ready' | 'in_progress' | 'completed';
260
+ questions: QuizQuestion[];
261
+ currentQuestionIndex: number;
262
+ answers: Map<number, string[]>;
263
+ startTime: number | null;
264
+ endTime: number | null;
265
+ timeRemaining: number;
266
+ };
267
+ /**
268
+ * Actions for the quiz reducer
269
+ */
270
+ type QuizAction = {
271
+ type: 'SET_QUESTIONS';
272
+ questions: QuizQuestion[];
273
+ } | {
274
+ type: 'START_QUIZ';
275
+ timeLimit: number;
276
+ } | {
277
+ type: 'SELECT_ANSWER';
278
+ questionId: number;
279
+ choiceId: string;
280
+ isMultiple: boolean;
281
+ } | {
282
+ type: 'DESELECT_ANSWER';
283
+ questionId: number;
284
+ choiceId: string;
285
+ } | {
286
+ type: 'NEXT_QUESTION';
287
+ } | {
288
+ type: 'PREV_QUESTION';
289
+ } | {
290
+ type: 'GO_TO_QUESTION';
291
+ index: number;
292
+ } | {
293
+ type: 'TICK_TIMER';
294
+ } | {
295
+ type: 'FINISH_QUIZ';
296
+ };
297
+ /**
298
+ * Result of the quiz
299
+ */
300
+ type QuizResult = {
301
+ totalQuestions: number;
302
+ correctAnswers: number;
303
+ incorrectAnswers: number;
304
+ unanswered: number;
305
+ score: number;
306
+ passed: boolean;
307
+ passMark: number;
308
+ timeTaken: number;
309
+ questionResults: QuestionResult[];
310
+ };
311
+ /**
312
+ * Result for a single question
313
+ */
314
+ type QuestionResult = {
315
+ questionId: number;
316
+ question: string;
317
+ requiredAnswers: number;
318
+ isCorrect: boolean;
319
+ userAnswers: string[];
320
+ correctAnswers: string[];
321
+ choices: {
322
+ id: string;
323
+ text: string;
324
+ isCorrect: boolean;
325
+ wasSelected: boolean;
326
+ explanation?: RichTextData;
327
+ }[];
328
+ explanation?: RichTextData;
329
+ };
330
+
331
+ /**
332
+ * Types for TestCard component
333
+ * These are standalone and don't depend on generated payload-types
334
+ */
335
+ /**
336
+ * Media object structure (simplified)
337
+ */
338
+ type TestCardMedia = {
339
+ url?: string | null;
340
+ alt?: string | null;
341
+ width?: number | null;
342
+ height?: number | null;
343
+ [key: string]: unknown;
344
+ };
345
+ /**
346
+ * Certificate type structure
347
+ */
348
+ type TestCardCertificateType = {
349
+ id?: number | string;
350
+ shortName?: string | null;
351
+ title?: string | null;
352
+ [key: string]: unknown;
353
+ };
354
+ /**
355
+ * Test meta information
356
+ */
357
+ type TestCardMeta = {
358
+ title?: string | null;
359
+ description?: string | null;
360
+ image?: TestCardMedia | number | string | null;
361
+ };
362
+ /**
363
+ * Data structure for a test card
364
+ */
365
+ type TestCardData = {
366
+ id?: number | string;
367
+ slug?: string | null;
368
+ title?: string | null;
369
+ certificateType?: TestCardCertificateType | number | string | null;
370
+ meta?: TestCardMeta | null;
371
+ questionCount?: number | null;
372
+ timeLimit?: number | null;
373
+ passMark?: number | null;
374
+ };
375
+ /**
376
+ * Props for the TestCard component
377
+ */
378
+ type TestCardProps = {
379
+ /** Test data to display */
380
+ doc?: TestCardData;
381
+ /** Override title */
382
+ title?: string;
383
+ /** Additional class names */
384
+ className?: string;
385
+ /** Custom media renderer component */
386
+ MediaComponent?: React.ComponentType<{
387
+ resource: TestCardMedia;
388
+ size?: string;
389
+ fill?: boolean;
390
+ pictureClassName?: string;
391
+ imgClassName?: string;
392
+ }>;
393
+ };
394
+
395
+ /**
396
+ * Create a translation function for a specific locale
397
+ */
398
+ declare function createTranslator(config: ResolvedQuizPluginConfig, locale?: string): (key: string, params?: Record<string, string | number>, fallback?: string) => string;
399
+ /**
400
+ * Get all merged translations for a locale (useful for passing to client components)
401
+ */
402
+ declare function getTranslations(config: ResolvedQuizPluginConfig, locale?: string): QuizTranslations;
403
+ /**
404
+ * Check if a locale has translations available
405
+ */
406
+ declare function hasLocale(config: ResolvedQuizPluginConfig, locale: string): boolean;
407
+ /**
408
+ * Get all available locales
409
+ */
410
+ declare function getAvailableLocales(config: ResolvedQuizPluginConfig): string[];
411
+
412
+ type TestsArchiveBlockConfig = {
413
+ /** Override the block slug */
414
+ slug?: string;
415
+ /** Override labels */
416
+ labels?: {
417
+ singular?: string;
418
+ plural?: string;
419
+ };
420
+ /** Additional fields to add to the block */
421
+ additionalFields?: Block['fields'];
422
+ };
423
+ /**
424
+ * Creates the TestsArchiveBlock configuration
425
+ * This block allows displaying a grid of tests filtered by certificate type
426
+ */
427
+ declare function createTestsArchiveBlock(pluginConfig: ResolvedQuizPluginConfig, blockConfig?: TestsArchiveBlockConfig): Block;
428
+ /**
429
+ * Default TestsArchive block for quick setup
430
+ * Uses default plugin configuration
431
+ */
432
+ declare const TestsArchiveBlock: Block;
433
+
434
+ export { type QuestionResult, type QuizAction, type QuizAdminConfig, type QuizChoice, type QuizCollectionOverrides, type QuizI18nConfig, type QuizPluginConfig, type QuizQuestion, type QuizResult, type QuizState, type QuizTest, type QuizTranslations, type ResolvedQuizPluginConfig, type RichTextData, type RichTextRenderer, type TestCardCertificateType, type TestCardData, type TestCardMedia, type TestCardMeta, type TestCardProps, TestsArchiveBlock, type TestsArchiveBlockConfig, type UserAnswer, createCertificateTypesCollection, createQuestionsCollection, createTestsArchiveBlock, createTestsCollection, createTranslator, getAvailableLocales, getQuizPluginConfig, getTranslations, hasLocale, quizPlugin };