@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.
Files changed (52) hide show
  1. package/dist/instructor/instructor.service.d.ts.map +1 -1
  2. package/dist/instructor/instructor.service.js +22 -16
  3. package/dist/instructor/instructor.service.js.map +1 -1
  4. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +18 -8
  5. package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +7 -5
  6. package/hedhog/frontend/app/_components/create-lms-person-sheet.tsx.ejs +5 -9
  7. package/hedhog/frontend/app/_components/create-lms-student-person-sheet.tsx.ejs +5 -9
  8. package/hedhog/frontend/app/certificates/models/LeftPanel.tsx.ejs +15 -14
  9. package/hedhog/frontend/app/certificates/models/RightPanel.tsx.ejs +66 -29
  10. package/hedhog/frontend/app/certificates/models/TemplateEditorPage.tsx.ejs +4 -2
  11. package/hedhog/frontend/app/certificates/models/TopBar.tsx.ejs +44 -34
  12. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +10 -10
  13. package/hedhog/frontend/app/classes/page.tsx.ejs +23 -15
  14. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +5 -3
  15. package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +5 -3
  16. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-panel.tsx.ejs +9 -7
  17. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-skeleton.tsx.ejs +3 -1
  18. package/hedhog/frontend/app/courses/[id]/structure/_components/drag-handle.tsx.ejs +4 -2
  19. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-bulk.tsx.ejs +24 -23
  20. package/hedhog/frontend/app/courses/[id]/structure/_components/multi-select-bar.tsx.ejs +21 -19
  21. package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +7 -5
  22. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-display-settings-popover.tsx.ejs +18 -16
  23. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +13 -11
  24. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +5 -3
  25. package/hedhog/frontend/app/courses/[id]/structure/_components/use-course-structure-shortcuts.ts.ejs +14 -9
  26. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +42 -25
  27. package/hedhog/frontend/app/enterprise/_components/enterprise-admin-create-sheet.tsx.ejs +3 -1
  28. package/hedhog/frontend/app/enterprise/_components/enterprise-administrators-tab.tsx.ejs +10 -8
  29. package/hedhog/frontend/app/enterprise/_components/enterprise-classes-tab.tsx.ejs +22 -20
  30. package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +3 -3
  31. package/hedhog/frontend/app/enterprise/_components/enterprise-courses-tab.tsx.ejs +21 -19
  32. package/hedhog/frontend/app/enterprise/_components/enterprise-sheet.tsx.ejs +34 -36
  33. package/hedhog/frontend/app/enterprise/_components/enterprise-student-create-sheet.tsx.ejs +3 -1
  34. package/hedhog/frontend/app/enterprise/_components/enterprise-students-tab.tsx.ejs +7 -5
  35. package/hedhog/frontend/app/enterprise/page.tsx.ejs +106 -54
  36. package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +79 -59
  37. package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +92 -26
  38. package/hedhog/frontend/app/instructors/page.tsx.ejs +4 -2
  39. package/hedhog/frontend/messages/en.json +619 -13
  40. package/hedhog/frontend/messages/pt.json +619 -13
  41. package/package.json +7 -7
  42. package/src/instructor/instructor.service.ts +22 -19
  43. package/hedhog/frontend/app/_components/create-lms-instructor-sheet.tsx.ejs +0 -591
  44. package/hedhog/frontend/app/courses/[id]/_components/CourseMainInfoCard.tsx.ejs +0 -109
  45. package/hedhog/frontend/app/courses/[id]/_components/CourseSummaryCard.tsx.ejs +0 -60
  46. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +0 -134
  47. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-course.tsx.ejs +0 -113
  48. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +0 -314
  49. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +0 -174
  50. package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +0 -185
  51. package/hedhog/frontend/app/enterprise/_components/enterprise-mocks.ts.ejs +0 -277
  52. package/hedhog/frontend/app/enterprise/_components/enterprise-user-create-sheet.tsx.ejs +0 -207
@@ -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="Configurações de exibição da lista"
40
+ title={t('title')}
39
41
  >
40
42
  <SlidersHorizontal className="size-3.5" />
41
- <span className="sr-only sm:not-sr-only">Exibição</span>
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">Informações nas aulas</p>
49
+ <p className="text-xs font-semibold">{t('sectionTitle')}</p>
48
50
  <p className="text-[0.65rem] text-muted-foreground mt-0.5">
49
- Escolha o que exibir em cada linha
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="Dot de status"
57
- description="Cor indicando etapa de produção"
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="Visibilidade"
67
- description="Ícone de público / privado / restrito"
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="Código"
77
- description="Exibe o código identificador"
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="Vídeo vinculado"
87
- description="Ícone quando aula de vídeo tem URL definida"
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="Recursos para download"
97
- description="Ícone quando há arquivos anexados"
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="Transcrição"
107
- description="Ícone quando vídeo tem transcrição"
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="Renomear aula"
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
- title={`Visibilidade: ${visibilityCfg.label}`}
213
- aria-label={`Visibilidade: ${visibilityCfg.label}`}
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={`Status: ${statusCfg.label}`}
227
- aria-label={`Status: ${statusCfg.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="Vídeo vinculado"
234
- aria-label="Vídeo vinculado"
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={`${data.resources.length} recurso${data.resources.length > 1 ? 's' : ''} para download`}
244
- aria-label={`${data.resources.length} recursos`}
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="Possui transcrição"
261
- aria-label="Possui transcrição"
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 ? 'Recolher sessão' : 'Expandir sessão'}
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="Renomear sessão"
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 ? 'Rascunho' : 'Publicada'}
163
+ title={data.published === false ? t('draft') : t('published')}
162
164
  />
163
165
  </div>
164
166
  )}
@@ -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: `Excluir ${count > 1 ? count + ' itens' : 'item selecionado'}?`,
240
- description: 'Esta ação não pode ser desfeita.',
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 ? `${count} itens excluídos` : 'Item excluído'
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
- `${ids.length > 1 ? ids.length + ' itens copiados' : 'Item copiado'}`
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('Item duplicado');
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('Nada para colar');
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('Nova sessão criada');
301
+ toast.success(t('newSessionCreated'));
297
302
  } else if (s.activeItemType === 'session' && s.activeItemId) {
298
303
  s.addLesson(s.activeItemId);
299
- toast.success('Nova aula criada');
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('Nova aula criada na mesma sessão');
310
+ toast.success(t('newLessonSameSession'));
306
311
  }
307
312
  }
308
313
  return;
@@ -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('Curso salvo');
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('Erro ao salvar curso');
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('Sessão criada');
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('Erro ao criar sessão');
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('Sessão salva');
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('Erro ao salvar sessão');
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('Aula criada');
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('Erro ao criar aula');
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('Aula salva');
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('Erro ao salvar aula');
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('Sessão excluída');
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('Erro ao excluir sessão');
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('Aula excluída');
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('Erro ao excluir aula');
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('Erro ao excluir itens');
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('Erro ao salvar ordem das sessões — revertido');
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('Erro ao salvar ordem das aulas — revertido');
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('Erro ao mover aula — revertido');
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(`Sessão duplicada`);
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('Erro ao duplicar sessão');
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('Aula duplicada');
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('Erro ao duplicar aula');
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('Erro ao colar aulas');
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('Erro ao colar sessões');
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('Erro ao mover aulas — revertido');
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('Failed to create administrator account.');
159
+ toast.error(t('sheet.adminCreated'));
158
160
  } finally {
159
161
  setSaving(false);
160
162
  }