@burdenoff/microfe-ecoimpacthub 2026.704.1 → 2026.704.3
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/ecoimpacthub/components/EcoDiscussion.js +109 -0
- package/dist/ecoimpacthub/components/EcoDiscussion.js.map +1 -0
- package/dist/ecoimpacthub/components/EcoTags.js +149 -0
- package/dist/ecoimpacthub/components/EcoTags.js.map +1 -0
- package/dist/ecoimpacthub/components/EvidenceUpload.js +93 -0
- package/dist/ecoimpacthub/components/EvidenceUpload.js.map +1 -0
- package/dist/ecoimpacthub/components/FileLink.js +36 -0
- package/dist/ecoimpacthub/components/FileLink.js.map +1 -0
- package/dist/ecoimpacthub/components/PageLayout.js +2 -2
- package/dist/ecoimpacthub/components/PageLayout.js.map +1 -1
- package/dist/ecoimpacthub/hooks/index.js +3 -0
- package/dist/ecoimpacthub/hooks/useCursorPager.js +1 -1
- package/dist/ecoimpacthub/hooks/useCursorPager.js.map +1 -1
- package/dist/ecoimpacthub/hooks/useEcoComments.js +45 -0
- package/dist/ecoimpacthub/hooks/useEcoComments.js.map +1 -0
- package/dist/ecoimpacthub/hooks/useEcoFiles.js +81 -0
- package/dist/ecoimpacthub/hooks/useEcoFiles.js.map +1 -0
- package/dist/ecoimpacthub/hooks/useEcoImpactHubApi.js +169 -166
- package/dist/ecoimpacthub/hooks/useEcoImpactHubApi.js.map +1 -1
- package/dist/ecoimpacthub/hooks/useEcoTags.js +89 -0
- package/dist/ecoimpacthub/hooks/useEcoTags.js.map +1 -0
- package/dist/ecoimpacthub/pages/observations/ObservationsPage.js +124 -101
- package/dist/ecoimpacthub/pages/observations/ObservationsPage.js.map +1 -1
- package/dist/ecoimpacthub/pages/reports/ReportDetailPage.js +153 -126
- package/dist/ecoimpacthub/pages/reports/ReportDetailPage.js.map +1 -1
- package/dist/ecoimpacthub/pages/sites/SiteDetailPage.js +241 -230
- package/dist/ecoimpacthub/pages/sites/SiteDetailPage.js.map +1 -1
- package/dist/ecoimpacthub/pages/species/SpeciesDetailPage.js +260 -225
- package/dist/ecoimpacthub/pages/species/SpeciesDetailPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { useEcoImpactHub as e } from "../context/EcoImpactHubContext.js";
|
|
2
|
+
import "../context/index.js";
|
|
3
|
+
import { useCallback as t, useState as n } from "react";
|
|
4
|
+
import { useQuery as r } from "@tanstack/react-query";
|
|
5
|
+
import { graphqlFetch as i } from "@burdenoff/fe-libs/shared/graphql";
|
|
6
|
+
//#region src/ecoimpacthub/hooks/useEcoFiles.ts
|
|
7
|
+
var a = () => {
|
|
8
|
+
let e = globalThis;
|
|
9
|
+
e.__graphqlFetchResponseCache?.clear(), e.__graphqlFetchPendingResponses?.clear(), e.__boffGraphQLResponseCache?.clear();
|
|
10
|
+
}, o = async (e, t, n) => {
|
|
11
|
+
let r = await i({
|
|
12
|
+
gateway: "workspace",
|
|
13
|
+
baseUrl: n,
|
|
14
|
+
query: e,
|
|
15
|
+
variables: t,
|
|
16
|
+
workspaceToken: !0
|
|
17
|
+
});
|
|
18
|
+
if (r.errors?.length) throw Error(r.errors[0]?.message ?? "GraphQL request failed");
|
|
19
|
+
if (!r.data) throw Error("GraphQL response did not include data");
|
|
20
|
+
return /^\s*mutation\b/.test(e) && a(), r.data;
|
|
21
|
+
};
|
|
22
|
+
function s(e, t, n, r) {
|
|
23
|
+
return new Promise((i, a) => {
|
|
24
|
+
let o = new XMLHttpRequest();
|
|
25
|
+
o.open("PUT", e), o.setRequestHeader("Content-Type", n), o.upload.onprogress = (e) => {
|
|
26
|
+
e.lengthComputable && r(Math.round(e.loaded / e.total * 100));
|
|
27
|
+
}, o.onload = () => {
|
|
28
|
+
o.status >= 200 && o.status < 300 ? i() : a(/* @__PURE__ */ Error(`File storage rejected the upload (HTTP ${o.status}).`));
|
|
29
|
+
}, o.onerror = () => a(/* @__PURE__ */ Error("Network error while uploading the file.")), o.onabort = () => a(/* @__PURE__ */ Error("The upload was cancelled.")), o.send(t);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
var c = () => {
|
|
33
|
+
let { apiGatewayUrl: r } = e(), [i, a] = n(!1), [c, l] = n(0), [u, d] = n(null), f = t(() => {
|
|
34
|
+
a(!1), l(0), d(null);
|
|
35
|
+
}, []);
|
|
36
|
+
return {
|
|
37
|
+
upload: t(async (e) => {
|
|
38
|
+
a(!0), l(0), d(null);
|
|
39
|
+
try {
|
|
40
|
+
let t = e.name.lastIndexOf("."), n = t > -1 ? e.name.slice(t + 1) : "", i = e.type || "application/octet-stream", a = await o("mutation EcoInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }", { input: {
|
|
41
|
+
fileName: e.name,
|
|
42
|
+
extension: n,
|
|
43
|
+
mimeType: i,
|
|
44
|
+
fileSize: String(e.size),
|
|
45
|
+
visibility: "WORKSPACE"
|
|
46
|
+
} }, r), { uploadUrl: c } = a.initiateUpload, u = a.initiateUpload.session.id;
|
|
47
|
+
if (!c) throw Error("The file service did not return an upload URL.");
|
|
48
|
+
await s(c, e, i, l);
|
|
49
|
+
let d = await o("mutation EcoCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file { id name }\n }\n }", { input: { sessionId: u } }, r);
|
|
50
|
+
return l(100), {
|
|
51
|
+
fileId: d.completeUpload.fileId,
|
|
52
|
+
fileName: d.completeUpload.file?.name ?? e.name
|
|
53
|
+
};
|
|
54
|
+
} catch (e) {
|
|
55
|
+
let t = e instanceof Error ? e.message : "The file could not be uploaded.";
|
|
56
|
+
throw d(t), e instanceof Error ? e : Error(t);
|
|
57
|
+
} finally {
|
|
58
|
+
a(!1);
|
|
59
|
+
}
|
|
60
|
+
}, [r]),
|
|
61
|
+
isUploading: i,
|
|
62
|
+
progress: c,
|
|
63
|
+
error: u,
|
|
64
|
+
reset: f
|
|
65
|
+
};
|
|
66
|
+
}, l = (t) => {
|
|
67
|
+
let { apiGatewayUrl: n } = e();
|
|
68
|
+
return r({
|
|
69
|
+
queryKey: [
|
|
70
|
+
"ecoimpacthub",
|
|
71
|
+
"file",
|
|
72
|
+
t ?? ""
|
|
73
|
+
],
|
|
74
|
+
enabled: !!t,
|
|
75
|
+
queryFn: async () => (await o("query EcoGetFile($id: ID!) {\n getFile(id: $id) {\n id\n name\n downloadUrl\n mimeType\n size\n }\n }", { id: t }, n)).getFile
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
//#endregion
|
|
79
|
+
export { l as useEcoFile, c as useEcoFileUpload, o as wspaceFetch };
|
|
80
|
+
|
|
81
|
+
//# sourceMappingURL=useEcoFiles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useEcoFiles.js","names":[],"sources":["../../../src/ecoimpacthub/hooks/useEcoFiles.ts"],"sourcesContent":["/**\n * EcoImpactHub — wspace-files-svc REUSE hooks (Integration A).\n * @module ecoimpacthub/hooks/useEcoFiles\n *\n * Reuses the platform file store (`wspace-files-svc`) WITHOUT any eco-svc\n * change: the eco schema already exposes `EcoObservation.evidenceFileId` /\n * `EcoReport.fileId`; this module lets the UI actually produce a `fileId` via\n * the canonical 3-step upload flow composed into the WORKSPACE supergraph:\n * 1. `initiateUpload(input)` → `{ uploadUrl, session { id } }`\n * 2. HTTP `PUT` the bytes to the returned signed `uploadUrl`\n * 3. `completeUpload(input: { sessionId })` → `{ fileId }`\n * plus `getFile(id)` to resolve an existing evidence/report file's name + URL.\n *\n * Transport mirrors `useEcoImpactHubApi` exactly — `graphqlFetch({ gateway:\n * 'workspace', workspaceToken: true })` — so no ApolloProvider is required.\n * These hooks live in a SEPARATE file by design; `useEcoImpactHubApi.ts` is\n * left untouched.\n */\n\nimport { useCallback, useState } from 'react';\nimport { useQuery } from '@tanstack/react-query';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { useEcoImpactHub } from '../context';\n\n// ============================================================================\n// Shared workspace-gateway transport (reused by the comments + tags hooks too)\n// ============================================================================\n\ntype GraphqlReadCacheGlobals = {\n __graphqlFetchResponseCache?: { clear: () => void };\n __graphqlFetchPendingResponses?: { clear: () => void };\n __boffGraphQLResponseCache?: { clear: () => void };\n};\n\n/**\n * fe-libs `graphqlFetch` coalesces `query` responses in a short-lived in-memory\n * cache. After a write we drop those caches so the invalidation refetch that\n * follows reads fresh data (same remedy `useEcoImpactHubApi` applies).\n */\nconst clearGraphqlReadCaches = (): void => {\n const g = globalThis as GraphqlReadCacheGlobals;\n g.__graphqlFetchResponseCache?.clear();\n g.__graphqlFetchPendingResponses?.clear();\n g.__boffGraphQLResponseCache?.clear();\n};\n\n/** Execute a workspace-gateway GraphQL operation with the workspace token. */\nexport const wspaceFetch = async <TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n baseUrl,\n query,\n variables,\n workspaceToken: true,\n });\n\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'GraphQL request failed');\n }\n if (!response.data) {\n throw new Error('GraphQL response did not include data');\n }\n if (/^\\s*mutation\\b/.test(query)) {\n clearGraphqlReadCaches();\n }\n return response.data;\n};\n\n// ============================================================================\n// File upload (initiate → PUT → complete)\n// ============================================================================\n\nexport interface UploadedEcoFile {\n fileId: string;\n fileName: string;\n}\n\n/** `PUT` the blob to the signed URL, reporting byte-progress via `onProgress`. */\nfunction putWithProgress(\n url: string,\n file: File,\n contentType: string,\n onProgress: (percent: number) => void\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open('PUT', url);\n xhr.setRequestHeader('Content-Type', contentType);\n xhr.upload.onprogress = (event) => {\n if (event.lengthComputable) {\n onProgress(Math.round((event.loaded / event.total) * 100));\n }\n };\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve();\n } else {\n reject(new Error(`File storage rejected the upload (HTTP ${xhr.status}).`));\n }\n };\n xhr.onerror = () => reject(new Error('Network error while uploading the file.'));\n xhr.onabort = () => reject(new Error('The upload was cancelled.'));\n xhr.send(file);\n });\n}\n\nexport interface UseEcoFileUploadResult {\n upload: (file: File) => Promise<UploadedEcoFile>;\n isUploading: boolean;\n /** 0–100 byte-progress of the signed-URL PUT. */\n progress: number;\n error: string | null;\n reset: () => void;\n}\n\n/**\n * Imperative upload hook: `await upload(file)` runs the platform 3-step flow and\n * resolves to `{ fileId, fileName }`. `fileId` is what the eco mutations accept\n * (`ecoRecordObservation.evidenceFileId`, `ecoPublishReport.fileId`).\n */\nexport const useEcoFileUpload = (): UseEcoFileUploadResult => {\n const { apiGatewayUrl } = useEcoImpactHub();\n const [isUploading, setIsUploading] = useState(false);\n const [progress, setProgress] = useState(0);\n const [error, setError] = useState<string | null>(null);\n\n const reset = useCallback(() => {\n setIsUploading(false);\n setProgress(0);\n setError(null);\n }, []);\n\n const upload = useCallback(\n async (file: File): Promise<UploadedEcoFile> => {\n setIsUploading(true);\n setProgress(0);\n setError(null);\n try {\n const dotIndex = file.name.lastIndexOf('.');\n const extension = dotIndex > -1 ? file.name.slice(dotIndex + 1) : '';\n const mimeType = file.type || 'application/octet-stream';\n\n // 1. initiate\n const initiated = await wspaceFetch<{\n initiateUpload: { uploadUrl: string | null; session: { id: string } };\n }>(\n `mutation EcoInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }`,\n {\n input: {\n fileName: file.name,\n extension,\n mimeType,\n fileSize: String(file.size),\n visibility: 'WORKSPACE',\n },\n },\n apiGatewayUrl\n );\n\n const { uploadUrl } = initiated.initiateUpload;\n const sessionId = initiated.initiateUpload.session.id;\n if (!uploadUrl) {\n throw new Error('The file service did not return an upload URL.');\n }\n\n // 2. PUT the bytes\n await putWithProgress(uploadUrl, file, mimeType, setProgress);\n\n // 3. complete\n const completed = await wspaceFetch<{\n completeUpload: { fileId: string; file: { id: string; name: string } | null };\n }>(\n `mutation EcoCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file { id name }\n }\n }`,\n { input: { sessionId } },\n apiGatewayUrl\n );\n\n setProgress(100);\n return {\n fileId: completed.completeUpload.fileId,\n fileName: completed.completeUpload.file?.name ?? file.name,\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : 'The file could not be uploaded.';\n setError(message);\n throw err instanceof Error ? err : new Error(message);\n } finally {\n setIsUploading(false);\n }\n },\n [apiGatewayUrl]\n );\n\n return { upload, isUploading, progress, error, reset };\n};\n\n// ============================================================================\n// Resolve an existing file (name + download URL) for rendering evidence links\n// ============================================================================\n\nexport interface EcoFileMeta {\n id: string;\n name: string;\n downloadUrl: string | null;\n mimeType: string;\n size: string;\n}\n\nexport const useEcoFile = (id: string | null | undefined) => {\n const { apiGatewayUrl } = useEcoImpactHub();\n return useQuery({\n queryKey: ['ecoimpacthub', 'file', id ?? ''],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await wspaceFetch<{ getFile: EcoFileMeta | null }>(\n `query EcoGetFile($id: ID!) {\n getFile(id: $id) {\n id\n name\n downloadUrl\n mimeType\n size\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return data.getFile;\n },\n });\n};\n"],"mappings":"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"}
|