@hed-hog/lms 0.0.328 → 0.0.330
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/dist/instructor/instructor.service.d.ts.map +1 -1
- package/dist/instructor/instructor.service.js +22 -16
- package/dist/instructor/instructor.service.js.map +1 -1
- package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +18 -8
- package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +7 -5
- package/hedhog/frontend/app/_components/create-lms-person-sheet.tsx.ejs +5 -9
- package/hedhog/frontend/app/_components/create-lms-student-person-sheet.tsx.ejs +5 -9
- package/hedhog/frontend/app/certificates/models/LeftPanel.tsx.ejs +15 -14
- package/hedhog/frontend/app/certificates/models/RightPanel.tsx.ejs +66 -29
- package/hedhog/frontend/app/certificates/models/TemplateEditorPage.tsx.ejs +4 -2
- package/hedhog/frontend/app/certificates/models/TopBar.tsx.ejs +44 -34
- package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +10 -10
- package/hedhog/frontend/app/classes/page.tsx.ejs +23 -15
- package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +5 -3
- package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +5 -3
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-panel.tsx.ejs +9 -7
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-skeleton.tsx.ejs +3 -1
- package/hedhog/frontend/app/courses/[id]/structure/_components/drag-handle.tsx.ejs +4 -2
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-bulk.tsx.ejs +24 -23
- package/hedhog/frontend/app/courses/[id]/structure/_components/multi-select-bar.tsx.ejs +21 -19
- package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +7 -5
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-display-settings-popover.tsx.ejs +18 -16
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +13 -11
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +5 -3
- package/hedhog/frontend/app/courses/[id]/structure/_components/use-course-structure-shortcuts.ts.ejs +14 -9
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +42 -25
- package/hedhog/frontend/app/enterprise/_components/enterprise-admin-create-sheet.tsx.ejs +3 -1
- package/hedhog/frontend/app/enterprise/_components/enterprise-administrators-tab.tsx.ejs +10 -8
- package/hedhog/frontend/app/enterprise/_components/enterprise-classes-tab.tsx.ejs +22 -20
- package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +3 -3
- package/hedhog/frontend/app/enterprise/_components/enterprise-courses-tab.tsx.ejs +21 -19
- package/hedhog/frontend/app/enterprise/_components/enterprise-sheet.tsx.ejs +34 -36
- package/hedhog/frontend/app/enterprise/_components/enterprise-student-create-sheet.tsx.ejs +3 -1
- package/hedhog/frontend/app/enterprise/_components/enterprise-students-tab.tsx.ejs +7 -5
- package/hedhog/frontend/app/enterprise/page.tsx.ejs +106 -54
- package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +79 -59
- package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +92 -26
- package/hedhog/frontend/app/instructors/page.tsx.ejs +4 -2
- package/hedhog/frontend/messages/en.json +619 -13
- package/hedhog/frontend/messages/pt.json +619 -13
- package/package.json +7 -7
- package/src/instructor/instructor.service.ts +22 -19
- package/hedhog/frontend/app/_components/create-lms-instructor-sheet.tsx.ejs +0 -591
- package/hedhog/frontend/app/courses/[id]/_components/CourseMainInfoCard.tsx.ejs +0 -109
- package/hedhog/frontend/app/courses/[id]/_components/CourseSummaryCard.tsx.ejs +0 -60
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +0 -134
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-course.tsx.ejs +0 -113
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +0 -314
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +0 -174
- package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +0 -185
- package/hedhog/frontend/app/enterprise/_components/enterprise-mocks.ts.ejs +0 -277
- package/hedhog/frontend/app/enterprise/_components/enterprise-user-create-sheet.tsx.ejs +0 -207
package/hedhog/frontend/app/courses/[id]/structure/_components/tree-display-settings-popover.tsx.ejs
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import { Separator } from '@/components/ui/separator';
|
|
11
11
|
import { Switch } from '@/components/ui/switch';
|
|
12
12
|
import { SlidersHorizontal } from 'lucide-react';
|
|
13
|
+
import { useTranslations } from 'next-intl';
|
|
13
14
|
|
|
14
15
|
import { useTreeDisplaySettings } from './use-tree-display-settings';
|
|
15
16
|
|
|
@@ -18,6 +19,7 @@ import { useTreeDisplaySettings } from './use-tree-display-settings';
|
|
|
18
19
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
19
20
|
|
|
20
21
|
export function TreeDisplaySettingsPopover() {
|
|
22
|
+
const t = useTranslations('lms.CoursesPage.StructurePage.displaySettings');
|
|
21
23
|
const {
|
|
22
24
|
showStatusDot,
|
|
23
25
|
showVisibility,
|
|
@@ -35,26 +37,26 @@ export function TreeDisplaySettingsPopover() {
|
|
|
35
37
|
variant="ghost"
|
|
36
38
|
size="sm"
|
|
37
39
|
className="h-8 gap-1.5 text-xs text-muted-foreground hover:text-foreground"
|
|
38
|
-
title=
|
|
40
|
+
title={t('title')}
|
|
39
41
|
>
|
|
40
42
|
<SlidersHorizontal className="size-3.5" />
|
|
41
|
-
<span className="sr-only sm:not-sr-only">
|
|
43
|
+
<span className="sr-only sm:not-sr-only">{t('label')}</span>
|
|
42
44
|
</Button>
|
|
43
45
|
</PopoverTrigger>
|
|
44
46
|
|
|
45
47
|
<PopoverContent align="end" className="w-56 p-0">
|
|
46
48
|
<div className="px-3 py-2.5 border-b">
|
|
47
|
-
<p className="text-xs font-semibold">
|
|
49
|
+
<p className="text-xs font-semibold">{t('sectionTitle')}</p>
|
|
48
50
|
<p className="text-[0.65rem] text-muted-foreground mt-0.5">
|
|
49
|
-
|
|
51
|
+
{t('sectionDescription')}
|
|
50
52
|
</p>
|
|
51
53
|
</div>
|
|
52
54
|
|
|
53
55
|
<div className="flex flex-col gap-0">
|
|
54
56
|
<SettingRow
|
|
55
57
|
id="show-status-dot"
|
|
56
|
-
label=
|
|
57
|
-
description=
|
|
58
|
+
label={t('showStatusDot.label')}
|
|
59
|
+
description={t('showStatusDot.description')}
|
|
58
60
|
checked={showStatusDot}
|
|
59
61
|
onCheckedChange={(v) => update({ showStatusDot: v })}
|
|
60
62
|
/>
|
|
@@ -63,8 +65,8 @@ export function TreeDisplaySettingsPopover() {
|
|
|
63
65
|
|
|
64
66
|
<SettingRow
|
|
65
67
|
id="show-visibility"
|
|
66
|
-
label=
|
|
67
|
-
description=
|
|
68
|
+
label={t('showVisibility.label')}
|
|
69
|
+
description={t('showVisibility.description')}
|
|
68
70
|
checked={showVisibility}
|
|
69
71
|
onCheckedChange={(v) => update({ showVisibility: v })}
|
|
70
72
|
/>
|
|
@@ -73,8 +75,8 @@ export function TreeDisplaySettingsPopover() {
|
|
|
73
75
|
|
|
74
76
|
<SettingRow
|
|
75
77
|
id="show-code"
|
|
76
|
-
label=
|
|
77
|
-
description=
|
|
78
|
+
label={t('showCode.label')}
|
|
79
|
+
description={t('showCode.description')}
|
|
78
80
|
checked={showCode}
|
|
79
81
|
onCheckedChange={(v) => update({ showCode: v })}
|
|
80
82
|
/>
|
|
@@ -83,8 +85,8 @@ export function TreeDisplaySettingsPopover() {
|
|
|
83
85
|
|
|
84
86
|
<SettingRow
|
|
85
87
|
id="show-video-indicator"
|
|
86
|
-
label=
|
|
87
|
-
description=
|
|
88
|
+
label={t('showVideoIndicator.label')}
|
|
89
|
+
description={t('showVideoIndicator.description')}
|
|
88
90
|
checked={showVideoIndicator}
|
|
89
91
|
onCheckedChange={(v) => update({ showVideoIndicator: v })}
|
|
90
92
|
/>
|
|
@@ -93,8 +95,8 @@ export function TreeDisplaySettingsPopover() {
|
|
|
93
95
|
|
|
94
96
|
<SettingRow
|
|
95
97
|
id="show-resources-indicator"
|
|
96
|
-
label=
|
|
97
|
-
description=
|
|
98
|
+
label={t('showResourcesIndicator.label')}
|
|
99
|
+
description={t('showResourcesIndicator.description')}
|
|
98
100
|
checked={showResourcesIndicator}
|
|
99
101
|
onCheckedChange={(v) => update({ showResourcesIndicator: v })}
|
|
100
102
|
/>
|
|
@@ -103,8 +105,8 @@ export function TreeDisplaySettingsPopover() {
|
|
|
103
105
|
|
|
104
106
|
<SettingRow
|
|
105
107
|
id="show-transcription-indicator"
|
|
106
|
-
label=
|
|
107
|
-
description=
|
|
108
|
+
label={t('showTranscriptionIndicator.label')}
|
|
109
|
+
description={t('showTranscriptionIndicator.description')}
|
|
108
110
|
checked={showTranscriptionIndicator}
|
|
109
111
|
onCheckedChange={(v) => update({ showTranscriptionIndicator: v })}
|
|
110
112
|
/>
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
Video,
|
|
16
16
|
type LucideIcon,
|
|
17
17
|
} from 'lucide-react';
|
|
18
|
+
import { useTranslations } from 'next-intl';
|
|
18
19
|
import { useEffect, useRef } from 'react';
|
|
19
20
|
import { useUpdateLessonMutation } from '../_data/use-course-structure-mutations';
|
|
20
21
|
import { HighlightedText } from './highlighted-text';
|
|
@@ -75,6 +76,7 @@ export function TreeRowLesson({
|
|
|
75
76
|
query,
|
|
76
77
|
onClick,
|
|
77
78
|
}: TreeRowLessonProps) {
|
|
79
|
+
const t = useTranslations('lms.CoursesPage.StructurePage.lessonRow');
|
|
78
80
|
const cfg = LESSON_TYPE_CONFIG[data.type];
|
|
79
81
|
const Icon = cfg.icon;
|
|
80
82
|
|
|
@@ -181,7 +183,7 @@ export function TreeRowLesson({
|
|
|
181
183
|
onClick={(e) => e.stopPropagation()}
|
|
182
184
|
onBlur={(e) => commitAndPersist(e.target.value)}
|
|
183
185
|
onKeyDown={handleKeyDown}
|
|
184
|
-
aria-label=
|
|
186
|
+
aria-label={t('rename')}
|
|
185
187
|
/>
|
|
186
188
|
) : (
|
|
187
189
|
<span className="truncate flex-1 text-xs leading-tight">
|
|
@@ -209,8 +211,8 @@ export function TreeRowLesson({
|
|
|
209
211
|
const VisIcon = visibilityCfg.icon;
|
|
210
212
|
return (
|
|
211
213
|
<span
|
|
212
|
-
|
|
213
|
-
|
|
214
|
+
title={t('visibility', { value: visibilityCfg.label })}
|
|
215
|
+
aria-label={t('visibility', { value: visibilityCfg.label })}
|
|
214
216
|
className="inline-flex items-center"
|
|
215
217
|
>
|
|
216
218
|
<VisIcon
|
|
@@ -223,15 +225,15 @@ export function TreeRowLesson({
|
|
|
223
225
|
{showStatusDot && statusCfg && (
|
|
224
226
|
<span
|
|
225
227
|
className={cn('size-1.5 rounded-full shrink-0', statusCfg.dot)}
|
|
226
|
-
title={
|
|
227
|
-
aria-label={
|
|
228
|
+
title={t('status', { value: statusCfg.label })}
|
|
229
|
+
aria-label={t('status', { value: statusCfg.label })}
|
|
228
230
|
/>
|
|
229
231
|
)}
|
|
230
232
|
{/* Video linked indicator */}
|
|
231
233
|
{showVideoIndicator && data.type === 'video' && data.videoUrl && (
|
|
232
234
|
<span
|
|
233
|
-
title=
|
|
234
|
-
aria-label=
|
|
235
|
+
title={t('videoLinked')}
|
|
236
|
+
aria-label={t('videoLinked')}
|
|
235
237
|
className="inline-flex items-center"
|
|
236
238
|
>
|
|
237
239
|
<Film className="size-3 shrink-0 text-violet-500" />
|
|
@@ -240,8 +242,8 @@ export function TreeRowLesson({
|
|
|
240
242
|
{/* Resources indicator */}
|
|
241
243
|
{showResourcesIndicator && data.resources.length > 0 && (
|
|
242
244
|
<span
|
|
243
|
-
title={
|
|
244
|
-
aria-label={
|
|
245
|
+
title={t('resources', { count: data.resources.length })}
|
|
246
|
+
aria-label={t('resourcesAria', { count: data.resources.length })}
|
|
245
247
|
className="inline-flex items-center gap-0.5"
|
|
246
248
|
>
|
|
247
249
|
<Paperclip className="size-3 shrink-0 text-sky-500" />
|
|
@@ -257,8 +259,8 @@ export function TreeRowLesson({
|
|
|
257
259
|
data.type === 'video' &&
|
|
258
260
|
data.transcription && (
|
|
259
261
|
<span
|
|
260
|
-
title=
|
|
261
|
-
aria-label=
|
|
262
|
+
title={t('hasTranscription')}
|
|
263
|
+
aria-label={t('hasTranscription')}
|
|
262
264
|
className="inline-flex items-center"
|
|
263
265
|
>
|
|
264
266
|
<ScrollText className="size-3 shrink-0 text-emerald-500" />
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { Input } from '@/components/ui/input';
|
|
4
4
|
import { cn } from '@/lib/utils';
|
|
5
5
|
import { ChevronDown, ChevronRight, Layers } from 'lucide-react';
|
|
6
|
+
import { useTranslations } from 'next-intl';
|
|
6
7
|
import { useEffect, useRef } from 'react';
|
|
7
8
|
import { useUpdateSessionMutation } from '../_data/use-course-structure-mutations';
|
|
8
9
|
import { HighlightedText } from './highlighted-text';
|
|
@@ -31,6 +32,7 @@ export function TreeRowSession({
|
|
|
31
32
|
onClick,
|
|
32
33
|
onToggleExpand,
|
|
33
34
|
}: TreeRowSessionProps) {
|
|
35
|
+
const t = useTranslations('lms.CoursesPage.StructurePage.sessionRow');
|
|
34
36
|
const inlineRenamingId = useStructureStore((s) => s.inlineRenamingId);
|
|
35
37
|
const cancelRename = useStructureStore((s) => s.cancelRename);
|
|
36
38
|
const startRename = useStructureStore((s) => s.startRename);
|
|
@@ -104,7 +106,7 @@ export function TreeRowSession({
|
|
|
104
106
|
<button
|
|
105
107
|
className="shrink-0 text-muted-foreground hover:text-foreground transition-colors p-0.5 rounded focus-visible:ring-1 focus-visible:ring-ring"
|
|
106
108
|
onClick={onToggleExpand}
|
|
107
|
-
aria-label={isExpanded ? '
|
|
109
|
+
aria-label={isExpanded ? t('collapse') : t('expand')}
|
|
108
110
|
tabIndex={-1}
|
|
109
111
|
>
|
|
110
112
|
{isExpanded ? (
|
|
@@ -129,7 +131,7 @@ export function TreeRowSession({
|
|
|
129
131
|
onClick={(e) => e.stopPropagation()}
|
|
130
132
|
onBlur={(e) => commitAndPersist(e.target.value)}
|
|
131
133
|
onKeyDown={handleKeyDown}
|
|
132
|
-
aria-label=
|
|
134
|
+
aria-label={t('rename')}
|
|
133
135
|
/>
|
|
134
136
|
) : (
|
|
135
137
|
<span className="truncate flex-1 font-medium leading-tight">
|
|
@@ -158,7 +160,7 @@ export function TreeRowSession({
|
|
|
158
160
|
? 'bg-muted-foreground/30'
|
|
159
161
|
: 'bg-emerald-500'
|
|
160
162
|
)}
|
|
161
|
-
title={data.published === false ? '
|
|
163
|
+
title={data.published === false ? t('draft') : t('published')}
|
|
162
164
|
/>
|
|
163
165
|
</div>
|
|
164
166
|
)}
|
package/hedhog/frontend/app/courses/[id]/structure/_components/use-course-structure-shortcuts.ts.ejs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useCallback, useEffect, useRef } from 'react';
|
|
4
|
+
import { useTranslations } from 'next-intl';
|
|
4
5
|
import { toast } from 'sonner';
|
|
5
6
|
|
|
6
7
|
import type { SearchFilterHandle } from './search-filter';
|
|
@@ -44,6 +45,7 @@ export function useCourseStructureShortcuts({
|
|
|
44
45
|
onDuplicate,
|
|
45
46
|
onPaste,
|
|
46
47
|
}: UseCourseStructureShortcutsOptions) {
|
|
48
|
+
const t = useTranslations('lms.CoursesPage.StructurePage.shortcuts');
|
|
47
49
|
const course = useStructureStore((s) => s.course);
|
|
48
50
|
const sessions = useStructureStore((s) => s.sessions);
|
|
49
51
|
const lessons = useStructureStore((s) => s.lessons);
|
|
@@ -236,12 +238,15 @@ export function useCourseStructureShortcuts({
|
|
|
236
238
|
if (!ids.length) return;
|
|
237
239
|
const count = ids.length;
|
|
238
240
|
s.showConfirm({
|
|
239
|
-
title:
|
|
240
|
-
|
|
241
|
+
title: t('deleteTitle', {
|
|
242
|
+
label:
|
|
243
|
+
count > 1 ? `${count} ${t('items')}` : t('selectedItem'),
|
|
244
|
+
}),
|
|
245
|
+
description: t('deleteDescription'),
|
|
241
246
|
onConfirm: () => {
|
|
242
247
|
s.deleteSelected();
|
|
243
248
|
toast.success(
|
|
244
|
-
count > 1 ?
|
|
249
|
+
count > 1 ? t('itemsDeleted', { count }) : t('itemDeleted')
|
|
245
250
|
);
|
|
246
251
|
},
|
|
247
252
|
});
|
|
@@ -260,7 +265,7 @@ export function useCourseStructureShortcuts({
|
|
|
260
265
|
const ids = selectedKeys.map((key) => key.slice(key.indexOf(':') + 1));
|
|
261
266
|
s.copyItems(ids, type);
|
|
262
267
|
toast.success(
|
|
263
|
-
|
|
268
|
+
ids.length > 1 ? t('itemsCopied', { count: ids.length }) : t('itemCopied')
|
|
264
269
|
);
|
|
265
270
|
return;
|
|
266
271
|
}
|
|
@@ -272,7 +277,7 @@ export function useCourseStructureShortcuts({
|
|
|
272
277
|
onDuplicate();
|
|
273
278
|
} else {
|
|
274
279
|
s.duplicateSelected();
|
|
275
|
-
toast.success('
|
|
280
|
+
toast.success(t('itemDuplicated'));
|
|
276
281
|
}
|
|
277
282
|
return;
|
|
278
283
|
}
|
|
@@ -283,7 +288,7 @@ export function useCourseStructureShortcuts({
|
|
|
283
288
|
if (onPaste) {
|
|
284
289
|
onPaste();
|
|
285
290
|
} else {
|
|
286
|
-
toast('
|
|
291
|
+
toast(t('nothingToPaste'));
|
|
287
292
|
}
|
|
288
293
|
return;
|
|
289
294
|
}
|
|
@@ -293,16 +298,16 @@ export function useCourseStructureShortcuts({
|
|
|
293
298
|
e.preventDefault();
|
|
294
299
|
if (s.activeItemType === 'course') {
|
|
295
300
|
s.addSession();
|
|
296
|
-
toast.success('
|
|
301
|
+
toast.success(t('newSessionCreated'));
|
|
297
302
|
} else if (s.activeItemType === 'session' && s.activeItemId) {
|
|
298
303
|
s.addLesson(s.activeItemId);
|
|
299
|
-
toast.success('
|
|
304
|
+
toast.success(t('newLessonCreated'));
|
|
300
305
|
} else if (s.activeItemType === 'lesson') {
|
|
301
306
|
// Find parent session and add there
|
|
302
307
|
const lesson = s.lessons.find((l) => l.id === s.activeItemId);
|
|
303
308
|
if (lesson) {
|
|
304
309
|
s.addLesson(lesson.sessionId);
|
|
305
|
-
toast.success('
|
|
310
|
+
toast.success(t('newLessonSameSession'));
|
|
306
311
|
}
|
|
307
312
|
}
|
|
308
313
|
return;
|
package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
24
|
+
import { useTranslations } from 'next-intl';
|
|
24
25
|
import { toast } from 'sonner';
|
|
25
26
|
|
|
26
27
|
import { useApp } from '@hed-hog/next-app-provider';
|
|
@@ -73,6 +74,7 @@ interface UpdateCourseVars {
|
|
|
73
74
|
* cache entry so the header immediately reflects the new title/slug.
|
|
74
75
|
*/
|
|
75
76
|
export function useUpdateCourseMutation() {
|
|
77
|
+
const t = useTranslations('lms.courseStructure');
|
|
76
78
|
const { request } = useApp();
|
|
77
79
|
const queryClient = useQueryClient();
|
|
78
80
|
const courseId = useStructureStore((s) => s.courseId);
|
|
@@ -106,13 +108,13 @@ export function useUpdateCourseMutation() {
|
|
|
106
108
|
},
|
|
107
109
|
};
|
|
108
110
|
});
|
|
109
|
-
toast.success('
|
|
111
|
+
toast.success(t('mutations.course.saveSuccess'));
|
|
110
112
|
},
|
|
111
113
|
onError: () => {
|
|
112
114
|
void queryClient.invalidateQueries({
|
|
113
115
|
queryKey: courseStructureQueryKey(courseId),
|
|
114
116
|
});
|
|
115
|
-
toast.error('
|
|
117
|
+
toast.error(t('mutations.course.saveError'));
|
|
116
118
|
},
|
|
117
119
|
});
|
|
118
120
|
}
|
|
@@ -128,6 +130,7 @@ export function useUpdateCourseMutation() {
|
|
|
128
130
|
* The newly created session becomes the active/selected item.
|
|
129
131
|
*/
|
|
130
132
|
export function useCreateSessionMutation() {
|
|
133
|
+
const t = useTranslations('lms.courseStructure');
|
|
131
134
|
const { request } = useApp();
|
|
132
135
|
const queryClient = useQueryClient();
|
|
133
136
|
const courseId = useStructureStore((s) => s.courseId);
|
|
@@ -153,13 +156,13 @@ export function useCreateSessionMutation() {
|
|
|
153
156
|
void queryClient.invalidateQueries({
|
|
154
157
|
queryKey: courseStructureQueryKey(courseId),
|
|
155
158
|
});
|
|
156
|
-
toast.success('
|
|
159
|
+
toast.success(t('mutations.session.createSuccess'));
|
|
157
160
|
},
|
|
158
161
|
onError: () => {
|
|
159
162
|
void queryClient.invalidateQueries({
|
|
160
163
|
queryKey: courseStructureQueryKey(courseId),
|
|
161
164
|
});
|
|
162
|
-
toast.error('
|
|
165
|
+
toast.error(t('mutations.session.createError'));
|
|
163
166
|
},
|
|
164
167
|
});
|
|
165
168
|
}
|
|
@@ -179,6 +182,7 @@ interface UpdateSessionVars {
|
|
|
179
182
|
* Persists the form values and updates the store on success.
|
|
180
183
|
*/
|
|
181
184
|
export function useUpdateSessionMutation() {
|
|
185
|
+
const t = useTranslations('lms.courseStructure');
|
|
182
186
|
const { request } = useApp();
|
|
183
187
|
const queryClient = useQueryClient();
|
|
184
188
|
const courseId = useStructureStore((s) => s.courseId);
|
|
@@ -214,14 +218,14 @@ export function useUpdateSessionMutation() {
|
|
|
214
218
|
};
|
|
215
219
|
}
|
|
216
220
|
);
|
|
217
|
-
toast.success('
|
|
221
|
+
toast.success(t('mutations.session.saveSuccess'));
|
|
218
222
|
},
|
|
219
223
|
onError: () => {
|
|
220
224
|
// Fall back to server truth
|
|
221
225
|
void queryClient.invalidateQueries({
|
|
222
226
|
queryKey: courseStructureQueryKey(courseId),
|
|
223
227
|
});
|
|
224
|
-
toast.error('
|
|
228
|
+
toast.error(t('mutations.session.saveError'));
|
|
225
229
|
},
|
|
226
230
|
});
|
|
227
231
|
}
|
|
@@ -241,6 +245,7 @@ interface CreateLessonVars {
|
|
|
241
245
|
* The newly created lesson becomes the active/selected item.
|
|
242
246
|
*/
|
|
243
247
|
export function useCreateLessonMutation() {
|
|
248
|
+
const t = useTranslations('lms.courseStructure');
|
|
244
249
|
const { request } = useApp();
|
|
245
250
|
const queryClient = useQueryClient();
|
|
246
251
|
const courseId = useStructureStore((s) => s.courseId);
|
|
@@ -271,13 +276,13 @@ export function useCreateLessonMutation() {
|
|
|
271
276
|
void queryClient.invalidateQueries({
|
|
272
277
|
queryKey: courseStructureQueryKey(courseId),
|
|
273
278
|
});
|
|
274
|
-
toast.success('
|
|
279
|
+
toast.success(t('mutations.lesson.createSuccess'));
|
|
275
280
|
},
|
|
276
281
|
onError: () => {
|
|
277
282
|
void queryClient.invalidateQueries({
|
|
278
283
|
queryKey: courseStructureQueryKey(courseId),
|
|
279
284
|
});
|
|
280
|
-
toast.error('
|
|
285
|
+
toast.error(t('mutations.lesson.createError'));
|
|
281
286
|
},
|
|
282
287
|
});
|
|
283
288
|
}
|
|
@@ -304,6 +309,7 @@ interface UpdateLessonVars {
|
|
|
304
309
|
* Persists the form values and updates the store on success.
|
|
305
310
|
*/
|
|
306
311
|
export function useUpdateLessonMutation() {
|
|
312
|
+
const t = useTranslations('lms.courseStructure');
|
|
307
313
|
const { request } = useApp();
|
|
308
314
|
const queryClient = useQueryClient();
|
|
309
315
|
const courseId = useStructureStore((s) => s.courseId);
|
|
@@ -340,13 +346,13 @@ export function useUpdateLessonMutation() {
|
|
|
340
346
|
};
|
|
341
347
|
}
|
|
342
348
|
);
|
|
343
|
-
toast.success('
|
|
349
|
+
toast.success(t('mutations.lesson.saveSuccess'));
|
|
344
350
|
},
|
|
345
351
|
onError: () => {
|
|
346
352
|
void queryClient.invalidateQueries({
|
|
347
353
|
queryKey: courseStructureQueryKey(courseId),
|
|
348
354
|
});
|
|
349
|
-
toast.error('
|
|
355
|
+
toast.error(t('mutations.lesson.saveError'));
|
|
350
356
|
},
|
|
351
357
|
});
|
|
352
358
|
}
|
|
@@ -366,6 +372,7 @@ interface DeleteSessionVars {
|
|
|
366
372
|
* from the Zustand store. Selection is reset to the course root.
|
|
367
373
|
*/
|
|
368
374
|
export function useDeleteSessionMutation() {
|
|
375
|
+
const t = useTranslations('lms.courseStructure');
|
|
369
376
|
const { request } = useApp();
|
|
370
377
|
const queryClient = useQueryClient();
|
|
371
378
|
const courseId = useStructureStore((s) => s.courseId);
|
|
@@ -389,13 +396,13 @@ export function useDeleteSessionMutation() {
|
|
|
389
396
|
};
|
|
390
397
|
}
|
|
391
398
|
);
|
|
392
|
-
toast.success('
|
|
399
|
+
toast.success(t('mutations.session.deleteSuccess'));
|
|
393
400
|
},
|
|
394
401
|
onError: () => {
|
|
395
402
|
void queryClient.invalidateQueries({
|
|
396
403
|
queryKey: courseStructureQueryKey(courseId),
|
|
397
404
|
});
|
|
398
|
-
toast.error('
|
|
405
|
+
toast.error(t('mutations.session.deleteError'));
|
|
399
406
|
},
|
|
400
407
|
});
|
|
401
408
|
}
|
|
@@ -415,6 +422,7 @@ interface DeleteLessonVars {
|
|
|
415
422
|
* Deletes a lesson and removes it from the Zustand store.
|
|
416
423
|
*/
|
|
417
424
|
export function useDeleteLessonMutation() {
|
|
425
|
+
const t = useTranslations('lms.courseStructure');
|
|
418
426
|
const { request } = useApp();
|
|
419
427
|
const queryClient = useQueryClient();
|
|
420
428
|
const courseId = useStructureStore((s) => s.courseId);
|
|
@@ -433,13 +441,13 @@ export function useDeleteLessonMutation() {
|
|
|
433
441
|
? { ...old, lessons: old.lessons.filter((l) => l.id !== lid) }
|
|
434
442
|
: old
|
|
435
443
|
);
|
|
436
|
-
toast.success('
|
|
444
|
+
toast.success(t('mutations.lesson.deleteSuccess'));
|
|
437
445
|
},
|
|
438
446
|
onError: () => {
|
|
439
447
|
void queryClient.invalidateQueries({
|
|
440
448
|
queryKey: courseStructureQueryKey(courseId),
|
|
441
449
|
});
|
|
442
|
-
toast.error('
|
|
450
|
+
toast.error(t('mutations.lesson.deleteError'));
|
|
443
451
|
},
|
|
444
452
|
});
|
|
445
453
|
}
|
|
@@ -466,6 +474,7 @@ interface BulkDeleteVars {
|
|
|
466
474
|
* 5. Show partial-error toast when some items fail.
|
|
467
475
|
*/
|
|
468
476
|
export function useBulkDeleteMutation() {
|
|
477
|
+
const t = useTranslations('lms.courseStructure');
|
|
469
478
|
const { request } = useApp();
|
|
470
479
|
const queryClient = useQueryClient();
|
|
471
480
|
const courseId = useStructureStore((s) => s.courseId);
|
|
@@ -538,7 +547,7 @@ export function useBulkDeleteMutation() {
|
|
|
538
547
|
void queryClient.invalidateQueries({
|
|
539
548
|
queryKey: courseStructureQueryKey(courseId),
|
|
540
549
|
});
|
|
541
|
-
toast.error('
|
|
550
|
+
toast.error(t('mutations.item.deleteError'));
|
|
542
551
|
},
|
|
543
552
|
});
|
|
544
553
|
}
|
|
@@ -561,6 +570,7 @@ interface ReorderSessionsVars {
|
|
|
561
570
|
* On error: rolls back via setStructureFromApi with the previous snapshot.
|
|
562
571
|
*/
|
|
563
572
|
export function useReorderSessionsMutation() {
|
|
573
|
+
const t = useTranslations('lms.courseStructure');
|
|
564
574
|
const { request } = useApp();
|
|
565
575
|
const queryClient = useQueryClient();
|
|
566
576
|
const courseId = useStructureStore((s) => s.courseId);
|
|
@@ -600,7 +610,7 @@ export function useReorderSessionsMutation() {
|
|
|
600
610
|
if (context?.previousCache) {
|
|
601
611
|
queryClient.setQueryData(qKey, context.previousCache);
|
|
602
612
|
}
|
|
603
|
-
toast.error('
|
|
613
|
+
toast.error(t('mutations.session.reorderError'));
|
|
604
614
|
},
|
|
605
615
|
});
|
|
606
616
|
}
|
|
@@ -624,6 +634,7 @@ interface ReorderLessonsVars {
|
|
|
624
634
|
* On error: rolls back via setStructureFromApi with the previous snapshot.
|
|
625
635
|
*/
|
|
626
636
|
export function useReorderLessonsMutation() {
|
|
637
|
+
const t = useTranslations('lms.courseStructure');
|
|
627
638
|
const { request } = useApp();
|
|
628
639
|
const queryClient = useQueryClient();
|
|
629
640
|
const courseId = useStructureStore((s) => s.courseId);
|
|
@@ -662,7 +673,7 @@ export function useReorderLessonsMutation() {
|
|
|
662
673
|
if (context?.previousCache) {
|
|
663
674
|
queryClient.setQueryData(qKey, context.previousCache);
|
|
664
675
|
}
|
|
665
|
-
toast.error('
|
|
676
|
+
toast.error(t('mutations.lesson.reorderError'));
|
|
666
677
|
},
|
|
667
678
|
});
|
|
668
679
|
}
|
|
@@ -687,6 +698,7 @@ interface MoveLessonVars {
|
|
|
687
698
|
* On error: rolls back via setStructureFromApi with the previous snapshot.
|
|
688
699
|
*/
|
|
689
700
|
export function useMoveLessonMutation() {
|
|
701
|
+
const t = useTranslations('lms.courseStructure');
|
|
690
702
|
const { request } = useApp();
|
|
691
703
|
const queryClient = useQueryClient();
|
|
692
704
|
const courseId = useStructureStore((s) => s.courseId);
|
|
@@ -729,7 +741,7 @@ export function useMoveLessonMutation() {
|
|
|
729
741
|
if (context?.previousCache) {
|
|
730
742
|
queryClient.setQueryData(qKey, context.previousCache);
|
|
731
743
|
}
|
|
732
|
-
toast.error('
|
|
744
|
+
toast.error(t('mutations.lesson.moveError'));
|
|
733
745
|
},
|
|
734
746
|
});
|
|
735
747
|
}
|
|
@@ -745,6 +757,7 @@ export function useMoveLessonMutation() {
|
|
|
745
757
|
* Non-optimistic: waits for API then adds real items to store.
|
|
746
758
|
*/
|
|
747
759
|
export function useDuplicateSessionMutation() {
|
|
760
|
+
const t = useTranslations('lms.courseStructure');
|
|
748
761
|
const { request } = useApp();
|
|
749
762
|
const queryClient = useQueryClient();
|
|
750
763
|
const courseId = useStructureStore((s) => s.courseId);
|
|
@@ -763,13 +776,13 @@ export function useDuplicateSessionMutation() {
|
|
|
763
776
|
void queryClient.invalidateQueries({
|
|
764
777
|
queryKey: courseStructureQueryKey(courseId),
|
|
765
778
|
});
|
|
766
|
-
toast.success(
|
|
779
|
+
toast.success(t('mutations.session.duplicateSuccess'));
|
|
767
780
|
},
|
|
768
781
|
onError: () => {
|
|
769
782
|
void queryClient.invalidateQueries({
|
|
770
783
|
queryKey: courseStructureQueryKey(courseId),
|
|
771
784
|
});
|
|
772
|
-
toast.error('
|
|
785
|
+
toast.error(t('mutations.session.duplicateError'));
|
|
773
786
|
},
|
|
774
787
|
});
|
|
775
788
|
}
|
|
@@ -785,6 +798,7 @@ export function useDuplicateSessionMutation() {
|
|
|
785
798
|
* Non-optimistic: waits for API then adds the real item to store.
|
|
786
799
|
*/
|
|
787
800
|
export function useDuplicateLessonMutation() {
|
|
801
|
+
const t = useTranslations('lms.courseStructure');
|
|
788
802
|
const { request } = useApp();
|
|
789
803
|
const queryClient = useQueryClient();
|
|
790
804
|
const courseId = useStructureStore((s) => s.courseId);
|
|
@@ -805,13 +819,13 @@ export function useDuplicateLessonMutation() {
|
|
|
805
819
|
void queryClient.invalidateQueries({
|
|
806
820
|
queryKey: courseStructureQueryKey(courseId),
|
|
807
821
|
});
|
|
808
|
-
toast.success('
|
|
822
|
+
toast.success(t('mutations.lesson.duplicateSuccess'));
|
|
809
823
|
},
|
|
810
824
|
onError: () => {
|
|
811
825
|
void queryClient.invalidateQueries({
|
|
812
826
|
queryKey: courseStructureQueryKey(courseId),
|
|
813
827
|
});
|
|
814
|
-
toast.error('
|
|
828
|
+
toast.error(t('mutations.lesson.duplicateError'));
|
|
815
829
|
},
|
|
816
830
|
});
|
|
817
831
|
}
|
|
@@ -827,6 +841,7 @@ export function useDuplicateLessonMutation() {
|
|
|
827
841
|
* Non-optimistic: waits for API then adds the new items to store.
|
|
828
842
|
*/
|
|
829
843
|
export function usePasteLessonsMutation() {
|
|
844
|
+
const t = useTranslations('lms.courseStructure');
|
|
830
845
|
const { request } = useApp();
|
|
831
846
|
const queryClient = useQueryClient();
|
|
832
847
|
const courseId = useStructureStore((s) => s.courseId);
|
|
@@ -864,7 +879,7 @@ export function usePasteLessonsMutation() {
|
|
|
864
879
|
void queryClient.invalidateQueries({
|
|
865
880
|
queryKey: courseStructureQueryKey(courseId),
|
|
866
881
|
});
|
|
867
|
-
toast.error('
|
|
882
|
+
toast.error(t('mutations.lesson.pasteError'));
|
|
868
883
|
},
|
|
869
884
|
});
|
|
870
885
|
}
|
|
@@ -880,6 +895,7 @@ export function usePasteLessonsMutation() {
|
|
|
880
895
|
* Non-optimistic: fires duplicate requests serially, then adds all results.
|
|
881
896
|
*/
|
|
882
897
|
export function usePasteSessionsMutation() {
|
|
898
|
+
const t = useTranslations('lms.courseStructure');
|
|
883
899
|
const { request } = useApp();
|
|
884
900
|
const queryClient = useQueryClient();
|
|
885
901
|
const courseId = useStructureStore((s) => s.courseId);
|
|
@@ -914,7 +930,7 @@ export function usePasteSessionsMutation() {
|
|
|
914
930
|
void queryClient.invalidateQueries({
|
|
915
931
|
queryKey: courseStructureQueryKey(courseId),
|
|
916
932
|
});
|
|
917
|
-
toast.error('
|
|
933
|
+
toast.error(t('mutations.session.pasteError'));
|
|
918
934
|
},
|
|
919
935
|
});
|
|
920
936
|
}
|
|
@@ -929,6 +945,7 @@ export function usePasteSessionsMutation() {
|
|
|
929
945
|
* On error: rolls back store to snapshot.
|
|
930
946
|
*/
|
|
931
947
|
export function useMoveLessonsMutation() {
|
|
948
|
+
const t = useTranslations('lms.courseStructure');
|
|
932
949
|
const { request } = useApp();
|
|
933
950
|
const queryClient = useQueryClient();
|
|
934
951
|
const courseId = useStructureStore((s) => s.courseId);
|
|
@@ -981,7 +998,7 @@ export function useMoveLessonsMutation() {
|
|
|
981
998
|
if (context?.previousCache) {
|
|
982
999
|
queryClient.setQueryData(qKey, context.previousCache);
|
|
983
1000
|
}
|
|
984
|
-
toast.error('
|
|
1001
|
+
toast.error(t('mutations.lesson.moveBatchError'));
|
|
985
1002
|
},
|
|
986
1003
|
});
|
|
987
1004
|
}
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
import { useApp } from '@hed-hog/next-app-provider';
|
|
30
30
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
31
31
|
import { Eye, EyeOff, Loader2, RefreshCw } from 'lucide-react';
|
|
32
|
+
import { useTranslations } from 'next-intl';
|
|
32
33
|
import { useEffect, useState } from 'react';
|
|
33
34
|
import { useForm } from 'react-hook-form';
|
|
34
35
|
import { toast } from 'sonner';
|
|
@@ -95,6 +96,7 @@ export function EnterpriseAdminCreateSheet({
|
|
|
95
96
|
onOpenChange,
|
|
96
97
|
onCreated,
|
|
97
98
|
}: EnterpriseAdminCreateSheetProps) {
|
|
99
|
+
const t = useTranslations('lms.EnterprisePage');
|
|
98
100
|
const { request } = useApp();
|
|
99
101
|
const [saving, setSaving] = useState(false);
|
|
100
102
|
const [showPassword, setShowPassword] = useState(false);
|
|
@@ -154,7 +156,7 @@ export function EnterpriseAdminCreateSheet({
|
|
|
154
156
|
onCreated?.(newUser);
|
|
155
157
|
handleOpenChange(false);
|
|
156
158
|
} catch {
|
|
157
|
-
toast.error('
|
|
159
|
+
toast.error(t('sheet.adminCreated'));
|
|
158
160
|
} finally {
|
|
159
161
|
setSaving(false);
|
|
160
162
|
}
|