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.
package/LICENSE ADDED
@@ -0,0 +1,35 @@
1
+ Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)
2
+
3
+ Copyright (c) 2024 Alexandr Studio
4
+
5
+ You are free to:
6
+ - Share — copy and redistribute the material in any medium or format
7
+ - Adapt — remix, transform, and build upon the material
8
+
9
+ Under the following terms:
10
+
11
+ 1. Attribution — You must give appropriate credit to "Alexandr Studio", provide a
12
+ link to the license, and indicate if changes were made. You may do so in any
13
+ reasonable manner, but not in any way that suggests the licensor endorses you
14
+ or your use.
15
+
16
+ 2. NonCommercial — You may not use the material for commercial purposes. You may
17
+ not sell this software or include it in a product or service that you sell.
18
+
19
+ 3. ShareAlike — If you remix, transform, or build upon the material, you must
20
+ distribute your contributions under the same license as the original.
21
+
22
+ 4. No additional restrictions — You may not apply legal terms or technological
23
+ measures that legally restrict others from doing anything the license permits.
24
+
25
+ DISCLAIMER:
26
+
27
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33
+ SOFTWARE.
34
+
35
+ Full license text: https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode
package/README.md ADDED
@@ -0,0 +1,413 @@
1
+ # Payload Quiz Plugin
2
+
3
+ A comprehensive quiz and test system plugin for [Payload CMS](https://payloadcms.com) v3. Create timed quizzes with multiple choice questions, automatic grading, and detailed results.
4
+
5
+ ## Features
6
+
7
+ - **Collections**: Questions, Tests, and Certificate Types
8
+ - **Multiple Choice Support**: Single or multiple correct answers per question
9
+ - **Timed Tests**: Configurable time limits with automatic submission
10
+ - **Results Tracking**: Detailed results with question-by-question review
11
+ - **i18n Support**: Built-in English and German translations, easily extendable
12
+ - **Customizable**: Override collections, add custom fields, and configure features
13
+ - **React Components**: Ready-to-use Quiz UI components for the frontend
14
+ - **SEO Ready**: Built-in SEO fields for tests using @payloadcms/plugin-seo
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install payload-quiz-plugin
20
+ # or
21
+ pnpm add payload-quiz-plugin
22
+ # or
23
+ yarn add payload-quiz-plugin
24
+ ```
25
+
26
+ ### Peer Dependencies
27
+
28
+ This plugin requires the following peer dependencies:
29
+
30
+ ```bash
31
+ npm install payload @payloadcms/richtext-lexical
32
+ ```
33
+
34
+ Optional (for SEO fields):
35
+ ```bash
36
+ npm install @payloadcms/plugin-seo
37
+ ```
38
+
39
+ ## Quick Start
40
+
41
+ ### 1. Add the Plugin to Payload Config
42
+
43
+ ```typescript
44
+ // payload.config.ts
45
+ import { buildConfig } from 'payload'
46
+ import { quizPlugin } from 'payload-quiz-plugin'
47
+
48
+ export default buildConfig({
49
+ // ... your config
50
+ plugins: [
51
+ quizPlugin({
52
+ i18n: {
53
+ enabled: true,
54
+ defaultLocale: 'en',
55
+ },
56
+ admin: {
57
+ group: 'Quiz', // Admin panel group name
58
+ },
59
+ }),
60
+ ],
61
+ })
62
+ ```
63
+
64
+ ### 2. Create Quiz UI Components
65
+
66
+ ```tsx
67
+ // app/tests/[slug]/page.tsx
68
+ import { getPayload } from 'payload'
69
+ import { QuizClient } from './QuizClient'
70
+
71
+ export default async function TestPage({ params }) {
72
+ const payload = await getPayload({ config: configPromise })
73
+
74
+ const test = await payload.findByID({
75
+ collection: 'tests',
76
+ id: params.slug,
77
+ })
78
+
79
+ const questions = await payload.find({
80
+ collection: 'questions',
81
+ where: {
82
+ certificateTypes: {
83
+ contains: test.certificateType,
84
+ },
85
+ },
86
+ })
87
+
88
+ return <QuizClient test={test} questions={questions.docs} />
89
+ }
90
+ ```
91
+
92
+ ```tsx
93
+ // app/tests/[slug]/QuizClient.tsx
94
+ 'use client'
95
+
96
+ import {
97
+ QuizProvider,
98
+ useQuiz,
99
+ QuizTimer,
100
+ QuizProgress,
101
+ QuestionCard,
102
+ QuizResults,
103
+ } from 'payload-quiz-plugin/client'
104
+
105
+ export function QuizClient({ test, questions }) {
106
+ return (
107
+ <QuizProvider questions={questions} timeLimit={test.timeLimit}>
108
+ <QuizContent test={test} />
109
+ </QuizProvider>
110
+ )
111
+ }
112
+
113
+ function QuizContent({ test }) {
114
+ const {
115
+ state,
116
+ currentQuestion,
117
+ selectAnswer,
118
+ deselectAnswer,
119
+ nextQuestion,
120
+ prevQuestion,
121
+ finishQuiz,
122
+ calculateResults,
123
+ } = useQuiz()
124
+
125
+ // Render your quiz UI using these hooks and components
126
+ return (
127
+ <div>
128
+ <QuizTimer timeRemaining={state.timeRemaining} />
129
+ <QuizProgress
130
+ currentIndex={state.currentQuestionIndex}
131
+ totalQuestions={state.questions.length}
132
+ />
133
+ {currentQuestion && (
134
+ <QuestionCard
135
+ question={currentQuestion}
136
+ selectedChoiceIds={state.answers.get(currentQuestion.id) || []}
137
+ onSelectChoice={selectAnswer}
138
+ onDeselectChoice={deselectAnswer}
139
+ />
140
+ )}
141
+ </div>
142
+ )
143
+ }
144
+ ```
145
+
146
+ ## Configuration
147
+
148
+ ### Plugin Options
149
+
150
+ ```typescript
151
+ interface QuizPluginConfig {
152
+ i18n?: {
153
+ /** Enable i18n support (default: true) */
154
+ enabled?: boolean
155
+ /** Default locale (default: 'en') */
156
+ defaultLocale?: string
157
+ /** Custom translations */
158
+ translations?: Record<string, QuizTranslations>
159
+ }
160
+
161
+ collections?: {
162
+ /** Override Questions collection config */
163
+ questions?: Partial<CollectionConfig>
164
+ /** Override Tests collection config */
165
+ tests?: Partial<CollectionConfig>
166
+ /** Override CertificateTypes collection config */
167
+ certificateTypes?: Partial<CollectionConfig>
168
+ }
169
+
170
+ admin?: {
171
+ /** Admin panel group name (default: 'Quiz') */
172
+ group?: string
173
+ }
174
+
175
+ features?: {
176
+ /** Enable certificate types (default: true) */
177
+ certificateTypes?: boolean
178
+ /** Enable tests archive block (default: true) */
179
+ testsArchiveBlock?: boolean
180
+ }
181
+ }
182
+ ```
183
+
184
+ ### Custom Translations
185
+
186
+ Add your own translations or override existing ones:
187
+
188
+ ```typescript
189
+ quizPlugin({
190
+ i18n: {
191
+ enabled: true,
192
+ translations: {
193
+ fr: {
194
+ tests: {
195
+ title: 'Tests',
196
+ startButton: 'Commencer le test',
197
+ finishButton: 'Terminer',
198
+ // ... more translations
199
+ },
200
+ results: {
201
+ congratulations: 'Félicitations !',
202
+ // ... more translations
203
+ },
204
+ },
205
+ },
206
+ },
207
+ })
208
+ ```
209
+
210
+ ### Collection Overrides
211
+
212
+ Customize the collections with additional fields or settings:
213
+
214
+ ```typescript
215
+ quizPlugin({
216
+ collections: {
217
+ questions: {
218
+ access: {
219
+ read: () => true,
220
+ create: ({ req }) => req.user?.role === 'admin',
221
+ },
222
+ fields: [
223
+ // Additional fields
224
+ {
225
+ name: 'difficulty',
226
+ type: 'select',
227
+ options: ['easy', 'medium', 'hard'],
228
+ },
229
+ ],
230
+ },
231
+ },
232
+ })
233
+ ```
234
+
235
+ ## Collections
236
+
237
+ ### Questions
238
+
239
+ | Field | Type | Description |
240
+ |-------|------|-------------|
241
+ | `question` | textarea | The question text (localized) |
242
+ | `certificateTypes` | relationship | Certificate types this question belongs to |
243
+ | `requiredAnswers` | number | Number of correct answers to select (1 for single choice) |
244
+ | `choices` | array | Answer choices with text, isCorrect flag, and optional explanation |
245
+ | `explanation` | richText | General explanation shown after answering |
246
+
247
+ ### Tests
248
+
249
+ | Field | Type | Description |
250
+ |-------|------|-------------|
251
+ | `title` | text | Test name (localized) |
252
+ | `certificateType` | relationship | Certificate type for question filtering |
253
+ | `questionCount` | number | Number of questions to include |
254
+ | `timeLimit` | number | Time limit in minutes |
255
+ | `passMark` | number | Percentage required to pass |
256
+ | `allowGoBack` | checkbox | Allow revisiting previous questions |
257
+ | `requireAllAnswered` | checkbox | Require all questions before submitting |
258
+ | `description` | richText | Test description (localized) |
259
+ | `instructions` | richText | Test instructions (localized) |
260
+ | `meta` | group | SEO fields (title, description, image) |
261
+
262
+ ### Certificate Types
263
+
264
+ | Field | Type | Description |
265
+ |-------|------|-------------|
266
+ | `title` | text | Full certificate name (localized) |
267
+ | `shortName` | text | Abbreviated name (e.g., "PSM-I") |
268
+ | `description` | textarea | Brief description |
269
+ | `slug` | text | URL-friendly identifier |
270
+
271
+ ## Components
272
+
273
+ ### Server-Side Imports
274
+
275
+ ```typescript
276
+ // For payload.config.ts and server components
277
+ import {
278
+ quizPlugin,
279
+ getQuizPluginConfig,
280
+ createQuestionsCollection,
281
+ createTestsCollection,
282
+ createCertificateTypesCollection,
283
+ TestsArchiveBlock,
284
+ createTestsArchiveBlock,
285
+ createTranslator,
286
+ getTranslations,
287
+ } from 'payload-quiz-plugin'
288
+ ```
289
+
290
+ ### Client-Side Imports
291
+
292
+ **IMPORTANT**: All React components with hooks must be imported from `payload-quiz-plugin/client` to ensure proper `"use client"` directive handling.
293
+
294
+ ```typescript
295
+ // For client components only - do NOT import these from 'payload-quiz-plugin'
296
+ import {
297
+ QuizProvider,
298
+ useQuiz,
299
+ QuizTimer,
300
+ QuizProgress,
301
+ QuestionCard,
302
+ ChoiceOption,
303
+ QuizResults,
304
+ TestCard,
305
+ cn, // Tailwind class merge utility
306
+ } from 'payload-quiz-plugin/client'
307
+ ```
308
+
309
+ ### QuizProvider
310
+
311
+ Wraps your quiz UI and manages state:
312
+
313
+ ```tsx
314
+ <QuizProvider questions={questions} timeLimit={30}>
315
+ {children}
316
+ </QuizProvider>
317
+ ```
318
+
319
+ ### useQuiz Hook
320
+
321
+ Access quiz state and actions:
322
+
323
+ ```typescript
324
+ const {
325
+ state, // Current quiz state
326
+ currentQuestion, // Current question object
327
+ isFirstQuestion, // Boolean
328
+ isLastQuestion, // Boolean
329
+ answeredCount, // Number of answered questions
330
+ selectAnswer, // (choiceId: string) => void
331
+ deselectAnswer, // (choiceId: string) => void
332
+ nextQuestion, // () => void
333
+ prevQuestion, // () => void
334
+ goToQuestion, // (index: number) => void
335
+ finishQuiz, // () => void
336
+ calculateResults, // (passMark: number) => QuizResult
337
+ } = useQuiz()
338
+ ```
339
+
340
+ ### TestCard
341
+
342
+ Display a test card with optional custom media component:
343
+
344
+ ```tsx
345
+ <TestCard
346
+ doc={test}
347
+ className="h-full"
348
+ MediaComponent={({ resource, size }) => (
349
+ <YourMediaComponent resource={resource} size={size} />
350
+ )}
351
+ />
352
+ ```
353
+
354
+ ## Blocks
355
+
356
+ ### TestsArchiveBlock
357
+
358
+ A Payload block for displaying a grid of tests:
359
+
360
+ ```typescript
361
+ // In your page collection
362
+ import { TestsArchiveBlock } from 'payload-quiz-plugin'
363
+
364
+ export const Pages = {
365
+ slug: 'pages',
366
+ fields: [
367
+ {
368
+ name: 'layout',
369
+ type: 'blocks',
370
+ blocks: [
371
+ TestsArchiveBlock,
372
+ // ... other blocks
373
+ ],
374
+ },
375
+ ],
376
+ }
377
+ ```
378
+
379
+ ## Styling
380
+
381
+ The components use Tailwind CSS classes and are designed to work with shadcn/ui-style theming. They use CSS variables like:
382
+
383
+ - `--background`, `--foreground`
384
+ - `--card`, `--card-foreground`
385
+ - `--primary`, `--primary-foreground`
386
+ - `--muted`, `--muted-foreground`
387
+ - `--border`
388
+
389
+ Make sure your project has these CSS variables defined.
390
+
391
+ ## TypeScript
392
+
393
+ Full TypeScript support with exported types:
394
+
395
+ ```typescript
396
+ import type {
397
+ QuizPluginConfig,
398
+ QuizQuestion,
399
+ QuizTest,
400
+ QuizResult,
401
+ QuestionResult,
402
+ QuizChoice,
403
+ TestCardData,
404
+ } from 'payload-quiz-plugin'
405
+ ```
406
+
407
+ ## License
408
+
409
+ MIT
410
+
411
+ ## Contributing
412
+
413
+ Contributions are welcome! Please feel free to submit a Pull Request.