@tuturuuu/ui 0.12.0 → 0.14.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 (51) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/package.json +4 -4
  3. package/src/components/ui/calendar-app/components/habits-panel.tsx +5 -2
  4. package/src/components/ui/calendar-app/components/priority-view.tsx +15 -7
  5. package/src/components/ui/calendar-app/components/scheduling-dialog.tsx +7 -3
  6. package/src/components/ui/calendar-app/components/task-scheduler-panel.tsx +7 -3
  7. package/src/components/ui/calendar-app/components/time-tracker/index.tsx +14 -9
  8. package/src/components/ui/custom/settings/task-settings.tsx +61 -2
  9. package/src/components/ui/custom/tables/data-table.tsx +58 -8
  10. package/src/components/ui/tu-do/boards/boardId/board-column.tsx +4 -0
  11. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/auto-scroll.test.ts +118 -0
  12. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/auto-scroll.ts +144 -70
  13. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.test.ts +63 -1
  14. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +21 -10
  15. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +6 -0
  16. package/src/components/ui/tu-do/boards/boardId/kanban.tsx +4 -2
  17. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.test.ts +11 -1
  18. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.ts +7 -3
  19. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-resource-context.test.ts +113 -0
  20. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-resource-context.ts +50 -0
  21. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +65 -18
  22. package/src/components/ui/tu-do/boards/boardId/timeline-board.tsx +1 -1
  23. package/src/components/ui/tu-do/cycles/task-cycles-client.tsx +23 -14
  24. package/src/components/ui/tu-do/estimates/use-task-estimates.ts +9 -4
  25. package/src/components/ui/tu-do/habits/client.tsx +4 -1
  26. package/src/components/ui/tu-do/my-tasks/__tests__/use-task-context-actions.test.ts +6 -3
  27. package/src/components/ui/tu-do/my-tasks/task-list-with-completion.tsx +19 -11
  28. package/src/components/ui/tu-do/my-tasks/use-my-tasks-query.ts +15 -6
  29. package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +18 -13
  30. package/src/components/ui/tu-do/my-tasks/use-task-context-actions.ts +17 -10
  31. package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +70 -0
  32. package/src/components/ui/tu-do/shared/board-views.tsx +33 -3
  33. package/src/components/ui/tu-do/shared/fade-setting-initializer.tsx +3 -1
  34. package/src/components/ui/tu-do/shared/list-view.tsx +1 -1
  35. package/src/components/ui/tu-do/shared/task-edit-dialog/components/quick-settings-popover.tsx +6 -2
  36. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/__tests__/use-update-shared-task.test.ts +9 -5
  37. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/task-api.ts +13 -8
  38. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-overrides.ts +21 -9
  39. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-update-shared-task.ts +10 -5
  40. package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +4 -2
  41. package/src/components/ui/tu-do/shared/task-estimation-picker.tsx +5 -2
  42. package/src/components/ui/tu-do/shared/task-quick-create-target-list.ts +20 -0
  43. package/src/components/ui/tu-do/shared/task-row-actions-menu.tsx +1 -1
  44. package/src/hooks/__tests__/use-task-actions.test.tsx +51 -0
  45. package/src/hooks/task-actions-personal-external.ts +2 -3
  46. package/src/hooks/use-board-actions.ts +27 -17
  47. package/src/hooks/use-calendar.tsx +7 -3
  48. package/src/hooks/use-settings-dialog-shortcut.ts +51 -0
  49. package/src/hooks/use-user-config.ts +10 -2
  50. package/src/lib/task-personal-external.ts +49 -0
  51. package/src/lib/tasks-app-url.ts +85 -0
