@tuturuuu/ui 0.30.2 → 0.31.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 +41 -0
- package/biome.json +1 -1
- package/package.json +95 -49
- package/src/components/ui/calendar-app/calendar-client-page.test.tsx +57 -0
- package/src/components/ui/calendar-app/calendar-client-page.tsx +13 -4
- package/src/components/ui/calendar-app/calendar-page-shell.tsx +7 -1
- package/src/components/ui/calendar-app/components/calendar-connections-compact.tsx +47 -39
- package/src/components/ui/calendar-app/components/calendar-connections-settings-content.tsx +14 -0
- package/src/components/ui/calendar-app/components/calendar-connections-unified.tsx +4 -0
- package/src/components/ui/calendar-app/components/calendar-sync-attention-button.tsx +47 -0
- package/src/components/ui/calendar-app/components/calendar-sync-recovery.test.tsx +112 -0
- package/src/components/ui/calendar-app/components/calendar-sync-recovery.tsx +206 -0
- package/src/components/ui/calendar-app/components/calendar-types.ts +1 -1
- package/src/components/ui/calendar-app/components/use-calendar-connections-manager.ts +86 -90
- package/src/components/ui/custom/language-dropdown-item.tsx +6 -14
- package/src/components/ui/custom/nav-link.tsx +7 -7
- package/src/components/ui/custom/nav.tsx +15 -63
- package/src/components/ui/custom/notification-card.tsx +361 -0
- package/src/components/ui/custom/notification-list.tsx +178 -0
- package/src/components/ui/custom/notification-popover-client.tsx +12 -780
- package/src/components/ui/custom/notification-runtime.tsx +23 -0
- package/src/components/ui/custom/onboarding-settings-panel.tsx +8 -55
- package/src/components/ui/custom/satellite-account-switcher-menu.tsx +212 -0
- package/src/components/ui/custom/satellite-brand.tsx +122 -0
- package/src/components/ui/custom/satellite-content.tsx +148 -0
- package/src/components/ui/custom/satellite-footer-actions.tsx +350 -0
- package/src/components/ui/custom/satellite-language-item.tsx +26 -0
- package/src/components/ui/custom/satellite-navigation.tsx +86 -0
- package/src/components/ui/custom/satellite-notification-popover.tsx +272 -0
- package/src/components/ui/custom/satellite-onboarding-settings-panel.tsx +80 -0
- package/src/components/ui/custom/satellite-settings-dialog-shell.tsx +621 -0
- package/src/components/ui/custom/satellite-shell-utils.ts +163 -0
- package/src/components/ui/custom/satellite-shell.test.tsx +169 -0
- package/src/components/ui/custom/satellite-shell.tsx +50 -0
- package/src/components/ui/custom/satellite-theme-dropdown-items.tsx +92 -0
- package/src/components/ui/custom/satellite-user-menu-items.tsx +190 -0
- package/src/components/ui/custom/satellite-user-menu.tsx +93 -0
- package/src/components/ui/custom/satellite-workspace-section.tsx +46 -0
- package/src/components/ui/custom/settings/keyboard-shortcuts-settings.tsx +16 -0
- package/src/components/ui/custom/settings-dialog-shell.tsx +21 -600
- package/src/components/ui/custom/sidebar-context.tsx +5 -14
- package/src/components/ui/custom/sidebar-footer-actions.tsx +19 -322
- package/src/components/ui/custom/structure.tsx +14 -0
- package/src/components/ui/custom/system-language-dropdown-item.tsx +7 -14
- package/src/components/ui/custom/theme-dropdown-items.tsx +2 -85
- package/src/components/ui/custom/use-satellite-shell.tsx +165 -0
- package/src/components/ui/legacy/calendar/agenda-view.tsx +139 -47
- package/src/components/ui/legacy/calendar/calendar-content.test.tsx +116 -0
- package/src/components/ui/legacy/calendar/calendar-content.tsx +38 -74
- package/src/components/ui/legacy/calendar/calendar-event-tone.ts +20 -0
- package/src/components/ui/legacy/calendar/calendar-meeting-link.test.ts +27 -0
- package/src/components/ui/legacy/calendar/calendar-meeting-link.ts +34 -0
- package/src/components/ui/legacy/calendar/calendar-period.ts +28 -0
- package/src/components/ui/legacy/calendar/calendar-shortcuts.test.tsx +107 -0
- package/src/components/ui/legacy/calendar/calendar-views.test.tsx +169 -0
- package/src/components/ui/legacy/calendar/event-modal-header.tsx +52 -0
- package/src/components/ui/legacy/calendar/event-modal.tsx +107 -156
- package/src/components/ui/legacy/calendar/month-calendar.tsx +242 -933
- package/src/components/ui/legacy/calendar/save-calendar-event-drafts.test.ts +25 -0
- package/src/components/ui/legacy/calendar/save-calendar-event-drafts.ts +32 -0
- package/src/components/ui/legacy/calendar/smart-calendar.tsx +3 -0
- package/src/components/ui/legacy/calendar/use-calendar-view-shortcuts.ts +54 -0
- package/src/components/ui/legacy/calendar/use-event-draft-session.test.tsx +59 -0
- package/src/components/ui/legacy/calendar/use-event-draft-session.ts +20 -0
- package/src/components/ui/legacy/calendar/year-calendar.tsx +34 -16
- package/src/components/ui/pwa-status.test.tsx +53 -0
- package/src/components/ui/pwa-status.tsx +113 -0
- package/src/components/ui/report-problem-dialog-content.tsx +350 -0
- package/src/components/ui/report-problem-dialog.tsx +6 -975
- package/src/components/ui/report-problem-model.ts +133 -0
- package/src/components/ui/report-problem-mutation.ts +122 -0
- package/src/components/ui/save-notification.test.tsx +36 -0
- package/src/components/ui/save-notification.tsx +17 -0
- package/src/components/ui/use-report-problem.ts +488 -0
- package/src/declarations.d.ts +2 -0
- package/src/hooks/__tests__/use-calendar-readonly.test.tsx +46 -4
- package/src/hooks/__tests__/use-calendar-sync.test.tsx +24 -0
- package/src/hooks/calendar-date-utils.ts +11 -0
- package/src/hooks/calendar-event-lookup.test.ts +77 -0
- package/src/hooks/calendar-event-lookup.ts +47 -0
- package/src/hooks/calendar-range-cache.ts +52 -0
- package/src/hooks/pwa-install-prompt.ts +44 -0
- package/src/hooks/use-calendar-sync.tsx +23 -100
- package/src/hooks/use-calendar.tsx +21 -77
- package/src/hooks/use-notifications.ts +9 -24
- package/src/hooks/use-open-initial-calendar-event.ts +27 -0
- package/src/hooks/use-settings-dialog-shortcut.test.tsx +41 -0
- package/src/hooks/use-settings-dialog-shortcut.ts +12 -14
- package/src/hooks/use-workspace-presence.ts +0 -18
- package/src/lib/tasks-app-url.test.ts +25 -0
- package/src/lib/tasks-app-url.ts +8 -0
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
type SupabaseClient,
|
|
12
12
|
} from '@tuturuuu/supabase/next/realtime-browser';
|
|
13
13
|
import { useEffect } from 'react';
|
|
14
|
-
|
|
15
14
|
export type NotificationType =
|
|
16
15
|
| 'task_assigned'
|
|
17
16
|
| 'task_updated'
|
|
@@ -44,13 +43,11 @@ export type NotificationType =
|
|
|
44
43
|
| 'time_tracking_request_approved'
|
|
45
44
|
| 'time_tracking_request_rejected'
|
|
46
45
|
| 'time_tracking_request_needs_info';
|
|
47
|
-
|
|
48
46
|
export interface NotificationActor {
|
|
49
47
|
id: string;
|
|
50
48
|
display_name: string | null;
|
|
51
49
|
avatar_url: string | null;
|
|
52
50
|
}
|
|
53
|
-
|
|
54
51
|
export interface Notification {
|
|
55
52
|
id: string;
|
|
56
53
|
ws_id: string | null; // Can be null for user-scoped notifications
|
|
@@ -66,14 +63,12 @@ export interface Notification {
|
|
|
66
63
|
created_by: string | null;
|
|
67
64
|
actor: NotificationActor | null;
|
|
68
65
|
}
|
|
69
|
-
|
|
70
66
|
interface NotificationsPage {
|
|
71
67
|
notifications: Notification[];
|
|
72
68
|
count: number;
|
|
73
69
|
limit: number;
|
|
74
70
|
offset: number;
|
|
75
71
|
}
|
|
76
|
-
|
|
77
72
|
interface UseNotificationsOptions {
|
|
78
73
|
cacheScope?: string;
|
|
79
74
|
wsId?: string;
|
|
@@ -83,22 +78,18 @@ interface UseNotificationsOptions {
|
|
|
83
78
|
readOnly?: boolean;
|
|
84
79
|
type?: NotificationType;
|
|
85
80
|
}
|
|
86
|
-
|
|
87
81
|
interface NotificationSubscriptionEntry {
|
|
88
82
|
channel: RealtimeChannel;
|
|
89
83
|
queryClientRefs: Map<QueryClient, number>;
|
|
90
84
|
subscriberCount: number;
|
|
91
85
|
supabase: SupabaseClient;
|
|
92
86
|
}
|
|
93
|
-
|
|
94
87
|
export const UNREAD_COUNT_STALE_TIME_MS = 5 * 60 * 1000;
|
|
95
88
|
export const UNREAD_COUNT_FALLBACK_INTERVAL_MS = 15 * 60 * 1000;
|
|
96
|
-
|
|
97
89
|
const notificationSubscriptionRegistry = new Map<
|
|
98
90
|
string,
|
|
99
91
|
NotificationSubscriptionEntry
|
|
100
92
|
>();
|
|
101
|
-
|
|
102
93
|
function invalidateNotificationQueries(
|
|
103
94
|
queryClientRefs: Map<QueryClient, number>
|
|
104
95
|
) {
|
|
@@ -109,7 +100,6 @@ function invalidateNotificationQueries(
|
|
|
109
100
|
});
|
|
110
101
|
}
|
|
111
102
|
}
|
|
112
|
-
|
|
113
103
|
function addNotificationQueryClientRef(
|
|
114
104
|
entry: NotificationSubscriptionEntry,
|
|
115
105
|
queryClient: QueryClient
|
|
@@ -120,22 +110,18 @@ function addNotificationQueryClientRef(
|
|
|
120
110
|
(entry.queryClientRefs.get(queryClient) ?? 0) + 1
|
|
121
111
|
);
|
|
122
112
|
}
|
|
123
|
-
|
|
124
113
|
function releaseNotificationQueryClientRef(
|
|
125
114
|
entry: NotificationSubscriptionEntry,
|
|
126
115
|
queryClient: QueryClient
|
|
127
116
|
) {
|
|
128
117
|
entry.subscriberCount -= 1;
|
|
129
|
-
|
|
130
118
|
const currentRefCount = entry.queryClientRefs.get(queryClient) ?? 0;
|
|
131
|
-
|
|
132
119
|
if (currentRefCount <= 1) {
|
|
133
120
|
entry.queryClientRefs.delete(queryClient);
|
|
134
121
|
} else {
|
|
135
122
|
entry.queryClientRefs.set(queryClient, currentRefCount - 1);
|
|
136
123
|
}
|
|
137
124
|
}
|
|
138
|
-
|
|
139
125
|
function createNotificationSubscriptionEntry(
|
|
140
126
|
userId: string,
|
|
141
127
|
queryClient: QueryClient
|
|
@@ -144,7 +130,6 @@ function createNotificationSubscriptionEntry(
|
|
|
144
130
|
const supabase = createRealtimeClient();
|
|
145
131
|
const invalidateQueries = () =>
|
|
146
132
|
invalidateNotificationQueries(queryClientRefs);
|
|
147
|
-
|
|
148
133
|
const channel = supabase
|
|
149
134
|
.channel(`notifications-${userId}`)
|
|
150
135
|
.on(
|
|
@@ -178,7 +163,6 @@ function createNotificationSubscriptionEntry(
|
|
|
178
163
|
invalidateQueries
|
|
179
164
|
)
|
|
180
165
|
.subscribe();
|
|
181
|
-
|
|
182
166
|
return {
|
|
183
167
|
channel,
|
|
184
168
|
queryClientRefs,
|
|
@@ -186,22 +170,18 @@ function createNotificationSubscriptionEntry(
|
|
|
186
170
|
supabase,
|
|
187
171
|
} satisfies NotificationSubscriptionEntry;
|
|
188
172
|
}
|
|
189
|
-
|
|
190
173
|
export function dedupeNotifications(
|
|
191
174
|
notifications: Notification[]
|
|
192
175
|
): Notification[] {
|
|
193
176
|
const seen = new Set<string>();
|
|
194
|
-
|
|
195
177
|
return notifications.filter((notification) => {
|
|
196
178
|
if (seen.has(notification.id)) {
|
|
197
179
|
return false;
|
|
198
180
|
}
|
|
199
|
-
|
|
200
181
|
seen.add(notification.id);
|
|
201
182
|
return true;
|
|
202
183
|
});
|
|
203
184
|
}
|
|
204
|
-
|
|
205
185
|
/**
|
|
206
186
|
* Hook to fetch notifications with pagination
|
|
207
187
|
* @param wsId - If provided, filters to specific workspace. If omitted, fetches all notifications across all workspaces.
|
|
@@ -268,6 +248,7 @@ export function useInfiniteNotifications({
|
|
|
268
248
|
readOnly = false,
|
|
269
249
|
pageSize = 20,
|
|
270
250
|
enabled = true,
|
|
251
|
+
refetchInterval,
|
|
271
252
|
}: {
|
|
272
253
|
cacheScope?: string;
|
|
273
254
|
wsId?: string;
|
|
@@ -275,6 +256,7 @@ export function useInfiniteNotifications({
|
|
|
275
256
|
readOnly?: boolean;
|
|
276
257
|
pageSize?: number;
|
|
277
258
|
enabled?: boolean;
|
|
259
|
+
refetchInterval?: number;
|
|
278
260
|
}) {
|
|
279
261
|
return useInfiniteQuery({
|
|
280
262
|
queryKey: [
|
|
@@ -306,6 +288,8 @@ export function useInfiniteNotifications({
|
|
|
306
288
|
},
|
|
307
289
|
initialPageParam: 0,
|
|
308
290
|
enabled,
|
|
291
|
+
refetchInterval,
|
|
292
|
+
refetchIntervalInBackground: false,
|
|
309
293
|
getNextPageParam: (lastPage) => {
|
|
310
294
|
const nextOffset = lastPage.offset + lastPage.limit;
|
|
311
295
|
return nextOffset < lastPage.count ? nextOffset : undefined;
|
|
@@ -321,7 +305,7 @@ export function useInfiniteNotifications({
|
|
|
321
305
|
*/
|
|
322
306
|
export function useUnreadCount(
|
|
323
307
|
wsId?: string,
|
|
324
|
-
options?: { cacheScope?: string; enabled?: boolean }
|
|
308
|
+
options?: { cacheScope?: string; enabled?: boolean; refetchInterval?: number }
|
|
325
309
|
) {
|
|
326
310
|
return useQuery({
|
|
327
311
|
queryKey: [
|
|
@@ -340,14 +324,15 @@ export function useUnreadCount(
|
|
|
340
324
|
throw new Error('Failed to fetch unread count');
|
|
341
325
|
}
|
|
342
326
|
|
|
343
|
-
const data = await response.json();
|
|
344
|
-
return data.count
|
|
327
|
+
const data = (await response.json()) as { count: number };
|
|
328
|
+
return data.count;
|
|
345
329
|
},
|
|
346
330
|
enabled: options?.enabled ?? true,
|
|
347
331
|
staleTime: UNREAD_COUNT_STALE_TIME_MS,
|
|
348
332
|
// Realtime invalidation is the primary update path. Keep a low-frequency
|
|
349
333
|
// refresh as a safety net for disconnected or suspended browser sessions.
|
|
350
|
-
refetchInterval:
|
|
334
|
+
refetchInterval:
|
|
335
|
+
options?.refetchInterval ?? UNREAD_COUNT_FALLBACK_INTERVAL_MS,
|
|
351
336
|
refetchIntervalInBackground: false,
|
|
352
337
|
refetchOnReconnect: true,
|
|
353
338
|
refetchOnWindowFocus: true,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { CalendarEvent } from '@tuturuuu/types/primitives/calendar-event';
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
|
+
|
|
4
|
+
export function useOpenInitialCalendarEvent({
|
|
5
|
+
events,
|
|
6
|
+
initialEventId,
|
|
7
|
+
onOpen,
|
|
8
|
+
}: {
|
|
9
|
+
events: CalendarEvent[];
|
|
10
|
+
initialEventId?: string;
|
|
11
|
+
onOpen: (eventId: string) => void;
|
|
12
|
+
}) {
|
|
13
|
+
const openedEventIdRef = useRef<string | null>(null);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (
|
|
17
|
+
!initialEventId ||
|
|
18
|
+
openedEventIdRef.current === initialEventId ||
|
|
19
|
+
!events.some((event) => event.id === initialEventId)
|
|
20
|
+
) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
onOpen(initialEventId);
|
|
25
|
+
openedEventIdRef.current = initialEventId;
|
|
26
|
+
}, [events, initialEventId, onOpen]);
|
|
27
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { useSettingsDialogShortcut } from './use-settings-dialog-shortcut';
|
|
4
|
+
|
|
5
|
+
function Shell({ onOpen }: { onOpen: () => void }) {
|
|
6
|
+
useSettingsDialogShortcut({ enabled: true, onOpen });
|
|
7
|
+
return (
|
|
8
|
+
<div contentEditable suppressContentEditableWarning>
|
|
9
|
+
<span>Editor content</span>
|
|
10
|
+
</div>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
describe('shared app settings shortcut', () => {
|
|
14
|
+
it('supports both platforms without stealing editor, composition, or modified commands', () => {
|
|
15
|
+
const onOpen = vi.fn();
|
|
16
|
+
render(<Shell onOpen={onOpen} />);
|
|
17
|
+
fireEvent.keyDown(window, { key: ',', ctrlKey: true });
|
|
18
|
+
fireEvent.keyDown(window, { key: ',', metaKey: true });
|
|
19
|
+
expect(onOpen).toHaveBeenCalledTimes(2);
|
|
20
|
+
fireEvent.keyDown(screen.getByText('Editor content'), {
|
|
21
|
+
key: ',',
|
|
22
|
+
ctrlKey: true,
|
|
23
|
+
});
|
|
24
|
+
for (const options of [
|
|
25
|
+
{ isComposing: true },
|
|
26
|
+
{ repeat: true },
|
|
27
|
+
{ shiftKey: true },
|
|
28
|
+
{ altKey: true },
|
|
29
|
+
]) {
|
|
30
|
+
fireEvent.keyDown(window, { key: ',', ctrlKey: true, ...options });
|
|
31
|
+
}
|
|
32
|
+
const event = new KeyboardEvent('keydown', {
|
|
33
|
+
key: ',',
|
|
34
|
+
ctrlKey: true,
|
|
35
|
+
cancelable: true,
|
|
36
|
+
});
|
|
37
|
+
event.preventDefault();
|
|
38
|
+
fireEvent(window, event);
|
|
39
|
+
expect(onOpen).toHaveBeenCalledTimes(2);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
isEditableShortcutTarget,
|
|
5
|
+
isShortcutEventIgnored,
|
|
6
|
+
} from '@tuturuuu/utils/keyboard-shortcuts';
|
|
7
|
+
|
|
3
8
|
import { useEffect } from 'react';
|
|
4
9
|
|
|
5
10
|
interface UseSettingsDialogShortcutOptions {
|
|
@@ -7,23 +12,15 @@ interface UseSettingsDialogShortcutOptions {
|
|
|
7
12
|
onOpen: () => void;
|
|
8
13
|
}
|
|
9
14
|
|
|
10
|
-
function
|
|
11
|
-
if (!(target instanceof HTMLElement)) return false;
|
|
12
|
-
|
|
13
|
-
const tagName = target.tagName.toLowerCase();
|
|
14
|
-
|
|
15
|
+
function isSettingsDialogShortcut(event: KeyboardEvent) {
|
|
15
16
|
return (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
(event.metaKey || event.ctrlKey) &&
|
|
18
|
+
!event.altKey &&
|
|
19
|
+
!event.shiftKey &&
|
|
20
|
+
event.key === ','
|
|
20
21
|
);
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
function isSettingsDialogShortcut(event: KeyboardEvent) {
|
|
24
|
-
return (event.metaKey || event.ctrlKey) && !event.altKey && event.key === ',';
|
|
25
|
-
}
|
|
26
|
-
|
|
27
24
|
/**
|
|
28
25
|
* Opens the app settings dialog on Cmd/Ctrl+, — the platform-wide convention.
|
|
29
26
|
* Ignores the shortcut while typing in editable fields and when another handler
|
|
@@ -38,7 +35,8 @@ export function useSettingsDialogShortcut({
|
|
|
38
35
|
if (!enabled) return;
|
|
39
36
|
|
|
40
37
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
41
|
-
if (event
|
|
38
|
+
if (isShortcutEventIgnored(event) || !isSettingsDialogShortcut(event))
|
|
39
|
+
return;
|
|
42
40
|
if (isEditableShortcutTarget(event.target)) return;
|
|
43
41
|
|
|
44
42
|
event.preventDefault();
|
|
@@ -56,24 +56,6 @@ function createPresenceSessionId(): string {
|
|
|
56
56
|
return crypto.randomUUID();
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
// Fallback to Node.js crypto if available
|
|
60
|
-
try {
|
|
61
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
62
|
-
const nodeCrypto = require('node:crypto') as
|
|
63
|
-
| { randomUUID?: () => string; randomBytes: (size: number) => Buffer }
|
|
64
|
-
| undefined;
|
|
65
|
-
|
|
66
|
-
if (nodeCrypto?.randomUUID) {
|
|
67
|
-
return nodeCrypto.randomUUID();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (typeof nodeCrypto?.randomBytes === 'function') {
|
|
71
|
-
return nodeCrypto.randomBytes(16).toString('hex');
|
|
72
|
-
}
|
|
73
|
-
} catch {
|
|
74
|
-
// Ignore and fall through to final fallback
|
|
75
|
-
}
|
|
76
|
-
|
|
77
59
|
// Final fallback using Web Crypto getRandomValues if available
|
|
78
60
|
if (
|
|
79
61
|
typeof crypto !== 'undefined' &&
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { getTaskApiUrl, getTasksAppUrl } from './tasks-app-url';
|
|
3
|
+
|
|
4
|
+
afterEach(() => {
|
|
5
|
+
vi.unstubAllGlobals();
|
|
6
|
+
vi.unstubAllEnvs();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
describe('Calendar task API routing', () => {
|
|
10
|
+
it.each(['calendar.tuturuuu.com', 'calendar.tuturuuu.localhost'])(
|
|
11
|
+
'uses the local authenticated proxy on %s',
|
|
12
|
+
(hostname) => {
|
|
13
|
+
vi.stubGlobal('window', {
|
|
14
|
+
location: { hostname, protocol: 'https:', port: '' },
|
|
15
|
+
});
|
|
16
|
+
vi.stubEnv('NEXT_PUBLIC_TASKS_APP_URL', 'https://tasks.tuturuuu.com');
|
|
17
|
+
expect(getTaskApiUrl('/api/v1/workspaces/ws/tasks/journal')).toBe(
|
|
18
|
+
'/api/v1/workspaces/ws/tasks/journal'
|
|
19
|
+
);
|
|
20
|
+
expect(getTasksAppUrl('/personal/tasks')).toBe(
|
|
21
|
+
'https://tasks.tuturuuu.com/personal/tasks'
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
});
|
package/src/lib/tasks-app-url.ts
CHANGED
|
@@ -81,5 +81,13 @@ export function getTasksAppUrl(path: string) {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
export function getTaskApiUrl(path: string) {
|
|
84
|
+
if (
|
|
85
|
+
typeof window !== 'undefined' &&
|
|
86
|
+
['calendar.tuturuuu.com', 'calendar.tuturuuu.localhost'].includes(
|
|
87
|
+
window.location.hostname.toLowerCase()
|
|
88
|
+
)
|
|
89
|
+
) {
|
|
90
|
+
return path.startsWith('/') ? path : `/${path}`;
|
|
91
|
+
}
|
|
84
92
|
return getTasksAppUrl(path);
|
|
85
93
|
}
|