@tuturuuu/ui 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/package.json +4 -4
  3. package/src/components/ui/finance/invoices/components/subscription-attendance-summary.tsx +19 -5
  4. package/src/components/ui/finance/invoices/components/subscription-prepaid-controls.test.tsx +41 -0
  5. package/src/components/ui/finance/invoices/components/subscription-prepaid-controls.tsx +93 -0
  6. package/src/components/ui/finance/invoices/hooks/use-subscription-auto-selection.ts +82 -70
  7. package/src/components/ui/finance/invoices/hooks/use-subscription-invoice-content.ts +50 -25
  8. package/src/components/ui/finance/invoices/hooks.ts +11 -2
  9. package/src/components/ui/finance/invoices/internal-api.ts +5 -0
  10. package/src/components/ui/finance/invoices/subscription-invoice.tsx +92 -31
  11. package/src/components/ui/finance/invoices/utils.test.ts +82 -0
  12. package/src/components/ui/finance/invoices/utils.ts +240 -7
  13. package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +7 -0
  14. package/src/components/ui/tu-do/shared/__tests__/task-board-loading-state.test.tsx +37 -0
  15. package/src/components/ui/tu-do/shared/board-client.tsx +3 -1
  16. package/src/components/ui/tu-do/shared/task-board-loading-state.tsx +55 -1
  17. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-description-editor.tsx +3 -0
  18. package/src/components/ui/tu-do/shared/task-edit-dialog/description-versions.test.ts +97 -0
  19. package/src/components/ui/tu-do/shared/task-edit-dialog/description-versions.ts +210 -0
  20. package/src/components/ui/tu-do/shared/task-edit-dialog/task-activity-section.tsx +56 -8
  21. package/src/components/ui/tu-do/shared/task-edit-dialog/task-description-change-dialog.test.tsx +63 -0
  22. package/src/components/ui/tu-do/shared/task-edit-dialog/task-description-change-dialog.tsx +218 -0
  23. package/src/components/ui/tu-do/shared/task-edit-dialog/task-description-restore-banner.tsx +83 -0
  24. package/src/components/ui/tu-do/shared/task-edit-dialog/task-description-version-restore-dialog.test.tsx +120 -0
  25. package/src/components/ui/tu-do/shared/task-edit-dialog/task-description-version-restore-dialog.tsx +180 -0
  26. package/src/components/ui/tu-do/shared/task-edit-dialog/utils.test.ts +100 -0
  27. package/src/components/ui/tu-do/shared/task-edit-dialog/utils.ts +109 -0
  28. package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +381 -34
  29. package/src/components/ui/tu-do/templates/task-template-api.ts +142 -0
  30. package/src/components/ui/tu-do/templates/task-template-card.tsx +118 -0
  31. package/src/components/ui/tu-do/templates/task-template-client.tsx +258 -0
  32. package/src/components/ui/tu-do/templates/task-template-dialogs.test.tsx +167 -0
  33. package/src/components/ui/tu-do/templates/task-template-dialogs.tsx +376 -0
  34. package/src/components/ui/tu-do/templates/task-templates-hub.test.tsx +114 -0
  35. package/src/components/ui/tu-do/templates/task-templates-hub.tsx +50 -0
  36. package/src/components/ui/tu-do/templates/task-templates-page.tsx +6 -11
