@tuturuuu/ui 0.37.2 → 0.39.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 +55 -0
- package/package.json +10 -7
- package/src/components/ui/custom/__tests__/workspace-picker-content.test.tsx +86 -0
- package/src/components/ui/custom/education-todo.test.tsx +131 -0
- package/src/components/ui/custom/education-todo.tsx +191 -0
- package/src/components/ui/custom/satellite-notification-polling.test.tsx +61 -0
- package/src/components/ui/custom/satellite-notification-popover.tsx +7 -3
- package/src/components/ui/custom/workspace-picker-content.tsx +93 -30
- package/src/components/ui/custom/workspace-select.tsx +12 -4
- package/src/components/ui/finance/invoices/attendance-calendar.test.tsx +294 -0
- package/src/components/ui/finance/invoices/attendance-calendar.tsx +14 -6
- package/src/components/ui/finance/invoices/components/invoice-history-actor.test.tsx +341 -0
- package/src/components/ui/finance/invoices/components/invoice-history-date.test.tsx +173 -0
- package/src/components/ui/finance/invoices/components/invoice-history-error.test.tsx +269 -0
- package/src/components/ui/finance/invoices/components/invoice-history-recovery.tsx +47 -0
- package/src/components/ui/finance/invoices/components/invoice-user-history-accordion.tsx +43 -10
- package/src/components/ui/finance/invoices/components/subscription-create-action.tsx +42 -0
- package/src/components/ui/finance/invoices/hooks/customer-search-scope.test.tsx +404 -0
- package/src/components/ui/finance/invoices/hooks/invoice-actor-lifetime-key.ts +11 -0
- package/src/components/ui/finance/invoices/hooks/use-infinite-user-invoices.ts +48 -0
- package/src/components/ui/finance/invoices/hooks/use-invoice-customer-search.test.tsx +6 -1
- package/src/components/ui/finance/invoices/hooks/use-invoice-customer-search.ts +58 -6
- package/src/components/ui/finance/invoices/hooks/use-subscription-auto-selection.test.tsx +34 -0
- package/src/components/ui/finance/invoices/hooks/use-subscription-auto-selection.ts +8 -2
- package/src/components/ui/finance/invoices/hooks/use-subscription-create.ts +219 -0
- package/src/components/ui/finance/invoices/hooks/use-subscription-invoice-context.ts +59 -0
- package/src/components/ui/finance/invoices/hooks/use-user-groups.ts +38 -0
- package/src/components/ui/finance/invoices/hooks.ts +3 -100
- package/src/components/ui/finance/invoices/invoiceId/compact-invoice-template.tsx +2 -7
- package/src/components/ui/finance/invoices/invoiceId/format-invoice-template-date.ts +13 -0
- package/src/components/ui/finance/invoices/invoiceId/full-invoice-template.tsx +2 -7
- package/src/components/ui/finance/invoices/invoiceId/invoice-card-date.test.tsx +169 -0
- package/src/components/ui/finance/invoices/invoiceId/invoice-card-storage.test.tsx +120 -0
- package/src/components/ui/finance/invoices/invoiceId/invoice-card.tsx +25 -62
- package/src/components/ui/finance/invoices/invoiceId/invoice-details-page.tsx +1 -0
- package/src/components/ui/finance/invoices/invoiceId/invoice-png-lifetime.test.tsx +438 -0
- package/src/components/ui/finance/invoices/invoiceId/use-invoice-png-export.ts +184 -0
- package/src/components/ui/finance/invoices/subscription-admission.test.ts +77 -0
- package/src/components/ui/finance/invoices/subscription-admission.ts +101 -0
- package/src/components/ui/finance/invoices/subscription-billing-groups.test.ts +162 -1
- package/src/components/ui/finance/invoices/subscription-billing-groups.ts +50 -9
- package/src/components/ui/finance/invoices/subscription-context-actor.test.tsx +246 -0
- package/src/components/ui/finance/invoices/subscription-create-scope.test.tsx +562 -0
- package/src/components/ui/finance/invoices/subscription-invoice.test.tsx +88 -6
- package/src/components/ui/finance/invoices/subscription-invoice.tsx +76 -135
- package/src/components/ui/finance/invoices/types.ts +3 -3
- package/src/components/ui/finance/invoices/user-groups-actor.test.tsx +334 -0
- package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-adjustment-dialog.tsx +47 -10
- package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoints.test.tsx +95 -1
- package/src/components/ui/legacy/calendar/agenda-view.tsx +4 -1
- package/src/components/ui/legacy/calendar/all-day-event-bar.tsx +33 -369
- package/src/components/ui/legacy/calendar/all-day-event-layout.test.ts +94 -0
- package/src/components/ui/legacy/calendar/all-day-event-layout.ts +322 -0
- package/src/components/ui/legacy/calendar/calendar-draft-recovery.ts +34 -0
- package/src/components/ui/legacy/calendar/calendar-views.test.tsx +58 -27
- package/src/components/ui/legacy/calendar/event-card.test.tsx +25 -1
- package/src/components/ui/legacy/calendar/event-card.tsx +10 -12
- package/src/components/ui/legacy/calendar/event-modal-header.tsx +17 -4
- package/src/components/ui/legacy/calendar/event-modal.tsx +26 -1614
- package/src/components/ui/legacy/calendar/event-save-payload.test.ts +40 -0
- package/src/components/ui/legacy/calendar/event-save-payload.ts +79 -0
- package/src/components/ui/legacy/calendar/event-source-selection.test.ts +95 -0
- package/src/components/ui/legacy/calendar/event-source-selection.ts +78 -0
- package/src/components/ui/legacy/calendar/legacy-event-modal.tsx +1559 -0
- package/src/components/ui/legacy/calendar/month-calendar.tsx +5 -2
- package/src/components/ui/legacy/calendar/native-recurrence-context-menu.tsx +21 -0
- package/src/components/ui/legacy/calendar/native-recurrence-dialog.test.tsx +245 -0
- package/src/components/ui/legacy/calendar/native-recurrence-dialog.tsx +95 -0
- package/src/components/ui/legacy/calendar/native-recurrence-fields.tsx +137 -0
- package/src/components/ui/legacy/calendar/native-recurrence-form.tsx +340 -0
- package/src/components/ui/legacy/calendar/native-recurrence-model.test.ts +234 -0
- package/src/components/ui/legacy/calendar/native-recurrence-model.ts +245 -0
- package/src/components/ui/legacy/calendar/native-recurrence-pattern-fields.test.tsx +142 -0
- package/src/components/ui/legacy/calendar/native-recurrence-pattern-fields.tsx +178 -0
- package/src/components/ui/legacy/calendar/native-recurrence-pattern-model.test.ts +205 -0
- package/src/components/ui/legacy/calendar/native-recurrence-provider-routing.test.tsx +255 -0
- package/src/components/ui/legacy/calendar/past-event-treatment.test.ts +137 -0
- package/src/components/ui/legacy/calendar/past-event-treatment.ts +25 -0
- package/src/components/ui/legacy/calendar/provider-color-picker.test.tsx +147 -0
- package/src/components/ui/legacy/calendar/provider-color-picker.tsx +115 -0
- package/src/components/ui/legacy/calendar/provider-readonly-dialog.test.tsx +52 -0
- package/src/components/ui/legacy/calendar/provider-readonly-dialog.tsx +48 -0
- package/src/components/ui/legacy/calendar/save-calendar-event-drafts.test.ts +35 -1
- package/src/components/ui/legacy/calendar/save-calendar-event-drafts.ts +23 -11
- package/src/components/ui/legacy/calendar/use-calendar-draft-save.test.tsx +119 -0
- package/src/components/ui/legacy/calendar/use-calendar-draft-save.tsx +141 -0
- package/src/components/ui/legacy/calendar/use-provider-recurrence-operation.test.tsx +185 -0
- package/src/components/ui/legacy/calendar/use-provider-recurrence-operation.ts +186 -0
- package/src/components/ui/popover.tsx +3 -1
- package/src/components/ui/text-editor/__tests__/collaboration-carets.test.ts +142 -0
- package/src/components/ui/text-editor/__tests__/markdown-document.test.ts +50 -0
- package/src/components/ui/text-editor/clipboard-serialization.ts +10 -0
- package/src/components/ui/text-editor/collaboration-carets.ts +56 -0
- package/src/components/ui/text-editor/editor-readonly-transition.test.tsx +2 -0
- package/src/components/ui/text-editor/editor.tsx +11 -3
- package/src/components/ui/text-editor/extensions.ts +8 -2
- package/src/components/ui/text-editor/fixed-toolbar-contents.test.tsx +59 -0
- package/src/components/ui/text-editor/fixed-toolbar-contents.tsx +156 -0
- package/src/components/ui/text-editor/markdown-paste-extension.ts +1 -1
- package/src/components/ui/text-editor/markdown.ts +11 -0
- package/src/components/ui/text-editor/tool-bar.tsx +45 -102
- package/src/hooks/__tests__/calendar-event-write-payload-readonly.test.ts +40 -0
- package/src/hooks/__tests__/collaboration-awareness.test.ts +106 -0
- package/src/hooks/__tests__/native-calendar-mutation-guard.test.ts +45 -0
- package/src/hooks/__tests__/supabase-provider.test.ts +110 -7
- package/src/hooks/__tests__/use-calendar-readonly.test.tsx +39 -2
- package/src/hooks/__tests__/use-local-storage.test.tsx +42 -0
- package/src/hooks/__tests__/use-native-calendar-occurrences.test.tsx +140 -0
- package/src/hooks/__tests__/use-notifications-subscription.test.tsx +31 -1
- package/src/hooks/__tests__/use-workspace-presence.test.tsx +3 -3
- package/src/hooks/__tests__/use-yjs-collaboration.test.tsx +147 -0
- package/src/hooks/calendar-connection-types.ts +49 -0
- package/src/hooks/calendar-creation-request.test.ts +62 -0
- package/src/hooks/calendar-creation-request.ts +34 -0
- package/src/hooks/calendar-event-update-queue-types.ts +12 -0
- package/src/hooks/calendar-event-write-payload.ts +57 -0
- package/src/hooks/collaboration-document-hydration.ts +35 -0
- package/src/hooks/notification-subscription.ts +5 -2
- package/src/hooks/supabase-provider.ts +76 -112
- package/src/hooks/use-calendar-sync.tsx +41 -62
- package/src/hooks/use-calendar.tsx +44 -82
- package/src/hooks/use-exchange-rates.ts +4 -15
- package/src/hooks/use-local-storage.ts +9 -11
- package/src/hooks/use-native-calendar-occurrences.ts +85 -0
- package/src/hooks/use-workspace-presence.ts +9 -9
- package/src/hooks/use-yjs-collaboration.ts +68 -27
|
@@ -33,11 +33,13 @@ export interface YjsCollaborationResult {
|
|
|
33
33
|
awareness: Awareness | null;
|
|
34
34
|
provider: SupabaseProvider | null;
|
|
35
35
|
synced: boolean;
|
|
36
|
+
hydrated: boolean;
|
|
37
|
+
hydrationFailed: boolean;
|
|
36
38
|
connected: boolean;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
/**
|
|
40
|
-
* Hook for managing Yjs collaboration with
|
|
42
|
+
* Hook for managing Yjs collaboration with Cloudflare realtime.
|
|
41
43
|
*
|
|
42
44
|
* Uses deferred cleanup so that React StrictMode's double-invoke cycle
|
|
43
45
|
* (mount → cleanup → remount) reuses the existing SupabaseProvider instead
|
|
@@ -62,6 +64,10 @@ export function useYjsCollaboration(
|
|
|
62
64
|
onSave,
|
|
63
65
|
} = config;
|
|
64
66
|
|
|
67
|
+
const [hydratedProvider, setHydratedProvider] =
|
|
68
|
+
useState<SupabaseProvider | null>(null);
|
|
69
|
+
const [hydrationErrorProvider, setHydrationErrorProvider] =
|
|
70
|
+
useState<SupabaseProvider | null>(null);
|
|
65
71
|
const [synced, setSynced] = useState(false);
|
|
66
72
|
const [connected, setConnected] = useState(false);
|
|
67
73
|
const [providerState, setProviderState] = useState<SupabaseProvider | null>(
|
|
@@ -72,17 +78,27 @@ export function useYjsCollaboration(
|
|
|
72
78
|
// Ref that event listeners check — survives StrictMode cleanup/remount
|
|
73
79
|
const mountedRef = useRef(false);
|
|
74
80
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
+
const hasUser = !!user;
|
|
82
|
+
// Create the document without starting awareness timers during render.
|
|
83
|
+
const documentScope = JSON.stringify([
|
|
84
|
+
channel,
|
|
85
|
+
tableName,
|
|
86
|
+
columnName,
|
|
87
|
+
id,
|
|
88
|
+
user?.id,
|
|
89
|
+
]);
|
|
90
|
+
const doc = useMemo(
|
|
91
|
+
() => (enabled && hasUser ? new Y.Doc({ guid: documentScope }) : null),
|
|
92
|
+
[enabled, hasUser, documentScope]
|
|
81
93
|
);
|
|
82
94
|
|
|
95
|
+
const awareness =
|
|
96
|
+
providerState && providerState.id === doc?.clientID
|
|
97
|
+
? providerState.awareness
|
|
98
|
+
: null;
|
|
99
|
+
|
|
83
100
|
// Stabilize callback and user refs — these should never cause provider
|
|
84
101
|
// destruction/recreation.
|
|
85
|
-
const hasUser = !!user;
|
|
86
102
|
const userRef = useRef(user);
|
|
87
103
|
userRef.current = user;
|
|
88
104
|
const onSyncRef = useRef(onSync);
|
|
@@ -94,19 +110,22 @@ export function useYjsCollaboration(
|
|
|
94
110
|
|
|
95
111
|
// Provider lifecycle: create/destroy based on channel config
|
|
96
112
|
useEffect(() => {
|
|
97
|
-
if (!enabled || !doc || !
|
|
113
|
+
if (!enabled || !doc || !hasUser) return;
|
|
98
114
|
|
|
99
115
|
mountedRef.current = true;
|
|
100
116
|
|
|
101
117
|
// ── StrictMode reuse path ───────────────────────────────────────
|
|
102
118
|
// If a pending deferred destruction exists, cancel it and reuse
|
|
103
|
-
// the existing provider
|
|
104
|
-
// "leave then immediate re-join" race that causes a 10s timeout.
|
|
119
|
+
// the existing provider only when its document identity still matches.
|
|
105
120
|
if (destroyTimerRef.current) {
|
|
106
121
|
clearTimeout(destroyTimerRef.current);
|
|
107
122
|
destroyTimerRef.current = null;
|
|
108
123
|
|
|
109
|
-
if (
|
|
124
|
+
if (
|
|
125
|
+
providerRef.current &&
|
|
126
|
+
!providerRef.current.destroyed &&
|
|
127
|
+
providerRef.current.id === doc.clientID
|
|
128
|
+
) {
|
|
110
129
|
console.log('♻️ Reusing existing SupabaseProvider (StrictMode)');
|
|
111
130
|
return () => {
|
|
112
131
|
mountedRef.current = false;
|
|
@@ -133,6 +152,7 @@ export function useYjsCollaboration(
|
|
|
133
152
|
}
|
|
134
153
|
|
|
135
154
|
const supabase = createClient();
|
|
155
|
+
const awareness = new Awareness(doc);
|
|
136
156
|
|
|
137
157
|
// Set initial awareness state
|
|
138
158
|
const currentUser = userRef.current;
|
|
@@ -152,6 +172,7 @@ export function useYjsCollaboration(
|
|
|
152
172
|
tableName: tableName,
|
|
153
173
|
columnName: columnName,
|
|
154
174
|
awareness,
|
|
175
|
+
ownsDocument: true,
|
|
155
176
|
resyncInterval: 30000,
|
|
156
177
|
saveDebounceMs: saveDebounceMs ?? 300,
|
|
157
178
|
loadState: loadDocumentState,
|
|
@@ -161,29 +182,38 @@ export function useYjsCollaboration(
|
|
|
161
182
|
|
|
162
183
|
providerRef.current = provider;
|
|
163
184
|
setProviderState(provider);
|
|
185
|
+
setSynced(false);
|
|
186
|
+
setConnected(false);
|
|
187
|
+
|
|
188
|
+
provider.on('hydrated', () => {
|
|
189
|
+
if (!mountedRef.current || providerRef.current !== provider) return;
|
|
190
|
+
// Publish a new state value so hydration is observable without transport.
|
|
191
|
+
setHydratedProvider(provider);
|
|
192
|
+
setHydrationErrorProvider(null);
|
|
193
|
+
});
|
|
164
194
|
|
|
165
195
|
// Listen to provider events — use mountedRef so listeners stay valid
|
|
166
196
|
// across StrictMode cleanup/remount without re-registration.
|
|
167
197
|
provider.on('status', ([{ status }]) => {
|
|
168
|
-
if (!mountedRef.current) return;
|
|
198
|
+
if (!mountedRef.current || providerRef.current !== provider) return;
|
|
169
199
|
console.log('📡 Provider status:', status);
|
|
170
200
|
setConnected(status === 'connected');
|
|
171
201
|
});
|
|
172
202
|
|
|
173
203
|
provider.on('synced', ([syncState]) => {
|
|
174
|
-
if (!mountedRef.current) return;
|
|
204
|
+
if (!mountedRef.current || providerRef.current !== provider) return;
|
|
175
205
|
console.log('🔄 Provider synced:', syncState);
|
|
176
206
|
setSynced(syncState);
|
|
177
207
|
onSyncRef.current?.(syncState);
|
|
178
208
|
});
|
|
179
209
|
|
|
180
210
|
provider.on('sync', ([syncState]) => {
|
|
181
|
-
if (!mountedRef.current) return;
|
|
211
|
+
if (!mountedRef.current || providerRef.current !== provider) return;
|
|
182
212
|
console.log('🔄 Provider sync event:', syncState);
|
|
183
213
|
});
|
|
184
214
|
|
|
185
215
|
provider.on('save', (version) => {
|
|
186
|
-
if (!mountedRef.current) return;
|
|
216
|
+
if (!mountedRef.current || providerRef.current !== provider) return;
|
|
187
217
|
console.log('💾 Document saved to database, version:', version);
|
|
188
218
|
onSaveRef.current?.(version);
|
|
189
219
|
});
|
|
@@ -196,30 +226,32 @@ export function useYjsCollaboration(
|
|
|
196
226
|
channel: string;
|
|
197
227
|
status: string;
|
|
198
228
|
}) => {
|
|
199
|
-
if (!mountedRef.current) return;
|
|
229
|
+
if (!mountedRef.current || providerRef.current !== provider) return;
|
|
200
230
|
console.error('❌ Provider error:', {
|
|
201
231
|
message: errorInfo.message,
|
|
202
232
|
channel: errorInfo.channel,
|
|
203
233
|
status: errorInfo.status,
|
|
204
234
|
});
|
|
235
|
+
if (errorInfo.status === 'DOCUMENT_ERROR')
|
|
236
|
+
setHydrationErrorProvider(provider);
|
|
205
237
|
onErrorRef.current?.(new Error(errorInfo.message));
|
|
206
238
|
}
|
|
207
239
|
);
|
|
208
240
|
|
|
209
241
|
provider.on('connect', () => {
|
|
210
|
-
if (!mountedRef.current) return;
|
|
242
|
+
if (!mountedRef.current || providerRef.current !== provider) return;
|
|
211
243
|
console.log('✅ Provider connected');
|
|
212
244
|
});
|
|
213
245
|
|
|
214
246
|
provider.on('disconnect', () => {
|
|
215
|
-
if (!mountedRef.current) return;
|
|
247
|
+
if (!mountedRef.current || providerRef.current !== provider) return;
|
|
216
248
|
console.log('🔌 Provider disconnected');
|
|
217
249
|
setConnected(false);
|
|
218
250
|
setSynced(false);
|
|
219
251
|
});
|
|
220
252
|
|
|
221
253
|
provider.on('dom-error', (error: DOMException) => {
|
|
222
|
-
if (!mountedRef.current) return;
|
|
254
|
+
if (!mountedRef.current || providerRef.current !== provider) return;
|
|
223
255
|
console.warn(
|
|
224
256
|
'⚠️ DOM reconciliation error handled gracefully:',
|
|
225
257
|
error.message
|
|
@@ -227,7 +259,7 @@ export function useYjsCollaboration(
|
|
|
227
259
|
});
|
|
228
260
|
|
|
229
261
|
provider.on('reconnect-failed', () => {
|
|
230
|
-
if (!mountedRef.current) return;
|
|
262
|
+
if (!mountedRef.current || providerRef.current !== provider) return;
|
|
231
263
|
// If page is visible (user is active but network dropped), restart
|
|
232
264
|
// the backoff cycle after a short delay to avoid hammering
|
|
233
265
|
if (
|
|
@@ -240,8 +272,8 @@ export function useYjsCollaboration(
|
|
|
240
272
|
setTimeout(() => {
|
|
241
273
|
if (
|
|
242
274
|
mountedRef.current &&
|
|
243
|
-
providerRef.current &&
|
|
244
|
-
!
|
|
275
|
+
providerRef.current === provider &&
|
|
276
|
+
!provider.destroyed
|
|
245
277
|
) {
|
|
246
278
|
providerRef.current.resetAndReconnect();
|
|
247
279
|
}
|
|
@@ -269,7 +301,6 @@ export function useYjsCollaboration(
|
|
|
269
301
|
columnName,
|
|
270
302
|
hasUser,
|
|
271
303
|
doc,
|
|
272
|
-
awareness,
|
|
273
304
|
enabled,
|
|
274
305
|
broadcastDebounceMs,
|
|
275
306
|
saveDebounceMs,
|
|
@@ -290,8 +321,18 @@ export function useYjsCollaboration(
|
|
|
290
321
|
return {
|
|
291
322
|
doc,
|
|
292
323
|
awareness,
|
|
293
|
-
provider:
|
|
294
|
-
|
|
295
|
-
|
|
324
|
+
provider:
|
|
325
|
+
providerState && providerState.id === doc?.clientID
|
|
326
|
+
? providerState
|
|
327
|
+
: null,
|
|
328
|
+
hydrated:
|
|
329
|
+
!!hydratedProvider &&
|
|
330
|
+
hydratedProvider.id === doc?.clientID &&
|
|
331
|
+
hydratedProvider.hydrated,
|
|
332
|
+
hydrationFailed:
|
|
333
|
+
!!hydrationErrorProvider && hydrationErrorProvider.id === doc?.clientID,
|
|
334
|
+
synced: !!providerState && providerState.id === doc?.clientID && synced,
|
|
335
|
+
connected:
|
|
336
|
+
!!providerState && providerState.id === doc?.clientID && connected,
|
|
296
337
|
};
|
|
297
338
|
}
|