@@ -0,0 +1,50 @@
1
+ import type { Task } from '@tuturuuu/types/primitives/Task';
2
+ import type { TaskList } from '@tuturuuu/types/primitives/TaskList';
3
+
4
+ export interface TaskCardResourceContextInput {
5
+ boardId: string;
6
+ pageWorkspaceId?: string;
7
+ propAvailableLists?: TaskList[];
8
+ task: Task;
9
+ }
10
+
11
+ export interface TaskCardResourceContext {
12
+ boardViewableMembersBoardId: string;
13
+ boardViewableMembersWorkspaceId?: string;
14
+ effectiveWorkspaceId?: string;
15
+ initialAvailableLists?: TaskList[];
16
+ isSourceWorkspaceTask: boolean;
17
+ taskBoardId: string;
18
+ }
19
+
20
+ export function getTaskCardResourceContext({
21
+ boardId,
22
+ pageWorkspaceId,
23
+ propAvailableLists,
24
+ task,
25
+ }: TaskCardResourceContextInput): TaskCardResourceContext {
26
+ const effectiveWorkspaceId = task.source_workspace_id ?? pageWorkspaceId;
27
+ const taskBoardId = task.source_board_id ?? boardId;
28
+ const isSourceWorkspaceTask = Boolean(
29
+ task.source_workspace_id || task.source_board_id
30
+ );
31
+ const boardViewableMembersWorkspaceId =
32
+ task.source_workspace_id ?? pageWorkspaceId;
33
+ const boardViewableMembersBoardId =
34
+ isSourceWorkspaceTask && task.source_board_id
35
+ ? task.source_board_id
36
+ : boardId;
37
+ const initialAvailableLists =
38
+ isSourceWorkspaceTask && task.source_board_id
39
+ ? undefined
40
+ : propAvailableLists;
41
+
42
+ return {
43
+ boardViewableMembersBoardId,
44
+ boardViewableMembersWorkspaceId,
45
+ effectiveWorkspaceId,
46
+ initialAvailableLists,
47
+ isSourceWorkspaceTask,
48
+ taskBoardId,
49
+ };
50
+ }
@@ -33,6 +33,7 @@ import {
33
33
  listWorkspaceTaskBoardViewableMembers,
34
34
  listWorkspaceTaskLists,
35
35
  listWorkspaceTaskProjects,
36
+ listWorkspaceTaskProjectsByIds,
36
37
  removeCurrentUserTaskPersonalPlacement,
37
38
  } from '@tuturuuu/internal-api/tasks';
38
39
  import type { SupportedColor } from '@tuturuuu/types/primitives/SupportedColors';
@@ -85,6 +86,7 @@ import React, {
85
86
  useRef,
86
87
  useState,
87
88
  } from 'react';