@@ -0,0 +1,376 @@
1
+ 'use client';
2
+
3
+ import type {
4
+ WorkspaceTaskBoardListItem,
5
+ WorkspaceTaskListSummary,
6
+ } from '@tuturuuu/internal-api/tasks';
7
+ import { Button } from '@tuturuuu/ui/button';
8
+ import {
9
+ Dialog,
10
+ DialogContent,
11
+ DialogDescription,
12
+ DialogFooter,
13
+ DialogHeader,
14
+ DialogTitle,
15
+ } from '@tuturuuu/ui/dialog';
16
+ import { Input } from '@tuturuuu/ui/input';
17
+ import { Label } from '@tuturuuu/ui/label';
18
+ import {
19
+ Select,
20
+ SelectContent,
21
+ SelectItem,
22
+ SelectTrigger,
23
+ SelectValue,
24
+ } from '@tuturuuu/ui/select';
25
+ import { Textarea } from '@tuturuuu/ui/textarea';
26
+ import { useTranslations } from 'next-intl';
27
+ import { useEffect, useId, useState } from 'react';
28
+ import type {
29
+ WorkspaceTaskTemplate,
30
+ WorkspaceTaskTemplatePayload,
31
+ } from './task-template-api';
32
+
33
+ type Visibility = 'private' | 'workspace';
34
+
35
+ interface CreateTaskTemplateDialogProps {
36
+ onCreate: (payload: WorkspaceTaskTemplatePayload) => void;
37
+ onOpenChange: (open: boolean) => void;
38
+ open: boolean;
39
+ pending: boolean;
40
+ }
41
+
42
+ export function CreateTaskTemplateDialog({
43
+ onCreate,
44
+ onOpenChange,
45
+ open,
46
+ pending,
47
+ }: CreateTaskTemplateDialogProps) {
48
+ const t = useTranslations('ws-task-templates');
49
+ const nameId = useId();
50
+ const keyId = useId();
51
+ const taskNameId = useId();
52
+ const descriptionId = useId();
53
+ const [name, setName] = useState('');
54
+ const [key, setKey] = useState('');
55
+ const [taskName, setTaskName] = useState('');
56
+ const [description, setDescription] = useState('');
57
+ const [priority, setPriority] = useState<string>('none');
58
+ const [visibility, setVisibility] = useState<Visibility>('private');
59
+
60
+ useEffect(() => {
61
+ if (!open) return;
62
+ setName('');
63
+ setKey('');
64
+ setTaskName('');
65
+ setDescription('');
66
+ setPriority('none');
67
+ setVisibility('private');
68
+ }, [open]);
69
+
70
+ const canSubmit = name.trim().length > 0 || taskName.trim().length > 0;
71
+
72
+ return (
73
+ <Dialog open={open} onOpenChange={onOpenChange}>
74
+ <DialogContent>
75
+ <DialogHeader>
76
+ <DialogTitle>{t('create.title')}</DialogTitle>
77
+ <DialogDescription>{t('create.description')}</DialogDescription>
78
+ </DialogHeader>
79
+ <div className="space-y-4">
80
+ <div className="space-y-2">
81
+ <Label htmlFor={nameId}>{t('fields.template_name')}</Label>
82
+ <Input
83
+ id={nameId}
84
+ value={name}
85
+ onChange={(event) => setName(event.target.value)}
86
+ placeholder={t('fields.template_name_placeholder')}
87
+ />
88
+ </div>
89
+ <div className="space-y-2">
90
+ <Label htmlFor={keyId}>{t('fields.key')}</Label>
91
+ <Input
92
+ id={keyId}
93
+ value={key}
94
+ onChange={(event) => setKey(event.target.value)}
95
+ placeholder={t('fields.key_placeholder')}
96
+ />
97
+ </div>
98
+ <div className="space-y-2">
99
+ <Label htmlFor={taskNameId}>{t('fields.task_name')}</Label>
100
+ <Input
101
+ id={taskNameId}
102
+ value={taskName}
103
+ onChange={(event) => setTaskName(event.target.value)}
104
+ placeholder={t('fields.task_name_placeholder')}
105
+ />
106
+ </div>
107
+ <div className="space-y-2">
108
+ <Label htmlFor={descriptionId}>{t('fields.description')}</Label>
109
+ <Textarea
110
+ id={descriptionId}
111
+ value={description}
112
+ onChange={(event) => setDescription(event.target.value)}
113
+ placeholder={t('fields.description_placeholder')}
114
+ />
115
+ </div>
116
+ <div className="grid gap-3 sm:grid-cols-2">
117
+ <Select value={priority} onValueChange={setPriority}>
118
+ <SelectTrigger>
119
+ <SelectValue placeholder={t('fields.priority')} />
120
+ </SelectTrigger>
121
+ <SelectContent>
122
+ <SelectItem value="none">{t('priority.none')}</SelectItem>
123
+ <SelectItem value="low">{t('priority.low')}</SelectItem>
124
+ <SelectItem value="normal">{t('priority.normal')}</SelectItem>
125
+ <SelectItem value="high">{t('priority.high')}</SelectItem>
126
+ <SelectItem value="critical">
127
+ {t('priority.critical')}
128
+ </SelectItem>
129
+ </SelectContent>
130
+ </Select>
131
+ <Select
132
+ value={visibility}
133
+ onValueChange={(value) => setVisibility(value as Visibility)}
134
+ >
135
+ <SelectTrigger>
136
+ <SelectValue placeholder={t('fields.visibility')} />
137
+ </SelectTrigger>
138
+ <SelectContent>
139
+ <SelectItem value="private">
140
+ {t('visibility.private')}
141
+ </SelectItem>
142
+ <SelectItem value="workspace">
143
+ {t('visibility.workspace')}
144
+ </SelectItem>
145
+ </SelectContent>
146
+ </Select>
147
+ </div>
148
+ </div>
149
+ <DialogFooter>
150
+ <Button variant="outline" onClick={() => onOpenChange(false)}>
151
+ {t('actions.cancel')}
152
+ </Button>
153
+ <Button
154
+ disabled={!canSubmit || pending}
155
+ onClick={() =>
156
+ onCreate({
157
+ description: description.trim() || null,
158
+ key: key.trim() || undefined,
159
+ name: name.trim() || taskName.trim(),
160
+ priority: priority === 'none' ? null : (priority as never),
161
+ task_name: taskName.trim() || name.trim(),
162
+ visibility,
163
+ })
164
+ }
165
+ >
166
+ {t('actions.create')}
167
+ </Button>
168
+ </DialogFooter>
169
+ </DialogContent>
170
+ </Dialog>
171
+ );
172
+ }
173
+
174
+ interface UseTaskTemplateDialogProps {
175
+ boards: WorkspaceTaskBoardListItem[];
176
+ lists: WorkspaceTaskListSummary[];
177
+ loadingLists: boolean;
178
+ onBoardChange: (boardId: string) => void;
179
+ onListChange: (listId: string) => void;
180
+ onOpenChange: (open: boolean) => void;
181
+ onUse: (payload: { listId: string; name?: string }) => void;
182
+ open: boolean;
183
+ pending: boolean;
184
+ selectedBoardId: string;
185
+ selectedListId: string;
186
+ template: WorkspaceTaskTemplate | null;
187
+ }
188
+
189
+ export function UseTaskTemplateDialog({
190
+ boards,
191
+ lists,
192
+ loadingLists,
193
+ onBoardChange,
194
+ onListChange,
195
+ onOpenChange,
196
+ onUse,
197
+ open,
198
+ pending,
199
+ selectedBoardId,
200
+ selectedListId,
201
+ template,
202
+ }: UseTaskTemplateDialogProps) {
203
+ const t = useTranslations('ws-task-templates');
204
+ const overrideNameId = useId();
205
+ const [name, setName] = useState('');
206
+
207
+ useEffect(() => {
208
+ if (open) setName('');
209
+ }, [open]);
210
+
211
+ return (
212
+ <Dialog open={open} onOpenChange={onOpenChange}>
213
+ <DialogContent>
214
+ <DialogHeader>
215
+ <DialogTitle>{t('use.title')}</DialogTitle>
216
+ <DialogDescription>
217
+ {template
218
+ ? t('use.description', { name: template.name })
219
+ : t('use.description_empty')}
220
+ </DialogDescription>
221
+ </DialogHeader>
222
+ <div className="space-y-4">
223
+ <Select value={selectedBoardId} onValueChange={onBoardChange}>
224
+ <SelectTrigger>
225
+ <SelectValue placeholder={t('fields.board')} />
226
+ </SelectTrigger>
227
+ <SelectContent>
228
+ {boards.map((board) => (
229
+ <SelectItem key={board.id} value={board.id}>
230
+ {board.name || t('fields.unnamed_board')}
231
+ </SelectItem>
232
+ ))}
233
+ </SelectContent>
234
+ </Select>
235
+ <Select
236
+ disabled={!selectedBoardId || loadingLists}
237
+ onValueChange={onListChange}
238
+ value={selectedListId}
239
+ >
240
+ <SelectTrigger>
241
+ <SelectValue placeholder={t('fields.list')} />
242
+ </SelectTrigger>
243
+ <SelectContent>
244
+ {lists.map((list) => (
245
+ <SelectItem key={list.id} value={list.id}>
246
+ {list.name || t('fields.unnamed_list')}
247
+ </SelectItem>
248
+ ))}
249
+ </SelectContent>
250
+ </Select>
251
+ <div className="space-y-2">
252
+ <Label htmlFor={overrideNameId}>{t('fields.override_name')}</Label>
253
+ <Input
254
+ id={overrideNameId}
255
+ value={name}
256
+ onChange={(event) => setName(event.target.value)}
257
+ placeholder={template?.task_name ?? t('fields.task_name')}
258
+ />
259
+ </div>
260
+ </div>
261
+ <DialogFooter>
262
+ <Button variant="outline" onClick={() => onOpenChange(false)}>
263
+ {t('actions.cancel')}
264
+ </Button>
265
+ <Button
266
+ disabled={!template || !selectedListId || pending}
267
+ onClick={() =>
268
+ onUse({
269
+ listId: selectedListId,
270
+ name: name.trim() || undefined,
271
+ })
272
+ }
273
+ >
274
+ {t('actions.create_task')}
275
+ </Button>
276
+ </DialogFooter>
277
+ </DialogContent>
278
+ </Dialog>
279
+ );
280
+ }
281
+
282
+ interface SaveTaskTemplateFromTaskDialogProps {
283
+ onOpenChange: (open: boolean) => void;
284
+ onSave: (payload: {
285
+ name?: string;
286
+ taskId: string;
287
+ visibility: Visibility;
288
+ }) => void;
289
+ open: boolean;
290
+ pending: boolean;
291
+ }
292
+
293
+ export function SaveTaskTemplateFromTaskDialog({
294
+ onOpenChange,
295
+ onSave,
296
+ open,
297
+ pending,
298
+ }: SaveTaskTemplateFromTaskDialogProps) {
299
+ const t = useTranslations('ws-task-templates');
300
+ const taskIdInput = useId();
301
+ const nameInput = useId();
302
+ const [taskId, setTaskId] = useState('');
303
+ const [name, setName] = useState('');
304
+ const [visibility, setVisibility] = useState<Visibility>('private');
305
+
306
+ useEffect(() => {
307
+ if (!open) return;
308
+ setTaskId('');
309
+ setName('');
310
+ setVisibility('private');
311
+ }, [open]);
312
+
313
+ return (
314
+ <Dialog open={open} onOpenChange={onOpenChange}>
315
+ <DialogContent>
316
+ <DialogHeader>
317
+ <DialogTitle>{t('save_from_task.title')}</DialogTitle>
318
+ <DialogDescription>
319
+ {t('save_from_task.description')}
320
+ </DialogDescription>
321
+ </DialogHeader>
322
+ <div className="space-y-4">
323
+ <div className="space-y-2">
324
+ <Label htmlFor={taskIdInput}>{t('fields.task_id')}</Label>
325
+ <Input
326
+ id={taskIdInput}
327
+ value={taskId}
328
+ onChange={(event) => setTaskId(event.target.value)}
329
+ placeholder={t('fields.task_id_placeholder')}
330
+ />
331
+ </div>
332
+ <div className="space-y-2">
333
+ <Label htmlFor={nameInput}>{t('fields.template_name')}</Label>
334
+ <Input
335
+ id={nameInput}
336
+ value={name}
337
+ onChange={(event) => setName(event.target.value)}
338
+ placeholder={t('fields.template_name_placeholder')}
339
+ />
340
+ </div>
341
+ <Select
342
+ value={visibility}
343
+ onValueChange={(value) => setVisibility(value as Visibility)}
344
+ >
345
+ <SelectTrigger>
346
+ <SelectValue placeholder={t('fields.visibility')} />
347
+ </SelectTrigger>
348
+ <SelectContent>
349
+ <SelectItem value="private">{t('visibility.private')}</SelectItem>
350
+ <SelectItem value="workspace">
351
+ {t('visibility.workspace')}
352
+ </SelectItem>
353
+ </SelectContent>
354
+ </Select>
355
+ </div>
356
+ <DialogFooter>
357
+ <Button variant="outline" onClick={() => onOpenChange(false)}>
358
+ {t('actions.cancel')}
359
+ </Button>
360
+ <Button
361
+ disabled={!taskId.trim() || pending}
362
+ onClick={() =>
363
+ onSave({
364
+ name: name.trim() || undefined,
365
+ taskId: taskId.trim(),
366
+ visibility,
367
+ })
368
+ }
369
+ >
370
+ {t('actions.save')}
371
+ </Button>
372
+ </DialogFooter>
373
+ </DialogContent>
374
+ </Dialog>
375
+ );
376
+ }
@@ -0,0 +1,114 @@
1
+ import '@testing-library/jest-dom';
2
+ import { fireEvent, render, screen } from '@testing-library/react';
3
+ import { describe, expect, it, vi } from 'vitest';
4
+ import { TaskTemplatesHub } from './task-templates-hub';
5
+
6
+ vi.mock('next-intl', () => ({
7
+ useTranslations: () => (key: string) => key,
8
+ }));
9
+
10
+ vi.mock('./task-template-client', () => ({
11
+ TaskTemplateClient: ({
12
+ initialTemplates,
13
+ wsId,
14
+ }: {
15
+ initialTemplates: Array<{ name: string }>;
16
+ wsId: string;
17
+ }) => (
18
+ <div data-testid="task-template-tab">
19
+ task templates for {wsId}:{' '}
20
+ {initialTemplates.map((item) => item.name).join(', ')}
21
+ </div>
22
+ ),
23
+ }));
24
+
25
+ vi.mock('./client', () => ({
26
+ default: ({
27
+ initialTemplates,
28
+ templatesBasePath,
29
+ wsId,
30
+ }: {
31
+ initialTemplates: Array<{ name: string }>;
32
+ templatesBasePath: string;
33
+ wsId: string;
34
+ }) => (
35
+ <div data-testid="board-template-tab">
36
+ board templates for {wsId}/{templatesBasePath}:{' '}
37
+ {initialTemplates.map((item) => item.name).join(', ')}
38
+ </div>
39
+ ),
40
+ }));
41
+
42
+ describe('TaskTemplatesHub', () => {
43
+ it('defaults to task templates and keeps board templates available', () => {
44
+ render(
45
+ <TaskTemplatesHub
46
+ boardTemplates={[
47
+ {
48
+ createdAt: '2026-06-29T00:00:00.000Z',
49
+ createdBy: 'user-1',
50
+ description: null,
51
+ id: 'board-template-1',
52
+ isOwner: true,
53
+ name: 'Sprint board',
54
+ sourceBoardId: 'board-1',
55
+ stats: { labels: 0, lists: 1, tasks: 0 },
56
+ updatedAt: '2026-06-29T00:00:00.000Z',
57
+ visibility: 'workspace',
58
+ wsId: 'ws-1',
59
+ },
60
+ ]}
61
+ taskTemplates={[
62
+ {
63
+ archived_at: null,
64
+ assignee_ids: [],
65
+ created_at: '2026-06-29T00:00:00.000Z',
66
+ created_by: 'user-1',
67
+ default_board_id: null,
68
+ default_list_id: null,
69
+ description: null,
70
+ description_yjs_state: null,
71
+ end_date: null,
72
+ estimation_points: null,
73
+ id: 'task-template-1',
74
+ isOwner: true,
75
+ label_ids: [],
76
+ name: 'Bug report',
77
+ priority: null,
78
+ project_ids: [],
79
+ slug: 'bug-report',
80
+ source_task_id: null,
81
+ start_date: null,
82
+ task_name: 'Investigate bug',
83
+ updated_at: '2026-06-29T00:00:00.000Z',
84
+ visibility: 'private',
85
+ ws_id: 'ws-1',
86
+ },
87
+ ]}
88
+ templatesBasePath="tasks/templates"
89
+ wsId="ws-1"
90
+ />
91
+ );
92
+
93
+ expect(screen.getByRole('tab', { name: /tabs.tasks/i })).toHaveAttribute(
94
+ 'data-state',
95
+ 'active'
96
+ );
97
+ expect(screen.getByTestId('task-template-tab')).toHaveTextContent(
98
+ 'Bug report'
99
+ );
100
+
101
+ const boardTab = screen.getByRole('tab', { name: /tabs.boards/i });
102
+ fireEvent.pointerDown(boardTab);
103
+ fireEvent.mouseDown(boardTab);
104
+ fireEvent.click(boardTab);
105
+
106
+ expect(boardTab).toHaveAttribute('data-state', 'active');
107
+ expect(screen.getByTestId('board-template-tab')).toHaveTextContent(
108
+ 'Sprint board'
109
+ );
110
+ expect(screen.getByTestId('board-template-tab')).toHaveTextContent(
111
+ 'tasks/templates'
112
+ );
113
+ });
114
+ });
@@ -0,0 +1,50 @@
1
+ 'use client';
2
+
3
+ import { KanbanSquare, ListTodo } from '@tuturuuu/icons';
4
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@tuturuuu/ui/tabs';
5
+ import { useTranslations } from 'next-intl';
6
+ import TemplatesClient from './client';
7
+ import type { WorkspaceTaskTemplate } from './task-template-api';
8
+ import { TaskTemplateClient } from './task-template-client';
9
+ import type { BoardTemplate } from './types';
10
+
11
+ interface TaskTemplatesHubProps {
12
+ boardTemplates: BoardTemplate[];
13
+ taskTemplates?: WorkspaceTaskTemplate[];
14
+ templatesBasePath?: string;
15
+ wsId: string;
16
+ }
17
+
18
+ export function TaskTemplatesHub({
19
+ boardTemplates,
20
+ taskTemplates = [],
21
+ templatesBasePath = 'templates',
22
+ wsId,
23
+ }: TaskTemplatesHubProps) {
24
+ const t = useTranslations('ws-task-templates');
25
+
26
+ return (
27
+ <Tabs defaultValue="tasks" className="space-y-4">
28
+ <TabsList>
29
+ <TabsTrigger value="tasks">
30
+ <ListTodo className="h-4 w-4" />
31
+ {t('tabs.tasks')}
32
+ </TabsTrigger>
33
+ <TabsTrigger value="boards">
34
+ <KanbanSquare className="h-4 w-4" />
35
+ {t('tabs.boards')}
36
+ </TabsTrigger>
37
+ </TabsList>
38
+ <TabsContent value="tasks">
39
+ <TaskTemplateClient initialTemplates={taskTemplates} wsId={wsId} />
40
+ </TabsContent>
41
+ <TabsContent value="boards">
42
+ <TemplatesClient
43
+ initialTemplates={boardTemplates}
44
+ templatesBasePath={templatesBasePath}
45
+ wsId={wsId}
46
+ />
47
+ </TabsContent>
48
+ </Tabs>
49
+ );
50
+ }
@@ -2,10 +2,10 @@ import { Store } from '@tuturuuu/icons';
2
2
  import { createAdminClient } from '@tuturuuu/supabase/next/server';
