@tuturuuu/ui 0.11.1 → 0.13.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.
- package/CHANGELOG.md +23 -0
- package/package.json +5 -5
- package/src/components/ui/calendar-app/components/habits-panel.tsx +5 -2
- package/src/components/ui/calendar-app/components/priority-view.tsx +15 -7
- package/src/components/ui/calendar-app/components/scheduling-dialog.tsx +7 -3
- package/src/components/ui/calendar-app/components/task-scheduler-panel.tsx +7 -3
- package/src/components/ui/calendar-app/components/time-tracker/index.tsx +14 -9
- package/src/components/ui/custom/settings/task-settings.tsx +6 -2
- package/src/components/ui/custom/tables/data-table.tsx +58 -8
- package/src/components/ui/storefront/bundle-selection-dialog.tsx +249 -0
- package/src/components/ui/storefront/cart-summary-parts.tsx +28 -8
- package/src/components/ui/storefront/listing-card.tsx +6 -3
- package/src/components/ui/storefront/storefront-surface.test.tsx +134 -1
- package/src/components/ui/storefront/storefront-surface.tsx +66 -7
- package/src/components/ui/storefront/types.ts +15 -0
- package/src/components/ui/storefront/utils.ts +164 -2
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.test.ts +63 -1
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +3 -6
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.test.ts +11 -1
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.ts +7 -3
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-resource-context.test.ts +113 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-resource-context.ts +50 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +65 -18
- package/src/components/ui/tu-do/boards/boardId/timeline-board.tsx +1 -1
- package/src/components/ui/tu-do/cycles/task-cycles-client.tsx +23 -14
- package/src/components/ui/tu-do/estimates/use-task-estimates.ts +9 -4
- package/src/components/ui/tu-do/habits/client.tsx +4 -1
- package/src/components/ui/tu-do/my-tasks/__tests__/use-task-context-actions.test.ts +6 -3
- package/src/components/ui/tu-do/my-tasks/task-list-with-completion.tsx +19 -11
- package/src/components/ui/tu-do/my-tasks/use-my-tasks-query.ts +15 -6
- package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +18 -13
- package/src/components/ui/tu-do/my-tasks/use-task-context-actions.ts +17 -10
- package/src/components/ui/tu-do/shared/fade-setting-initializer.tsx +3 -1
- package/src/components/ui/tu-do/shared/list-view.tsx +1 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/quick-settings-popover.tsx +6 -2
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/__tests__/use-update-shared-task.test.ts +9 -5
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/task-api.ts +13 -8
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-overrides.ts +21 -9
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-update-shared-task.ts +10 -5
- package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +4 -2
- package/src/components/ui/tu-do/shared/task-estimation-picker.tsx +5 -2
- package/src/components/ui/tu-do/shared/task-row-actions-menu.tsx +1 -1
- package/src/hooks/__tests__/use-task-actions.test.tsx +51 -0
- package/src/hooks/task-actions-personal-external.ts +2 -3
- package/src/hooks/use-board-actions.ts +27 -17
- package/src/hooks/use-calendar.tsx +7 -3
- package/src/hooks/use-settings-dialog-shortcut.ts +51 -0
- package/src/lib/task-personal-external.ts +49 -0
- package/src/lib/tasks-app-url.ts +85 -0
|
@@ -128,9 +128,10 @@ describe('useTaskContextActions', () => {
|
|
|
128
128
|
});
|
|
129
129
|
|
|
130
130
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
131
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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(
|
|
66
|
-
|
|
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(
|
|
121
|
-
|
|
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(
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
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(
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useQuery } from '@tanstack/react-query';
|
|
4
4
|
import { createClient } from '@tuturuuu/supabase/next/client';
|
|
5
5
|
import { useEffect, useState } from 'react';
|
|
6
|
+
import { getTaskApiUrl } from '../../../../lib/tasks-app-url';
|
|
6
7
|
|
|
7
8
|
interface TaskSettingsData {
|
|
8
9
|
task_auto_assign_to_self: boolean;
|
|
@@ -32,8 +33,9 @@ export function FadeSettingInitializer() {
|
|
|
32
33
|
const { data: settings } = useQuery({
|
|
33
34
|
queryKey: ['user-task-settings'],
|
|
34
35
|
queryFn: async (): Promise<TaskSettingsData> => {
|
|
35
|
-
const res = await fetch('/api/v1/users/task-settings', {
|
|
36
|
+
const res = await fetch(getTaskApiUrl('/api/v1/users/task-settings'), {
|
|
36
37
|
cache: 'no-store',
|
|
38
|
+
credentials: 'include',
|
|
37
39
|
});
|
|
38
40
|
if (!res.ok) {
|
|
39
41
|
// Return defaults if API fails
|
|
@@ -528,7 +528,7 @@ function InteractiveListView({
|
|
|
528
528
|
isPersonalWorkspace,
|
|
529
529
|
canUseBoardAssignees: task.source_workspace_id ? true : showAssignees,
|
|
530
530
|
assigneeMemberSource: task.source_workspace_id
|
|
531
|
-
? '
|
|
531
|
+
? 'board'
|
|
532
532
|
: effectiveAssigneeMemberSource,
|
|
533
533
|
})
|
|
534
534
|
);
|
package/src/components/ui/tu-do/shared/task-edit-dialog/components/quick-settings-popover.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import { Popover, PopoverContent, PopoverTrigger } from '@tuturuuu/ui/popover';
|
|
|
9
9
|
import { Switch } from '@tuturuuu/ui/switch';
|
|
10
10
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@tuturuuu/ui/tooltip';
|
|
11
11
|
import { useTranslations } from 'next-intl';
|
|
12
|
+
import { getTaskApiUrl } from '../../../../../../lib/tasks-app-url';
|
|
12
13
|
import { TASK_SOUND_EFFECTS_ENABLED_CONFIG_ID } from '../../task-sound-effects';
|
|
13
14
|
|
|
14
15
|
interface TaskSettingsData {
|
|
@@ -54,7 +55,9 @@ export function QuickSettingsPopover({
|
|
|
54
55
|
const { data: settings, isLoading } = useQuery({
|
|
55
56
|
queryKey: ['user-task-settings'],
|
|
56
57
|
queryFn: async (): Promise<TaskSettingsData> => {
|
|
57
|
-
const res = await fetch('/api/v1/users/task-settings')
|
|
58
|
+
const res = await fetch(getTaskApiUrl('/api/v1/users/task-settings'), {
|
|
59
|
+
credentials: 'include',
|
|
60
|
+
});
|
|
58
61
|
if (!res.ok) {
|
|
59
62
|
// Return defaults if API fails
|
|
60
63
|
return { task_auto_assign_to_self: false, fade_completed_tasks: false };
|
|
@@ -68,8 +71,9 @@ export function QuickSettingsPopover({
|
|
|
68
71
|
|
|
69
72
|
const updateSettings = useMutation({
|
|
70
73
|
mutationFn: async (data: Partial<TaskSettingsData>) => {
|
|
71
|
-
const res = await fetch('/api/v1/users/task-settings', {
|
|
74
|
+
const res = await fetch(getTaskApiUrl('/api/v1/users/task-settings'), {
|
|
72
75
|
method: 'PATCH',
|
|
76
|
+
credentials: 'include',
|
|
73
77
|
headers: { 'Content-Type': 'application/json' },
|
|
74
78
|
body: JSON.stringify(data),
|
|
75
79
|
});
|
|
@@ -48,11 +48,15 @@ describe('useUpdateSharedTask', () => {
|
|
|
48
48
|
// Cast to any to access mutationFn which is available in the mock return value
|
|
49
49
|
const response = await (result.current as any).mutationFn(payload);
|
|
50
50
|
|
|
51
|
-
expect(global.fetch).toHaveBeenCalledWith(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
expect(global.fetch).toHaveBeenCalledWith(
|
|
52
|
+
'http://localhost:7809/api/v1/shared/tasks/SHARE123',
|
|
53
|
+
{
|
|
54
|
+
method: 'PATCH',
|
|
55
|
+
credentials: 'include',
|
|
56
|
+
headers: { 'Content-Type': 'application/json' },
|
|
57
|
+
body: JSON.stringify(payload.updates),
|
|
58
|
+
}
|
|
59
|
+
);
|
|
56
60
|
expect(response).toEqual(mockResponse);
|
|
57
61
|
});
|
|
58
62
|
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
type WorkspaceTaskDescriptionUpdatePayload,
|
|
15
15
|
type WorkspaceTaskUpdatePayload,
|
|
16
16
|
} from '@tuturuuu/internal-api/tasks';
|
|
17
|
+
import { getTaskApiUrl } from '../../../../../../lib/tasks-app-url';
|
|
17
18
|
import type { WorkspaceTaskLabel } from '../types';
|
|
18
19
|
|
|
19
20
|
const TASK_DESCRIPTION_DIRECT_BODY_LIMIT_BYTES = 192 * 1024;
|
|
@@ -261,14 +262,18 @@ export async function createWorkspaceLabel(
|
|
|
261
262
|
wsId: string,
|
|
262
263
|
payload: { name: string; color: string }
|
|
263
264
|
) {
|
|
264
|
-
const response = await fetch(
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
265
|
+
const response = await fetch(
|
|
266
|
+
getTaskApiUrl(`/api/v1/workspaces/${wsId}/labels`),
|
|
267
|
+
{
|
|
268
|
+
method: 'POST',
|
|
269
|
+
credentials: 'include',
|
|
270
|
+
headers: {
|
|
271
|
+
'Content-Type': 'application/json',
|
|
272
|
+
},
|
|
273
|
+
body: JSON.stringify(payload),
|
|
274
|
+
cache: 'no-store',
|
|
275
|
+
}
|
|
276
|
+
);
|
|
272
277
|
|
|
273
278
|
if (!response.ok) {
|
|
274
279
|
throw new Error(await getErrorMessage(response, 'Failed to create label'));
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
4
4
|
import type { TaskUserOverride } from '@tuturuuu/types';
|
|
5
5
|
import { toast } from '@tuturuuu/ui/sonner';
|
|
6
|
+
import { getTaskApiUrl } from '../../../../../../lib/tasks-app-url';
|
|
6
7
|
|
|
7
8
|
type OverrideInput = Partial<
|
|
8
9
|
Omit<TaskUserOverride, 'task_id' | 'user_id' | 'created_at' | 'updated_at'>
|
|
@@ -23,7 +24,10 @@ export function useTaskOverrides(
|
|
|
23
24
|
queryKey,
|
|
24
25
|
queryFn: async (): Promise<TaskUserOverride | null> => {
|
|
25
26
|
if (!taskId) return null;
|
|
26
|
-
const res = await fetch(
|
|
27
|
+
const res = await fetch(
|
|
28
|
+
getTaskApiUrl(`/api/v1/users/me/tasks/${taskId}/overrides`),
|
|
29
|
+
{ credentials: 'include' }
|
|
30
|
+
);
|
|
27
31
|
if (!res.ok) throw new Error('Failed to fetch override');
|
|
28
32
|
const json = await res.json();
|
|
29
33
|
return json.data ?? null;
|
|
@@ -35,11 +39,15 @@ export function useTaskOverrides(
|
|
|
35
39
|
const upsertMutation = useMutation({
|
|
36
40
|
mutationFn: async (input: OverrideInput): Promise<TaskUserOverride> => {
|
|
37
41
|
if (!taskId) throw new Error('No task ID');
|
|
38
|
-
const res = await fetch(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
const res = await fetch(
|
|
43
|
+
getTaskApiUrl(`/api/v1/users/me/tasks/${taskId}/overrides`),
|
|
44
|
+
{
|
|
45
|
+
method: 'PUT',
|
|
46
|
+
credentials: 'include',
|
|
47
|
+
headers: { 'Content-Type': 'application/json' },
|
|
48
|
+
body: JSON.stringify(input),
|
|
49
|
+
}
|
|
50
|
+
);
|
|
43
51
|
if (!res.ok) {
|
|
44
52
|
const err = await res.json().catch(() => null);
|
|
45
53
|
throw new Error(err?.error || 'Failed to save override');
|
|
@@ -85,9 +93,13 @@ export function useTaskOverrides(
|
|
|
85
93
|
const deleteMutation = useMutation({
|
|
86
94
|
mutationFn: async () => {
|
|
87
95
|
if (!taskId) throw new Error('No task ID');
|
|
88
|
-
const res = await fetch(
|
|
89
|
-
|
|
90
|
-
|
|
96
|
+
const res = await fetch(
|
|
97
|
+
getTaskApiUrl(`/api/v1/users/me/tasks/${taskId}/overrides`),
|
|
98
|
+
{
|
|
99
|
+
method: 'DELETE',
|
|
100
|
+
credentials: 'include',
|
|
101
|
+
}
|
|
102
|
+
);
|
|
91
103
|
if (!res.ok) throw new Error('Failed to delete override');
|
|
92
104
|
},
|
|
93
105
|
onMutate: async () => {
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
useQueryClient,
|
|
5
5
|
} from '@tanstack/react-query';
|
|
6
6
|
import type { Task } from '@tuturuuu/types/primitives/Task';
|
|
7
|
+
import { getTaskApiUrl } from '../../../../../../lib/tasks-app-url';
|
|
7
8
|
|
|
8
9
|
export interface TaskUpdatePayload {
|
|
9
10
|
name?: string;
|
|
@@ -33,11 +34,15 @@ export function useUpdateSharedTask(): UseMutationResult<
|
|
|
33
34
|
shareCode: string;
|
|
34
35
|
updates: TaskUpdatePayload;
|
|
35
36
|
}) => {
|
|
36
|
-
const response = await fetch(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
const response = await fetch(
|
|
38
|
+
getTaskApiUrl(`/api/v1/shared/tasks/${shareCode}`),
|
|
39
|
+
{
|
|
40
|
+
method: 'PATCH',
|
|
41
|
+
credentials: 'include',
|
|
42
|
+
headers: { 'Content-Type': 'application/json' },
|
|
43
|
+
body: JSON.stringify(updates),
|
|
44
|
+
}
|
|
45
|
+
);
|
|
41
46
|
|
|
42
47
|
if (!response.ok) {
|
|
43
48
|
const error = await response.json().catch(() => null);
|
|
@@ -35,6 +35,7 @@ import { usePathname } from 'next/navigation';
|
|
|
35
35
|
import { useLocale, useTranslations } from 'next-intl';
|
|
36
36
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
37
37
|
import * as Y from 'yjs';
|
|
38
|
+
import { getTaskApiUrl } from '../../../../lib/tasks-app-url';
|
|
38
39
|
import { BoardEstimationConfigDialog } from '../boards/boardId/task-dialogs/BoardEstimationConfigDialog';
|
|
39
40
|
import { TaskNewLabelDialog } from '../boards/boardId/task-dialogs/TaskNewLabelDialog';
|
|
40
41
|
import { TaskNewProjectDialog } from '../boards/boardId/task-dialogs/TaskNewProjectDialog';
|
|
@@ -391,8 +392,9 @@ export function TaskEditDialog({
|
|
|
391
392
|
const { data: userTaskSettings } = useQuery({
|
|
392
393
|
queryKey: ['user-task-settings'],
|
|
393
394
|
queryFn: async () => {
|
|
394
|
-
const res = await fetch('/api/v1/users/task-settings', {
|
|
395
|
+
const res = await fetch(getTaskApiUrl('/api/v1/users/task-settings'), {
|
|
395
396
|
cache: 'no-store',
|
|
397
|
+
credentials: 'include',
|
|
396
398
|
});
|
|
397
399
|
if (!res.ok) return { task_auto_assign_to_self: false };
|
|
398
400
|
return res.json() as Promise<{ task_auto_assign_to_self: boolean }>;
|
|
@@ -793,7 +795,7 @@ export function TaskEditDialog({
|
|
|
793
795
|
enabled: !!isOpen && !isCreateMode && !!task?.id,
|
|
794
796
|
queryFn: async () => {
|
|
795
797
|
const response = await fetch(
|
|
796
|
-
`/api/v1/users/me/tasks/${task!.id}/schedule
|
|
798
|
+
getTaskApiUrl(`/api/v1/users/me/tasks/${task!.id}/schedule`),
|
|
797
799
|
{ cache: 'no-store' }
|
|
798
800
|
);
|
|
799
801
|
if (!response.ok) return null;
|
|
@@ -17,6 +17,7 @@ import { Label } from '@tuturuuu/ui/label';
|
|
|
17
17
|
import { Popover, PopoverContent, PopoverTrigger } from '@tuturuuu/ui/popover';
|
|
18
18
|
import { cn } from '@tuturuuu/utils/format';
|
|
19
19
|
import { useMemo, useState } from 'react';
|
|
20
|
+
import { getTaskApiUrl } from '../../../../lib/tasks-app-url';
|
|
20
21
|
import {
|
|
21
22
|
buildEstimationIndices,
|
|
22
23
|
mapEstimationPoints,
|
|
@@ -58,11 +59,13 @@ export function TaskEstimationPicker({
|
|
|
58
59
|
queryFn: async () => {
|
|
59
60
|
if (!boardId) return null;
|
|
60
61
|
const response = await fetch(
|
|
61
|
-
`/api/v1/workspaces/${wsId}/boards/${boardId}/estimation`
|
|
62
|
+
getTaskApiUrl(`/api/v1/workspaces/${wsId}/boards/${boardId}/estimation`)
|
|
62
63
|
);
|
|
63
64
|
if (!response.ok) {
|
|
64
65
|
// If the endpoint doesn't exist, we'll create a fallback
|
|
65
|
-
const boardResponse = await fetch(
|
|
66
|
+
const boardResponse = await fetch(
|
|
67
|
+
getTaskApiUrl(`/api/v1/workspaces/${wsId}/boards`)
|
|
68
|
+
);
|
|
66
69
|
if (!boardResponse.ok) throw new Error('Failed to fetch boards');
|
|
67
70
|
const boards = await boardResponse.json();
|
|
68
71
|
const board = boards.find((b: any) => b.id === boardId);
|
|
@@ -41,6 +41,8 @@ vi.mock('@tuturuuu/internal-api/tasks', () => ({
|
|
|
41
41
|
}));
|
|
42
42
|
|
|
43
43
|
vi.mock('@tuturuuu/utils/task-helper', () => ({
|
|
44
|
+
isPersonalExternalStagingListId: (listId: string | null) =>
|
|
45
|
+
listId?.startsWith('personal-external-staging:') ?? false,
|
|
44
46
|
useUpdateTask: vi.fn(),
|
|
45
47
|
}));
|
|
46
48
|
|
|
@@ -1689,6 +1691,55 @@ describe('useTaskActions', () => {
|
|
|
1689
1691
|
expect(setMenuOpen).toHaveBeenCalledWith(false);
|
|
1690
1692
|
});
|
|
1691
1693
|
|
|
1694
|
+
it('moves a personal-workspace task with personal board metadata through the normal task route', async () => {
|
|
1695
|
+
const personalTask = {
|
|
1696
|
+
...mockTask,
|
|
1697
|
+
personal_board_id: 'board-1',
|
|
1698
|
+
personal_list_id: 'list-1',
|
|
1699
|
+
source_workspace_id: 'ws-1',
|
|
1700
|
+
source_board_id: 'board-1',
|
|
1701
|
+
source_list_id: 'list-1',
|
|
1702
|
+
} as unknown as Task;
|
|
1703
|
+
const targetList = {
|
|
1704
|
+
id: 'list-2',
|
|
1705
|
+
name: 'Later',
|
|
1706
|
+
board_id: 'board-1',
|
|
1707
|
+
status: 'active',
|
|
1708
|
+
created_at: '2025-01-01T00:00:00Z',
|
|
1709
|
+
archived: false,
|
|
1710
|
+
deleted: false,
|
|
1711
|
+
creator_id: 'user-1',
|
|
1712
|
+
color: null,
|
|
1713
|
+
position: 3,
|
|
1714
|
+
} as unknown as TaskList;
|
|
1715
|
+
|
|
1716
|
+
queryClient.setQueryData(['tasks', 'board-1'], [personalTask]);
|
|
1717
|
+
|
|
1718
|
+
const { result } = renderHook(
|
|
1719
|
+
() =>
|
|
1720
|
+
useTaskActions({
|
|
1721
|
+
task: personalTask,
|
|
1722
|
+
boardId: 'board-1',
|
|
1723
|
+
targetCompletionList: mockCompletionList,
|
|
1724
|
+
targetClosedList: mockClosedList,
|
|
1725
|
+
availableLists: [...mockAvailableLists, targetList],
|
|
1726
|
+
onUpdate: vi.fn(),
|
|
1727
|
+
setIsLoading: vi.fn(),
|
|
1728
|
+
setMenuOpen: vi.fn(),
|
|
1729
|
+
}),
|
|
1730
|
+
{ wrapper }
|
|
1731
|
+
);
|
|
1732
|
+
|
|
1733
|
+
await act(async () => {
|
|
1734
|
+
await result.current.handleMoveToList('list-2');
|
|
1735
|
+
});
|
|
1736
|
+
|
|
1737
|
+
expect(mockUpdateWorkspaceTask).toHaveBeenCalledWith('ws-1', 'task-1', {
|
|
1738
|
+
list_id: 'list-2',
|
|
1739
|
+
});
|
|
1740
|
+
expect(mockUpsertCurrentUserTaskPersonalPlacement).not.toHaveBeenCalled();
|
|
1741
|
+
});
|
|
1742
|
+
|
|
1692
1743
|
it('moves an external task between personal lists through personal placement only', async () => {
|
|
1693
1744
|
const targetList = {
|
|
1694
1745
|
id: 'list-2',
|
|
@@ -11,11 +11,10 @@ import {
|
|
|
11
11
|
isTaskBoardCompletedStatus,
|
|
12
12
|
isTaskBoardTerminalStatus,
|
|
13
13
|
} from '@tuturuuu/utils/task-list-status';
|
|
14
|
+
import { isPersonalExternalOverlayTask } from '../lib/task-personal-external';
|
|
14
15
|
|
|
15
16
|
export function isPersonalExternalTask(task?: Task) {
|
|
16
|
-
return (
|
|
17
|
-
task?.is_personal_external === true || Boolean(task?.personal_board_id)
|
|
18
|
-
);
|
|
17
|
+
return isPersonalExternalOverlayTask(task);
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
function findFirstListByStatus(lists: TaskList[], status: TaskBoardStatus) {
|