89
+ import { isPersonalExternalOverlayTask } from '../../../../../../lib/task-personal-external';
88
90
  import {
89
91
  enqueueTaskCardRelationshipRequest,
90
92
  useTaskCardRelationships,
@@ -153,6 +155,7 @@ import {
153
155
  getTaskCardHydratingOpenOptions,
154
156
  isExternalTaskSnapshot,
155
157
  } from './task-card-open-options';
158
+ import { getTaskCardResourceContext } from './task-card-resource-context';
156
159
  import { getTaskCardVisibilityState } from './task-card-visibility';
157
160
  import { TaskSchedulingBadge } from './task-scheduling-badge';
158
161
 
@@ -390,7 +393,24 @@ function TaskCardInner({
390
393
  // Use React Query hooks for shared data (cached across all task cards)
391
394
  const workspaceContextWsId = workspaceId ?? wsId;
392
395
  const { data: boardConfig } = useBoardConfig(boardId, workspaceContextWsId);
393
- const effectiveWorkspaceId = workspaceId ?? boardConfig?.ws_id ?? wsId;
396
+ const pageWorkspaceId = workspaceId ?? boardConfig?.ws_id ?? wsId;
397
+ const {
398
+ boardViewableMembersBoardId,
399
+ boardViewableMembersWorkspaceId,
400
+ effectiveWorkspaceId,
401
+ initialAvailableLists,
402
+ isSourceWorkspaceTask,
403
+ taskBoardId,
404
+ } = getTaskCardResourceContext({
405
+ boardId,
406
+ pageWorkspaceId,
407
+ propAvailableLists,
408
+ task,
409
+ });
410
+ const taskProjectIds = useMemo(
411
+ () => task.projects?.map((project) => project.id).filter(Boolean) ?? [],
412
+ [task.projects]
413
+ );
394
414
  const taskShareWsId = effectiveWorkspaceId;
395
415
  const { data: workspaceLabels = [], isLoading: labelsLoading } =
396
416
  useWorkspaceLabels(effectiveWorkspaceId);
@@ -431,12 +451,24 @@ function TaskCardInner({
431
451
  // Fetch workspace projects
432
452
  const { data: workspaceProjects = [], isLoading: projectsLoading } = useQuery(
433
453
  {
434
- queryKey: ['task_projects', effectiveWorkspaceId],
454
+ queryKey: [
455
+ 'task_projects',
456
+ effectiveWorkspaceId,
457
+ isSourceWorkspaceTask ? taskProjectIds.join(',') : 'all',
458
+ ],
435
459
  queryFn: async () => {
436
460
  if (!effectiveWorkspaceId) return [];
461
+ if (isSourceWorkspaceTask) {
462
+ return listWorkspaceTaskProjectsByIds(
463
+ effectiveWorkspaceId,
464
+ taskProjectIds
465
+ );
466
+ }
437
467
  return listWorkspaceTaskProjects(effectiveWorkspaceId);
438
468
  },
439
- enabled: !!effectiveWorkspaceId,
469
+ enabled:
470
+ !!effectiveWorkspaceId &&
471
+ (!isSourceWorkspaceTask || taskProjectIds.length > 0),
440
472
  staleTime: 5 * 60 * 1000, // 5 minutes - projects rarely change
441
473
  }
442
474
  );
@@ -470,27 +502,35 @@ function TaskCardInner({
470
502
  enabled: !!effectiveWorkspaceId && shouldLoadWorkspaceMembers,
471
503
  });
472
504
  const boardViewableMembersQuery = useQuery({
473
- queryKey: ['task-board-viewable-members', effectiveWorkspaceId, boardId],
505
+ queryKey: [
506
+ 'task-board-viewable-members',
507
+ boardViewableMembersWorkspaceId,
508
+ boardViewableMembersBoardId,
509
+ ],
474
510
  queryFn: async (): Promise<WorkspaceMember[]> => {
475
- if (!effectiveWorkspaceId || !boardId) return [];
511
+ if (!boardViewableMembersWorkspaceId || !boardViewableMembersBoardId) {
512
+ return [];
513
+ }
476
514
 
477
515
  const payload = await listWorkspaceTaskBoardViewableMembers(
478
- effectiveWorkspaceId,
479
- boardId
516
+ boardViewableMembersWorkspaceId,
517
+ boardViewableMembersBoardId
480
518
  );
481
519
  const members = Array.isArray(payload?.members) ? payload.members : [];
482
520
 
483
521
  return members.map((member) => ({
484
522
  id: member.user_id,
485
523
  user_id: member.user_id,
486
- workspace_id: effectiveWorkspaceId,
524
+ workspace_id: boardViewableMembersWorkspaceId,
487
525
  display_name: member.display_name ?? member.email ?? member.user_id,
488
526
  email: member.email ?? undefined,
489
527
  avatar_url: member.avatar_url ?? undefined,
490
528
  }));
491
529
  },
492
530
  enabled:
493
- !!effectiveWorkspaceId && !!boardId && shouldLoadBoardViewableMembers,
531
+ !!boardViewableMembersWorkspaceId &&
532
+ !!boardViewableMembersBoardId &&
533
+ shouldLoadBoardViewableMembers,
494
534
  staleTime: 5 * 60 * 1000,
495
535
  });
496
536
  const normalWorkspaceMembers = normalMembersQuery.data ?? [];
@@ -717,15 +757,15 @@ function TaskCardInner({
717
757
 
718
758
  // Fetch available task lists using React Query (same key as other components)
719
759
  const { data: availableLists = [] } = useQuery({
720
- queryKey: ['task_lists', boardId],
760
+ queryKey: ['task_lists', taskBoardId],
721
761
  queryFn: async () => {
722
- if (!effectiveWorkspaceId) {
762
+ if (!effectiveWorkspaceId || !taskBoardId) {
723
763
  return [];
724
764
  }
725
765
 
726
766
  const { lists } = await listWorkspaceTaskLists(
727
767
  effectiveWorkspaceId,
728
- boardId,
768
+ taskBoardId,
729
769
  typeof window !== 'undefined'
730
770
  ? { baseUrl: window.location.origin }
731
771
  : undefined
@@ -733,8 +773,8 @@ function TaskCardInner({
733
773
 
734
774
  return lists.filter((list) => !list.deleted) as TaskList[];
735
775
  },
736
- enabled: !propAvailableLists && !!effectiveWorkspaceId, // Only fetch if not provided as prop
737
- initialData: propAvailableLists,
776
+ enabled: !initialAvailableLists && !!effectiveWorkspaceId && !!taskBoardId,
777
+ initialData: initialAvailableLists,
738
778
  staleTime: 60 * 1000, // 1 minute - lists change less frequently
739
779
  });
740
780
 
@@ -852,6 +892,7 @@ function TaskCardInner({
852
892
  // Check if task is optimistically added (pending realtime confirmation)
853
893
  const isOptimistic = '_isOptimistic' in task && task._isOptimistic === true;
854
894
  const isPersonalExternalTask = isExternalTaskSnapshot(task);
895
+ const isPersonalExternalOverlay = isPersonalExternalOverlayTask(task);
855
896
  const sourceBoardUrl =
856
897
  task.source_workspace_id && task.source_board_id
857
898
  ? `/${task.source_workspace_id}${tasksHref(`/boards/${task.source_board_id}`)}`
@@ -1098,7 +1139,7 @@ function TaskCardInner({
1098
1139
  ? true
1099
1140
  : shouldUseBoardAssignees,
1100
1141
  assigneeMemberSource: task.source_workspace_id
1101
- ? 'workspace'
1142
+ ? 'board'
1102
1143
  : effectiveAssigneeMemberSource,
1103
1144
  effectiveWorkspaceId,
1104
1145
  isPersonalWorkspace,
@@ -1201,7 +1242,13 @@ function TaskCardInner({
1201
1242
  const handleAddSubtask = () => {
1202
1243
  setMenuOpen(false);
1203
1244
  // Open subtask creation dialog - the relationship will be created when the user saves
1204
- createSubtask(task.id, task.name, boardId, task.list_id, availableLists);
1245
+ createSubtask(
1246
+ task.id,
1247
+ task.name,
1248
+ taskBoardId,
1249
+ task.list_id,
1250
+ availableLists
1251
+ );
1205
1252
  };
1206
1253
 
1207
1254
  const handleMoveToExternalStaging = async () => {
@@ -2303,7 +2350,7 @@ function TaskCardInner({
2303
2350
  />
2304
2351
  )}
2305
2352
 
2306
- {isPersonalExternalTask && (
2353
+ {isPersonalExternalOverlay && (
2307
2354
  <>
2308
2355
  {task.personal_board_id && task.personal_list_id && (
2309
2356
  <DropdownMenuItem
@@ -2715,7 +2762,7 @@ function TaskCardInner({
2715
2762
  <CreateListDialog
2716
2763
  open={isCreateListDialogOpen}
2717
2764
  onOpenChange={setIsCreateListDialogOpen}
2718
- boardId={boardId}
2765
+ boardId={taskBoardId}
2719
2766
  wsId={effectiveWorkspaceId}
2720
2767
  initialStatus="active"
2721
2768
  onSuccess={(listId) => {
@@ -254,7 +254,7 @@ export function TimelineBoard({
254
254
  ? true
255
255
  : showAssignees,
256
256
  assigneeMemberSource: task.source_workspace_id
257
- ? 'workspace'
257
+ ? 'board'
258
258
  : effectiveAssigneeMemberSource,
259
259
  })
260
260
  );
@@ -47,6 +47,7 @@ import { toast } from '@tuturuuu/ui/sonner';
47
47
  import { Textarea } from '@tuturuuu/ui/textarea';
48
48
  import dayjs from 'dayjs';
49
49
  import { useCallback, useEffect, useMemo, useState } from 'react';
50
+ import { getTaskApiUrl } from '../../../../lib/tasks-app-url';
50
51
 
51
52
  type CycleStatus = 'planned' | 'active' | 'completed' | 'cancelled';
52
53
 
@@ -128,7 +129,9 @@ export function TaskCyclesClient({
128
129
  } = useQuery<TaskCycle[]>({
129
130
  queryKey: ['workspace', wsId, 'task-cycles'],
130
131
  queryFn: async () => {
131
- const response = await fetch(`/api/v1/workspaces/${wsId}/task-cycles`);
132
+ const response = await fetch(
133
+ getTaskApiUrl(`/api/v1/workspaces/${wsId}/task-cycles`)
134
+ );
132
135
  if (!response.ok) throw new Error('Failed to fetch cycles');
133
136
  return response.json();
134
137
  },
@@ -149,17 +152,21 @@ export function TaskCyclesClient({
149
152
  start_date?: string | null;
150
153
  end_date?: string | null;
151
154
  }) => {
152
- const response = await fetch(`/api/v1/workspaces/${wsId}/task-cycles`, {
153
- method: 'POST',
154
- headers: { 'Content-Type': 'application/json' },
155
- body: JSON.stringify({
156
- name,
157
- description,
158
- status,
159
- start_date,
160
- end_date,
161
- }),
162
- });
155
+ const response = await fetch(
156
+ getTaskApiUrl(`/api/v1/workspaces/${wsId}/task-cycles`),
157
+ {
158
+ method: 'POST',
159
+ credentials: 'include',
160
+ headers: { 'Content-Type': 'application/json' },
161
+ body: JSON.stringify({
162
+ name,
163
+ description,
164
+ status,
165
+ start_date,
166
+ end_date,
167
+ }),
168
+ }
169
+ );
163
170
  if (!response.ok) {
164
171
  const data = await response.json();
165
172
  throw new Error(data.error || 'Failed to create cycle');
@@ -196,9 +203,10 @@ export function TaskCyclesClient({
196
203
  end_date?: string | null;
197
204
  }) => {
198
205
  const response = await fetch(
199
- `/api/v1/workspaces/${wsId}/task-cycles/${id}`,
206
+ getTaskApiUrl(`/api/v1/workspaces/${wsId}/task-cycles/${id}`),
200
207
  {
201
208
  method: 'PUT',
209
+ credentials: 'include',
202
210
  headers: { 'Content-Type': 'application/json' },
203
211
  body: JSON.stringify({
204
212
  name,
@@ -232,9 +240,10 @@ export function TaskCyclesClient({
232
240
  const deleteMutation = useMutation({
233
241
  mutationFn: async (id: string) => {
234
242
  const response = await fetch(
235
- `/api/v1/workspaces/${wsId}/task-cycles/${id}`,
243
+ getTaskApiUrl(`/api/v1/workspaces/${wsId}/task-cycles/${id}`),
236
244
  {
237
245
  method: 'DELETE',
246
+ credentials: 'include',
238
247
  }
239
248
  );
240
249
  if (!response.ok) {
@@ -3,6 +3,7 @@
3
3
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
4
4
  import type { WorkspaceTaskBoard } from '@tuturuuu/types';
5
5
  import { useTranslations } from 'next-intl';
6
+ import { getTaskApiUrl } from '../../../../lib/tasks-app-url';
6
7
 
7
8
  export interface TaskEstimateBoardsResponse {
8
9
  boards: Partial<WorkspaceTaskBoard>[];
@@ -34,9 +35,12 @@ export const taskEstimateBoardKeys = {
34
35
  export async function fetchTaskEstimateBoards(
35
36
  wsId: string
36
37
  ): Promise<TaskEstimateBoardsResponse> {
37
- const response = await fetch(`/api/v1/workspaces/${wsId}/boards/estimation`, {
38
- cache: 'no-store',
39
- });
38
+ const response = await fetch(
39
+ getTaskApiUrl(`/api/v1/workspaces/${wsId}/boards/estimation`),
40
+ {
41
+ cache: 'no-store',
42
+ }
43
+ );
40
44
 
41
45
  if (!response.ok) {
42
46
  throw new Error('Failed to fetch task estimate boards');
@@ -56,9 +60,10 @@ async function updateTaskEstimateBoard(
56
60
  }: UpdateTaskEstimateBoardInput
57
61
  ) {
58
62
  const response = await fetch(
59
- `/api/v1/workspaces/${wsId}/boards/${boardId}/estimation`,
63
+ getTaskApiUrl(`/api/v1/workspaces/${wsId}/boards/${boardId}/estimation`),
60
64
  {
61
65
  cache: 'no-store',
66
+ credentials: 'include',
62
67
  method: 'PATCH',
63
68
  headers: {
64
69
  'Content-Type': 'application/json',
@@ -14,6 +14,7 @@ import {
14
14
  import { Skeleton } from '@tuturuuu/ui/skeleton';
15
15
  import { toast } from '@tuturuuu/ui/sonner';
16
16
  import { useState } from 'react';
17
+ import { getTaskApiUrl } from '../../../../lib/tasks-app-url';
17
18
  import HabitCard from './habit-card';
18
19
  import HabitFormDialog from './habit-form-dialog';
19
20
 
@@ -39,7 +40,9 @@ export default function HabitsClientPage({ wsId }: HabitsClientPageProps) {
39
40
  } = useQuery({
40
41
  queryKey: ['habits', wsId],
41
42
  queryFn: async () => {
42
- const response = await fetch(`/api/v1/workspaces/${wsId}/habits`);
43
+ const response = await fetch(
44
+ getTaskApiUrl(`/api/v1/workspaces/${wsId}/habits`)
45
+ );
43
46
  if (!response.ok) {
44
47
  throw new Error('Failed to fetch habits');
45
48
  }
@@ -128,9 +128,10 @@ describe('useTaskContextActions', () => {
128
128
  });
129
129
 
130
130
  expect(global.fetch).toHaveBeenCalledWith(
131
- `/api/v1/users/me/tasks/${mockTask.id}/overrides`,
131
+ `http://localhost:7809/api/v1/users/me/tasks/${mockTask.id}/overrides`,
132
132
  {
133
133
  method: 'PUT',
134
+ credentials: 'include',
134
135
  headers: { 'Content-Type': 'application/json' },
135
136
  body: JSON.stringify({ personally_unassigned: true }),
136
137
  }
@@ -160,9 +161,10 @@ describe('useTaskContextActions', () => {
160
161
  });
161
162
 
162
163
  expect(global.fetch).toHaveBeenCalledWith(
163
- `/api/v1/users/me/tasks/${mockTask.id}/overrides`,
164
+ `http://localhost:7809/api/v1/users/me/tasks/${mockTask.id}/overrides`,
164
165
  {
165
166
  method: 'PUT',
167
+ credentials: 'include',
166
168
  headers: { 'Content-Type': 'application/json' },
167
169
  body: JSON.stringify({
168
170
  personally_unassigned: false,
@@ -219,9 +221,10 @@ describe('useTaskContextActions', () => {
219
221
 
220
222
  // Verify override cleanup
221
223
  expect(global.fetch).toHaveBeenCalledWith(
222
- `/api/v1/users/me/tasks/${taskWithOverrides.id}/overrides`,
224
+ `http://localhost:7809/api/v1/users/me/tasks/${taskWithOverrides.id}/overrides`,
223
225
  expect.objectContaining({
224
226
  method: 'PUT',
227
+ credentials: 'include',
225
228
  headers: { 'Content-Type': 'application/json' },
226
229
  })
227
230
  );
@@ -34,6 +34,7 @@ import {
34
34
  import Link from 'next/link';
35
35
  import { useTranslations } from 'next-intl';
36
36
  import { type MouseEvent, useState } from 'react';
37
+ import { getTaskApiUrl } from '../../../../lib/tasks-app-url';
37
38
  import { useTasksHref } from '../tasks-route-context';
38
39
  import { MyTaskContextMenu } from './my-task-context-menu';
39
40
  import { PersonalPlacementDialog } from './personal-placement-dialog';
@@ -128,9 +129,10 @@ export default function TaskListWithCompletion({
128
129
  setCompletingTasks((prev) => new Set(prev).add(task.id));
129
130
  try {
130
131
  const response = await fetch(
131
- `/api/v1/users/me/tasks/${task.id}/overrides`,
132
+ getTaskApiUrl(`/api/v1/users/me/tasks/${task.id}/overrides`),
132
133
  {
133
134
  method: 'PUT',
135
+ credentials: 'include',
134
136
  headers: { 'Content-Type': 'application/json' },
135
137
  body: JSON.stringify({ personally_unassigned: false }),
136
138
  }
@@ -160,9 +162,10 @@ export default function TaskListWithCompletion({
160
162
 
161
163
  try {
162
164
  const response = await fetch(
163
- `/api/v1/users/me/tasks/${task.id}/overrides`,
165
+ getTaskApiUrl(`/api/v1/users/me/tasks/${task.id}/overrides`),
164
166
  {
165
167
  method: 'PUT',
168
+ credentials: 'include',
166
169
  headers: { 'Content-Type': 'application/json' },
167
170
  body: JSON.stringify({
168
171
  completed_at: isAlreadyDone ? null : new Date().toISOString(),
@@ -234,14 +237,18 @@ export default function TaskListWithCompletion({
234
237
  !isCompleted &&
235
238
  (task.overrides?.completed_at || task.overrides?.personally_unassigned)
236
239
  ) {
237
- await fetch(`/api/v1/users/me/tasks/${task.id}/overrides`, {
238
- method: 'PUT',
239
- headers: { 'Content-Type': 'application/json' },
240
- body: JSON.stringify({
241
- completed_at: null,
242
- personally_unassigned: false,
243
- }),
244
- }).catch(() => {
240
+ await fetch(
241
+ getTaskApiUrl(`/api/v1/users/me/tasks/${task.id}/overrides`),
242
+ {
243
+ method: 'PUT',
244
+ credentials: 'include',
245
+ headers: { 'Content-Type': 'application/json' },
246
+ body: JSON.stringify({
247
+ completed_at: null,
248
+ personally_unassigned: false,
249
+ }),
250
+ }
251
+ ).catch(() => {
245
252
  // Non-critical cleanup — don't block the completion flow
246
253
  });
247
254
  }
@@ -275,9 +282,10 @@ export default function TaskListWithCompletion({
275
282
 
276
283
  try {
277
284
  const response = await fetch(
278
- `/api/v1/users/me/tasks/${task.id}/overrides`,
285
+ getTaskApiUrl(`/api/v1/users/me/tasks/${task.id}/overrides`),
279
286
  {
280
287
  method: 'PUT',
288
+ credentials: 'include',
281
289
  headers: { 'Content-Type': 'application/json' },
282
290
  body: JSON.stringify({ personally_unassigned: true }),
283
291
  }
@@ -6,6 +6,7 @@ import {
6
6
  useQuery,
7
7
  } from '@tanstack/react-query';
8
8
  import type { TaskWithRelations } from '@tuturuuu/types';
9
+ import { getTaskApiUrl } from '../../../../lib/tasks-app-url';
9
10
 
10
11
  export interface MyTasksData {
11
12
  overdue: TaskWithRelations[];
@@ -62,9 +63,13 @@ export function useMyTasksQuery(
62
63
  }
63
64
  }
64
65
 
65
- const res = await fetch(`/api/v1/users/me/tasks?${params}`, {
66
- cache: 'no-store',
67
- });
66
+ const res = await fetch(
67
+ getTaskApiUrl(`/api/v1/users/me/tasks?${params}`),
68
+ {
69
+ cache: 'no-store',
70
+ credentials: 'include',
71
+ }
72
+ );
68
73
  if (!res.ok) {
69
74
  throw new Error('Failed to fetch tasks');
70
75
  }
@@ -117,9 +122,13 @@ export function useCompletedTasksQuery(
117
122
  }
118
123
  }
119
124
 
120
- const res = await fetch(`/api/v1/users/me/tasks?${params}`, {
121
- cache: 'no-store',
122
- });
125
+ const res = await fetch(
126
+ getTaskApiUrl(`/api/v1/users/me/tasks?${params}`),
127
+ {
128
+ cache: 'no-store',
129
+ credentials: 'include',
130
+ }
131
+ );
123
132
  if (!res.ok) {
124
133
  throw new Error('Failed to fetch completed tasks');
125
134
  }
@@ -25,6 +25,7 @@ import timezone from 'dayjs/plugin/timezone';
25
25
  import utc from 'dayjs/plugin/utc';
26
26
  import { useTranslations } from 'next-intl';
27
27
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
28
+ import { getTaskApiUrl } from '../../../../lib/tasks-app-url';
28
29
  import { getRandomNewLabelColor } from '../utils/taskConstants';
29
30
  import type { TaskOptions } from './command-bar';
30
31
  import type { JournalTaskResponse } from './task-preview-dialog';
@@ -552,19 +553,23 @@ export function useMyTasksState({
552
553
  }
553
554
  >({
554
555
  mutationFn: async (variables) => {
555
- const response = await fetch(`/api/v1/workspaces/${wsId}/tasks/journal`, {
556
- method: 'POST',
557
- headers: { 'Content-Type': 'application/json' },
558
- body: JSON.stringify({
559
- entry: variables.entry,
560
- previewOnly: true,
561
- generateDescriptions: variables.generateDescriptions,
562
- generatePriority: variables.generatePriority,
563
- generateLabels: variables.generateLabels,
564
- clientTimezone: variables.clientTimezone,
565
- clientTimestamp: variables.clientTimestamp,
566
- }),
567
- });
556
+ const response = await fetch(
557
+ getTaskApiUrl(`/api/v1/workspaces/${wsId}/tasks/journal`),
558
+ {
559
+ method: 'POST',
560
+ credentials: 'include',
561
+ headers: { 'Content-Type': 'application/json' },
562
+ body: JSON.stringify({
563
+ entry: variables.entry,
564
+ previewOnly: true,
565
+ generateDescriptions: variables.generateDescriptions,
566
+ generatePriority: variables.generatePriority,
567
+ generateLabels: variables.generateLabels,
568
+ clientTimezone: variables.clientTimezone,
569
+ clientTimestamp: variables.clientTimestamp,
570
+ }),
571
+ }
572
+ );
568
573
 
569
574
  const payload = await response.json().catch(() => null);
570
575
  if (!response.ok) {
@@ -14,6 +14,7 @@ import { getActiveTaskUserBroadcast } from '@tuturuuu/ui/hooks/useTaskUserRealti
14
14
  import { toast } from '@tuturuuu/ui/sonner';
15
15
  import { useTranslations } from 'next-intl';
16
16
  import { useCallback, useState } from 'react';
17
+ import { getTaskApiUrl } from '../../../../lib/tasks-app-url';
17
18
  import { dispatchTaskSoundCue } from '../shared/task-sound-effects';
18
19
  import {
19
20
  MY_COMPLETED_TASKS_QUERY_KEY,
@@ -243,14 +244,18 @@ export function useTaskContextActions({
243
244
  task.overrides?.completed_at ||
244
245
  task.overrides?.personally_unassigned
245
246
  ) {
246
- await fetch(`/api/v1/users/me/tasks/${task.id}/overrides`, {
247
- method: 'PUT',
248
- headers: { 'Content-Type': 'application/json' },
249
- body: JSON.stringify({
250
- completed_at: null,
251
- personally_unassigned: false,
252
- }),
253
- }).catch(() => {
247
+ await fetch(
248
+ getTaskApiUrl(`/api/v1/users/me/tasks/${task.id}/overrides`),
249
+ {
250
+ method: 'PUT',
251
+ credentials: 'include',
252
+ headers: { 'Content-Type': 'application/json' },
253
+ body: JSON.stringify({
254
+ completed_at: null,
255
+ personally_unassigned: false,
256
+ }),
257
+ }
258
+ ).catch(() => {
254
259
  // Non-critical cleanup
255
260
  });
256
261
  }
@@ -278,9 +283,10 @@ export function useTaskContextActions({
278
283
  setIsLoading(true);
279
284
  try {
280
285
  const response = await fetch(
281
- `/api/v1/users/me/tasks/${task.id}/overrides`,
286
+ getTaskApiUrl(`/api/v1/users/me/tasks/${task.id}/overrides`),
282
287
  {
283
288
  method: 'PUT',
289
+ credentials: 'include',
284
290
  headers: { 'Content-Type': 'application/json' },
285
291
  body: JSON.stringify({ personally_unassigned: true }),
286
292
  }
@@ -301,9 +307,10 @@ export function useTaskContextActions({
301
307
  setIsLoading(true);
302
308
  try {
303
309
  const response = await fetch(
304
- `/api/v1/users/me/tasks/${task.id}/overrides`,
310
+ getTaskApiUrl(`/api/v1/users/me/tasks/${task.id}/overrides`),
305
311
  {
306
312
  method: 'PUT',
313
+ credentials: 'include',
307
314
  headers: { 'Content-Type': 'application/json' },
308
315
  body: JSON.stringify({
309
316
  personally_unassigned: false,