Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
273 commits
Select commit Hold shift + click to select a range
6ff3c77
fix: added type assertion in `useRHF` to resolve input type
Marty-Byrde Mar 3, 2026
084e9ec
ref: migrated manual server-error <-> form add logic
Marty-Byrde Mar 3, 2026
88bf7f7
ref: migrated deprecated `ZodSchema` type
Marty-Byrde Mar 3, 2026
e4acfc4
fix: added type assertion resolve input type in `CQD`
Marty-Byrde Mar 3, 2026
1c6fef0
ref: added explicit zod@3 positive error message
Marty-Byrde Mar 3, 2026
dc46882
ref: migrated deprecated `.string().uuid()` operand
Marty-Byrde Mar 3, 2026
7fc1029
ref: resolved potential enum instantiation issue
Marty-Byrde Mar 3, 2026
fdc0be0
ref: updated re-applying of `.default` in `stripEffecst`
Marty-Byrde Mar 3, 2026
e76af84
ref: added stripping of nested effects for arrays
Marty-Byrde Mar 3, 2026
13bacaa
chore: updated `stripZodDefault` for pipe type
Marty-Byrde Mar 3, 2026
97e7c9c
chore: re-applied `readonly`, `nonoptional` in `stripZDefault`
Marty-Byrde Mar 3, 2026
e14582b
fix: excluded transform effect from `stripZDefault` in pipe
Marty-Byrde Mar 3, 2026
17d7c46
ref: added warning logs for stripping of checks
Marty-Byrde Mar 3, 2026
e29f0e7
feat: introduced shadcn `input-otp` and dependencies
Marty-Byrde Mar 9, 2026
57ff6f5
feat: created `ShareTokenOTP` component
Marty-Byrde Mar 9, 2026
845dee4
feat: added share-token check user actions
Marty-Byrde Mar 9, 2026
1ef97a2
feat: introduced `/checks/start` page
Marty-Byrde Mar 9, 2026
33bfddb
fix: added abort result discarding in useEffect
Marty-Byrde Mar 10, 2026
22426a0
ref: increased min parsing indicator in `ShareTokenOTP`
Marty-Byrde Mar 10, 2026
1f4843d
ref: added error message display to `ShareTokenOTP`
Marty-Byrde Mar 10, 2026
135a526
ref: introduced icon argument to `FormFieldError`
Marty-Byrde Mar 10, 2026
8686ba4
ref: improved share-token input page layout
Marty-Byrde Mar 10, 2026
32ca7be
feat: introduced clean share-token input variant
Marty-Byrde Mar 10, 2026
4c58e6c
feat: created `ShareTokenFormContext` component
Marty-Byrde Mar 10, 2026
5788782
ref: applied `ShareFromFormContext` in token-input
Marty-Byrde Mar 10, 2026
61aeafa
feat: introduced `ShareTokenOptions` component
Marty-Byrde Mar 10, 2026
0e90980
ref: improved animations pending and errors
Marty-Byrde Mar 10, 2026
3bd007e
ref: added form-error reset on input clear
Marty-Byrde Mar 10, 2026
ae29638
ref: added translations to new share-token components
Marty-Byrde Mar 10, 2026
7a0b8ce
ref: removed deprecated `ShareTokenOTP` component
Marty-Byrde Mar 10, 2026
f0035c6
ref: removed unused todo comment
Marty-Byrde Mar 10, 2026
576e1d4
style: visually aligned share-token input
Marty-Byrde Mar 11, 2026
348270b
ref: added share-token result clearing on input change
Marty-Byrde Mar 11, 2026
07eb19b
fix: resolved stale options on change when input valid
Marty-Byrde Mar 11, 2026
28eefb7
ref: simplified share-token options debounce effect
Marty-Byrde Mar 11, 2026
0dd2e3a
ref: added missing share-token page translations
Marty-Byrde Mar 11, 2026
2ef6491
ref: disabled share-token option link, button when unavailable
Marty-Byrde Mar 11, 2026
5078563
Merge branch 'main' into feat/introduce-explicit–share-token-input-page
Marty-Byrde Mar 11, 2026
48d3479
chore: configured build-in zod locale on client-side
Marty-Byrde Mar 11, 2026
0a461b0
ref: improved share-token translation messages
Marty-Byrde Mar 11, 2026
e734000
Merge (Feat): Introduce explicit–share token input page (#286)
Marty-Byrde Mar 11, 2026
f500439
ref!: renamed all `checks` href segments to `courses`
Marty-Byrde Mar 12, 2026
ed517dc
chore!: renamed `checks` dirs into `courses`
Marty-Byrde Mar 12, 2026
1f9498c
chore!: renamed `KnowledgeCheck` schema to `Course`
Marty-Byrde Mar 12, 2026
7d4a710
Merge (Chore): Migrate codebase and utility functions to use zod@4 (#…
Marty-Byrde Mar 12, 2026
25e5bfa
chore!: updated `knowledgeCheck` dao handlers
Marty-Byrde Mar 12, 2026
4630d03
chore!: updated drizzle table and relation names
Marty-Byrde Mar 12, 2026
b0907c4
Merge branch 'canary' into chore/migrate-misconstrued-knowledgeCheck-…
Marty-Byrde Mar 12, 2026
e736c3d
ref: renamed left-out drizzle relation usages
Marty-Byrde Mar 12, 2026
ad0fbb1
ref: updated `checks` term to `courses` in comments
Marty-Byrde Mar 12, 2026
9340cc2
chore!: renamed `checks` to `courses` in `hooks`, `lib`
Marty-Byrde Mar 12, 2026
9cee686
ref: renamed `check-store` cacheKey to `courses-store`
Marty-Byrde Mar 12, 2026
792c476
chore!: updated file- and comp- names to use `Course`
Marty-Byrde Mar 12, 2026
ec9111a
ref: renamed variable declarations to use `course` term
Marty-Byrde Mar 12, 2026
b3b1da6
ref: updated translations texts and keys
Marty-Byrde Mar 12, 2026
8acca20
chore: re-applied catch, default after effect strip for arrays
Marty-Byrde Mar 12, 2026
2596d08
test: added simplistic `stripEffect` test suite
Marty-Byrde Mar 12, 2026
1edca27
Merge (Chore): Reapply default, catch values in stripEffect for array…
Marty-Byrde Mar 13, 2026
b69512e
Merge (Chore): Migrate misconstrued `knowledgeCheck` term to `course`…
Marty-Byrde Mar 13, 2026
bc72e26
Merge branch 'canary' into chore/localize-schemas
Marty-Byrde Mar 13, 2026
e467016
ref: replaced texts with i18n keys in `CourseSchema`
Marty-Byrde Mar 13, 2026
6f8e583
ref: declared custom i18n translator types
Marty-Byrde Mar 13, 2026
145ddf4
ref: added param support to custom i18n types
Marty-Byrde Mar 13, 2026
3a598d6
ref: mimicked i18n translator type definition
Marty-Byrde Mar 13, 2026
9b7fbbe
chore: wrapped `CourseSchema` in getter function
Marty-Byrde Mar 13, 2026
63de9bd
ref: simplified `convertToDatabase` utility function
Marty-Byrde Mar 13, 2026
14c0388
feat: introduced `localizedSchemaUtils` util function
Marty-Byrde Mar 13, 2026
015ac2a
chore: localized `ExaminationSchema` through wrapper
Marty-Byrde Mar 13, 2026
27da481
chore: localized `PracticeSchema` through wrappers
Marty-Byrde Mar 13, 2026
b522e72
ref: removed deprecated constant `CourseSchema`
Marty-Byrde Mar 13, 2026
82100da
chore: installed tiptap editor dependencies
Marty-Byrde Mar 17, 2026
935482a
feat: introduced tiptap editor template
Marty-Byrde Mar 15, 2026
1361450
fix: removed conflicting tiptap css variables
Marty-Byrde Mar 15, 2026
ba1003a
style: moved simple-editor css styles into tw-classes
Marty-Byrde Mar 15, 2026
c68e1a3
style: applied data-theme color mode to tiptap components
Marty-Byrde Mar 15, 2026
880a519
feat: moved font-actions into popover on mobile screens
Marty-Byrde Mar 15, 2026
a2a47bd
ref: cleaned up simple-editor unused feature comments
Marty-Byrde Mar 15, 2026
2316518
feat: moved align-actions into popover on small screens
Marty-Byrde Mar 15, 2026
77823cb
ref: moved link and highlight into popover on mobile
Marty-Byrde Mar 15, 2026
ee99152
ref: compacted simple-text editor toolbar on small screens
Marty-Byrde Mar 15, 2026
6918a42
style: removed toolbar bottom position on mobile screens
Marty-Byrde Mar 15, 2026
2f7ab9b
ref: aligned mobile-submenu toolbar centered
Marty-Byrde Mar 15, 2026
2ab07c3
feat: added ghost variant to shadcn `DropDownMenu`
Marty-Byrde Mar 15, 2026
325be92
ref: applied shadcn dropdown-menu in `HeadingDropdown`
Marty-Byrde Mar 15, 2026
7ce2bdf
feat: introduced rich-text-editor `TextAlignmentMenu`
Marty-Byrde Mar 15, 2026
486e30f
ref: replaced text-alignment submenu with dropdown
Marty-Byrde Mar 15, 2026
451d18d
style: unified rich-text-editor max-w constraint
Marty-Byrde Mar 15, 2026
41fdeb1
ref: improved automatic editor toolbar via container queries
Marty-Byrde Mar 15, 2026
65fdcbc
ref: removed unused text-alignmnet submenu
Marty-Byrde Mar 15, 2026
3f8267b
fix: resolved editor text-input click area
Marty-Byrde Mar 15, 2026
5d4533d
ref: enabled editor auto-focus behavior
Marty-Byrde Mar 15, 2026
2c6d11a
ref: removed Link tool from text-editor
Marty-Byrde Mar 16, 2026
c3ff935
ref: unified submenu-keys in type definition
Marty-Byrde Mar 16, 2026
3d6b75b
ref: removed default content from editor
Marty-Byrde Mar 16, 2026
4e504ec
style: applied shadcn tooltip bg, text colors to tiptap
Marty-Byrde Mar 16, 2026
e0c56f1
ref: added SimpleEditor to create sections
Marty-Byrde Mar 15, 2026
5943b90
style: constrained editor max and min heights
Marty-Byrde Mar 15, 2026
57da793
ref: removed wrapper div in `MultiStateRenderer`
Marty-Byrde Mar 15, 2026
071d3da
style: slightly increased multi-stage-bar inset size
Marty-Byrde Mar 16, 2026
3b167d2
ref: removed unused variable declarations in editor
Marty-Byrde Mar 16, 2026
bd811ca
style: resolved create-check stage alignment issue
Marty-Byrde Mar 16, 2026
3bd475d
fix: resolved alignment of overview title container
Marty-Byrde Mar 16, 2026
5b8480f
Revert "ref: removed wrapper div in `MultiStateRenderer`"
Marty-Byrde Mar 17, 2026
1e1ec64
Revert "fix: resolved alignment of overview title container"
Marty-Byrde Mar 17, 2026
737cabe
style: resolved text editor height growth constraints
Marty-Byrde Mar 17, 2026
9ecf51c
ref: removed unused tiptap template components
Marty-Byrde Mar 17, 2026
a984c56
ref: added eslint ignore directory in tiptap tooltip
Marty-Byrde Mar 17, 2026
0fe8519
style: updated editor selection, cursor and active colors
Marty-Byrde Mar 17, 2026
dd1d174
ref: added expect-error directive to internal editor type issue
Marty-Byrde Mar 17, 2026
7f2c2b5
ref: improved editor input focus click handler
Marty-Byrde Mar 17, 2026
914ccae
chore: removed unused @base-ui dependency
Marty-Byrde Mar 17, 2026
4cc1da9
ref: removed `cn` utility function from tiptap-utils
Marty-Byrde Mar 17, 2026
a9d28a1
Merge (Feat): Introduce (tiptap) rich-text editor component (#290)
Marty-Byrde Mar 17, 2026
9b0a4f0
ref: updated `MultiStageRender` to be generic
Marty-Byrde Mar 17, 2026
59bfa58
feat: added `onUpdateAction` and `defaultContent` to Editor
Marty-Byrde Mar 17, 2026
9974e96
chore: added contents prop to `Course` schemas
Marty-Byrde Mar 17, 2026
8a269d4
ref: added `updateCourse` course store action
Marty-Byrde Mar 17, 2026
b2bc870
feat: created exemplary content section component
Marty-Byrde Mar 17, 2026
ba89b46
ref: added disabled and readOnly arg to editor
Marty-Byrde Mar 18, 2026
f42c63b
ref: added `asChild` arg to simple shadcn button
Marty-Byrde Mar 18, 2026
1bf3981
ref: added `asChild` support in `RippleButton`
Marty-Byrde Mar 18, 2026
6aef458
style: extended button variants with additional sizes
Marty-Byrde Mar 18, 2026
25ffe60
feat: introduced shadcn combobox components
Marty-Byrde Mar 18, 2026
78f5789
feat: introduced category specific content create dialog
Marty-Byrde Mar 18, 2026
c8902a7
ref: added `addCategory` to `CourseStore`
Marty-Byrde Mar 20, 2026
3d2025f
ref: added create category logic to combox in `ContentSection`
Marty-Byrde Mar 20, 2026
5dea5d6
ref: applied existing createble `Select` in `ContentSection`
Marty-Byrde Mar 21, 2026
dbee9e8
Revert "feat: introduced shadcn combobox components"
Marty-Byrde Mar 21, 2026
2fb6db5
style: emphasized `Select` create option
Marty-Byrde Mar 21, 2026
84d06e7
feat!: replaced onChange with onSelect, onCreate in `Select`
Marty-Byrde Mar 21, 2026
b4a9a24
ref: updated `Select` usages to breaking change
Marty-Byrde Mar 21, 2026
454a2bb
feat: introduced type inference to `Select` component
Marty-Byrde Mar 21, 2026
3968b2b
fix: resolved `Select` option filtering issue
Marty-Byrde Mar 21, 2026
4153683
chore: externalized `CourseContent` schema
Marty-Byrde Mar 21, 2026
8796250
ref: rearranged create-content dialog layout
Marty-Byrde Mar 21, 2026
4c66075
ref: narrowed `Field` input properties
Marty-Byrde Mar 21, 2026
bf05108
feat: parameterized `Select` static texts
Marty-Byrde Mar 21, 2026
f97b6d4
style: removed capitalization of placeholder in `Select`
Marty-Byrde Mar 21, 2026
7a1ae91
chore: added descriptions to `CourseContent` schema
Marty-Byrde Mar 21, 2026
d0f52d7
feat: introduced edit functionality to `ContentSection`
Marty-Byrde Mar 21, 2026
7f6fe72
ref: externalized `CourseContentDialog` into component
Marty-Byrde Mar 21, 2026
8d40a34
ref: improved `CourseContentDialog` component
Marty-Byrde Mar 21, 2026
16d9650
fix: added form validation on category selection
Marty-Byrde Mar 21, 2026
cb86fad
style: improved `ContentSection` grid layout
Marty-Byrde Mar 21, 2026
2111ad9
style: improved content-appearance in `ContentSection`
Marty-Byrde Mar 21, 2026
08877d1
feat: added delete course-content functionality
Marty-Byrde Mar 21, 2026
2414ea5
chore: added default-value to contents column in drizzle
Marty-Byrde Mar 21, 2026
3429f41
chore: added contents property to insert handler
Marty-Byrde Mar 21, 2026
b57feb5
fix: widened `CourseContent` schema content type
Marty-Byrde Mar 22, 2026
67332ad
ref: added object type to `convertToDatabase` util functio
Marty-Byrde Mar 22, 2026
c8f6177
ref: added edited course content to form default values
Marty-Byrde Mar 22, 2026
aecc5a0
ref: moved transaction rollbacks call order
Marty-Byrde Mar 22, 2026
1b82044
ref: updated `convertToDB` type to recursive contents prop
Marty-Byrde Mar 22, 2026
a316129
ref: localized `ContentSection` and `CContentDialog`
Marty-Byrde Mar 22, 2026
0a3a51e
ref: renamed `SimpleEditor` to `RichTextEditor`
Marty-Byrde Mar 22, 2026
b5d8aed
ref: localized `RichtTextEditor` user visible labels
Marty-Byrde Mar 22, 2026
220ac91
fix: resolved text-editor tool accessibility within dialogs
Marty-Byrde Mar 22, 2026
88fa807
ref: added human-readable zod error msg for `title`
Marty-Byrde Mar 22, 2026
db969ec
style: unified `FormFieldError` text color
Marty-Byrde Mar 22, 2026
e83dcf3
ref: added missing category selection error message
Marty-Byrde Mar 22, 2026
da6dcd0
ref: added error display for missing category selection
Marty-Byrde Mar 22, 2026
0c26126
style: resolved `ContentSection` card overflow
Marty-Byrde Mar 22, 2026
4772745
fix: resolved editor unscrollable in `ContentSection` issue
Marty-Byrde Mar 22, 2026
b9f3063
style: decreased editor content text size in `ContentSection`
Marty-Byrde Mar 22, 2026
6baa262
ref: exported `RichtTextEditor` extensions
Marty-Byrde Mar 22, 2026
84c2782
fix: resolved missing content hydration in `ContentSection`
Marty-Byrde Mar 22, 2026
34fc74c
style: added rich-text-editor sm sizing variables
Marty-Byrde Mar 22, 2026
2bfcb0f
style: added `md`, `lg` editor sizing variant css styles
Marty-Byrde Mar 22, 2026
9bcab4d
style: fixed sizing issue for first-content paragraph
Marty-Byrde Mar 22, 2026
7791366
ref: added size argument to `RichTextEditor`
Marty-Byrde Mar 22, 2026
d60d207
ref: applied `RichTextEditor` size in `ContentSection` card
Marty-Byrde Mar 22, 2026
67b295f
ref: localized `Contents` stage in `ConfigureCourse`
Marty-Byrde Mar 22, 2026
73b427b
ref: resolved potential runtime error in `ContentSection`
Marty-Byrde Mar 23, 2026
859942b
ref: updated available categories in `CourseContDialog`
Marty-Byrde Mar 23, 2026
ad53e50
fix: resolved ineffective filtering `Select`
Marty-Byrde Mar 23, 2026
1d85837
ref: resolved current stage condition in `MultiStageRenderer`
Marty-Byrde Mar 23, 2026
ace6a59
ref: added dependency array to `useEditor` hook
Marty-Byrde Mar 23, 2026
77fe785
ref: resolved potential object type order issue
Marty-Byrde Mar 23, 2026
03d9060
ref: added `unsavedChanges` flag to new store actions
Marty-Byrde Mar 23, 2026
e1aa8f9
ref: improved `content` zod type in `CourseContent` schema
Marty-Byrde Mar 23, 2026
5ca9dae
chore: combined content related migrations
Marty-Byrde Mar 23, 2026
1d84b38
ref: renamed obscure content related translation key
Marty-Byrde Mar 24, 2026
9ef3be1
ref: resolved typos in content related translations
Marty-Byrde Mar 24, 2026
b99d36a
Merge (Feat): Introduce course content create feature (#291)
Marty-Byrde Mar 24, 2026
b7b4230
chore: localized `QuestionSchema` through wrappers
Marty-Byrde Mar 13, 2026
5edd490
chore: removed deprecated `QuestionSchema` declarations
Marty-Byrde Mar 24, 2026
546b7ac
ref: added translator to generic `ZustandStore
Marty-Byrde Mar 13, 2026
70ca081
ref: added missing translators to utility functions
Marty-Byrde Mar 13, 2026
90dfd6f
feat: created i18n test-case accessible translator
Marty-Byrde Mar 23, 2026
08a79f8
ref: introduced `TestTranslator` constant
Marty-Byrde Mar 23, 2026
5c0febd
ref: applied `TestTranslator` to instantiations in tests
Marty-Byrde Mar 23, 2026
dcbc48d
fix: simplified `createTranslator` type definition
Marty-Byrde Mar 23, 2026
ed2dc84
ref: applied `TestTranslator` to safeParse in tests
Marty-Byrde Mar 23, 2026
4fa8249
ref: applied `TestTranslator` to jest tests
Marty-Byrde Mar 23, 2026
a939fd4
chore: mocked `getI18n` in jest test-cases
Marty-Byrde Mar 23, 2026
e3d4416
fix: resolved translator inaccessibility in `useZustandStore`
Marty-Byrde Mar 23, 2026
7d70618
ref: moved default store props initializations to use translator
Marty-Byrde Mar 23, 2026
273a9b1
chore: localized `QuestionInputSchema` through wrappers
Marty-Byrde Mar 24, 2026
b6792e7
chore: localized auth schemas through wrappers
Marty-Byrde Mar 24, 2026
7b7cc9a
chore: localized `CategorySchema` through wrappers
Marty-Byrde Mar 24, 2026
d72faed
chore: localized `CourseSettings` schema through wrappers
Marty-Byrde Mar 24, 2026
4800770
chore: wrapped `StringDate` in translator wrapper
Marty-Byrde Mar 24, 2026
c3c7d30
Merge branch 'canary' into chore/prepare-schema-localization-via-wrap…
Marty-Byrde Mar 24, 2026
90321fd
chore: localized `CategorySchema` through wrappers
Marty-Byrde Mar 24, 2026
fed072d
ref: resolved `Category` type import statement
Marty-Byrde Mar 24, 2026
16eee6b
ref: removed conditional i18n hook in `useZustandStore`
Marty-Byrde Mar 24, 2026
447775a
ref: translated authentication zod schema texts
Marty-Byrde Mar 25, 2026
3b8467c
ref: translated `Category` zod schema texts
Marty-Byrde Mar 25, 2026
707af6a
chore: ignored auto generated locales by prettier
Marty-Byrde Mar 25, 2026
2963cdf
ref: translated `CourseContent` zod schema messages
Marty-Byrde Mar 25, 2026
940cd2b
ref: translated `CourseSettings` zod schema messages
Marty-Byrde Mar 25, 2026
15f2838
ref: translated nan-date refinement messages
Marty-Byrde Mar 25, 2026
7f3622b
ref: translated `Question` zod schema messages
Marty-Byrde Mar 25, 2026
0b65578
ref: translated `QuestionInput` zod schema messages
Marty-Byrde Mar 25, 2026
a9501ef
Merge (Chore): Update schema definitions to support localization + tr…
Marty-Byrde Mar 25, 2026
23a9b01
ref: moved examination result pages into `[locale]/results/`
Marty-Byrde Mar 27, 2026
f6eb132
ref: mimiced exam results page structure to components
Marty-Byrde Mar 27, 2026
a37b565
feat: added show exam results to `CourseActionMenu`
Marty-Byrde Mar 27, 2026
4b05f64
feat: added examination results breadcrumb components
Marty-Byrde Mar 28, 2026
45a1ff6
ref: applied breadcrumbs to exam result pages
Marty-Byrde Mar 28, 2026
286e654
ref: localized new exam breadcrumbs texts
Marty-Byrde Mar 28, 2026
58e3274
ref: resolved moved component import path issue
Marty-Byrde Mar 28, 2026
9514611
ref: added tooltip to show exam results action-item
Marty-Byrde Mar 28, 2026
b84e8fb
Merge (Ref): Improve course exam results accessibility (#294)
Marty-Byrde Mar 28, 2026
ba67ddd
feat: drafted `CourseContentOverview` component
Marty-Byrde Mar 28, 2026
5c22859
ref: added course contents to `OverviewSection`
Marty-Byrde Mar 28, 2026
97a6bfc
feat: drafted `/courses/contents/[courseId]` page
Marty-Byrde Mar 28, 2026
57740ba
feat: added "show contents" item to `CourseActionMenu`
Marty-Byrde Mar 28, 2026
f5a34b4
ref: modularized `ContentSection` into subcomponents
Marty-Byrde Mar 28, 2026
510a43f
feat: redesigned `ContentSection` layout
Marty-Byrde Mar 28, 2026
26bc7a1
ref: re-used `ContentSection` in `OverviewSection`
Marty-Byrde Mar 28, 2026
d5ff949
ref: added course content count to `CourseCard`
Marty-Byrde Mar 28, 2026
5248f24
feat: drafted course contents navigation panel
Marty-Byrde Apr 1, 2026
5d0e918
feat: created `ContentProvider` component
Marty-Byrde Apr 1, 2026
7028762
feat: introduced `ContentSwitchButton` component
Marty-Byrde Apr 1, 2026
9b53b64
feat: added `ContentRenderer` component
Marty-Byrde Apr 1, 2026
3ca2726
feat: created `ContentsPageBreadcrumbs` component
Marty-Byrde Apr 1, 2026
a7f3978
feat: added `ContentNavigationButtons` components
Marty-Byrde Apr 1, 2026
45fe45c
ref: updated course contents page layout
Marty-Byrde Apr 1, 2026
f6dfcf7
feat: redesigned course content navigation btn appearances
Marty-Byrde Apr 1, 2026
da8c70a
ref: added `editorContainerClassname` arg to `RichTextEditor`
Marty-Byrde Apr 1, 2026
470cb20
ref: adjusted `RichTextEditor` height in course content page
Marty-Byrde Apr 1, 2026
d698946
ref: localized course contents page and components
Marty-Byrde Apr 1, 2026
62daab8
ref: removed contents navigation on small screens
Marty-Byrde Apr 1, 2026
5f628ba
Revert "ref: adjusted `RichTextEditor` height in course content page"
Marty-Byrde Apr 1, 2026
2ca081b
ref: added empty course page safeguard redirect
Marty-Byrde Apr 1, 2026
b8815d0
ref: added safeguard against invalid index in `ContentSBtn`
Marty-Byrde Apr 1, 2026
1156afa
ref: added missing translation in `ContentSection`
Marty-Byrde Apr 1, 2026
7005501
ref: restructured internal `CreateContentButton` component
Marty-Byrde Apr 2, 2026
a567fce
Merge (Feat): Update and introduce new course content renderings (#295)
Marty-Byrde Apr 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/config/eslint_rules/color-mode-styles/*.js
/config/eslint_rules/color-mode-styles/*.js
src/i18n/locales/*.ts
4 changes: 2 additions & 2 deletions cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,8 @@ Cypress.Commands.add('simulatePracticeSelection', (question, options = {}) => {
}
})

Cypress.Commands.add('insertKnowledgeCheck', (check) => {
cy.request('POST', '/api/insert/knowledgeCheck', check).should('have.property', 'status').and('eq', 200)
Cypress.Commands.add('insertCourse', (course) => {
cy.request('POST', '/api/insert/course', course).should('have.property', 'status').and('eq', 200)
})

Cypress.Commands.add('loginAnonymously', () => {
Expand Down
42 changes: 42 additions & 0 deletions cypress/support/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* eslint-disable unused-imports/no-unused-imports */
/* eslint-disable simple-import-sort/imports */
// cypress/support/i18n.ts
import messagesEn from '../../src/i18n/locales/en'
import messagesDe from '../../src/i18n/locales/de'
import { FlattenedTranslations, TranslationKeys, TranslationValue, Translator } from '@/src/i18n/locales/types'
import type { CreateParams, LocaleValue, ParamsObject } from 'international-types'
import { Any } from '@/types'

type Messages = Record<string, unknown>

function getByPath(obj: Messages, key: string): unknown {
return key.split('.').reduce<unknown>((acc, part) => {
if (acc && typeof acc === 'object' && part in (acc as Record<string, unknown>)) {
return (acc as Record<string, unknown>)[part]
}
return undefined
}, obj)
}

function interpolate(template: string, params?: Record<string, string | number>) {
if (!params) return template

return template.replace(/\{(\w+)\}/g, (_, name) => {
const value = params[name]
return value == null ? `{${name}}` : String(value)
})
}

/**
* This utility function initializes a i18n translator function that may be used within tests to e.g. pass along to schema-utility functions
* @param locale The language 'de' | 'en' that should be used, by default 'en'.
* @returns The translator function that is identical to i18n's `t`.
*/
export function createTranslator(locale: 'de' | 'en' = 'en') {
const messages = locale === 'de' ? messagesDe : messagesEn

return ((key: string, params?: Any) => {
const raw = getByPath(messages, key) as string | undefined
return typeof raw === 'string' ? interpolate(raw, params) : key
}) as unknown as Translator
}
2 changes: 1 addition & 1 deletion cypress/support/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ declare namespace Cypress {

removeDBUser(email: string, username: string): void

insertKnowledgeCheck(check: KnowledgeCheck): void
insertCourse(course: Course): void

dragDrop(dragLocator: Cypress.Chainable<JQuery<HTMLElement>>, dropLocator: Cypress.Chainable<JQuery<HTMLElement>>): void

Expand Down
6 changes: 3 additions & 3 deletions database/cleanup/cleanup-test-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as dotenv from 'dotenv'
import { eq } from 'drizzle-orm'
import deleteUser from '@/database/cleanup/deleteUser'
import getDatabase from '@/database/Database'
import { db_knowledgeCheck, db_user } from '@/database/drizzle/schema'
import { db_course, db_user } from '@/database/drizzle/schema'

dotenv.config()

Expand All @@ -24,9 +24,9 @@ async function cleanupTestData() {
process.exit(1)
}

await db.delete(db_knowledgeCheck).where(eq(db_knowledgeCheck.owner_id, users.at(0)!.userId))
await db.delete(db_course).where(eq(db_course.owner_id, users.at(0)!.userId))

console.log(`Deleted all KnowledgeChecks created by the test-user with the email ${testEmail}`)
console.log(`Deleted all Courses created by the test-user with the email ${testEmail}`)

await deleteUser({ email: 'e2e.user@example.test' })
await deleteUser({ name: 'Anonymous' }, { limit: -1 })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,25 @@ import 'server-only'
import { and, eq } from 'drizzle-orm'
import getDatabase, { DrizzleDB } from '@/database/Database'
import { db_category } from '@/database/drizzle/schema'
import { KnowledgeCheck } from '@/src/schemas/KnowledgeCheck'
import { Course } from '@/src/schemas/CourseSchema'
// eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-imports
import { Any } from '@/types'

export async function insertQuestionCategories(db: DrizzleDB | undefined, checkId: KnowledgeCheck['id'], categories: KnowledgeCheck['questionCategories']) {
export async function insertQuestionCategories(db: DrizzleDB | undefined, courseId: Course['id'], categories: Course['questionCategories']) {
if (!db) db = await getDatabase()

const insertedCategories: Awaited<ReturnType<typeof insertCategory>>[] = []

for (const category of categories) {
const insertion = await insertCategory(db, { ...category, knowledgecheckId: checkId })
const insertion = await insertCategory(db, { ...category, knowledgecheckId: courseId })
insertedCategories.push(insertion)
}

return insertedCategories
}

/**
* This function inserts a new category that is associated to a given check. When a category that already exists is inserted again the duplicate-entry-error is caught and the respective category is selected instead.
* This function inserts a new category that is associated to a given course. When a category that already exists is inserted again the duplicate-entry-error is caught and the respective category is selected instead.
* @param db The database / db-transaction to use
* @param props The category-props that are to be inserted
* @returns The inserted data / retrieved category that was already inserted
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import 'server-only'
import { eq } from 'drizzle-orm'
import getDatabase from '@/database/Database'
import { db_category } from '@/database/drizzle/schema'
import { KnowledgeCheck } from '@/src/schemas/KnowledgeCheck'
import { Course } from '@/src/schemas/CourseSchema'

export async function getCategoriesByCheckId(knowledgeCheckId: KnowledgeCheck['id']) {
export async function getCategoriesByCourseId(courseId: Course['id']) {
const db = await getDatabase()

return await db.select().from(db_category).where(eq(db_category.knowledgecheckId, knowledgeCheckId))
return await db.select().from(db_category).where(eq(db_category.knowledgecheckId, courseId))
}
13 changes: 13 additions & 0 deletions database/course/catagories/update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { eq } from 'drizzle-orm'
import { insertQuestionCategories } from '@/database/course/catagories/insert'
import { DrizzleDB } from '@/database/Database'
import { db_category } from '@/database/drizzle'
import { Course } from '@/src/schemas/CourseSchema'

export async function updateCategories(db: DrizzleDB, courseId: Course['id'], categories: Course['questionCategories']) {
await db.delete(db_category).where(eq(db_category.knowledgecheckId, courseId))

if (categories.length === 0) return

await insertQuestionCategories(db, courseId, categories)
}
11 changes: 11 additions & 0 deletions database/course/collaborators/insert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { DrizzleDB } from '@/database/Database'
import { db_userContributesToCourse } from '@/database/drizzle/schema'
import { Course } from '@/src/schemas/CourseSchema'

export async function insertCollaboratorsToCourse(db: DrizzleDB, courseId: Course['id'], collaborators: Course['collaborators']) {
if (collaborators.length === 0) return

const values: (typeof db_userContributesToCourse.$inferInsert)[] = collaborators.map((collaboratorId) => ({ knowledgecheckId: courseId, userId: collaboratorId }))

await db.insert(db_userContributesToCourse).values(values)
}
10 changes: 10 additions & 0 deletions database/course/collaborators/select.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { eq } from 'drizzle-orm'
import getDatabase from '@/database/Database'
import { db_userContributesToCourse } from '@/database/drizzle/schema'
import { Course } from '@/src/schemas/CourseSchema'

export async function getCourseCollaborators(courseId: Course['id']) {
const db = await getDatabase()

return await db.select({ userId: db_userContributesToCourse.userId }).from(db_userContributesToCourse).where(eq(db_userContributesToCourse.knowledgecheckId, courseId))
}
13 changes: 13 additions & 0 deletions database/course/collaborators/update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'server-only'
import { eq } from 'drizzle-orm'
import { DrizzleDB } from '@/database/Database'
import { db_userContributesToCourse } from '@/database/drizzle'
import { Course } from '@/src/schemas/CourseSchema'

export async function updateCollaborators(db: DrizzleDB, courseId: Course['id'], newCollaborators: Course['collaborators']) {
await db.delete(db_userContributesToCourse).where(eq(db_userContributesToCourse.knowledgecheckId, courseId))

if (newCollaborators.length === 0) return

await db.insert(db_userContributesToCourse).values(newCollaborators.map((collabId) => ({ knowledgecheckId: courseId, userId: collabId })))
}
18 changes: 18 additions & 0 deletions database/course/delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use server'

import { and, eq } from 'drizzle-orm'
import getDatabase from '@/database/Database'
import { db_course } from '@/database/drizzle/schema'
import requireAuthentication from '@/src/lib/auth/requireAuthentication'
import { Course } from '@/src/schemas/CourseSchema'

export async function removeCourse({ courseId }: { courseId: Course['id'] }) {
const {
user: { id: userId },
} = await requireAuthentication()

const db = await getDatabase()

const result = await db.delete(db_course).where(and(eq(db_course.id, courseId), eq(db_course.owner_id, userId)))
return result[0].affectedRows > 0
}
64 changes: 64 additions & 0 deletions database/course/insert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use server'

import { eq } from 'drizzle-orm'
import { insertQuestionCategories } from '@/database/course/catagories/insert'
import { insertCollaboratorsToCourse } from '@/database/course/collaborators/insert'
import insertCourseQuestions from '@/database/course/questions/insert'
import insertCourseSettings from '@/database/course/settings/insert'
import getDatabase from '@/database/Database'
import { db_course } from '@/database/drizzle/schema'
import requireAuthentication from '@/src/lib/auth/requireAuthentication'
import _logger from '@/src/lib/log/Logger'
import { formatDatetime } from '@/src/lib/Shared/formatDatetime'
import { Course } from '@/src/schemas/CourseSchema'

const logger = _logger.createModuleLogger('/' + import.meta.url.split('/').reverse().slice(0, 2).reverse().join('/')!)

export default async function insertCourse(course: Course) {
await requireAuthentication()

const db = await getDatabase()
await db.transaction(async (transaction) => {
try {
const [{ id }] = await transaction
.insert(db_course)
.values({
id: course.id,
name: course.name,
description: course.description,
difficulty: course.difficulty,
share_key: course.share_key,
owner_id: course.owner_id,
openDate: formatDatetime(course.openDate),
closeDate: course.closeDate ? formatDatetime(course.closeDate) : null,
contents: course.contents,
})
.$returningId()

if (!id) throw new Error('Database insert statement did not return inserted-`id`')

await insertCollaboratorsToCourse(transaction, course.id, course.collaborators)
await insertCourseSettings(transaction, course)
const categories = await insertQuestionCategories(transaction, id, course.questionCategories)
const questionsWithCategoryIds = course.questions.map((q) => {
const category = categories.find((c) => c.name === q.category)

if (!category) throw new Error(`Category "${q.category}" not found for question "${q.id}"`)

return { ...q, categoryId: category.id }
})

await insertCourseQuestions(transaction, questionsWithCategoryIds, id)
} catch (err) {
logger.info('[Rollback]: Inserting db_knowledgecheck was unsuccessful!', err)
transaction.rollback()
}
})
}

export async function storeCourseShareToken(check_id: Course['id'], token: string) {
await requireAuthentication()
const db = await getDatabase()

await db.update(db_course).set({ share_key: token }).where(eq(db_course.id, check_id))
}
60 changes: 34 additions & 26 deletions database/knowledgeCheck/query.ts → database/course/query.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
'use server'

import { BuildQueryResult, DBQueryConfig, ExtractTablesWithRelations } from 'drizzle-orm'
import { convertSettings } from '@/database/course/settings/transform'
import { DatabaseOptions } from '@/database/course/type'
import getDatabase from '@/database/Database'
import { DrizzleSchema } from '@/database/drizzle'
import { convertSettings } from '@/database/knowledgeCheck/settings/transform'
import { DatabaseOptions } from '@/database/knowledgeCheck/type'
import buildKnowledgeCheckWhere, { KnowledgeCheckFilterBundle } from '@/database/utils/buildKnowledgeCheckWhere'
import { KnowledgeCheck, safeParseKnowledgeCheck } from '@/src/schemas/KnowledgeCheck'
import { instantiateKnowledgeCheckSettings, KnowledgeCheckSettings } from '@/src/schemas/KnowledgeCheckSettingsSchema'
import buildCourseWhere, { CourseFilterBundle } from '@/database/utils/buildCourseWhere'
import { Translator } from '@/src/i18n/locales/types'
import { getI18n } from '@/src/i18n/server-localization'
import _logger from '@/src/lib/log/Logger'
import { Course, safeParseCourse } from '@/src/schemas/CourseSchema'
import { CourseSettings, instantiateCourseSettings } from '@/src/schemas/CourseSettingsSchema'
import { ChoiceQuestion, DragDropQuestion, OpenQuestion, Question } from '@/src/schemas/QuestionSchema'
import { Any } from '@/types'

const logger = _logger.createModuleLogger('/' + import.meta.url.split('/').reverse().slice(0, 2).reverse().join('/')!)

//* joins the knowledgeCheck table with the following tables and aggregates the results: `Settings`, `Questions`, `Answers` and `Category`
const knowledgeCheckWithAllConfig = {
const courseWithAllConfig = {
userContributesToKnowledgeChecks: {
columns: {
knowledgecheckId: false,
Expand Down Expand Up @@ -47,56 +54,57 @@ const knowledgeCheckWithAllConfig = {
},
},
},
} as const satisfies KcWith
} as const satisfies CourseWith

type Tables = ExtractTablesWithRelations<DrizzleSchema>
type KcTable = Tables['db_knowledgeCheck']
type KcWith = DBQueryConfig<'one' | 'many', boolean, Tables, KcTable>['with']
type CourseTable = Tables['db_course']
type CourseWith = DBQueryConfig<'one' | 'many', boolean, Tables, CourseTable>['with']

type KnowledgeCheckTableConfig = Tables['db_knowledgeCheck']
type CourseTableConfig = Tables['db_course']

export type KnowledgeCheckWithAll = BuildQueryResult<Tables, KnowledgeCheckTableConfig, { with: typeof knowledgeCheckWithAllConfig }>
export type CourseWithAll = BuildQueryResult<Tables, CourseTableConfig, { with: typeof courseWithAllConfig }>

export async function getKnowledgeChecks({ limit = 10, offset, ...filterBundle }: {} & KnowledgeCheckFilterBundle & DatabaseOptions = {}): Promise<KnowledgeCheck[]> {
export async function getCourses({ limit = 10, offset, ...filterBundle }: {} & CourseFilterBundle & DatabaseOptions = {}): Promise<Course[]> {
const t = await getI18n()
const db = await getDatabase()

const checks = await db.query.db_knowledgeCheck.findMany({
where: buildKnowledgeCheckWhere(db, filterBundle),
with: knowledgeCheckWithAllConfig,
const courses = await db.query.db_course.findMany({
where: buildCourseWhere(db, filterBundle),
with: courseWithAllConfig,
orderBy: (kc, { desc }) => [desc(kc.updatedAt)],
limit: limit,
offset,
})

return checks.map(parseCheck).filter((check) => check !== null)
return courses.map((c) => parseCourse({ ...c, t })).filter((course) => course !== null)
}

function parseCheck({ questions, knowledgeCheckSettings: settings, categories, userContributesToKnowledgeChecks: collaboratorIds, ...check }: KnowledgeCheckWithAll): KnowledgeCheck | null {
const result = safeParseKnowledgeCheck({
...check,
function parseCourse({ t, questions, knowledgeCheckSettings: settings, categories, userContributesToKnowledgeChecks: collaboratorIds, ...course }: CourseWithAll & { t: Translator }): Course | null {
const result = safeParseCourse(t, {
...course,
collaborators: collaboratorIds.map((c) => c.userId),
questions: questions.map(parseQuestion),
questionCategories: categories.map((c): KnowledgeCheck['questionCategories'][number] => ({
questionCategories: categories.map((c): Course['questionCategories'][number] => ({
...c,
skipOnMissingPrequisite: false,
})),
settings: parseSetting(settings),
settings: parseSetting(t, settings),
})

if (!result.success) {
console.error(`Failed to parse knowledgeCheck instance ${check.id} because: `, result.error)
logger.error(`Failed to parse course instance ${course.id} because: `, result.error)
}

return result.data ?? null
}

function parseQuestion({ category, answers, ...question }: KnowledgeCheckWithAll['questions'][number]): Question {
function parseQuestion({ category, answers, ...question }: CourseWithAll['questions'][number]): Question {
return { ...question, type: question.type as Any, category: category.name, ...parseAnswers(question.type, answers) }
}

function parseAnswers(
question_type: Question['type'],
answers: KnowledgeCheckWithAll['questions'][number]['answers'],
answers: CourseWithAll['questions'][number]['answers'],
): Pick<ChoiceQuestion, 'answers'> | Pick<OpenQuestion, 'expectation'> | Pick<DragDropQuestion, 'answers'> {
switch (question_type) {
case 'multiple-choice':
Expand Down Expand Up @@ -130,6 +138,6 @@ function parseAnswers(
}
}

function parseSetting(setting: KnowledgeCheckWithAll['knowledgeCheckSettings']): KnowledgeCheckSettings {
return convertSettings('from-database', setting) ?? instantiateKnowledgeCheckSettings()
function parseSetting(t: Translator, setting: CourseWithAll['knowledgeCheckSettings']): CourseSettings {
return convertSettings('from-database', setting, t) ?? instantiateCourseSettings(t)
}
Loading
Loading