@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.
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/react/deferred-upload-form-shell.d.ts +39 -0
- package/dist/react/deferred-upload-form-shell.d.ts.map +1 -0
- package/dist/react/deferred-upload-form-shell.js +56 -0
- package/dist/react/deferred-upload-form-shell.js.map +1 -0
- package/dist/react/guarded-link.d.ts +38 -0
- package/dist/react/guarded-link.d.ts.map +1 -0
- package/dist/react/guarded-link.js +14 -0
- package/dist/react/guarded-link.js.map +1 -0
- package/dist/react/in-flight-uploads-provider.d.ts +214 -0
- package/dist/react/in-flight-uploads-provider.d.ts.map +1 -0
- package/dist/react/in-flight-uploads-provider.js +286 -0
- package/dist/react/in-flight-uploads-provider.js.map +1 -0
- package/dist/react/index.d.ts +11 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +10 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/local-upload-guard-provider.d.ts +76 -0
- package/dist/react/local-upload-guard-provider.d.ts.map +1 -0
- package/dist/react/local-upload-guard-provider.js +110 -0
- package/dist/react/local-upload-guard-provider.js.map +1 -0
- package/dist/react/upload-activity-tray.d.ts +54 -0
- package/dist/react/upload-activity-tray.d.ts.map +1 -0
- package/dist/react/upload-activity-tray.js +87 -0
- package/dist/react/upload-activity-tray.js.map +1 -0
- package/dist/react/use-clear-upload-activity.d.ts +8 -0
- package/dist/react/use-clear-upload-activity.d.ts.map +1 -0
- package/dist/react/use-clear-upload-activity.js +24 -0
- package/dist/react/use-clear-upload-activity.js.map +1 -0
- package/dist/react/use-guarded-navigation.d.ts +8 -0
- package/dist/react/use-guarded-navigation.d.ts.map +1 -0
- package/dist/react/use-guarded-navigation.js +34 -0
- package/dist/react/use-guarded-navigation.js.map +1 -0
- package/dist/react/use-guarded-upload.d.ts +37 -0
- package/dist/react/use-guarded-upload.d.ts.map +1 -0
- package/dist/react/use-guarded-upload.js +39 -0
- package/dist/react/use-guarded-upload.js.map +1 -0
- package/dist/react/use-upload-processing.d.ts +27 -0
- package/dist/react/use-upload-processing.d.ts.map +1 -0
- package/dist/react/use-upload-processing.js +112 -0
- package/dist/react/use-upload-processing.js.map +1 -0
- package/package.json +53 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React, { type ReactNode, type Ref } from 'react';
|
|
2
|
+
import type { MediaOriginSpec, UploadState } from '@ttt-productions/media-schemas';
|
|
3
|
+
export interface DeferredUploadFormShellHandle {
|
|
4
|
+
submit: () => void;
|
|
5
|
+
}
|
|
6
|
+
export interface DeferredUploadFormShellProps<TVariables extends {
|
|
7
|
+
onProgress?: (s: UploadState | null) => void;
|
|
8
|
+
signal?: AbortSignal;
|
|
9
|
+
}, TResult = unknown> {
|
|
10
|
+
/** The media-origin spec describing accept patterns, size caps, and label.
|
|
11
|
+
* Consumers pass this directly — the shell does not look it up from a registry. */
|
|
12
|
+
spec: MediaOriginSpec;
|
|
13
|
+
/** Opaque caller-supplied identifier for this upload origin (e.g. "streetz", "project-file").
|
|
14
|
+
* The shell does not interpret it; it is forwarded only via consumer-controlled callbacks. */
|
|
15
|
+
originId: string;
|
|
16
|
+
mutation: {
|
|
17
|
+
mutateAsync: (vars: TVariables) => Promise<TResult>;
|
|
18
|
+
isPending: boolean;
|
|
19
|
+
};
|
|
20
|
+
buildVariables: (file: File | null, onProgress: (s: UploadState | null) => void, signal: AbortSignal) => TVariables;
|
|
21
|
+
onSuccess?: (result: TResult) => void;
|
|
22
|
+
onError?: (err: unknown) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Optional callback fired whenever the shell's internal selectedFile state
|
|
25
|
+
* changes — on user select, on user clear, and on the post-success internal
|
|
26
|
+
* reset. Forms typically use this to mirror selectedFile externally so they
|
|
27
|
+
* can compute their own submit-button `disabled` flag (e.g. require-a-file
|
|
28
|
+
* submit gating).
|
|
29
|
+
*/
|
|
30
|
+
onFileChange?: (file: File | null) => void;
|
|
31
|
+
children?: ReactNode;
|
|
32
|
+
}
|
|
33
|
+
export declare const DeferredUploadFormShell: <TVariables extends {
|
|
34
|
+
onProgress?: (s: UploadState | null) => void;
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
}, TResult = unknown>(props: DeferredUploadFormShellProps<TVariables, TResult> & {
|
|
37
|
+
ref?: Ref<DeferredUploadFormShellHandle>;
|
|
38
|
+
}) => React.ReactElement;
|
|
39
|
+
//# sourceMappingURL=deferred-upload-form-shell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deferred-upload-form-shell.d.ts","sourceRoot":"","sources":["../../src/react/deferred-upload-form-shell.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAMZ,KAAK,SAAS,EACd,KAAK,GAAG,EACT,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAEnF,MAAM,WAAW,6BAA6B;IAC5C,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,4BAA4B,CAC3C,UAAU,SAAS;IACjB,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IAC7C,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,EACD,OAAO,GAAG,OAAO;IAEjB;wFACoF;IACpF,IAAI,EAAE,eAAe,CAAC;IACtB;mGAC+F;IAC/F,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE;QACR,WAAW,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;QACpD,SAAS,EAAE,OAAO,CAAC;KACpB,CAAC;IACF,cAAc,EAAE,CACd,IAAI,EAAE,IAAI,GAAG,IAAI,EACjB,UAAU,EAAE,CAAC,CAAC,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,EAC3C,MAAM,EAAE,WAAW,KAChB,UAAU,CAAC;IAChB,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACjC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,CAAC;IAC3C,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAmGD,eAAO,MAAM,uBAAuB,EAA+C,CACjF,UAAU,SAAS;IACjB,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IAC7C,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,EACD,OAAO,GAAG,OAAO,EAEjB,KAAK,EAAE,4BAA4B,CAAC,UAAU,EAAE,OAAO,CAAC,GAAG;IAAE,GAAG,CAAC,EAAE,GAAG,CAAC,6BAA6B,CAAC,CAAA;CAAE,KACpG,KAAK,CAAC,YAAY,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useRef, useCallback, useImperativeHandle, forwardRef, } from 'react';
|
|
4
|
+
import { MediaInput } from '@ttt-productions/file-input/react';
|
|
5
|
+
function DeferredUploadFormShellInner(props, ref) {
|
|
6
|
+
const { spec, originId: _originId, mutation, buildVariables, onSuccess, onError, onFileChange, children, } = props;
|
|
7
|
+
const [selectedFile, setSelectedFile] = useState(null);
|
|
8
|
+
const [uploadState, setUploadState] = useState(null);
|
|
9
|
+
const fileAreaRef = useRef(null);
|
|
10
|
+
const abortControllerRef = useRef(null);
|
|
11
|
+
const handleMediaChange = useCallback((payload) => {
|
|
12
|
+
const next = payload.error ? null : (payload.file ?? null);
|
|
13
|
+
setSelectedFile(next);
|
|
14
|
+
onFileChange?.(next);
|
|
15
|
+
}, [onFileChange]);
|
|
16
|
+
const handleClear = useCallback(() => {
|
|
17
|
+
setSelectedFile(null);
|
|
18
|
+
onFileChange?.(null);
|
|
19
|
+
}, [onFileChange]);
|
|
20
|
+
const handleCancel = useCallback(() => {
|
|
21
|
+
abortControllerRef.current?.abort();
|
|
22
|
+
}, []);
|
|
23
|
+
const handleSubmit = useCallback(async () => {
|
|
24
|
+
if (mutation.isPending)
|
|
25
|
+
return;
|
|
26
|
+
if (selectedFile) {
|
|
27
|
+
fileAreaRef.current?.focus();
|
|
28
|
+
}
|
|
29
|
+
const controller = new AbortController();
|
|
30
|
+
abortControllerRef.current = controller;
|
|
31
|
+
try {
|
|
32
|
+
const result = await mutation.mutateAsync(buildVariables(selectedFile, setUploadState, controller.signal));
|
|
33
|
+
setUploadState(null);
|
|
34
|
+
setSelectedFile(null);
|
|
35
|
+
onFileChange?.(null);
|
|
36
|
+
onSuccess?.(result);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
setUploadState(null);
|
|
40
|
+
// User-initiated cancel: swallow the AbortError, reset selection, do not fire onError.
|
|
41
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
42
|
+
setSelectedFile(null);
|
|
43
|
+
onFileChange?.(null);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
onError?.(err);
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
abortControllerRef.current = null;
|
|
50
|
+
}
|
|
51
|
+
}, [mutation, selectedFile, buildVariables, onSuccess, onError, onFileChange]);
|
|
52
|
+
useImperativeHandle(ref, () => ({ submit: () => { void handleSubmit(); } }), [handleSubmit]);
|
|
53
|
+
return (_jsxs(_Fragment, { children: [children, _jsx("div", { ref: fileAreaRef, tabIndex: -1, "aria-live": "polite", className: "outline-none", children: _jsx(MediaInput, { spec: spec, selectedFile: selectedFile, uploadState: uploadState, isLoading: mutation.isPending, disabled: mutation.isPending, onChange: handleMediaChange, onClear: handleClear, onCancel: handleCancel }) })] }));
|
|
54
|
+
}
|
|
55
|
+
export const DeferredUploadFormShell = forwardRef(DeferredUploadFormShellInner);
|
|
56
|
+
//# sourceMappingURL=deferred-upload-form-shell.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deferred-upload-form-shell.js","sourceRoot":"","sources":["../../src/react/deferred-upload-form-shell.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render-prop link wrapper that guards navigation when an upload is in
|
|
3
|
+
* progress. The consumer owns the actual navigator component (Next.js Link,
|
|
4
|
+
* React Router Link, plain anchor, etc.) — this wrapper only injects the
|
|
5
|
+
* onClick interceptor.
|
|
6
|
+
*
|
|
7
|
+
* Why render-prop and not `as={Link}`? Polymorphic `as` props create
|
|
8
|
+
* notoriously bad TypeScript inference and force the wrapper to forward all
|
|
9
|
+
* link-specific props. Render-prop keeps the consumer in full control of the
|
|
10
|
+
* link API, costs one extra wrapper closure, and keeps upload-ui agnostic of
|
|
11
|
+
* Next.js / React Router / etc.
|
|
12
|
+
*
|
|
13
|
+
* Design note: the design spec lists `<GuardedNavigationButton>` as a second
|
|
14
|
+
* component. We deliberately don't ship it — buttons that navigate use
|
|
15
|
+
* `useGuardedNavigation()` directly in one line. A second component would
|
|
16
|
+
* split the same surface for no payoff.
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
*
|
|
20
|
+
* <GuardedLink renderLink={({ onClick, children }) => (
|
|
21
|
+
* <Link href="/your/destination" onClick={onClick}>{children}</Link>
|
|
22
|
+
* )}>
|
|
23
|
+
* View all uploads →
|
|
24
|
+
* </GuardedLink>
|
|
25
|
+
*/
|
|
26
|
+
import type { MouseEvent, ReactNode } from 'react';
|
|
27
|
+
export interface GuardedLinkRenderArgs {
|
|
28
|
+
/** Click handler the consumer MUST attach to its underlying link element. */
|
|
29
|
+
onClick: (event: MouseEvent<HTMLElement>) => void;
|
|
30
|
+
/** The children passed to `<GuardedLink>` — forward them to the link. */
|
|
31
|
+
children: ReactNode;
|
|
32
|
+
}
|
|
33
|
+
export interface GuardedLinkProps {
|
|
34
|
+
children: ReactNode;
|
|
35
|
+
renderLink: (args: GuardedLinkRenderArgs) => ReactNode;
|
|
36
|
+
}
|
|
37
|
+
export declare function GuardedLink(props: GuardedLinkProps): import("react/jsx-runtime").JSX.Element;
|
|
38
|
+
//# sourceMappingURL=guarded-link.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guarded-link.d.ts","sourceRoot":"","sources":["../../src/react/guarded-link.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGnD,MAAM,WAAW,qBAAqB;IACpC,6EAA6E;IAC7E,OAAO,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAClD,yEAAyE;IACzE,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,SAAS,CAAC;IACpB,UAAU,EAAE,CAAC,IAAI,EAAE,qBAAqB,KAAK,SAAS,CAAC;CACxD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,2CAWlD"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useLocalUploadGuard } from './local-upload-guard-provider.js';
|
|
4
|
+
export function GuardedLink(props) {
|
|
5
|
+
const { children, renderLink } = props;
|
|
6
|
+
const { confirmNavigation } = useLocalUploadGuard();
|
|
7
|
+
const onClick = (event) => {
|
|
8
|
+
if (!confirmNavigation()) {
|
|
9
|
+
event.preventDefault();
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
return _jsx(_Fragment, { children: renderLink({ onClick, children }) });
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=guarded-link.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guarded-link.js","sourceRoot":"","sources":["../../src/react/guarded-link.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AA6Bb,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAcvE,MAAM,UAAU,WAAW,CAAC,KAAuB;IACjD,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,KAAK,CAAC;IACvC,MAAM,EAAE,iBAAiB,EAAE,GAAG,mBAAmB,EAAE,CAAC;IAEpD,MAAM,OAAO,GAAG,CAAC,KAA8B,EAAE,EAAE;QACjD,IAAI,CAAC,iBAAiB,EAAE,EAAE,CAAC;YACzB,KAAK,CAAC,cAAc,EAAE,CAAC;QACzB,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,4BAAG,UAAU,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,GAAI,CAAC;AAClD,CAAC"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic in-flight uploads provider.
|
|
3
|
+
*
|
|
4
|
+
* Provides a global Map of in-flight upload state by `pendingMediaId`, derived
|
|
5
|
+
* from a Firestore listener over the consumer-supplied collection. On terminal
|
|
6
|
+
* status transitions observed THIS SESSION, fires consumer-supplied success /
|
|
7
|
+
* failure / rejection callbacks. Records monitoring breadcrumbs and parse
|
|
8
|
+
* errors through the injected monitoring callback.
|
|
9
|
+
*
|
|
10
|
+
* EVERY TTT-specific value is supplied by the consumer:
|
|
11
|
+
* - userId resolution
|
|
12
|
+
* - Firestore `db` reference
|
|
13
|
+
* - collection path
|
|
14
|
+
* - listener time window (ms)
|
|
15
|
+
* - parser/schema for raw Firestore doc → typed PendingMedia
|
|
16
|
+
* - success copy per origin
|
|
17
|
+
* - notification callback (cache invalidation, etc.)
|
|
18
|
+
* - toast callback
|
|
19
|
+
* - rejection callback (for the media-rejection dialog or equivalent)
|
|
20
|
+
* - monitoring callback (breadcrumbs + captured errors)
|
|
21
|
+
*
|
|
22
|
+
* Read APIs:
|
|
23
|
+
* - `useInFlightUploadsState()` — full Map.
|
|
24
|
+
* - `useInFlightUpload(id)` — single doc, narrowable on status.
|
|
25
|
+
* - `useUploadActivityState()` — tray-visible items, sorted newest-first.
|
|
26
|
+
*
|
|
27
|
+
* The package owns the deduplication, initial-snapshot suppression, and
|
|
28
|
+
* non-terminal → terminal transition tracking. It does NOT own anything about
|
|
29
|
+
* what a file-origin is or what message text appears in toasts.
|
|
30
|
+
*/
|
|
31
|
+
import { type ReactNode } from 'react';
|
|
32
|
+
/**
|
|
33
|
+
* Generic pending-media status. Identical to TTT's PendingMedia status union,
|
|
34
|
+
* but defined here so upload-ui has no ttt-core dependency.
|
|
35
|
+
*/
|
|
36
|
+
export type InFlightUploadStatus = 'pending' | 'processing' | 'completed' | 'failed' | 'rejected';
|
|
37
|
+
/**
|
|
38
|
+
* Generic in-flight upload record. The provider keeps a Map<id, InFlightUpload>
|
|
39
|
+
* built from each parsed PendingMedia. Consumers parameterize the `fileOrigin`
|
|
40
|
+
* type via the parser's return type (the provider treats it as opaque).
|
|
41
|
+
*
|
|
42
|
+
* `TFileOrigin` defaults to `string` so unannotated consumers get a permissive
|
|
43
|
+
* shape; ttt-prod will bind it to its `FileOrigin` union at the call site.
|
|
44
|
+
*/
|
|
45
|
+
export type InFlightUploadBase<TFileOrigin extends string = string> = {
|
|
46
|
+
id: string;
|
|
47
|
+
fileOrigin: TFileOrigin;
|
|
48
|
+
surface: string;
|
|
49
|
+
createdAt: number;
|
|
50
|
+
targetIds?: string[];
|
|
51
|
+
};
|
|
52
|
+
export type InFlightUploadActive<TFileOrigin extends string = string> = InFlightUploadBase<TFileOrigin> & {
|
|
53
|
+
status: 'pending' | 'processing';
|
|
54
|
+
};
|
|
55
|
+
export type InFlightUploadCompleted<TFileOrigin extends string = string> = InFlightUploadBase<TFileOrigin> & {
|
|
56
|
+
status: 'completed';
|
|
57
|
+
completedAt: number;
|
|
58
|
+
uploadTrayClearedAt?: number;
|
|
59
|
+
uploadTrayClearedBy?: string;
|
|
60
|
+
};
|
|
61
|
+
export type InFlightUploadFailed<TFileOrigin extends string = string> = InFlightUploadBase<TFileOrigin> & {
|
|
62
|
+
status: 'failed';
|
|
63
|
+
failedAt: number;
|
|
64
|
+
errorCategory: string;
|
|
65
|
+
errorMessage: string;
|
|
66
|
+
uploadTrayClearedAt?: number;
|
|
67
|
+
uploadTrayClearedBy?: string;
|
|
68
|
+
};
|
|
69
|
+
export type InFlightUploadRejected<TFileOrigin extends string = string> = InFlightUploadBase<TFileOrigin> & {
|
|
70
|
+
status: 'rejected';
|
|
71
|
+
rejectedAt: number;
|
|
72
|
+
rejectionType: 'text' | 'media';
|
|
73
|
+
errorMessage: string;
|
|
74
|
+
violationId?: string;
|
|
75
|
+
uploadTrayClearedAt?: number;
|
|
76
|
+
uploadTrayClearedBy?: string;
|
|
77
|
+
};
|
|
78
|
+
export type InFlightUpload<TFileOrigin extends string = string> = InFlightUploadActive<TFileOrigin> | InFlightUploadCompleted<TFileOrigin> | InFlightUploadFailed<TFileOrigin> | InFlightUploadRejected<TFileOrigin>;
|
|
79
|
+
/**
|
|
80
|
+
* Result of the consumer-supplied parser. The provider keeps the raw parsed
|
|
81
|
+
* record for callbacks (so consumers can read domain events, target IDs, etc.)
|
|
82
|
+
* AND derives the generic InFlightUpload from it for the public Map.
|
|
83
|
+
*
|
|
84
|
+
* The parser MUST throw if validation fails; the provider catches and routes
|
|
85
|
+
* the error to the monitoring callback.
|
|
86
|
+
*/
|
|
87
|
+
export type ParsedPendingMedia<TFileOrigin extends string = string> = {
|
|
88
|
+
id: string;
|
|
89
|
+
userId: string;
|
|
90
|
+
fileOrigin: TFileOrigin;
|
|
91
|
+
status: InFlightUploadStatus;
|
|
92
|
+
createdAt: number;
|
|
93
|
+
surface: string;
|
|
94
|
+
targetIds?: string[];
|
|
95
|
+
completedAt?: number;
|
|
96
|
+
failedAt?: number;
|
|
97
|
+
rejectedAt?: number;
|
|
98
|
+
errorCategory?: string;
|
|
99
|
+
errorMessage?: string;
|
|
100
|
+
rejectionType?: 'text' | 'media';
|
|
101
|
+
violationId?: string;
|
|
102
|
+
uploadTrayClearedAt?: number;
|
|
103
|
+
uploadTrayClearedBy?: string;
|
|
104
|
+
/**
|
|
105
|
+
* Opaque consumer-controlled domain-event payload, surfaced to the
|
|
106
|
+
* `onUploadCompleted` and `onUploadRejected` callbacks for cache
|
|
107
|
+
* invalidation. The provider does not interpret it.
|
|
108
|
+
*/
|
|
109
|
+
domainEvents?: unknown;
|
|
110
|
+
};
|
|
111
|
+
export type InFlightUploadsAdapter<TFileOrigin extends string = string> = {
|
|
112
|
+
/** Returns the current user's uid, or null if not signed in. */
|
|
113
|
+
getCurrentUserId: () => string | null;
|
|
114
|
+
/** Firestore database adapter (loose typing — `unknown` keeps zero firebase coupling at the type level). */
|
|
115
|
+
db: unknown;
|
|
116
|
+
/** Collection path holding pending-media docs (e.g. "pendingMedia"). */
|
|
117
|
+
collectionPath: string;
|
|
118
|
+
/** Listener window in milliseconds — only docs with `createdAt >= now - window` are tracked. */
|
|
119
|
+
listenerWindowMs: number;
|
|
120
|
+
/**
|
|
121
|
+
* Parse a raw Firestore doc snapshot's `.data()` into the generic shape the
|
|
122
|
+
* provider tracks. Throws on validation failure; the provider routes thrown
|
|
123
|
+
* errors to `onMonitoringEvent`.
|
|
124
|
+
*/
|
|
125
|
+
parsePendingMedia: (raw: unknown) => ParsedPendingMedia<TFileOrigin>;
|
|
126
|
+
/** Called once per observed non-terminal → terminal completed transition. */
|
|
127
|
+
onUploadCompleted: (parsed: ParsedPendingMedia<TFileOrigin>) => void;
|
|
128
|
+
/** Called once per observed non-terminal → terminal failed transition. */
|
|
129
|
+
onUploadFailed: (parsed: ParsedPendingMedia<TFileOrigin>) => void;
|
|
130
|
+
/** Called once per observed non-terminal → terminal rejected transition. */
|
|
131
|
+
onUploadRejected: (parsed: ParsedPendingMedia<TFileOrigin>) => void;
|
|
132
|
+
/**
|
|
133
|
+
* Monitoring callback. Receives:
|
|
134
|
+
* - `breadcrumb`: informational, fired on every observed terminal transition.
|
|
135
|
+
* - `parse-error`: when `parsePendingMedia` throws.
|
|
136
|
+
* - `listener-error`: when the Firestore listener's error callback fires.
|
|
137
|
+
*
|
|
138
|
+
* Consumers wire this to Sentry, OpenTelemetry, or any other observability
|
|
139
|
+
* provider. The provider does NOT import any monitoring SDK directly.
|
|
140
|
+
*/
|
|
141
|
+
onMonitoringEvent: (event: InFlightUploadsMonitoringEvent<TFileOrigin>) => void;
|
|
142
|
+
};
|
|
143
|
+
export type InFlightUploadsMonitoringEvent<TFileOrigin extends string = string> = {
|
|
144
|
+
type: 'breadcrumb';
|
|
145
|
+
category: 'upload.terminal';
|
|
146
|
+
level: 'info';
|
|
147
|
+
message: string;
|
|
148
|
+
data: {
|
|
149
|
+
id: string;
|
|
150
|
+
fileOrigin: TFileOrigin;
|
|
151
|
+
status: InFlightUploadStatus;
|
|
152
|
+
surface: string;
|
|
153
|
+
};
|
|
154
|
+
} | {
|
|
155
|
+
type: 'parse-error';
|
|
156
|
+
docId: string;
|
|
157
|
+
error: unknown;
|
|
158
|
+
data: unknown;
|
|
159
|
+
} | {
|
|
160
|
+
type: 'listener-error';
|
|
161
|
+
error: unknown;
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* Minimal subscription contract used by the provider. The default
|
|
165
|
+
* implementation lives in `firestore-subscribe.ts` and uses the standard
|
|
166
|
+
* `firebase/firestore` `onSnapshot` + `query` + `where` builders.
|
|
167
|
+
*
|
|
168
|
+
* Tests inject a fake to drive the listener without spinning up Firestore.
|
|
169
|
+
*/
|
|
170
|
+
export type FirestoreSubscribeFn = (args: {
|
|
171
|
+
db: unknown;
|
|
172
|
+
collectionPath: string;
|
|
173
|
+
userId: string;
|
|
174
|
+
windowStartMs: number;
|
|
175
|
+
onSnapshot: (snapshot: FirestoreLikeSnapshot) => void;
|
|
176
|
+
onError: (error: unknown) => void;
|
|
177
|
+
}) => () => void;
|
|
178
|
+
export type FirestoreLikeSnapshot = {
|
|
179
|
+
docChanges: () => FirestoreLikeDocChange[];
|
|
180
|
+
};
|
|
181
|
+
export type FirestoreLikeDocChange = {
|
|
182
|
+
type: 'added' | 'modified' | 'removed';
|
|
183
|
+
doc: {
|
|
184
|
+
id: string;
|
|
185
|
+
data: () => unknown;
|
|
186
|
+
};
|
|
187
|
+
};
|
|
188
|
+
export interface InFlightUploadsProviderProps<TFileOrigin extends string = string> {
|
|
189
|
+
adapter: InFlightUploadsAdapter<TFileOrigin>;
|
|
190
|
+
/** Firestore subscription function. Defaults to the firebase/firestore implementation. */
|
|
191
|
+
subscribe?: FirestoreSubscribeFn;
|
|
192
|
+
children: ReactNode;
|
|
193
|
+
}
|
|
194
|
+
export declare function InFlightUploadsProvider<TFileOrigin extends string = string>(props: InFlightUploadsProviderProps<TFileOrigin>): import("react/jsx-runtime").JSX.Element;
|
|
195
|
+
export declare function useInFlightUploadsState<TFileOrigin extends string = string>(): Map<string, InFlightUpload<TFileOrigin>>;
|
|
196
|
+
export declare function useInFlightUpload<TFileOrigin extends string = string>(id: string | null | undefined): InFlightUpload<TFileOrigin> | undefined;
|
|
197
|
+
/**
|
|
198
|
+
* `true` for `completed` / `failed` / `rejected`. Used by tray UI to decide
|
|
199
|
+
* whether a Clear action is available for an item.
|
|
200
|
+
*/
|
|
201
|
+
export declare function isTerminalUpload<TFileOrigin extends string = string>(item: InFlightUpload<TFileOrigin>): item is Exclude<InFlightUpload<TFileOrigin>, {
|
|
202
|
+
status: 'pending' | 'processing';
|
|
203
|
+
}>;
|
|
204
|
+
/**
|
|
205
|
+
* Tray-visible items. Includes every pending/processing upload, plus terminal
|
|
206
|
+
* uploads that have NOT been cleared from the tray. Sorted newest-first by
|
|
207
|
+
* createdAt.
|
|
208
|
+
*
|
|
209
|
+
* Renamed per F22: was `useUploadActivityTrayItems` in ttt-prod; the public
|
|
210
|
+
* selector name in upload-ui is `useUploadActivityState`. The mutation hook
|
|
211
|
+
* is `useClearUploadActivity` (in a separate file).
|
|
212
|
+
*/
|
|
213
|
+
export declare function useUploadActivityState<TFileOrigin extends string = string>(): InFlightUpload<TFileOrigin>[];
|
|
214
|
+
//# sourceMappingURL=in-flight-uploads-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"in-flight-uploads-provider.d.ts","sourceRoot":"","sources":["../../src/react/in-flight-uploads-provider.tsx"],"names":[],"mappings":"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"}
|