3
3
  import { Button } from '@tuturuuu/ui/button';
4
4
  import FeatureSummary from '@tuturuuu/ui/custom/feature-summary';
5
- import TemplatesClient from '@tuturuuu/ui/tu-do/templates/client';
5
+ import { TaskTemplatesHub } from '@tuturuuu/ui/tu-do/templates/task-templates-hub';
6
6
  import type { BoardTemplate } from '@tuturuuu/ui/tu-do/templates/types';
7
7
  import { getCurrentUser } from '@tuturuuu/utils/user-helper';
8
- import { getPermissions, getWorkspace } from '@tuturuuu/utils/workspace-helper';
8
+ import { getWorkspace } from '@tuturuuu/utils/workspace-helper';
9
9
  import Link from 'next/link';
10
10
  import { notFound, redirect } from 'next/navigation';
11
11
  import { getTranslations } from 'next-intl/server';
@@ -121,13 +121,8 @@ export default async function TaskTemplatesPage({
121
121
 
122
122
  const wsId = workspace.id;
123
123
 
124
- const permissions = await getPermissions({ wsId });
125
- if (!permissions) notFound();
126
- const { withoutPermission } = permissions;
127
- if (withoutPermission('manage_projects')) redirect(`/${wsId}`);
128
-
129
124
  const { templates } = await getTemplates(wsId, user.id);
130
- const t = await getTranslations('ws-board-templates');
125
+ const t = await getTranslations('ws-task-templates');
131
126
 
132
127
  const marketplaceUrl = `/${wsId}/${templatesBasePath}/marketplace`;
133
128
 
@@ -144,14 +139,14 @@ export default async function TaskTemplatesPage({
144
139
  <Link href={marketplaceUrl}>
145
140
  <Button variant="outline" size="sm" className="gap-2">
146
141
  <Store className="h-4 w-4" />
147
- {t('gallery.marketplace')}
142
+ {t('gallery.board_marketplace')}
148
143
  </Button>
149
144
  </Link>
150
145
  }
151
146
  />
152
- <TemplatesClient
147
+ <TaskTemplatesHub
148
+ boardTemplates={templates}
153
149
  wsId={wsId}
154
- initialTemplates={templates}
155
150
  templatesBasePath={templatesBasePath}
156
151
  />
157
152
  </div>