@ttt-productions/upload-ui 0.2.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 (45) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +2 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/react/deferred-upload-form-shell.d.ts +39 -0
  6. package/dist/react/deferred-upload-form-shell.d.ts.map +1 -0
  7. package/dist/react/deferred-upload-form-shell.js +56 -0
  8. package/dist/react/deferred-upload-form-shell.js.map +1 -0
  9. package/dist/react/guarded-link.d.ts +38 -0
  10. package/dist/react/guarded-link.d.ts.map +1 -0
  11. package/dist/react/guarded-link.js +14 -0
  12. package/dist/react/guarded-link.js.map +1 -0
  13. package/dist/react/in-flight-uploads-provider.d.ts +214 -0
  14. package/dist/react/in-flight-uploads-provider.d.ts.map +1 -0
  15. package/dist/react/in-flight-uploads-provider.js +286 -0
  16. package/dist/react/in-flight-uploads-provider.js.map +1 -0
  17. package/dist/react/index.d.ts +11 -0
  18. package/dist/react/index.d.ts.map +1 -0
  19. package/dist/react/index.js +10 -0
  20. package/dist/react/index.js.map +1 -0
  21. package/dist/react/local-upload-guard-provider.d.ts +76 -0
  22. package/dist/react/local-upload-guard-provider.d.ts.map +1 -0
  23. package/dist/react/local-upload-guard-provider.js +110 -0
  24. package/dist/react/local-upload-guard-provider.js.map +1 -0
  25. package/dist/react/upload-activity-tray.d.ts +54 -0
  26. package/dist/react/upload-activity-tray.d.ts.map +1 -0
  27. package/dist/react/upload-activity-tray.js +87 -0
  28. package/dist/react/upload-activity-tray.js.map +1 -0
  29. package/dist/react/use-clear-upload-activity.d.ts +8 -0
  30. package/dist/react/use-clear-upload-activity.d.ts.map +1 -0
  31. package/dist/react/use-clear-upload-activity.js +24 -0
  32. package/dist/react/use-clear-upload-activity.js.map +1 -0
  33. package/dist/react/use-guarded-navigation.d.ts +8 -0
  34. package/dist/react/use-guarded-navigation.d.ts.map +1 -0
  35. package/dist/react/use-guarded-navigation.js +34 -0
  36. package/dist/react/use-guarded-navigation.js.map +1 -0
  37. package/dist/react/use-guarded-upload.d.ts +37 -0
  38. package/dist/react/use-guarded-upload.d.ts.map +1 -0
  39. package/dist/react/use-guarded-upload.js +39 -0
  40. package/dist/react/use-guarded-upload.js.map +1 -0
  41. package/dist/react/use-upload-processing.d.ts +27 -0
  42. package/dist/react/use-upload-processing.d.ts.map +1 -0
  43. package/dist/react/use-upload-processing.js +112 -0
  44. package/dist/react/use-upload-processing.js.map +1 -0
  45. package/package.json +53 -0
