@intrig/plugin-react 0.0.1
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/.swcrc +29 -0
- package/README.md +7 -0
- package/eslint.config.mjs +19 -0
- package/package.json +25 -0
- package/project.json +29 -0
- package/rollup.config.cjs +54 -0
- package/rollup.config.mjs +33 -0
- package/src/index.ts +2 -0
- package/src/lib/code-generator.ts +79 -0
- package/src/lib/get-endpoint-documentation.ts +35 -0
- package/src/lib/get-schema-documentation.ts +11 -0
- package/src/lib/internal-types.ts +15 -0
- package/src/lib/plugin-react.ts +22 -0
- package/src/lib/templates/context.template.ts +74 -0
- package/src/lib/templates/docs/__snapshots__/async-hook.spec.ts.snap +889 -0
- package/src/lib/templates/docs/__snapshots__/download-hook.spec.ts.snap +1445 -0
- package/src/lib/templates/docs/__snapshots__/react-hook.spec.ts.snap +1371 -0
- package/src/lib/templates/docs/__snapshots__/sse-hook.spec.ts.snap +2008 -0
- package/src/lib/templates/docs/async-hook.spec.ts +92 -0
- package/src/lib/templates/docs/async-hook.ts +226 -0
- package/src/lib/templates/docs/download-hook.spec.ts +182 -0
- package/src/lib/templates/docs/download-hook.ts +170 -0
- package/src/lib/templates/docs/react-hook.spec.ts +97 -0
- package/src/lib/templates/docs/react-hook.ts +323 -0
- package/src/lib/templates/docs/schema.ts +105 -0
- package/src/lib/templates/docs/sse-hook.spec.ts +207 -0
- package/src/lib/templates/docs/sse-hook.ts +221 -0
- package/src/lib/templates/extra.template.ts +198 -0
- package/src/lib/templates/index.template.ts +14 -0
- package/src/lib/templates/intrigMiddleware.template.ts +21 -0
- package/src/lib/templates/logger.template.ts +67 -0
- package/src/lib/templates/media-type-utils.template.ts +191 -0
- package/src/lib/templates/network-state.template.ts +702 -0
- package/src/lib/templates/packageJson.template.ts +63 -0
- package/src/lib/templates/provider/__tests__/provider-templates.spec.ts +209 -0
- package/src/lib/templates/provider/axios-config.template.ts +49 -0
- package/src/lib/templates/provider/hooks.template.ts +240 -0
- package/src/lib/templates/provider/interfaces.template.ts +72 -0
- package/src/lib/templates/provider/intrig-provider-stub.template.ts +73 -0
- package/src/lib/templates/provider/intrig-provider.template.ts +185 -0
- package/src/lib/templates/provider/main.template.ts +48 -0
- package/src/lib/templates/provider/reducer.template.ts +50 -0
- package/src/lib/templates/provider/status-trap.template.ts +80 -0
- package/src/lib/templates/provider.template.ts +698 -0
- package/src/lib/templates/source/controller/method/asyncFunctionHook.template.ts +196 -0
- package/src/lib/templates/source/controller/method/clientIndex.template.ts +38 -0
- package/src/lib/templates/source/controller/method/download.template.ts +256 -0
- package/src/lib/templates/source/controller/method/params.template.ts +31 -0
- package/src/lib/templates/source/controller/method/requestHook.template.ts +220 -0
- package/src/lib/templates/source/type/typeTemplate.ts +257 -0
- package/src/lib/templates/swcrc.template.ts +25 -0
- package/src/lib/templates/tsconfig.template.ts +37 -0
- package/src/lib/templates/type-utils.template.ts +28 -0
- package/tsconfig.json +13 -0
- package/tsconfig.lib.json +20 -0
|
@@ -0,0 +1,1445 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`reactDownloadHookDocs > handles case-insensitive path parameter detection 1`] = `
|
|
4
|
+
"# Intrig Download Hooks — Quick Guide
|
|
5
|
+
|
|
6
|
+
## Copy-paste starter (fast lane)
|
|
7
|
+
|
|
8
|
+
### Auto-download (most common)
|
|
9
|
+
|
|
10
|
+
\`\`\`ts
|
|
11
|
+
import { useDownloadInvoiceDownload } from "@intrig/react/invoices/{invoiceId}/client";
|
|
12
|
+
\`\`\`
|
|
13
|
+
|
|
14
|
+
\`\`\`ts
|
|
15
|
+
import { isPending, isError } from "@intrig/react";
|
|
16
|
+
\`\`\`
|
|
17
|
+
|
|
18
|
+
\`\`\`tsx
|
|
19
|
+
const [downloadInvoiceResp, downloadInvoice] = useDownloadInvoiceDownload({
|
|
20
|
+
clearOnUnmount: true,
|
|
21
|
+
});
|
|
22
|
+
// e.g., in a click handler:
|
|
23
|
+
downloadInvoice(downloadInvoiceParams);
|
|
24
|
+
\`\`\`
|
|
25
|
+
|
|
26
|
+
### Manual/stateful (you handle the blob/UI)
|
|
27
|
+
|
|
28
|
+
\`\`\`ts
|
|
29
|
+
import { useDownloadInvoice } from "@intrig/react/invoices/{invoiceId}/client";
|
|
30
|
+
\`\`\`
|
|
31
|
+
|
|
32
|
+
\`\`\`ts
|
|
33
|
+
import { isSuccess, isPending, isError } from "@intrig/react";
|
|
34
|
+
\`\`\`
|
|
35
|
+
|
|
36
|
+
\`\`\`tsx
|
|
37
|
+
const [downloadInvoiceResp, downloadInvoice] = useDownloadInvoice({
|
|
38
|
+
clearOnUnmount: true,
|
|
39
|
+
});
|
|
40
|
+
// later:
|
|
41
|
+
downloadInvoice(downloadInvoiceParams);
|
|
42
|
+
\`\`\`
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## TL;DR (auto-download)
|
|
47
|
+
|
|
48
|
+
\`\`\`tsx
|
|
49
|
+
import { useDownloadInvoiceDownload } from "@intrig/react/invoices/{invoiceId}/client";
|
|
50
|
+
import { isPending, isError } from "@intrig/react";
|
|
51
|
+
|
|
52
|
+
export default function Example() {
|
|
53
|
+
const [downloadInvoiceResp, downloadInvoice] = useDownloadInvoiceDownload({
|
|
54
|
+
clearOnUnmount: true,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<button
|
|
59
|
+
onClick={() => downloadInvoice(downloadInvoiceParams)}
|
|
60
|
+
disabled={isPending(downloadInvoiceResp)}
|
|
61
|
+
>
|
|
62
|
+
{isPending(downloadInvoiceResp) ? "Downloading…" : "Download"}
|
|
63
|
+
</button>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
\`\`\`
|
|
67
|
+
|
|
68
|
+
### Optional types (if generated by your build)
|
|
69
|
+
|
|
70
|
+
\`\`\`ts
|
|
71
|
+
import type { DownloadInvoiceParams } from "@intrig/react/invoices/{invoiceId}/DownloadInvoice.params";
|
|
72
|
+
import type { DownloadInvoiceResponseBody } from "@intrig/react/invoices/{invoiceId}/DownloadInvoice.response";
|
|
73
|
+
\`\`\`
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Hook APIs
|
|
78
|
+
|
|
79
|
+
### \`useDownloadInvoiceDownload\` (auto-download)
|
|
80
|
+
|
|
81
|
+
- **What it does:** requests the file with \`responseType: 'blob'\` + \`adapter: 'fetch'\`, derives filename from \`Content-Disposition\` if present, creates a temporary object URL, clicks a hidden \`<a>\`, **downloads**, then resets state to \`init\`.
|
|
82
|
+
- **Signature:** \`[state, download, clear]\`
|
|
83
|
+
- \`download(params: DownloadInvoiceParams) => void\`
|
|
84
|
+
|
|
85
|
+
### \`useDownloadInvoice\` (manual/stateful)
|
|
86
|
+
|
|
87
|
+
- **What it does:** same request but **does not** auto-save. You control preview/saving using \`state.data\` + \`state.headers\`.
|
|
88
|
+
- **Signature:** \`[state, fetchFile, clear]\`
|
|
89
|
+
- \`fetchFile(params: DownloadInvoiceParams) => void\`
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Usage Patterns
|
|
94
|
+
|
|
95
|
+
### 1) Auto-download on click (recommended)
|
|
96
|
+
|
|
97
|
+
\`\`\`tsx
|
|
98
|
+
const [downloadInvoiceResp, downloadInvoice] = useDownloadInvoiceDownload({
|
|
99
|
+
clearOnUnmount: true,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
<button
|
|
103
|
+
onClick={() => downloadInvoice(downloadInvoiceParams)}
|
|
104
|
+
disabled={isPending(downloadInvoiceResp)}
|
|
105
|
+
>
|
|
106
|
+
{isPending(downloadInvoiceResp) ? "Downloading…" : "Download file"}
|
|
107
|
+
</button>;
|
|
108
|
+
{
|
|
109
|
+
isError(downloadInvoiceResp) ? (
|
|
110
|
+
<p className="text-red-500">Failed to download.</p>
|
|
111
|
+
) : null;
|
|
112
|
+
}
|
|
113
|
+
\`\`\`
|
|
114
|
+
|
|
115
|
+
<details><summary>Description</summary>
|
|
116
|
+
<p>Most users just need a button that saves the file. This variant handles object URL creation, filename extraction, click, and state reset.</p>
|
|
117
|
+
</details>
|
|
118
|
+
|
|
119
|
+
### 2) Auto-download on mount (e.g., “Your file is ready” page)
|
|
120
|
+
|
|
121
|
+
\`\`\`tsx
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
downloadInvoice(downloadInvoiceParams);
|
|
124
|
+
}, [downloadInvoice]);
|
|
125
|
+
\`\`\`
|
|
126
|
+
|
|
127
|
+
<details><summary>Description</summary>
|
|
128
|
+
<p>Good for post-processing routes that immediately start a download.</p>
|
|
129
|
+
</details>
|
|
130
|
+
|
|
131
|
+
### 3) Manual handling (preview or custom filename)
|
|
132
|
+
|
|
133
|
+
\`\`\`tsx
|
|
134
|
+
const [downloadInvoiceResp, downloadInvoice] = useDownloadInvoice({
|
|
135
|
+
clearOnUnmount: true,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
if (isSuccess(downloadInvoiceResp)) {
|
|
140
|
+
const ct =
|
|
141
|
+
downloadInvoiceResp.headers?.["content-type"] ??
|
|
142
|
+
"application/octet-stream";
|
|
143
|
+
const parts = Array.isArray(downloadInvoiceResp.data)
|
|
144
|
+
? downloadInvoiceResp.data
|
|
145
|
+
: [downloadInvoiceResp.data];
|
|
146
|
+
const url = URL.createObjectURL(new Blob(parts, { type: ct }));
|
|
147
|
+
// preview/save with your own UI...
|
|
148
|
+
return () => URL.revokeObjectURL(url);
|
|
149
|
+
}
|
|
150
|
+
}, [downloadInvoiceResp]);
|
|
151
|
+
\`\`\`
|
|
152
|
+
|
|
153
|
+
<details><summary>Description</summary>
|
|
154
|
+
<p>Use when you need to inspect headers, show a preview, or control the filename/UI flow.</p>
|
|
155
|
+
</details>
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Behavior notes (what the auto-download variant does)
|
|
160
|
+
|
|
161
|
+
- Forces \`responseType: 'blob'\` and \`adapter: 'fetch'\`.
|
|
162
|
+
- If \`content-type\` is JSON, stringifies payload so the saved file is human-readable.
|
|
163
|
+
- Parses \`Content-Disposition\` to derive a filename; falls back to a default.
|
|
164
|
+
- Creates and clicks a temporary link, then **resets state to \`init\`**.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Gotchas & Tips
|
|
169
|
+
|
|
170
|
+
- **Expose headers in CORS:** server should send
|
|
171
|
+
\`Access-Control-Expose-Headers: Content-Disposition, Content-Type\`
|
|
172
|
+
- **Disable double clicks:** guard with \`isPending(state)\`.
|
|
173
|
+
- **Revoke URLs** when you create them manually in the stateful variant.
|
|
174
|
+
- **iOS Safari:** blob downloads may open a new tab—consider server-side direct-download URLs for a smoother UX.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Troubleshooting
|
|
179
|
+
|
|
180
|
+
- **No filename shown:** your server didn’t include \`Content-Disposition\`. Add it.
|
|
181
|
+
- **Got JSON instead of a file:** server returned \`application/json\` (maybe an error). The auto hook saves it as text; surface the error in UI.
|
|
182
|
+
- **Nothing happens on click:** ensure you’re using the **Download** variant and the request succeeds (check Network tab); verify CORS headers.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
"
|
|
186
|
+
`;
|
|
187
|
+
|
|
188
|
+
exports[`reactDownloadHookDocs > handles empty variables array 1`] = `
|
|
189
|
+
"# Intrig Download Hooks — Quick Guide
|
|
190
|
+
|
|
191
|
+
## Copy-paste starter (fast lane)
|
|
192
|
+
|
|
193
|
+
### Auto-download (most common)
|
|
194
|
+
|
|
195
|
+
\`\`\`ts
|
|
196
|
+
import { useDownloadBackupDownload } from "@intrig/react/backup/client";
|
|
197
|
+
\`\`\`
|
|
198
|
+
|
|
199
|
+
\`\`\`ts
|
|
200
|
+
import { isPending, isError } from "@intrig/react";
|
|
201
|
+
\`\`\`
|
|
202
|
+
|
|
203
|
+
\`\`\`tsx
|
|
204
|
+
const [downloadBackupResp, downloadBackup] = useDownloadBackupDownload({
|
|
205
|
+
clearOnUnmount: true,
|
|
206
|
+
});
|
|
207
|
+
// e.g., in a click handler:
|
|
208
|
+
downloadBackup({});
|
|
209
|
+
\`\`\`
|
|
210
|
+
|
|
211
|
+
### Manual/stateful (you handle the blob/UI)
|
|
212
|
+
|
|
213
|
+
\`\`\`ts
|
|
214
|
+
import { useDownloadBackup } from "@intrig/react/backup/client";
|
|
215
|
+
\`\`\`
|
|
216
|
+
|
|
217
|
+
\`\`\`ts
|
|
218
|
+
import { isSuccess, isPending, isError } from "@intrig/react";
|
|
219
|
+
\`\`\`
|
|
220
|
+
|
|
221
|
+
\`\`\`tsx
|
|
222
|
+
const [downloadBackupResp, downloadBackup] = useDownloadBackup({
|
|
223
|
+
clearOnUnmount: true,
|
|
224
|
+
});
|
|
225
|
+
// later:
|
|
226
|
+
downloadBackup({});
|
|
227
|
+
\`\`\`
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## TL;DR (auto-download)
|
|
232
|
+
|
|
233
|
+
\`\`\`tsx
|
|
234
|
+
import { useDownloadBackupDownload } from "@intrig/react/backup/client";
|
|
235
|
+
import { isPending, isError } from "@intrig/react";
|
|
236
|
+
|
|
237
|
+
export default function Example() {
|
|
238
|
+
const [downloadBackupResp, downloadBackup] = useDownloadBackupDownload({
|
|
239
|
+
clearOnUnmount: true,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
return (
|
|
243
|
+
<button
|
|
244
|
+
onClick={() => downloadBackup({})}
|
|
245
|
+
disabled={isPending(downloadBackupResp)}
|
|
246
|
+
>
|
|
247
|
+
{isPending(downloadBackupResp) ? "Downloading…" : "Download"}
|
|
248
|
+
</button>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
\`\`\`
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Hook APIs
|
|
256
|
+
|
|
257
|
+
### \`useDownloadBackupDownload\` (auto-download)
|
|
258
|
+
|
|
259
|
+
- **What it does:** requests the file with \`responseType: 'blob'\` + \`adapter: 'fetch'\`, derives filename from \`Content-Disposition\` if present, creates a temporary object URL, clicks a hidden \`<a>\`, **downloads**, then resets state to \`init\`.
|
|
260
|
+
- **Signature:** \`[state, download, clear]\`
|
|
261
|
+
- \`download(params: Record<string, unknown>) => void\`
|
|
262
|
+
|
|
263
|
+
### \`useDownloadBackup\` (manual/stateful)
|
|
264
|
+
|
|
265
|
+
- **What it does:** same request but **does not** auto-save. You control preview/saving using \`state.data\` + \`state.headers\`.
|
|
266
|
+
- **Signature:** \`[state, fetchFile, clear]\`
|
|
267
|
+
- \`fetchFile(params: Record<string, unknown>) => void\`
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Usage Patterns
|
|
272
|
+
|
|
273
|
+
### 1) Auto-download on click (recommended)
|
|
274
|
+
|
|
275
|
+
\`\`\`tsx
|
|
276
|
+
const [downloadBackupResp, downloadBackup] = useDownloadBackupDownload({
|
|
277
|
+
clearOnUnmount: true,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
<button
|
|
281
|
+
onClick={() => downloadBackup({})}
|
|
282
|
+
disabled={isPending(downloadBackupResp)}
|
|
283
|
+
>
|
|
284
|
+
{isPending(downloadBackupResp) ? "Downloading…" : "Download file"}
|
|
285
|
+
</button>;
|
|
286
|
+
{
|
|
287
|
+
isError(downloadBackupResp) ? (
|
|
288
|
+
<p className="text-red-500">Failed to download.</p>
|
|
289
|
+
) : null;
|
|
290
|
+
}
|
|
291
|
+
\`\`\`
|
|
292
|
+
|
|
293
|
+
<details><summary>Description</summary>
|
|
294
|
+
<p>Most users just need a button that saves the file. This variant handles object URL creation, filename extraction, click, and state reset.</p>
|
|
295
|
+
</details>
|
|
296
|
+
|
|
297
|
+
### 2) Auto-download on mount (e.g., “Your file is ready” page)
|
|
298
|
+
|
|
299
|
+
\`\`\`tsx
|
|
300
|
+
useEffect(() => {
|
|
301
|
+
downloadBackup({});
|
|
302
|
+
}, [downloadBackup]);
|
|
303
|
+
\`\`\`
|
|
304
|
+
|
|
305
|
+
<details><summary>Description</summary>
|
|
306
|
+
<p>Good for post-processing routes that immediately start a download.</p>
|
|
307
|
+
</details>
|
|
308
|
+
|
|
309
|
+
### 3) Manual handling (preview or custom filename)
|
|
310
|
+
|
|
311
|
+
\`\`\`tsx
|
|
312
|
+
const [downloadBackupResp, downloadBackup] = useDownloadBackup({
|
|
313
|
+
clearOnUnmount: true,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
useEffect(() => {
|
|
317
|
+
if (isSuccess(downloadBackupResp)) {
|
|
318
|
+
const ct =
|
|
319
|
+
downloadBackupResp.headers?.["content-type"] ??
|
|
320
|
+
"application/octet-stream";
|
|
321
|
+
const parts = Array.isArray(downloadBackupResp.data)
|
|
322
|
+
? downloadBackupResp.data
|
|
323
|
+
: [downloadBackupResp.data];
|
|
324
|
+
const url = URL.createObjectURL(new Blob(parts, { type: ct }));
|
|
325
|
+
// preview/save with your own UI...
|
|
326
|
+
return () => URL.revokeObjectURL(url);
|
|
327
|
+
}
|
|
328
|
+
}, [downloadBackupResp]);
|
|
329
|
+
\`\`\`
|
|
330
|
+
|
|
331
|
+
<details><summary>Description</summary>
|
|
332
|
+
<p>Use when you need to inspect headers, show a preview, or control the filename/UI flow.</p>
|
|
333
|
+
</details>
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## Behavior notes (what the auto-download variant does)
|
|
338
|
+
|
|
339
|
+
- Forces \`responseType: 'blob'\` and \`adapter: 'fetch'\`.
|
|
340
|
+
- If \`content-type\` is JSON, stringifies payload so the saved file is human-readable.
|
|
341
|
+
- Parses \`Content-Disposition\` to derive a filename; falls back to a default.
|
|
342
|
+
- Creates and clicks a temporary link, then **resets state to \`init\`**.
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Gotchas & Tips
|
|
347
|
+
|
|
348
|
+
- **Expose headers in CORS:** server should send
|
|
349
|
+
\`Access-Control-Expose-Headers: Content-Disposition, Content-Type\`
|
|
350
|
+
- **Disable double clicks:** guard with \`isPending(state)\`.
|
|
351
|
+
- **Revoke URLs** when you create them manually in the stateful variant.
|
|
352
|
+
- **iOS Safari:** blob downloads may open a new tab—consider server-side direct-download URLs for a smoother UX.
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## Troubleshooting
|
|
357
|
+
|
|
358
|
+
- **No filename shown:** your server didn’t include \`Content-Disposition\`. Add it.
|
|
359
|
+
- **Got JSON instead of a file:** server returned \`application/json\` (maybe an error). The auto hook saves it as text; surface the error in UI.
|
|
360
|
+
- **Nothing happens on click:** ensure you’re using the **Download** variant and the request succeeds (check Network tab); verify CORS headers.
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
"
|
|
364
|
+
`;
|
|
365
|
+
|
|
366
|
+
exports[`reactDownloadHookDocs > handles multiple path params 1`] = `
|
|
367
|
+
"# Intrig Download Hooks — Quick Guide
|
|
368
|
+
|
|
369
|
+
## Copy-paste starter (fast lane)
|
|
370
|
+
|
|
371
|
+
### Auto-download (most common)
|
|
372
|
+
|
|
373
|
+
\`\`\`ts
|
|
374
|
+
import { useDownloadProjectAssetDownload } from "@intrig/react/projects/{projectId}/assets/{assetId}/client";
|
|
375
|
+
\`\`\`
|
|
376
|
+
|
|
377
|
+
\`\`\`ts
|
|
378
|
+
import { isPending, isError } from "@intrig/react";
|
|
379
|
+
\`\`\`
|
|
380
|
+
|
|
381
|
+
\`\`\`tsx
|
|
382
|
+
const [downloadProjectAssetResp, downloadProjectAsset] =
|
|
383
|
+
useDownloadProjectAssetDownload({ clearOnUnmount: true });
|
|
384
|
+
// e.g., in a click handler:
|
|
385
|
+
downloadProjectAsset(downloadProjectAssetParams);
|
|
386
|
+
\`\`\`
|
|
387
|
+
|
|
388
|
+
### Manual/stateful (you handle the blob/UI)
|
|
389
|
+
|
|
390
|
+
\`\`\`ts
|
|
391
|
+
import { useDownloadProjectAsset } from "@intrig/react/projects/{projectId}/assets/{assetId}/client";
|
|
392
|
+
\`\`\`
|
|
393
|
+
|
|
394
|
+
\`\`\`ts
|
|
395
|
+
import { isSuccess, isPending, isError } from "@intrig/react";
|
|
396
|
+
\`\`\`
|
|
397
|
+
|
|
398
|
+
\`\`\`tsx
|
|
399
|
+
const [downloadProjectAssetResp, downloadProjectAsset] =
|
|
400
|
+
useDownloadProjectAsset({ clearOnUnmount: true });
|
|
401
|
+
// later:
|
|
402
|
+
downloadProjectAsset(downloadProjectAssetParams);
|
|
403
|
+
\`\`\`
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## TL;DR (auto-download)
|
|
408
|
+
|
|
409
|
+
\`\`\`tsx
|
|
410
|
+
import { useDownloadProjectAssetDownload } from "@intrig/react/projects/{projectId}/assets/{assetId}/client";
|
|
411
|
+
import { isPending, isError } from "@intrig/react";
|
|
412
|
+
|
|
413
|
+
export default function Example() {
|
|
414
|
+
const [downloadProjectAssetResp, downloadProjectAsset] =
|
|
415
|
+
useDownloadProjectAssetDownload({ clearOnUnmount: true });
|
|
416
|
+
|
|
417
|
+
return (
|
|
418
|
+
<button
|
|
419
|
+
onClick={() => downloadProjectAsset(downloadProjectAssetParams)}
|
|
420
|
+
disabled={isPending(downloadProjectAssetResp)}
|
|
421
|
+
>
|
|
422
|
+
{isPending(downloadProjectAssetResp) ? "Downloading…" : "Download"}
|
|
423
|
+
</button>
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
\`\`\`
|
|
427
|
+
|
|
428
|
+
### Optional types (if generated by your build)
|
|
429
|
+
|
|
430
|
+
\`\`\`ts
|
|
431
|
+
import type { DownloadProjectAssetParams } from "@intrig/react/projects/{projectId}/assets/{assetId}/DownloadProjectAsset.params";
|
|
432
|
+
import type { DownloadProjectAssetResponseBody } from "@intrig/react/projects/{projectId}/assets/{assetId}/DownloadProjectAsset.response";
|
|
433
|
+
\`\`\`
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
## Hook APIs
|
|
438
|
+
|
|
439
|
+
### \`useDownloadProjectAssetDownload\` (auto-download)
|
|
440
|
+
|
|
441
|
+
- **What it does:** requests the file with \`responseType: 'blob'\` + \`adapter: 'fetch'\`, derives filename from \`Content-Disposition\` if present, creates a temporary object URL, clicks a hidden \`<a>\`, **downloads**, then resets state to \`init\`.
|
|
442
|
+
- **Signature:** \`[state, download, clear]\`
|
|
443
|
+
- \`download(params: DownloadProjectAssetParams) => void\`
|
|
444
|
+
|
|
445
|
+
### \`useDownloadProjectAsset\` (manual/stateful)
|
|
446
|
+
|
|
447
|
+
- **What it does:** same request but **does not** auto-save. You control preview/saving using \`state.data\` + \`state.headers\`.
|
|
448
|
+
- **Signature:** \`[state, fetchFile, clear]\`
|
|
449
|
+
- \`fetchFile(params: DownloadProjectAssetParams) => void\`
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## Usage Patterns
|
|
454
|
+
|
|
455
|
+
### 1) Auto-download on click (recommended)
|
|
456
|
+
|
|
457
|
+
\`\`\`tsx
|
|
458
|
+
const [downloadProjectAssetResp, downloadProjectAsset] =
|
|
459
|
+
useDownloadProjectAssetDownload({ clearOnUnmount: true });
|
|
460
|
+
|
|
461
|
+
<button
|
|
462
|
+
onClick={() => downloadProjectAsset(downloadProjectAssetParams)}
|
|
463
|
+
disabled={isPending(downloadProjectAssetResp)}
|
|
464
|
+
>
|
|
465
|
+
{isPending(downloadProjectAssetResp) ? "Downloading…" : "Download file"}
|
|
466
|
+
</button>;
|
|
467
|
+
{
|
|
468
|
+
isError(downloadProjectAssetResp) ? (
|
|
469
|
+
<p className="text-red-500">Failed to download.</p>
|
|
470
|
+
) : null;
|
|
471
|
+
}
|
|
472
|
+
\`\`\`
|
|
473
|
+
|
|
474
|
+
<details><summary>Description</summary>
|
|
475
|
+
<p>Most users just need a button that saves the file. This variant handles object URL creation, filename extraction, click, and state reset.</p>
|
|
476
|
+
</details>
|
|
477
|
+
|
|
478
|
+
### 2) Auto-download on mount (e.g., “Your file is ready” page)
|
|
479
|
+
|
|
480
|
+
\`\`\`tsx
|
|
481
|
+
useEffect(() => {
|
|
482
|
+
downloadProjectAsset(downloadProjectAssetParams);
|
|
483
|
+
}, [downloadProjectAsset]);
|
|
484
|
+
\`\`\`
|
|
485
|
+
|
|
486
|
+
<details><summary>Description</summary>
|
|
487
|
+
<p>Good for post-processing routes that immediately start a download.</p>
|
|
488
|
+
</details>
|
|
489
|
+
|
|
490
|
+
### 3) Manual handling (preview or custom filename)
|
|
491
|
+
|
|
492
|
+
\`\`\`tsx
|
|
493
|
+
const [downloadProjectAssetResp, downloadProjectAsset] =
|
|
494
|
+
useDownloadProjectAsset({ clearOnUnmount: true });
|
|
495
|
+
|
|
496
|
+
useEffect(() => {
|
|
497
|
+
if (isSuccess(downloadProjectAssetResp)) {
|
|
498
|
+
const ct =
|
|
499
|
+
downloadProjectAssetResp.headers?.["content-type"] ??
|
|
500
|
+
"application/octet-stream";
|
|
501
|
+
const parts = Array.isArray(downloadProjectAssetResp.data)
|
|
502
|
+
? downloadProjectAssetResp.data
|
|
503
|
+
: [downloadProjectAssetResp.data];
|
|
504
|
+
const url = URL.createObjectURL(new Blob(parts, { type: ct }));
|
|
505
|
+
// preview/save with your own UI...
|
|
506
|
+
return () => URL.revokeObjectURL(url);
|
|
507
|
+
}
|
|
508
|
+
}, [downloadProjectAssetResp]);
|
|
509
|
+
\`\`\`
|
|
510
|
+
|
|
511
|
+
<details><summary>Description</summary>
|
|
512
|
+
<p>Use when you need to inspect headers, show a preview, or control the filename/UI flow.</p>
|
|
513
|
+
</details>
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## Behavior notes (what the auto-download variant does)
|
|
518
|
+
|
|
519
|
+
- Forces \`responseType: 'blob'\` and \`adapter: 'fetch'\`.
|
|
520
|
+
- If \`content-type\` is JSON, stringifies payload so the saved file is human-readable.
|
|
521
|
+
- Parses \`Content-Disposition\` to derive a filename; falls back to a default.
|
|
522
|
+
- Creates and clicks a temporary link, then **resets state to \`init\`**.
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
## Gotchas & Tips
|
|
527
|
+
|
|
528
|
+
- **Expose headers in CORS:** server should send
|
|
529
|
+
\`Access-Control-Expose-Headers: Content-Disposition, Content-Type\`
|
|
530
|
+
- **Disable double clicks:** guard with \`isPending(state)\`.
|
|
531
|
+
- **Revoke URLs** when you create them manually in the stateful variant.
|
|
532
|
+
- **iOS Safari:** blob downloads may open a new tab—consider server-side direct-download URLs for a smoother UX.
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## Troubleshooting
|
|
537
|
+
|
|
538
|
+
- **No filename shown:** your server didn’t include \`Content-Disposition\`. Add it.
|
|
539
|
+
- **Got JSON instead of a file:** server returned \`application/json\` (maybe an error). The auto hook saves it as text; surface the error in UI.
|
|
540
|
+
- **Nothing happens on click:** ensure you’re using the **Download** variant and the request succeeds (check Network tab); verify CORS headers.
|
|
541
|
+
|
|
542
|
+
---
|
|
543
|
+
"
|
|
544
|
+
`;
|
|
545
|
+
|
|
546
|
+
exports[`reactDownloadHookDocs > handles query params mixed with path params 1`] = `
|
|
547
|
+
"# Intrig Download Hooks — Quick Guide
|
|
548
|
+
|
|
549
|
+
## Copy-paste starter (fast lane)
|
|
550
|
+
|
|
551
|
+
### Auto-download (most common)
|
|
552
|
+
|
|
553
|
+
\`\`\`ts
|
|
554
|
+
import { useDownloadTaskFileDownload } from "@intrig/react/tasks/{taskId}/files/client";
|
|
555
|
+
\`\`\`
|
|
556
|
+
|
|
557
|
+
\`\`\`ts
|
|
558
|
+
import { isPending, isError } from "@intrig/react";
|
|
559
|
+
\`\`\`
|
|
560
|
+
|
|
561
|
+
\`\`\`tsx
|
|
562
|
+
const [downloadTaskFileResp, downloadTaskFile] = useDownloadTaskFileDownload({
|
|
563
|
+
clearOnUnmount: true,
|
|
564
|
+
});
|
|
565
|
+
// e.g., in a click handler:
|
|
566
|
+
downloadTaskFile(downloadTaskFileParams);
|
|
567
|
+
\`\`\`
|
|
568
|
+
|
|
569
|
+
### Manual/stateful (you handle the blob/UI)
|
|
570
|
+
|
|
571
|
+
\`\`\`ts
|
|
572
|
+
import { useDownloadTaskFile } from "@intrig/react/tasks/{taskId}/files/client";
|
|
573
|
+
\`\`\`
|
|
574
|
+
|
|
575
|
+
\`\`\`ts
|
|
576
|
+
import { isSuccess, isPending, isError } from "@intrig/react";
|
|
577
|
+
\`\`\`
|
|
578
|
+
|
|
579
|
+
\`\`\`tsx
|
|
580
|
+
const [downloadTaskFileResp, downloadTaskFile] = useDownloadTaskFile({
|
|
581
|
+
clearOnUnmount: true,
|
|
582
|
+
});
|
|
583
|
+
// later:
|
|
584
|
+
downloadTaskFile(downloadTaskFileParams);
|
|
585
|
+
\`\`\`
|
|
586
|
+
|
|
587
|
+
---
|
|
588
|
+
|
|
589
|
+
## TL;DR (auto-download)
|
|
590
|
+
|
|
591
|
+
\`\`\`tsx
|
|
592
|
+
import { useDownloadTaskFileDownload } from "@intrig/react/tasks/{taskId}/files/client";
|
|
593
|
+
import { isPending, isError } from "@intrig/react";
|
|
594
|
+
|
|
595
|
+
export default function Example() {
|
|
596
|
+
const [downloadTaskFileResp, downloadTaskFile] = useDownloadTaskFileDownload({
|
|
597
|
+
clearOnUnmount: true,
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
return (
|
|
601
|
+
<button
|
|
602
|
+
onClick={() => downloadTaskFile(downloadTaskFileParams)}
|
|
603
|
+
disabled={isPending(downloadTaskFileResp)}
|
|
604
|
+
>
|
|
605
|
+
{isPending(downloadTaskFileResp) ? "Downloading…" : "Download"}
|
|
606
|
+
</button>
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
\`\`\`
|
|
610
|
+
|
|
611
|
+
### Optional types (if generated by your build)
|
|
612
|
+
|
|
613
|
+
\`\`\`ts
|
|
614
|
+
import type { DownloadTaskFileParams } from "@intrig/react/tasks/{taskId}/files/DownloadTaskFile.params";
|
|
615
|
+
import type { DownloadTaskFileResponseBody } from "@intrig/react/tasks/{taskId}/files/DownloadTaskFile.response";
|
|
616
|
+
\`\`\`
|
|
617
|
+
|
|
618
|
+
---
|
|
619
|
+
|
|
620
|
+
## Hook APIs
|
|
621
|
+
|
|
622
|
+
### \`useDownloadTaskFileDownload\` (auto-download)
|
|
623
|
+
|
|
624
|
+
- **What it does:** requests the file with \`responseType: 'blob'\` + \`adapter: 'fetch'\`, derives filename from \`Content-Disposition\` if present, creates a temporary object URL, clicks a hidden \`<a>\`, **downloads**, then resets state to \`init\`.
|
|
625
|
+
- **Signature:** \`[state, download, clear]\`
|
|
626
|
+
- \`download(params: DownloadTaskFileParams) => void\`
|
|
627
|
+
|
|
628
|
+
### \`useDownloadTaskFile\` (manual/stateful)
|
|
629
|
+
|
|
630
|
+
- **What it does:** same request but **does not** auto-save. You control preview/saving using \`state.data\` + \`state.headers\`.
|
|
631
|
+
- **Signature:** \`[state, fetchFile, clear]\`
|
|
632
|
+
- \`fetchFile(params: DownloadTaskFileParams) => void\`
|
|
633
|
+
|
|
634
|
+
---
|
|
635
|
+
|
|
636
|
+
## Usage Patterns
|
|
637
|
+
|
|
638
|
+
### 1) Auto-download on click (recommended)
|
|
639
|
+
|
|
640
|
+
\`\`\`tsx
|
|
641
|
+
const [downloadTaskFileResp, downloadTaskFile] = useDownloadTaskFileDownload({
|
|
642
|
+
clearOnUnmount: true,
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
<button
|
|
646
|
+
onClick={() => downloadTaskFile(downloadTaskFileParams)}
|
|
647
|
+
disabled={isPending(downloadTaskFileResp)}
|
|
648
|
+
>
|
|
649
|
+
{isPending(downloadTaskFileResp) ? "Downloading…" : "Download file"}
|
|
650
|
+
</button>;
|
|
651
|
+
{
|
|
652
|
+
isError(downloadTaskFileResp) ? (
|
|
653
|
+
<p className="text-red-500">Failed to download.</p>
|
|
654
|
+
) : null;
|
|
655
|
+
}
|
|
656
|
+
\`\`\`
|
|
657
|
+
|
|
658
|
+
<details><summary>Description</summary>
|
|
659
|
+
<p>Most users just need a button that saves the file. This variant handles object URL creation, filename extraction, click, and state reset.</p>
|
|
660
|
+
</details>
|
|
661
|
+
|
|
662
|
+
### 2) Auto-download on mount (e.g., “Your file is ready” page)
|
|
663
|
+
|
|
664
|
+
\`\`\`tsx
|
|
665
|
+
useEffect(() => {
|
|
666
|
+
downloadTaskFile(downloadTaskFileParams);
|
|
667
|
+
}, [downloadTaskFile]);
|
|
668
|
+
\`\`\`
|
|
669
|
+
|
|
670
|
+
<details><summary>Description</summary>
|
|
671
|
+
<p>Good for post-processing routes that immediately start a download.</p>
|
|
672
|
+
</details>
|
|
673
|
+
|
|
674
|
+
### 3) Manual handling (preview or custom filename)
|
|
675
|
+
|
|
676
|
+
\`\`\`tsx
|
|
677
|
+
const [downloadTaskFileResp, downloadTaskFile] = useDownloadTaskFile({
|
|
678
|
+
clearOnUnmount: true,
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
useEffect(() => {
|
|
682
|
+
if (isSuccess(downloadTaskFileResp)) {
|
|
683
|
+
const ct =
|
|
684
|
+
downloadTaskFileResp.headers?.["content-type"] ??
|
|
685
|
+
"application/octet-stream";
|
|
686
|
+
const parts = Array.isArray(downloadTaskFileResp.data)
|
|
687
|
+
? downloadTaskFileResp.data
|
|
688
|
+
: [downloadTaskFileResp.data];
|
|
689
|
+
const url = URL.createObjectURL(new Blob(parts, { type: ct }));
|
|
690
|
+
// preview/save with your own UI...
|
|
691
|
+
return () => URL.revokeObjectURL(url);
|
|
692
|
+
}
|
|
693
|
+
}, [downloadTaskFileResp]);
|
|
694
|
+
\`\`\`
|
|
695
|
+
|
|
696
|
+
<details><summary>Description</summary>
|
|
697
|
+
<p>Use when you need to inspect headers, show a preview, or control the filename/UI flow.</p>
|
|
698
|
+
</details>
|
|
699
|
+
|
|
700
|
+
---
|
|
701
|
+
|
|
702
|
+
## Behavior notes (what the auto-download variant does)
|
|
703
|
+
|
|
704
|
+
- Forces \`responseType: 'blob'\` and \`adapter: 'fetch'\`.
|
|
705
|
+
- If \`content-type\` is JSON, stringifies payload so the saved file is human-readable.
|
|
706
|
+
- Parses \`Content-Disposition\` to derive a filename; falls back to a default.
|
|
707
|
+
- Creates and clicks a temporary link, then **resets state to \`init\`**.
|
|
708
|
+
|
|
709
|
+
---
|
|
710
|
+
|
|
711
|
+
## Gotchas & Tips
|
|
712
|
+
|
|
713
|
+
- **Expose headers in CORS:** server should send
|
|
714
|
+
\`Access-Control-Expose-Headers: Content-Disposition, Content-Type\`
|
|
715
|
+
- **Disable double clicks:** guard with \`isPending(state)\`.
|
|
716
|
+
- **Revoke URLs** when you create them manually in the stateful variant.
|
|
717
|
+
- **iOS Safari:** blob downloads may open a new tab—consider server-side direct-download URLs for a smoother UX.
|
|
718
|
+
|
|
719
|
+
---
|
|
720
|
+
|
|
721
|
+
## Troubleshooting
|
|
722
|
+
|
|
723
|
+
- **No filename shown:** your server didn’t include \`Content-Disposition\`. Add it.
|
|
724
|
+
- **Got JSON instead of a file:** server returned \`application/json\` (maybe an error). The auto hook saves it as text; surface the error in UI.
|
|
725
|
+
- **Nothing happens on click:** ensure you’re using the **Download** variant and the request succeeds (check Network tab); verify CORS headers.
|
|
726
|
+
|
|
727
|
+
---
|
|
728
|
+
"
|
|
729
|
+
`;
|
|
730
|
+
|
|
731
|
+
exports[`reactDownloadHookDocs > snapshot — path params only (no request body) 1`] = `
|
|
732
|
+
"# Intrig Download Hooks — Quick Guide
|
|
733
|
+
|
|
734
|
+
## Copy-paste starter (fast lane)
|
|
735
|
+
|
|
736
|
+
### Auto-download (most common)
|
|
737
|
+
|
|
738
|
+
\`\`\`ts
|
|
739
|
+
import { useDownloadFileByIdDownload } from "@intrig/react/files/{id}/client";
|
|
740
|
+
\`\`\`
|
|
741
|
+
|
|
742
|
+
\`\`\`ts
|
|
743
|
+
import { isPending, isError } from "@intrig/react";
|
|
744
|
+
\`\`\`
|
|
745
|
+
|
|
746
|
+
\`\`\`tsx
|
|
747
|
+
const [downloadFileByIdResp, downloadFileById] = useDownloadFileByIdDownload({
|
|
748
|
+
clearOnUnmount: true,
|
|
749
|
+
});
|
|
750
|
+
// e.g., in a click handler:
|
|
751
|
+
downloadFileById(downloadFileByIdParams);
|
|
752
|
+
\`\`\`
|
|
753
|
+
|
|
754
|
+
### Manual/stateful (you handle the blob/UI)
|
|
755
|
+
|
|
756
|
+
\`\`\`ts
|
|
757
|
+
import { useDownloadFileById } from "@intrig/react/files/{id}/client";
|
|
758
|
+
\`\`\`
|
|
759
|
+
|
|
760
|
+
\`\`\`ts
|
|
761
|
+
import { isSuccess, isPending, isError } from "@intrig/react";
|
|
762
|
+
\`\`\`
|
|
763
|
+
|
|
764
|
+
\`\`\`tsx
|
|
765
|
+
const [downloadFileByIdResp, downloadFileById] = useDownloadFileById({
|
|
766
|
+
clearOnUnmount: true,
|
|
767
|
+
});
|
|
768
|
+
// later:
|
|
769
|
+
downloadFileById(downloadFileByIdParams);
|
|
770
|
+
\`\`\`
|
|
771
|
+
|
|
772
|
+
---
|
|
773
|
+
|
|
774
|
+
## TL;DR (auto-download)
|
|
775
|
+
|
|
776
|
+
\`\`\`tsx
|
|
777
|
+
import { useDownloadFileByIdDownload } from "@intrig/react/files/{id}/client";
|
|
778
|
+
import { isPending, isError } from "@intrig/react";
|
|
779
|
+
|
|
780
|
+
export default function Example() {
|
|
781
|
+
const [downloadFileByIdResp, downloadFileById] = useDownloadFileByIdDownload({
|
|
782
|
+
clearOnUnmount: true,
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
return (
|
|
786
|
+
<button
|
|
787
|
+
onClick={() => downloadFileById(downloadFileByIdParams)}
|
|
788
|
+
disabled={isPending(downloadFileByIdResp)}
|
|
789
|
+
>
|
|
790
|
+
{isPending(downloadFileByIdResp) ? "Downloading…" : "Download"}
|
|
791
|
+
</button>
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
\`\`\`
|
|
795
|
+
|
|
796
|
+
### Optional types (if generated by your build)
|
|
797
|
+
|
|
798
|
+
\`\`\`ts
|
|
799
|
+
import type { DownloadFileByIdParams } from "@intrig/react/files/{id}/DownloadFileById.params";
|
|
800
|
+
import type { DownloadFileByIdResponseBody } from "@intrig/react/files/{id}/DownloadFileById.response";
|
|
801
|
+
\`\`\`
|
|
802
|
+
|
|
803
|
+
---
|
|
804
|
+
|
|
805
|
+
## Hook APIs
|
|
806
|
+
|
|
807
|
+
### \`useDownloadFileByIdDownload\` (auto-download)
|
|
808
|
+
|
|
809
|
+
- **What it does:** requests the file with \`responseType: 'blob'\` + \`adapter: 'fetch'\`, derives filename from \`Content-Disposition\` if present, creates a temporary object URL, clicks a hidden \`<a>\`, **downloads**, then resets state to \`init\`.
|
|
810
|
+
- **Signature:** \`[state, download, clear]\`
|
|
811
|
+
- \`download(params: DownloadFileByIdParams) => void\`
|
|
812
|
+
|
|
813
|
+
### \`useDownloadFileById\` (manual/stateful)
|
|
814
|
+
|
|
815
|
+
- **What it does:** same request but **does not** auto-save. You control preview/saving using \`state.data\` + \`state.headers\`.
|
|
816
|
+
- **Signature:** \`[state, fetchFile, clear]\`
|
|
817
|
+
- \`fetchFile(params: DownloadFileByIdParams) => void\`
|
|
818
|
+
|
|
819
|
+
---
|
|
820
|
+
|
|
821
|
+
## Usage Patterns
|
|
822
|
+
|
|
823
|
+
### 1) Auto-download on click (recommended)
|
|
824
|
+
|
|
825
|
+
\`\`\`tsx
|
|
826
|
+
const [downloadFileByIdResp, downloadFileById] = useDownloadFileByIdDownload({
|
|
827
|
+
clearOnUnmount: true,
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
<button
|
|
831
|
+
onClick={() => downloadFileById(downloadFileByIdParams)}
|
|
832
|
+
disabled={isPending(downloadFileByIdResp)}
|
|
833
|
+
>
|
|
834
|
+
{isPending(downloadFileByIdResp) ? "Downloading…" : "Download file"}
|
|
835
|
+
</button>;
|
|
836
|
+
{
|
|
837
|
+
isError(downloadFileByIdResp) ? (
|
|
838
|
+
<p className="text-red-500">Failed to download.</p>
|
|
839
|
+
) : null;
|
|
840
|
+
}
|
|
841
|
+
\`\`\`
|
|
842
|
+
|
|
843
|
+
<details><summary>Description</summary>
|
|
844
|
+
<p>Most users just need a button that saves the file. This variant handles object URL creation, filename extraction, click, and state reset.</p>
|
|
845
|
+
</details>
|
|
846
|
+
|
|
847
|
+
### 2) Auto-download on mount (e.g., “Your file is ready” page)
|
|
848
|
+
|
|
849
|
+
\`\`\`tsx
|
|
850
|
+
useEffect(() => {
|
|
851
|
+
downloadFileById(downloadFileByIdParams);
|
|
852
|
+
}, [downloadFileById]);
|
|
853
|
+
\`\`\`
|
|
854
|
+
|
|
855
|
+
<details><summary>Description</summary>
|
|
856
|
+
<p>Good for post-processing routes that immediately start a download.</p>
|
|
857
|
+
</details>
|
|
858
|
+
|
|
859
|
+
### 3) Manual handling (preview or custom filename)
|
|
860
|
+
|
|
861
|
+
\`\`\`tsx
|
|
862
|
+
const [downloadFileByIdResp, downloadFileById] = useDownloadFileById({
|
|
863
|
+
clearOnUnmount: true,
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
useEffect(() => {
|
|
867
|
+
if (isSuccess(downloadFileByIdResp)) {
|
|
868
|
+
const ct =
|
|
869
|
+
downloadFileByIdResp.headers?.["content-type"] ??
|
|
870
|
+
"application/octet-stream";
|
|
871
|
+
const parts = Array.isArray(downloadFileByIdResp.data)
|
|
872
|
+
? downloadFileByIdResp.data
|
|
873
|
+
: [downloadFileByIdResp.data];
|
|
874
|
+
const url = URL.createObjectURL(new Blob(parts, { type: ct }));
|
|
875
|
+
// preview/save with your own UI...
|
|
876
|
+
return () => URL.revokeObjectURL(url);
|
|
877
|
+
}
|
|
878
|
+
}, [downloadFileByIdResp]);
|
|
879
|
+
\`\`\`
|
|
880
|
+
|
|
881
|
+
<details><summary>Description</summary>
|
|
882
|
+
<p>Use when you need to inspect headers, show a preview, or control the filename/UI flow.</p>
|
|
883
|
+
</details>
|
|
884
|
+
|
|
885
|
+
---
|
|
886
|
+
|
|
887
|
+
## Behavior notes (what the auto-download variant does)
|
|
888
|
+
|
|
889
|
+
- Forces \`responseType: 'blob'\` and \`adapter: 'fetch'\`.
|
|
890
|
+
- If \`content-type\` is JSON, stringifies payload so the saved file is human-readable.
|
|
891
|
+
- Parses \`Content-Disposition\` to derive a filename; falls back to a default.
|
|
892
|
+
- Creates and clicks a temporary link, then **resets state to \`init\`**.
|
|
893
|
+
|
|
894
|
+
---
|
|
895
|
+
|
|
896
|
+
## Gotchas & Tips
|
|
897
|
+
|
|
898
|
+
- **Expose headers in CORS:** server should send
|
|
899
|
+
\`Access-Control-Expose-Headers: Content-Disposition, Content-Type\`
|
|
900
|
+
- **Disable double clicks:** guard with \`isPending(state)\`.
|
|
901
|
+
- **Revoke URLs** when you create them manually in the stateful variant.
|
|
902
|
+
- **iOS Safari:** blob downloads may open a new tab—consider server-side direct-download URLs for a smoother UX.
|
|
903
|
+
|
|
904
|
+
---
|
|
905
|
+
|
|
906
|
+
## Troubleshooting
|
|
907
|
+
|
|
908
|
+
- **No filename shown:** your server didn’t include \`Content-Disposition\`. Add it.
|
|
909
|
+
- **Got JSON instead of a file:** server returned \`application/json\` (maybe an error). The auto hook saves it as text; surface the error in UI.
|
|
910
|
+
- **Nothing happens on click:** ensure you’re using the **Download** variant and the request succeeds (check Network tab); verify CORS headers.
|
|
911
|
+
|
|
912
|
+
---
|
|
913
|
+
"
|
|
914
|
+
`;
|
|
915
|
+
|
|
916
|
+
exports[`reactDownloadHookDocs > snapshot — request body and path params 1`] = `
|
|
917
|
+
"# Intrig Download Hooks — Quick Guide
|
|
918
|
+
|
|
919
|
+
## Copy-paste starter (fast lane)
|
|
920
|
+
|
|
921
|
+
### Auto-download (most common)
|
|
922
|
+
|
|
923
|
+
\`\`\`ts
|
|
924
|
+
import { useDownloadUserDocumentDownload } from "@intrig/react/users/{userId}/documents/client";
|
|
925
|
+
\`\`\`
|
|
926
|
+
|
|
927
|
+
\`\`\`ts
|
|
928
|
+
import { isPending, isError } from "@intrig/react";
|
|
929
|
+
\`\`\`
|
|
930
|
+
|
|
931
|
+
\`\`\`tsx
|
|
932
|
+
const [downloadUserDocumentResp, downloadUserDocument] =
|
|
933
|
+
useDownloadUserDocumentDownload({ clearOnUnmount: true });
|
|
934
|
+
// e.g., in a click handler:
|
|
935
|
+
downloadUserDocument(downloadUserDocumentParams);
|
|
936
|
+
\`\`\`
|
|
937
|
+
|
|
938
|
+
### Manual/stateful (you handle the blob/UI)
|
|
939
|
+
|
|
940
|
+
\`\`\`ts
|
|
941
|
+
import { useDownloadUserDocument } from "@intrig/react/users/{userId}/documents/client";
|
|
942
|
+
\`\`\`
|
|
943
|
+
|
|
944
|
+
\`\`\`ts
|
|
945
|
+
import { isSuccess, isPending, isError } from "@intrig/react";
|
|
946
|
+
\`\`\`
|
|
947
|
+
|
|
948
|
+
\`\`\`tsx
|
|
949
|
+
const [downloadUserDocumentResp, downloadUserDocument] =
|
|
950
|
+
useDownloadUserDocument({ clearOnUnmount: true });
|
|
951
|
+
// later:
|
|
952
|
+
downloadUserDocument(downloadUserDocumentParams);
|
|
953
|
+
\`\`\`
|
|
954
|
+
|
|
955
|
+
---
|
|
956
|
+
|
|
957
|
+
## TL;DR (auto-download)
|
|
958
|
+
|
|
959
|
+
\`\`\`tsx
|
|
960
|
+
import { useDownloadUserDocumentDownload } from "@intrig/react/users/{userId}/documents/client";
|
|
961
|
+
import { isPending, isError } from "@intrig/react";
|
|
962
|
+
|
|
963
|
+
export default function Example() {
|
|
964
|
+
const [downloadUserDocumentResp, downloadUserDocument] =
|
|
965
|
+
useDownloadUserDocumentDownload({ clearOnUnmount: true });
|
|
966
|
+
|
|
967
|
+
return (
|
|
968
|
+
<button
|
|
969
|
+
onClick={() => downloadUserDocument(downloadUserDocumentParams)}
|
|
970
|
+
disabled={isPending(downloadUserDocumentResp)}
|
|
971
|
+
>
|
|
972
|
+
{isPending(downloadUserDocumentResp) ? "Downloading…" : "Download"}
|
|
973
|
+
</button>
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
\`\`\`
|
|
977
|
+
|
|
978
|
+
### Optional types (if generated by your build)
|
|
979
|
+
|
|
980
|
+
\`\`\`ts
|
|
981
|
+
import type { DownloadUserDocumentParams } from "@intrig/react/users/{userId}/documents/DownloadUserDocument.params";
|
|
982
|
+
import type { DownloadUserDocumentResponseBody } from "@intrig/react/users/{userId}/documents/DownloadUserDocument.response";
|
|
983
|
+
\`\`\`
|
|
984
|
+
|
|
985
|
+
---
|
|
986
|
+
|
|
987
|
+
## Hook APIs
|
|
988
|
+
|
|
989
|
+
### \`useDownloadUserDocumentDownload\` (auto-download)
|
|
990
|
+
|
|
991
|
+
- **What it does:** requests the file with \`responseType: 'blob'\` + \`adapter: 'fetch'\`, derives filename from \`Content-Disposition\` if present, creates a temporary object URL, clicks a hidden \`<a>\`, **downloads**, then resets state to \`init\`.
|
|
992
|
+
- **Signature:** \`[state, download, clear]\`
|
|
993
|
+
- \`download(params: DownloadUserDocumentParams) => void\`
|
|
994
|
+
|
|
995
|
+
### \`useDownloadUserDocument\` (manual/stateful)
|
|
996
|
+
|
|
997
|
+
- **What it does:** same request but **does not** auto-save. You control preview/saving using \`state.data\` + \`state.headers\`.
|
|
998
|
+
- **Signature:** \`[state, fetchFile, clear]\`
|
|
999
|
+
- \`fetchFile(params: DownloadUserDocumentParams) => void\`
|
|
1000
|
+
|
|
1001
|
+
---
|
|
1002
|
+
|
|
1003
|
+
## Usage Patterns
|
|
1004
|
+
|
|
1005
|
+
### 1) Auto-download on click (recommended)
|
|
1006
|
+
|
|
1007
|
+
\`\`\`tsx
|
|
1008
|
+
const [downloadUserDocumentResp, downloadUserDocument] =
|
|
1009
|
+
useDownloadUserDocumentDownload({ clearOnUnmount: true });
|
|
1010
|
+
|
|
1011
|
+
<button
|
|
1012
|
+
onClick={() => downloadUserDocument(downloadUserDocumentParams)}
|
|
1013
|
+
disabled={isPending(downloadUserDocumentResp)}
|
|
1014
|
+
>
|
|
1015
|
+
{isPending(downloadUserDocumentResp) ? "Downloading…" : "Download file"}
|
|
1016
|
+
</button>;
|
|
1017
|
+
{
|
|
1018
|
+
isError(downloadUserDocumentResp) ? (
|
|
1019
|
+
<p className="text-red-500">Failed to download.</p>
|
|
1020
|
+
) : null;
|
|
1021
|
+
}
|
|
1022
|
+
\`\`\`
|
|
1023
|
+
|
|
1024
|
+
<details><summary>Description</summary>
|
|
1025
|
+
<p>Most users just need a button that saves the file. This variant handles object URL creation, filename extraction, click, and state reset.</p>
|
|
1026
|
+
</details>
|
|
1027
|
+
|
|
1028
|
+
### 2) Auto-download on mount (e.g., “Your file is ready” page)
|
|
1029
|
+
|
|
1030
|
+
\`\`\`tsx
|
|
1031
|
+
useEffect(() => {
|
|
1032
|
+
downloadUserDocument(downloadUserDocumentParams);
|
|
1033
|
+
}, [downloadUserDocument]);
|
|
1034
|
+
\`\`\`
|
|
1035
|
+
|
|
1036
|
+
<details><summary>Description</summary>
|
|
1037
|
+
<p>Good for post-processing routes that immediately start a download.</p>
|
|
1038
|
+
</details>
|
|
1039
|
+
|
|
1040
|
+
### 3) Manual handling (preview or custom filename)
|
|
1041
|
+
|
|
1042
|
+
\`\`\`tsx
|
|
1043
|
+
const [downloadUserDocumentResp, downloadUserDocument] =
|
|
1044
|
+
useDownloadUserDocument({ clearOnUnmount: true });
|
|
1045
|
+
|
|
1046
|
+
useEffect(() => {
|
|
1047
|
+
if (isSuccess(downloadUserDocumentResp)) {
|
|
1048
|
+
const ct =
|
|
1049
|
+
downloadUserDocumentResp.headers?.["content-type"] ??
|
|
1050
|
+
"application/octet-stream";
|
|
1051
|
+
const parts = Array.isArray(downloadUserDocumentResp.data)
|
|
1052
|
+
? downloadUserDocumentResp.data
|
|
1053
|
+
: [downloadUserDocumentResp.data];
|
|
1054
|
+
const url = URL.createObjectURL(new Blob(parts, { type: ct }));
|
|
1055
|
+
// preview/save with your own UI...
|
|
1056
|
+
return () => URL.revokeObjectURL(url);
|
|
1057
|
+
}
|
|
1058
|
+
}, [downloadUserDocumentResp]);
|
|
1059
|
+
\`\`\`
|
|
1060
|
+
|
|
1061
|
+
<details><summary>Description</summary>
|
|
1062
|
+
<p>Use when you need to inspect headers, show a preview, or control the filename/UI flow.</p>
|
|
1063
|
+
</details>
|
|
1064
|
+
|
|
1065
|
+
---
|
|
1066
|
+
|
|
1067
|
+
## Behavior notes (what the auto-download variant does)
|
|
1068
|
+
|
|
1069
|
+
- Forces \`responseType: 'blob'\` and \`adapter: 'fetch'\`.
|
|
1070
|
+
- If \`content-type\` is JSON, stringifies payload so the saved file is human-readable.
|
|
1071
|
+
- Parses \`Content-Disposition\` to derive a filename; falls back to a default.
|
|
1072
|
+
- Creates and clicks a temporary link, then **resets state to \`init\`**.
|
|
1073
|
+
|
|
1074
|
+
---
|
|
1075
|
+
|
|
1076
|
+
## Gotchas & Tips
|
|
1077
|
+
|
|
1078
|
+
- **Expose headers in CORS:** server should send
|
|
1079
|
+
\`Access-Control-Expose-Headers: Content-Disposition, Content-Type\`
|
|
1080
|
+
- **Disable double clicks:** guard with \`isPending(state)\`.
|
|
1081
|
+
- **Revoke URLs** when you create them manually in the stateful variant.
|
|
1082
|
+
- **iOS Safari:** blob downloads may open a new tab—consider server-side direct-download URLs for a smoother UX.
|
|
1083
|
+
|
|
1084
|
+
---
|
|
1085
|
+
|
|
1086
|
+
## Troubleshooting
|
|
1087
|
+
|
|
1088
|
+
- **No filename shown:** your server didn’t include \`Content-Disposition\`. Add it.
|
|
1089
|
+
- **Got JSON instead of a file:** server returned \`application/json\` (maybe an error). The auto hook saves it as text; surface the error in UI.
|
|
1090
|
+
- **Nothing happens on click:** ensure you’re using the **Download** variant and the request succeeds (check Network tab); verify CORS headers.
|
|
1091
|
+
|
|
1092
|
+
---
|
|
1093
|
+
"
|
|
1094
|
+
`;
|
|
1095
|
+
|
|
1096
|
+
exports[`reactDownloadHookDocs > snapshot — request body only (no path params) 1`] = `
|
|
1097
|
+
"# Intrig Download Hooks — Quick Guide
|
|
1098
|
+
|
|
1099
|
+
## Copy-paste starter (fast lane)
|
|
1100
|
+
|
|
1101
|
+
### Auto-download (most common)
|
|
1102
|
+
|
|
1103
|
+
\`\`\`ts
|
|
1104
|
+
import { useDownloadCustomReportDownload } from "@intrig/react/reports/client";
|
|
1105
|
+
\`\`\`
|
|
1106
|
+
|
|
1107
|
+
\`\`\`ts
|
|
1108
|
+
import { isPending, isError } from "@intrig/react";
|
|
1109
|
+
\`\`\`
|
|
1110
|
+
|
|
1111
|
+
\`\`\`tsx
|
|
1112
|
+
const [downloadCustomReportResp, downloadCustomReport] =
|
|
1113
|
+
useDownloadCustomReportDownload({ clearOnUnmount: true });
|
|
1114
|
+
// e.g., in a click handler:
|
|
1115
|
+
downloadCustomReport({});
|
|
1116
|
+
\`\`\`
|
|
1117
|
+
|
|
1118
|
+
### Manual/stateful (you handle the blob/UI)
|
|
1119
|
+
|
|
1120
|
+
\`\`\`ts
|
|
1121
|
+
import { useDownloadCustomReport } from "@intrig/react/reports/client";
|
|
1122
|
+
\`\`\`
|
|
1123
|
+
|
|
1124
|
+
\`\`\`ts
|
|
1125
|
+
import { isSuccess, isPending, isError } from "@intrig/react";
|
|
1126
|
+
\`\`\`
|
|
1127
|
+
|
|
1128
|
+
\`\`\`tsx
|
|
1129
|
+
const [downloadCustomReportResp, downloadCustomReport] =
|
|
1130
|
+
useDownloadCustomReport({ clearOnUnmount: true });
|
|
1131
|
+
// later:
|
|
1132
|
+
downloadCustomReport({});
|
|
1133
|
+
\`\`\`
|
|
1134
|
+
|
|
1135
|
+
---
|
|
1136
|
+
|
|
1137
|
+
## TL;DR (auto-download)
|
|
1138
|
+
|
|
1139
|
+
\`\`\`tsx
|
|
1140
|
+
import { useDownloadCustomReportDownload } from "@intrig/react/reports/client";
|
|
1141
|
+
import { isPending, isError } from "@intrig/react";
|
|
1142
|
+
|
|
1143
|
+
export default function Example() {
|
|
1144
|
+
const [downloadCustomReportResp, downloadCustomReport] =
|
|
1145
|
+
useDownloadCustomReportDownload({ clearOnUnmount: true });
|
|
1146
|
+
|
|
1147
|
+
return (
|
|
1148
|
+
<button
|
|
1149
|
+
onClick={() => downloadCustomReport({})}
|
|
1150
|
+
disabled={isPending(downloadCustomReportResp)}
|
|
1151
|
+
>
|
|
1152
|
+
{isPending(downloadCustomReportResp) ? "Downloading…" : "Download"}
|
|
1153
|
+
</button>
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
\`\`\`
|
|
1157
|
+
|
|
1158
|
+
---
|
|
1159
|
+
|
|
1160
|
+
## Hook APIs
|
|
1161
|
+
|
|
1162
|
+
### \`useDownloadCustomReportDownload\` (auto-download)
|
|
1163
|
+
|
|
1164
|
+
- **What it does:** requests the file with \`responseType: 'blob'\` + \`adapter: 'fetch'\`, derives filename from \`Content-Disposition\` if present, creates a temporary object URL, clicks a hidden \`<a>\`, **downloads**, then resets state to \`init\`.
|
|
1165
|
+
- **Signature:** \`[state, download, clear]\`
|
|
1166
|
+
- \`download(params: Record<string, unknown>) => void\`
|
|
1167
|
+
|
|
1168
|
+
### \`useDownloadCustomReport\` (manual/stateful)
|
|
1169
|
+
|
|
1170
|
+
- **What it does:** same request but **does not** auto-save. You control preview/saving using \`state.data\` + \`state.headers\`.
|
|
1171
|
+
- **Signature:** \`[state, fetchFile, clear]\`
|
|
1172
|
+
- \`fetchFile(params: Record<string, unknown>) => void\`
|
|
1173
|
+
|
|
1174
|
+
---
|
|
1175
|
+
|
|
1176
|
+
## Usage Patterns
|
|
1177
|
+
|
|
1178
|
+
### 1) Auto-download on click (recommended)
|
|
1179
|
+
|
|
1180
|
+
\`\`\`tsx
|
|
1181
|
+
const [downloadCustomReportResp, downloadCustomReport] =
|
|
1182
|
+
useDownloadCustomReportDownload({ clearOnUnmount: true });
|
|
1183
|
+
|
|
1184
|
+
<button
|
|
1185
|
+
onClick={() => downloadCustomReport({})}
|
|
1186
|
+
disabled={isPending(downloadCustomReportResp)}
|
|
1187
|
+
>
|
|
1188
|
+
{isPending(downloadCustomReportResp) ? "Downloading…" : "Download file"}
|
|
1189
|
+
</button>;
|
|
1190
|
+
{
|
|
1191
|
+
isError(downloadCustomReportResp) ? (
|
|
1192
|
+
<p className="text-red-500">Failed to download.</p>
|
|
1193
|
+
) : null;
|
|
1194
|
+
}
|
|
1195
|
+
\`\`\`
|
|
1196
|
+
|
|
1197
|
+
<details><summary>Description</summary>
|
|
1198
|
+
<p>Most users just need a button that saves the file. This variant handles object URL creation, filename extraction, click, and state reset.</p>
|
|
1199
|
+
</details>
|
|
1200
|
+
|
|
1201
|
+
### 2) Auto-download on mount (e.g., “Your file is ready” page)
|
|
1202
|
+
|
|
1203
|
+
\`\`\`tsx
|
|
1204
|
+
useEffect(() => {
|
|
1205
|
+
downloadCustomReport({});
|
|
1206
|
+
}, [downloadCustomReport]);
|
|
1207
|
+
\`\`\`
|
|
1208
|
+
|
|
1209
|
+
<details><summary>Description</summary>
|
|
1210
|
+
<p>Good for post-processing routes that immediately start a download.</p>
|
|
1211
|
+
</details>
|
|
1212
|
+
|
|
1213
|
+
### 3) Manual handling (preview or custom filename)
|
|
1214
|
+
|
|
1215
|
+
\`\`\`tsx
|
|
1216
|
+
const [downloadCustomReportResp, downloadCustomReport] =
|
|
1217
|
+
useDownloadCustomReport({ clearOnUnmount: true });
|
|
1218
|
+
|
|
1219
|
+
useEffect(() => {
|
|
1220
|
+
if (isSuccess(downloadCustomReportResp)) {
|
|
1221
|
+
const ct =
|
|
1222
|
+
downloadCustomReportResp.headers?.["content-type"] ??
|
|
1223
|
+
"application/octet-stream";
|
|
1224
|
+
const parts = Array.isArray(downloadCustomReportResp.data)
|
|
1225
|
+
? downloadCustomReportResp.data
|
|
1226
|
+
: [downloadCustomReportResp.data];
|
|
1227
|
+
const url = URL.createObjectURL(new Blob(parts, { type: ct }));
|
|
1228
|
+
// preview/save with your own UI...
|
|
1229
|
+
return () => URL.revokeObjectURL(url);
|
|
1230
|
+
}
|
|
1231
|
+
}, [downloadCustomReportResp]);
|
|
1232
|
+
\`\`\`
|
|
1233
|
+
|
|
1234
|
+
<details><summary>Description</summary>
|
|
1235
|
+
<p>Use when you need to inspect headers, show a preview, or control the filename/UI flow.</p>
|
|
1236
|
+
</details>
|
|
1237
|
+
|
|
1238
|
+
---
|
|
1239
|
+
|
|
1240
|
+
## Behavior notes (what the auto-download variant does)
|
|
1241
|
+
|
|
1242
|
+
- Forces \`responseType: 'blob'\` and \`adapter: 'fetch'\`.
|
|
1243
|
+
- If \`content-type\` is JSON, stringifies payload so the saved file is human-readable.
|
|
1244
|
+
- Parses \`Content-Disposition\` to derive a filename; falls back to a default.
|
|
1245
|
+
- Creates and clicks a temporary link, then **resets state to \`init\`**.
|
|
1246
|
+
|
|
1247
|
+
---
|
|
1248
|
+
|
|
1249
|
+
## Gotchas & Tips
|
|
1250
|
+
|
|
1251
|
+
- **Expose headers in CORS:** server should send
|
|
1252
|
+
\`Access-Control-Expose-Headers: Content-Disposition, Content-Type\`
|
|
1253
|
+
- **Disable double clicks:** guard with \`isPending(state)\`.
|
|
1254
|
+
- **Revoke URLs** when you create them manually in the stateful variant.
|
|
1255
|
+
- **iOS Safari:** blob downloads may open a new tab—consider server-side direct-download URLs for a smoother UX.
|
|
1256
|
+
|
|
1257
|
+
---
|
|
1258
|
+
|
|
1259
|
+
## Troubleshooting
|
|
1260
|
+
|
|
1261
|
+
- **No filename shown:** your server didn’t include \`Content-Disposition\`. Add it.
|
|
1262
|
+
- **Got JSON instead of a file:** server returned \`application/json\` (maybe an error). The auto hook saves it as text; surface the error in UI.
|
|
1263
|
+
- **Nothing happens on click:** ensure you’re using the **Download** variant and the request succeeds (check Network tab); verify CORS headers.
|
|
1264
|
+
|
|
1265
|
+
---
|
|
1266
|
+
"
|
|
1267
|
+
`;
|
|
1268
|
+
|
|
1269
|
+
exports[`reactDownloadHookDocs > snapshot — simple REST descriptor (no body, no path params) 1`] = `
|
|
1270
|
+
"# Intrig Download Hooks — Quick Guide
|
|
1271
|
+
|
|
1272
|
+
## Copy-paste starter (fast lane)
|
|
1273
|
+
|
|
1274
|
+
### Auto-download (most common)
|
|
1275
|
+
|
|
1276
|
+
\`\`\`ts
|
|
1277
|
+
import { useDownloadReportDownload } from "@intrig/react/reports/client";
|
|
1278
|
+
\`\`\`
|
|
1279
|
+
|
|
1280
|
+
\`\`\`ts
|
|
1281
|
+
import { isPending, isError } from "@intrig/react";
|
|
1282
|
+
\`\`\`
|
|
1283
|
+
|
|
1284
|
+
\`\`\`tsx
|
|
1285
|
+
const [downloadReportResp, downloadReport] = useDownloadReportDownload({
|
|
1286
|
+
clearOnUnmount: true,
|
|
1287
|
+
});
|
|
1288
|
+
// e.g., in a click handler:
|
|
1289
|
+
downloadReport({});
|
|
1290
|
+
\`\`\`
|
|
1291
|
+
|
|
1292
|
+
### Manual/stateful (you handle the blob/UI)
|
|
1293
|
+
|
|
1294
|
+
\`\`\`ts
|
|
1295
|
+
import { useDownloadReport } from "@intrig/react/reports/client";
|
|
1296
|
+
\`\`\`
|
|
1297
|
+
|
|
1298
|
+
\`\`\`ts
|
|
1299
|
+
import { isSuccess, isPending, isError } from "@intrig/react";
|
|
1300
|
+
\`\`\`
|
|
1301
|
+
|
|
1302
|
+
\`\`\`tsx
|
|
1303
|
+
const [downloadReportResp, downloadReport] = useDownloadReport({
|
|
1304
|
+
clearOnUnmount: true,
|
|
1305
|
+
});
|
|
1306
|
+
// later:
|
|
1307
|
+
downloadReport({});
|
|
1308
|
+
\`\`\`
|
|
1309
|
+
|
|
1310
|
+
---
|
|
1311
|
+
|
|
1312
|
+
## TL;DR (auto-download)
|
|
1313
|
+
|
|
1314
|
+
\`\`\`tsx
|
|
1315
|
+
import { useDownloadReportDownload } from "@intrig/react/reports/client";
|
|
1316
|
+
import { isPending, isError } from "@intrig/react";
|
|
1317
|
+
|
|
1318
|
+
export default function Example() {
|
|
1319
|
+
const [downloadReportResp, downloadReport] = useDownloadReportDownload({
|
|
1320
|
+
clearOnUnmount: true,
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1323
|
+
return (
|
|
1324
|
+
<button
|
|
1325
|
+
onClick={() => downloadReport({})}
|
|
1326
|
+
disabled={isPending(downloadReportResp)}
|
|
1327
|
+
>
|
|
1328
|
+
{isPending(downloadReportResp) ? "Downloading…" : "Download"}
|
|
1329
|
+
</button>
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
\`\`\`
|
|
1333
|
+
|
|
1334
|
+
---
|
|
1335
|
+
|
|
1336
|
+
## Hook APIs
|
|
1337
|
+
|
|
1338
|
+
### \`useDownloadReportDownload\` (auto-download)
|
|
1339
|
+
|
|
1340
|
+
- **What it does:** requests the file with \`responseType: 'blob'\` + \`adapter: 'fetch'\`, derives filename from \`Content-Disposition\` if present, creates a temporary object URL, clicks a hidden \`<a>\`, **downloads**, then resets state to \`init\`.
|
|
1341
|
+
- **Signature:** \`[state, download, clear]\`
|
|
1342
|
+
- \`download(params: Record<string, unknown>) => void\`
|
|
1343
|
+
|
|
1344
|
+
### \`useDownloadReport\` (manual/stateful)
|
|
1345
|
+
|
|
1346
|
+
- **What it does:** same request but **does not** auto-save. You control preview/saving using \`state.data\` + \`state.headers\`.
|
|
1347
|
+
- **Signature:** \`[state, fetchFile, clear]\`
|
|
1348
|
+
- \`fetchFile(params: Record<string, unknown>) => void\`
|
|
1349
|
+
|
|
1350
|
+
---
|
|
1351
|
+
|
|
1352
|
+
## Usage Patterns
|
|
1353
|
+
|
|
1354
|
+
### 1) Auto-download on click (recommended)
|
|
1355
|
+
|
|
1356
|
+
\`\`\`tsx
|
|
1357
|
+
const [downloadReportResp, downloadReport] = useDownloadReportDownload({
|
|
1358
|
+
clearOnUnmount: true,
|
|
1359
|
+
});
|
|
1360
|
+
|
|
1361
|
+
<button
|
|
1362
|
+
onClick={() => downloadReport({})}
|
|
1363
|
+
disabled={isPending(downloadReportResp)}
|
|
1364
|
+
>
|
|
1365
|
+
{isPending(downloadReportResp) ? "Downloading…" : "Download file"}
|
|
1366
|
+
</button>;
|
|
1367
|
+
{
|
|
1368
|
+
isError(downloadReportResp) ? (
|
|
1369
|
+
<p className="text-red-500">Failed to download.</p>
|
|
1370
|
+
) : null;
|
|
1371
|
+
}
|
|
1372
|
+
\`\`\`
|
|
1373
|
+
|
|
1374
|
+
<details><summary>Description</summary>
|
|
1375
|
+
<p>Most users just need a button that saves the file. This variant handles object URL creation, filename extraction, click, and state reset.</p>
|
|
1376
|
+
</details>
|
|
1377
|
+
|
|
1378
|
+
### 2) Auto-download on mount (e.g., “Your file is ready” page)
|
|
1379
|
+
|
|
1380
|
+
\`\`\`tsx
|
|
1381
|
+
useEffect(() => {
|
|
1382
|
+
downloadReport({});
|
|
1383
|
+
}, [downloadReport]);
|
|
1384
|
+
\`\`\`
|
|
1385
|
+
|
|
1386
|
+
<details><summary>Description</summary>
|
|
1387
|
+
<p>Good for post-processing routes that immediately start a download.</p>
|
|
1388
|
+
</details>
|
|
1389
|
+
|
|
1390
|
+
### 3) Manual handling (preview or custom filename)
|
|
1391
|
+
|
|
1392
|
+
\`\`\`tsx
|
|
1393
|
+
const [downloadReportResp, downloadReport] = useDownloadReport({
|
|
1394
|
+
clearOnUnmount: true,
|
|
1395
|
+
});
|
|
1396
|
+
|
|
1397
|
+
useEffect(() => {
|
|
1398
|
+
if (isSuccess(downloadReportResp)) {
|
|
1399
|
+
const ct =
|
|
1400
|
+
downloadReportResp.headers?.["content-type"] ??
|
|
1401
|
+
"application/octet-stream";
|
|
1402
|
+
const parts = Array.isArray(downloadReportResp.data)
|
|
1403
|
+
? downloadReportResp.data
|
|
1404
|
+
: [downloadReportResp.data];
|
|
1405
|
+
const url = URL.createObjectURL(new Blob(parts, { type: ct }));
|
|
1406
|
+
// preview/save with your own UI...
|
|
1407
|
+
return () => URL.revokeObjectURL(url);
|
|
1408
|
+
}
|
|
1409
|
+
}, [downloadReportResp]);
|
|
1410
|
+
\`\`\`
|
|
1411
|
+
|
|
1412
|
+
<details><summary>Description</summary>
|
|
1413
|
+
<p>Use when you need to inspect headers, show a preview, or control the filename/UI flow.</p>
|
|
1414
|
+
</details>
|
|
1415
|
+
|
|
1416
|
+
---
|
|
1417
|
+
|
|
1418
|
+
## Behavior notes (what the auto-download variant does)
|
|
1419
|
+
|
|
1420
|
+
- Forces \`responseType: 'blob'\` and \`adapter: 'fetch'\`.
|
|
1421
|
+
- If \`content-type\` is JSON, stringifies payload so the saved file is human-readable.
|
|
1422
|
+
- Parses \`Content-Disposition\` to derive a filename; falls back to a default.
|
|
1423
|
+
- Creates and clicks a temporary link, then **resets state to \`init\`**.
|
|
1424
|
+
|
|
1425
|
+
---
|
|
1426
|
+
|
|
1427
|
+
## Gotchas & Tips
|
|
1428
|
+
|
|
1429
|
+
- **Expose headers in CORS:** server should send
|
|
1430
|
+
\`Access-Control-Expose-Headers: Content-Disposition, Content-Type\`
|
|
1431
|
+
- **Disable double clicks:** guard with \`isPending(state)\`.
|
|
1432
|
+
- **Revoke URLs** when you create them manually in the stateful variant.
|
|
1433
|
+
- **iOS Safari:** blob downloads may open a new tab—consider server-side direct-download URLs for a smoother UX.
|
|
1434
|
+
|
|
1435
|
+
---
|
|
1436
|
+
|
|
1437
|
+
## Troubleshooting
|
|
1438
|
+
|
|
1439
|
+
- **No filename shown:** your server didn’t include \`Content-Disposition\`. Add it.
|
|
1440
|
+
- **Got JSON instead of a file:** server returned \`application/json\` (maybe an error). The auto hook saves it as text; surface the error in UI.
|
|
1441
|
+
- **Nothing happens on click:** ensure you’re using the **Download** variant and the request succeeds (check Network tab); verify CORS headers.
|
|
1442
|
+
|
|
1443
|
+
---
|
|
1444
|
+
"
|
|
1445
|
+
`;
|