@@ -0,0 +1,286 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ /**
4
+ * Generic in-flight uploads provider.
5
+ *
6
+ * Provides a global Map of in-flight upload state by `pendingMediaId`, derived
7
+ * from a Firestore listener over the consumer-supplied collection. On terminal
8
+ * status transitions observed THIS SESSION, fires consumer-supplied success /
9
+ * failure / rejection callbacks. Records monitoring breadcrumbs and parse
10
+ * errors through the injected monitoring callback.
11
+ *
12
+ * EVERY TTT-specific value is supplied by the consumer:
13
+ * - userId resolution
14
+ * - Firestore `db` reference
15
+ * - collection path
16
+ * - listener time window (ms)
17
+ * - parser/schema for raw Firestore doc → typed PendingMedia
18
+ * - success copy per origin
19
+ * - notification callback (cache invalidation, etc.)
20
+ * - toast callback
21
+ * - rejection callback (for the media-rejection dialog or equivalent)
22
+ * - monitoring callback (breadcrumbs + captured errors)
23
+ *
24
+ * Read APIs:
25
+ * - `useInFlightUploadsState()` — full Map.
26
+ * - `useInFlightUpload(id)` — single doc, narrowable on status.
27
+ * - `useUploadActivityState()` — tray-visible items, sorted newest-first.
28
+ *
29
+ * The package owns the deduplication, initial-snapshot suppression, and
30
+ * non-terminal → terminal transition tracking. It does NOT own anything about
31
+ * what a file-origin is or what message text appears in toasts.
32
+ */
33
+ import { createContext, useContext, useEffect, useMemo, useRef, useState, } from 'react';
34
+ const InFlightUploadsContext = createContext(undefined);
35
+ function fromParsed(parsed) {
36
+ const base = {
37
+ id: parsed.id,
38
+ fileOrigin: parsed.fileOrigin,
39
+ surface: parsed.surface,
40
+ createdAt: parsed.createdAt,
41
+ targetIds: parsed.targetIds,
42
+ };
43
+ if (parsed.status === 'pending' || parsed.status === 'processing') {
44
+ return { ...base, status: parsed.status };
45
+ }
46
+ if (parsed.status === 'completed') {
47
+ return {
48
+ ...base,
49
+ status: 'completed',
50
+ completedAt: parsed.completedAt ?? parsed.createdAt,
51
+ uploadTrayClearedAt: parsed.uploadTrayClearedAt,
52
+ uploadTrayClearedBy: parsed.uploadTrayClearedBy,
53
+ };
54
+ }
55
+ if (parsed.status === 'failed') {
56
+ return {
57
+ ...base,
58
+ status: 'failed',
59
+ failedAt: parsed.failedAt ?? parsed.createdAt,
60
+ errorCategory: parsed.errorCategory ?? 'system',
61
+ errorMessage: parsed.errorMessage ?? '',
62
+ uploadTrayClearedAt: parsed.uploadTrayClearedAt,
63
+ uploadTrayClearedBy: parsed.uploadTrayClearedBy,
64
+ };
65
+ }
66
+ // rejected
67
+ return {
68
+ ...base,
69
+ status: 'rejected',
70
+ rejectedAt: parsed.rejectedAt ?? parsed.createdAt,
71
+ rejectionType: parsed.rejectionType ?? 'media',
72
+ errorMessage: parsed.errorMessage ?? '',
73
+ violationId: parsed.violationId,
74
+ uploadTrayClearedAt: parsed.uploadTrayClearedAt,
75
+ uploadTrayClearedBy: parsed.uploadTrayClearedBy,
76
+ };
77
+ }
78
+ export function InFlightUploadsProvider(props) {
79
+ const { adapter, subscribe = defaultFirestoreSubscribe, children } = props;
80
+ const [uploads, setUploads] = useState(new Map());
81
+ const seenTerminalIdsRef = useRef(new Set());
82
+ const statusByIdRef = useRef(new Map());
83
+ const hasProcessedInitialSnapshotRef = useRef(false);
84
+ // Stable refs for adapter callbacks — read from latest adapter inside effect.
85
+ const adapterRef = useRef(adapter);
86
+ useEffect(() => {
87
+ adapterRef.current = adapter;
88
+ });
89
+ const userId = adapter.getCurrentUserId();
90
+ useEffect(() => {
91
+ if (!userId) {
92
+ // Sign-out: clear all state.
93
+ setUploads(new Map());
94
+ seenTerminalIdsRef.current = new Set();
95
+ statusByIdRef.current = new Map();
96
+ hasProcessedInitialSnapshotRef.current = false;
97
+ return;
98
+ }
99
+ const a = adapterRef.current;
100
+ const windowStartMs = Date.now() - a.listenerWindowMs;
101
+ const handleSnapshot = (snapshot) => {
102
+ const updates = [];
103
+ const newlyTerminal = [];
104
+ const isInitialSnapshot = !hasProcessedInitialSnapshotRef.current;
105
+ for (const change of snapshot.docChanges()) {
106
+ if (change.type === 'removed') {
107
+ updates.push({ id: change.doc.id, upload: null });
108
+ statusByIdRef.current.delete(change.doc.id);
109
+ seenTerminalIdsRef.current.delete(change.doc.id);
110
+ continue;
111
+ }
112
+ let parsed;
113
+ try {
114
+ parsed = adapterRef.current.parsePendingMedia(change.doc.data());
115
+ }
116
+ catch (error) {
117
+ adapterRef.current.onMonitoringEvent({
118
+ type: 'parse-error',
119
+ docId: change.doc.id,
120
+ error,
121
+ data: change.doc.data(),
122
+ });
123
+ continue;
124
+ }
125
+ const upload = fromParsed(parsed);
126
+ updates.push({ id: parsed.id, upload });
127
+ const previousStatus = statusByIdRef.current.get(parsed.id);
128
+ const currentStatus = parsed.status;
129
+ const currentIsTerminal = currentStatus === 'completed' ||
130
+ currentStatus === 'failed' ||
131
+ currentStatus === 'rejected';
132
+ const previousWasNonTerminal = previousStatus === 'pending' || previousStatus === 'processing';
133
+ if (isInitialSnapshot) {
134
+ // First snapshot after mount: mark already-terminal docs as seen,
135
+ // but DO NOT fire callbacks. Prevents duplicate toasts on refresh.
136
+ if (currentIsTerminal) {
137
+ seenTerminalIdsRef.current.add(parsed.id);
138
+ }
139
+ }
140
+ else if (currentIsTerminal &&
141
+ previousWasNonTerminal &&
142
+ !seenTerminalIdsRef.current.has(parsed.id)) {
143
+ seenTerminalIdsRef.current.add(parsed.id);
144
+ newlyTerminal.push(parsed);
145
+ }
146
+ else if (currentIsTerminal) {
147
+ // Terminal doc appeared without an observed non-terminal predecessor
148
+ // (race in a non-initial snapshot). Mark as seen, no callback fire.
149
+ seenTerminalIdsRef.current.add(parsed.id);
150
+ }
151
+ statusByIdRef.current.set(parsed.id, currentStatus);
152
+ }
153
+ hasProcessedInitialSnapshotRef.current = true;
154
+ if (updates.length > 0) {
155
+ setUploads((prev) => {
156
+ const next = new Map(prev);
157
+ for (const { id, upload } of updates) {
158
+ if (upload === null)
159
+ next.delete(id);
160
+ else
161
+ next.set(id, upload);
162
+ }
163
+ return next;
164
+ });
165
+ }
166
+ for (const parsed of newlyTerminal) {
167
+ handleTerminal(parsed);
168
+ }
169
+ };
170
+ function handleTerminal(parsed) {
171
+ adapterRef.current.onMonitoringEvent({
172
+ type: 'breadcrumb',
173
+ category: 'upload.terminal',
174
+ level: 'info',
175
+ message: `${parsed.fileOrigin}:${parsed.status}`,
176
+ data: {
177
+ id: parsed.id,
178
+ fileOrigin: parsed.fileOrigin,
179
+ status: parsed.status,
180
+ surface: parsed.surface,
181
+ },
182
+ });
183
+ if (parsed.status === 'completed') {
184
+ adapterRef.current.onUploadCompleted(parsed);
185
+ }
186
+ else if (parsed.status === 'failed') {
187
+ adapterRef.current.onUploadFailed(parsed);
188
+ }
189
+ else if (parsed.status === 'rejected') {
190
+ adapterRef.current.onUploadRejected(parsed);
191
+ }
192
+ }
193
+ const unsubscribe = subscribe({
194
+ db: a.db,
195
+ collectionPath: a.collectionPath,
196
+ userId,
197
+ windowStartMs,
198
+ onSnapshot: handleSnapshot,
199
+ onError: (error) => {
200
+ adapterRef.current.onMonitoringEvent({ type: 'listener-error', error });
201
+ },
202
+ });
203
+ return () => {
204
+ unsubscribe();
205
+ };
206
+ // adapter is intentionally NOT in the deps array — adapter is read via
207
+ // adapterRef on every snapshot, so identity changes don't re-subscribe.
208
+ // eslint-disable-next-line react-hooks/exhaustive-deps
209
+ }, [userId, subscribe]);
210
+ const value = useMemo(() => ({ uploads }), [uploads]);
211
+ return (_jsx(InFlightUploadsContext.Provider, { value: value, children: children }));
212
+ }
213
+ // =============================================================================
214
+ // Read APIs
215
+ // =============================================================================
216
+ export function useInFlightUploadsState() {
217
+ const ctx = useContext(InFlightUploadsContext);
218
+ if (ctx === undefined) {
219
+ throw new Error('useInFlightUploadsState must be used within InFlightUploadsProvider');
220
+ }
221
+ return ctx.uploads;
222
+ }
223
+ export function useInFlightUpload(id) {
224
+ const ctx = useContext(InFlightUploadsContext);
225
+ if (ctx === undefined) {
226
+ throw new Error('useInFlightUpload must be used within InFlightUploadsProvider');
227
+ }
228
+ if (!id)
229
+ return undefined;
230
+ return ctx.uploads.get(id);
231
+ }
232
+ // =============================================================================
233
+ // Tray helpers
234
+ // =============================================================================
235
+ /**
236
+ * `true` for `completed` / `failed` / `rejected`. Used by tray UI to decide
237
+ * whether a Clear action is available for an item.
238
+ */
239
+ export function isTerminalUpload(item) {
240
+ return (item.status === 'completed' ||
241
+ item.status === 'failed' ||
242
+ item.status === 'rejected');
243
+ }
244
+ /**
245
+ * Tray-visible items. Includes every pending/processing upload, plus terminal
246
+ * uploads that have NOT been cleared from the tray. Sorted newest-first by
247
+ * createdAt.
248
+ *
249
+ * Renamed per F22: was `useUploadActivityTrayItems` in ttt-prod; the public
250
+ * selector name in upload-ui is `useUploadActivityState`. The mutation hook
251
+ * is `useClearUploadActivity` (in a separate file).
252
+ */
253
+ export function useUploadActivityState() {
254
+ const uploads = useInFlightUploadsState();
255
+ return useMemo(() => {
256
+ return Array.from(uploads.values())
257
+ .filter((item) => {
258
+ if (!isTerminalUpload(item))
259
+ return true;
260
+ return item.uploadTrayClearedAt === undefined;
261
+ })
262
+ .sort((a, b) => b.createdAt - a.createdAt);
263
+ }, [uploads]);
264
+ }
265
+ // =============================================================================
266
+ // Default Firestore subscription implementation
267
+ // =============================================================================
268
+ /**
269
+ * Default Firestore subscription. Imported lazily so tests can inject a fake
270
+ * `subscribe` and skip the firebase module load entirely.
271
+ *
272
+ * The dynamic import keeps the bundle clean: consumers who pass a custom
273
+ * `subscribe` (e.g. for tests or alternative backends) never pay for the
274
+ * firebase code path.
275
+ */
276
+ const defaultFirestoreSubscribe = (args) => {
277
+ // Lazy require to keep firebase out of the synchronous module graph.
278
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
279
+ const firestore = require('firebase/firestore');
280
+ const { collection, onSnapshot, query, where } = firestore;
281
+ const q = query(
282
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
283
+ collection(args.db, args.collectionPath), where('userId', '==', args.userId), where('createdAt', '>=', args.windowStartMs));
284
+ return onSnapshot(q, args.onSnapshot, args.onError);
285
+ };
286
+ //# sourceMappingURL=in-flight-uploads-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-flight-uploads-provider.js","sourceRoot":"","sources":["../../src/react/in-flight-uploads-provider.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export { LocalUploadGuardProvider, useLocalUploadGuard, useLocalUploadGuardContext, } from './local-upload-guard-provider.js';
2
+ export { useGuardedUpload, type GuardedUploadArgs } from './use-guarded-upload.js';
3
+ export { DeferredUploadFormShell, type DeferredUploadFormShellHandle, type DeferredUploadFormShellProps, } from './deferred-upload-form-shell.js';
4
+ export { InFlightUploadsProvider, useInFlightUploadsState, useInFlightUpload, useUploadActivityState, isTerminalUpload, type InFlightUpload, type InFlightUploadActive, type InFlightUploadCompleted, type InFlightUploadFailed, type InFlightUploadRejected, type InFlightUploadBase, type InFlightUploadStatus, type InFlightUploadsAdapter, type InFlightUploadsMonitoringEvent, type InFlightUploadsProviderProps, type ParsedPendingMedia, type FirestoreSubscribeFn, type FirestoreLikeSnapshot, type FirestoreLikeDocChange, } from './in-flight-uploads-provider.js';
5
+ export { useUploadProcessing, type UploadProcessingState, type UseUploadProcessingOptions, } from './use-upload-processing.js';
6
+ export { useClearUploadActivity, type ClearUploadActivityOptions, } from './use-clear-upload-activity.js';
7
+ export { useGuardedNavigation, } from './use-guarded-navigation.js';
8
+ export { GuardedLink, type GuardedLinkProps, type GuardedLinkRenderArgs, } from './guarded-link.js';
9
+ export { type LocalUploadGuardProviderProps, } from './local-upload-guard-provider.js';
10
+ export { UploadActivityTray, type UploadActivityTrayProps, type ViewAllLinkRenderArgs, } from './upload-activity-tray.js';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,KAAK,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACnF,OAAO,EACL,uBAAuB,EACvB,KAAK,6BAA6B,EAClC,KAAK,4BAA4B,GAClC,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,8BAA8B,EACnC,KAAK,4BAA4B,EACjC,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,GAC5B,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,mBAAmB,EACnB,KAAK,qBAAqB,EAC1B,KAAK,0BAA0B,GAChC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,sBAAsB,EACtB,KAAK,0BAA0B,GAChC,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,oBAAoB,GACrB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,WAAW,EACX,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,GAC3B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,KAAK,6BAA6B,GACnC,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,kBAAkB,EAClB,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,GAC3B,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1,10 @@
1
+ export { LocalUploadGuardProvider, useLocalUploadGuard, useLocalUploadGuardContext, } from './local-upload-guard-provider.js';
2
+ export { useGuardedUpload } from './use-guarded-upload.js';
3
+ export { DeferredUploadFormShell, } from './deferred-upload-form-shell.js';
4
+ export { InFlightUploadsProvider, useInFlightUploadsState, useInFlightUpload, useUploadActivityState, isTerminalUpload, } from './in-flight-uploads-provider.js';
5
+ export { useUploadProcessing, } from './use-upload-processing.js';
6
+ export { useClearUploadActivity, } from './use-clear-upload-activity.js';
7
+ export { useGuardedNavigation, } from './use-guarded-navigation.js';
8
+ export { GuardedLink, } from './guarded-link.js';
9
+ export { UploadActivityTray, } from './upload-activity-tray.js';
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAA0B,MAAM,yBAAyB,CAAC;AACnF,OAAO,EACL,uBAAuB,GAGxB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,GAejB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,mBAAmB,GAGpB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,sBAAsB,GAEvB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,oBAAoB,GACrB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,WAAW,GAGZ,MAAM,mBAAmB,CAAC;AAI3B,OAAO,EACL,kBAAkB,GAGnB,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Local upload navigation guard provider.
3
+ *
4
+ * Tracks the count of in-progress LOCAL byte uploads (file → Firebase
5
+ * Storage). While the count is > 0, installs a `beforeunload` listener
6
+ * to warn the user before they close the tab or reload mid-upload.
7
+ *
8
+ * Scope:
9
+ * - Covers phases `preparing` and `uploading` only.
10
+ * - Does NOT cover `finalizing` — at that point bytes are already in
11
+ * Storage and the only remaining work is the `startUpload` callable.
12
+ * - Does NOT cover backend `pendingMedia` status (`pending` /
13
+ * `processing`). Those run server-side and are safe to leave.
14
+ *
15
+ * Consumers register with a stable id (typically the local upload
16
+ * session id or `pendingMedia` doc id once known) on entering
17
+ * `preparing`/`uploading`, and unregister on transition to
18
+ * `finalizing`/success/error/cancel/unmount.
19
+ *
20
+ * Public hook: `useLocalUploadGuard()` returns
21
+ * `{ activeUploadCount, registerUpload, unregisterUpload }`. Throws if
22
+ * used outside a `LocalUploadGuardProvider`.
23
+ */
24
+ import { type ReactNode } from 'react';
25
+ interface LocalUploadGuardContextValue {
26
+ activeUploadCount: number;
27
+ registerUpload: (id: string) => void;
28
+ unregisterUpload: (id: string) => void;
29
+ /** Returns true iff at least one upload is currently registered. Pure read. */
30
+ shouldConfirmNavigation: () => boolean;
31
+ /**
32
+ * Shows a confirm() dialog with the configured message and returns the user's
33
+ * choice. Safe to call when no upload is active — returns true without prompting.
34
+ */
35
+ confirmNavigation: () => boolean;
36
+ }
37
+ export interface LocalUploadGuardProviderProps {
38
+ /**
39
+ * Message shown by the browser when the user attempts to close the tab or
40
+ * reload during an active upload. Modern browsers may ignore the custom
41
+ * string and show a generic dialog.
42
+ */
43
+ beforeUnloadMessage?: string;
44
+ /**
45
+ * Message shown by `confirmNavigation()` when an upload is in progress and
46
+ * the user attempts to navigate away within the app. Defaults to a
47
+ * reasonable English string.
48
+ */
49
+ navigationConfirmMessage?: string;
50
+ children: ReactNode;
51
+ }
52
+ export declare function LocalUploadGuardProvider(props: LocalUploadGuardProviderProps): import("react/jsx-runtime").JSX.Element;
53
+ /**
54
+ * Low-level accessor for the local-upload guard context. Throws if used
55
+ * outside a `LocalUploadGuardProvider`. Most consumers should use
56
+ * `useLocalUploadGuard` (the public re-export below) instead.
57
+ */
58
+ export declare function useLocalUploadGuardContext(): LocalUploadGuardContextValue;
59
+ /**
60
+ * Public hook for the local upload navigation guard.
61
+ *
62
+ * Returns `{ activeUploadCount, registerUpload, unregisterUpload }`.
63
+ *
64
+ * Typical usage in an upload flow:
65
+ *
66
+ * const { registerUpload, unregisterUpload } = useLocalUploadGuard();
67
+ * useEffect(() => {
68
+ * if (phase === 'preparing' || phase === 'uploading') {
69
+ * registerUpload(uploadId);
70
+ * return () => unregisterUpload(uploadId);
71
+ * }
72
+ * }, [phase, uploadId, registerUpload, unregisterUpload]);
73
+ */
74
+ export declare function useLocalUploadGuard(): LocalUploadGuardContextValue;
75
+ export {};
76
+ //# sourceMappingURL=local-upload-guard-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-upload-guard-provider.d.ts","sourceRoot":"","sources":["../../src/react/local-upload-guard-provider.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,UAAU,4BAA4B;IACpC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,gBAAgB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,+EAA+E;IAC/E,uBAAuB,EAAE,MAAM,OAAO,CAAC;IACvC;;;OAGG;IACH,iBAAiB,EAAE,MAAM,OAAO,CAAC;CAClC;AAID,MAAM,WAAW,6BAA6B;IAC5C;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,EAAE,SAAS,CAAC;CACrB;AAOD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,6BAA6B,2CAmE5E;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,IAAI,4BAA4B,CAQzE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,IAAI,4BAA4B,CAElE"}
@@ -0,0 +1,110 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ /**
4
+ * Local upload navigation guard provider.
5
+ *
6
+ * Tracks the count of in-progress LOCAL byte uploads (file → Firebase
7
+ * Storage). While the count is > 0, installs a `beforeunload` listener
8
+ * to warn the user before they close the tab or reload mid-upload.
9
+ *
10
+ * Scope:
11
+ * - Covers phases `preparing` and `uploading` only.
12
+ * - Does NOT cover `finalizing` — at that point bytes are already in
13
+ * Storage and the only remaining work is the `startUpload` callable.
14
+ * - Does NOT cover backend `pendingMedia` status (`pending` /
15
+ * `processing`). Those run server-side and are safe to leave.
16
+ *
17
+ * Consumers register with a stable id (typically the local upload
18
+ * session id or `pendingMedia` doc id once known) on entering
19
+ * `preparing`/`uploading`, and unregister on transition to
20
+ * `finalizing`/success/error/cancel/unmount.
21
+ *
22
+ * Public hook: `useLocalUploadGuard()` returns
23
+ * `{ activeUploadCount, registerUpload, unregisterUpload }`. Throws if
24
+ * used outside a `LocalUploadGuardProvider`.
25
+ */
26
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
27
+ const LocalUploadGuardContext = createContext(undefined);
28
+ const DEFAULT_BEFORE_UNLOAD_MESSAGE = 'File upload in progress. Are you sure you want to leave and end the file upload?';
29
+ const DEFAULT_NAVIGATION_CONFIRM_MESSAGE = 'An upload is currently in progress. If you leave this page, the upload will be canceled. Continue?';
30
+ export function LocalUploadGuardProvider(props) {
31
+ const { children, beforeUnloadMessage = DEFAULT_BEFORE_UNLOAD_MESSAGE, navigationConfirmMessage = DEFAULT_NAVIGATION_CONFIRM_MESSAGE, } = props;
32
+ const activeIdsRef = useRef(new Set());
33
+ const [activeUploadCount, setActiveUploadCount] = useState(0);
34
+ const registerUpload = useCallback((id) => {
35
+ if (activeIdsRef.current.has(id))
36
+ return;
37
+ activeIdsRef.current.add(id);
38
+ setActiveUploadCount(activeIdsRef.current.size);
39
+ }, []);
40
+ const unregisterUpload = useCallback((id) => {
41
+ if (!activeIdsRef.current.has(id))
42
+ return;
43
+ activeIdsRef.current.delete(id);
44
+ setActiveUploadCount(activeIdsRef.current.size);
45
+ }, []);
46
+ useEffect(() => {
47
+ if (activeUploadCount <= 0)
48
+ return;
49
+ const handleBeforeUnload = (event) => {
50
+ event.preventDefault();
51
+ // Modern browsers may ignore the custom string and show their own
52
+ // generic confirmation. The key behavior is that navigation is
53
+ // blocked/warned, not the exact text.
54
+ event.returnValue = beforeUnloadMessage;
55
+ return event.returnValue;
56
+ };
57
+ window.addEventListener('beforeunload', handleBeforeUnload);
58
+ return () => {
59
+ window.removeEventListener('beforeunload', handleBeforeUnload);
60
+ };
61
+ }, [activeUploadCount, beforeUnloadMessage]);
62
+ const shouldConfirmNavigation = useCallback(() => activeIdsRef.current.size > 0, []);
63
+ const confirmNavigation = useCallback(() => {
64
+ if (activeIdsRef.current.size === 0)
65
+ return true;
66
+ // SSR / non-browser safety: if `window` isn't available, default to allow.
67
+ if (typeof window === 'undefined')
68
+ return true;
69
+ return window.confirm(navigationConfirmMessage);
70
+ }, [navigationConfirmMessage]);
71
+ const value = useMemo(() => ({
72
+ activeUploadCount,
73
+ registerUpload,
74
+ unregisterUpload,
75
+ shouldConfirmNavigation,
76
+ confirmNavigation,
77
+ }), [activeUploadCount, registerUpload, unregisterUpload, shouldConfirmNavigation, confirmNavigation]);
78
+ return (_jsx(LocalUploadGuardContext.Provider, { value: value, children: children }));
79
+ }
80
+ /**
81
+ * Low-level accessor for the local-upload guard context. Throws if used
82
+ * outside a `LocalUploadGuardProvider`. Most consumers should use
83
+ * `useLocalUploadGuard` (the public re-export below) instead.
84
+ */
85
+ export function useLocalUploadGuardContext() {
86
+ const ctx = useContext(LocalUploadGuardContext);
87
+ if (ctx === undefined) {
88
+ throw new Error('useLocalUploadGuard must be used within LocalUploadGuardProvider');
89
+ }
90
+ return ctx;
91
+ }
92
+ /**
93
+ * Public hook for the local upload navigation guard.
94
+ *
95
+ * Returns `{ activeUploadCount, registerUpload, unregisterUpload }`.
96
+ *
97
+ * Typical usage in an upload flow:
98
+ *
99
+ * const { registerUpload, unregisterUpload } = useLocalUploadGuard();
100
+ * useEffect(() => {
101
+ * if (phase === 'preparing' || phase === 'uploading') {
102
+ * registerUpload(uploadId);
103
+ * return () => unregisterUpload(uploadId);
104
+ * }
105
+ * }, [phase, uploadId, registerUpload, unregisterUpload]);
106
+ */
107
+ export function useLocalUploadGuard() {
108
+ return useLocalUploadGuardContext();
109
+ }
110
+ //# sourceMappingURL=local-upload-guard-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-upload-guard-provider.js","sourceRoot":"","sources":["../../src/react/local-upload-guard-provider.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Global upload activity tray.
3
+ *
4
+ * Renders a fixed bottom-left floating action button when there is any
5
+ * active or non-cleared terminal upload. Opens a popover listing each
6
+ * item with backend status. Terminal items (completed / failed / rejected)
7
+ * get a Clear (×) button that calls the consumer-supplied clear mutation.
8
+ *
9
+ * Framework-agnostic via render props:
10
+ * - `renderViewAllLink` lets the consumer plug in their app's Link
11
+ * component (Next.js Link, React Router Link, anchor, etc.) routed at
12
+ * their canonical "view all uploads" destination.
13
+ * - `renderFooter` lets the consumer fully replace the footer slot.
14
+ * - Both omitted — footer is not rendered.
15
+ */
16
+ import { type ReactNode } from 'react';
17
+ export interface ViewAllLinkRenderArgs {
18
+ /** The href the consumer's link should navigate to (passed through verbatim). */
19
+ href: string;
20
+ /** Children for the link element (i.e. the label text + arrow). */
21
+ children: ReactNode;
22
+ }
23
+ export interface UploadActivityTrayProps {
24
+ /**
25
+ * Mutation hook result from `useClearUploadActivity({ clearFn })`. The tray
26
+ * uses `.mutate(item.id)`, `.isPending`, and `.variables`. The consumer
27
+ * owns the underlying network call.
28
+ */
29
+ clearMutation: {
30
+ mutate: (pendingMediaId: string) => void;
31
+ isPending: boolean;
32
+ variables: string | undefined;
33
+ };
34
+ /**
35
+ * Optional view-all destination href. When omitted, no footer is rendered
36
+ * unless `renderFooter` is also supplied.
37
+ */
38
+ viewAllHref?: string;
39
+ /**
40
+ * Render-prop for the view-all link. Receives the href and label children
41
+ * the consumer should attach. Omit to drop the link.
42
+ */
43
+ renderViewAllLink?: (args: ViewAllLinkRenderArgs) => ReactNode;
44
+ /**
45
+ * Fully custom footer slot. Replaces the default footer entirely. If both
46
+ * `renderFooter` and `viewAllHref + renderViewAllLink` are supplied,
47
+ * `renderFooter` wins.
48
+ */
49
+ renderFooter?: () => ReactNode;
50
+ /** Visible label for the view-all link. Default: "View all uploads →". */
51
+ viewAllLabel?: ReactNode;
52
+ }
53
+ export declare function UploadActivityTray<TFileOrigin extends string = string>(props: UploadActivityTrayProps): import("react/jsx-runtime").JSX.Element | null;
54
+ //# sourceMappingURL=upload-activity-tray.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload-activity-tray.d.ts","sourceRoot":"","sources":["../../src/react/upload-activity-tray.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAW,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAmGhD,MAAM,WAAW,qBAAqB;IACpC,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,aAAa,EAAE;QACb,MAAM,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;QACzC,SAAS,EAAE,OAAO,CAAC;QACnB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;KAC/B,CAAC;IACF;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,qBAAqB,KAAK,SAAS,CAAC;IAC/D;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,SAAS,CAAC;IAC/B,0EAA0E;IAC1E,YAAY,CAAC,EAAE,SAAS,CAAC;CAC1B;AAED,wBAAgB,kBAAkB,CAAC,WAAW,SAAS,MAAM,GAAG,MAAM,EACpE,KAAK,EAAE,uBAAuB,kDAoG/B"}