@nbt-dev/components 0.0.7 → 0.0.8
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/core/index.d.ts +2 -0
- package/dist/core/use-gsheets.d.ts +29 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
- package/src/core/index.ts +2 -0
- package/src/core/use-gsheets.ts +88 -0
package/dist/core/index.d.ts
CHANGED
|
@@ -5,5 +5,7 @@ export { BulkStreamProvider, useBulkSubscription, useBulkRowCounts, } from "./us
|
|
|
5
5
|
export type { BulkSubscription } from "./use-bulk-stream";
|
|
6
6
|
export { useLiveBulkRegistry } from "./use-cartridge-info";
|
|
7
7
|
export type { BulkEntity, BulkRegistry, LiveRegistryState, } from "./use-cartridge-info";
|
|
8
|
+
export { useGsheetsApi } from "./use-gsheets";
|
|
9
|
+
export type { GsheetSyncConfig, GsheetSaveInput } from "./use-gsheets";
|
|
8
10
|
export { getDevToolsToken, setDevToolsToken, clearDevToolsToken, authHeaders, fetchWhoAmI, devToolsSignIn, devToolsSignUp, devToolsSignOut, wsAuthProtocols, } from "./auth";
|
|
9
11
|
export type { WhoAmI } from "./auth";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type GsheetSyncConfig = {
|
|
2
|
+
cartridge: string;
|
|
3
|
+
spreadsheetId: string;
|
|
4
|
+
saEmail: string;
|
|
5
|
+
hasServiceAccount: boolean;
|
|
6
|
+
intervalSeconds: number;
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
lastSyncAt: number;
|
|
9
|
+
status: string;
|
|
10
|
+
lastError: string;
|
|
11
|
+
};
|
|
12
|
+
export type GsheetSaveInput = {
|
|
13
|
+
cartridge: string;
|
|
14
|
+
spreadsheetUrl: string;
|
|
15
|
+
serviceAccountJson?: string;
|
|
16
|
+
intervalSeconds: number;
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
};
|
|
19
|
+
export declare function useGsheetsApi(): {
|
|
20
|
+
list: () => Promise<GsheetSyncConfig[]>;
|
|
21
|
+
save: (input: GsheetSaveInput) => Promise<GsheetSyncConfig>;
|
|
22
|
+
syncNow: (cartridge: string) => Promise<{
|
|
23
|
+
ok: boolean;
|
|
24
|
+
error: string;
|
|
25
|
+
}>;
|
|
26
|
+
remove: (cartridge: string) => Promise<{
|
|
27
|
+
ok: boolean;
|
|
28
|
+
}>;
|
|
29
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -101,6 +101,59 @@ function useLiveBulkRegistry() {
|
|
|
101
101
|
const carts = Object.keys(registry).sort();
|
|
102
102
|
return { registry, carts, coreCarts, loading, error };
|
|
103
103
|
}
|
|
104
|
+
|
|
105
|
+
// src/core/use-gsheets.ts
|
|
106
|
+
import { useCallback, useMemo } from "react";
|
|
107
|
+
async function jsonOrThrow(r) {
|
|
108
|
+
const body = await r.json().catch(() => ({}));
|
|
109
|
+
if (!r.ok) throw new Error(body?.error ?? `HTTP ${r.status}`);
|
|
110
|
+
return body;
|
|
111
|
+
}
|
|
112
|
+
function useGsheetsApi() {
|
|
113
|
+
const { apiBaseUrl } = useDevToolsConfig();
|
|
114
|
+
const list = useCallback(
|
|
115
|
+
async () => jsonOrThrow(
|
|
116
|
+
await fetch(`${apiBaseUrl}/_console/integrations/gsheets`, {
|
|
117
|
+
credentials: "include",
|
|
118
|
+
headers: authHeaders()
|
|
119
|
+
})
|
|
120
|
+
),
|
|
121
|
+
[apiBaseUrl]
|
|
122
|
+
);
|
|
123
|
+
const save = useCallback(
|
|
124
|
+
async (input) => jsonOrThrow(
|
|
125
|
+
await fetch(`${apiBaseUrl}/_console/integrations/gsheets`, {
|
|
126
|
+
method: "POST",
|
|
127
|
+
credentials: "include",
|
|
128
|
+
headers: authHeaders({ "content-type": "application/json" }),
|
|
129
|
+
body: JSON.stringify(input)
|
|
130
|
+
})
|
|
131
|
+
),
|
|
132
|
+
[apiBaseUrl]
|
|
133
|
+
);
|
|
134
|
+
const syncNow = useCallback(
|
|
135
|
+
async (cartridge) => jsonOrThrow(
|
|
136
|
+
await fetch(`${apiBaseUrl}/_console/integrations/gsheets/sync`, {
|
|
137
|
+
method: "POST",
|
|
138
|
+
credentials: "include",
|
|
139
|
+
headers: authHeaders({ "content-type": "application/json" }),
|
|
140
|
+
body: JSON.stringify({ cartridge })
|
|
141
|
+
})
|
|
142
|
+
),
|
|
143
|
+
[apiBaseUrl]
|
|
144
|
+
);
|
|
145
|
+
const remove = useCallback(
|
|
146
|
+
async (cartridge) => jsonOrThrow(
|
|
147
|
+
await fetch(`${apiBaseUrl}/_console/integrations/gsheets/${encodeURIComponent(cartridge)}`, {
|
|
148
|
+
method: "DELETE",
|
|
149
|
+
credentials: "include",
|
|
150
|
+
headers: authHeaders()
|
|
151
|
+
})
|
|
152
|
+
),
|
|
153
|
+
[apiBaseUrl]
|
|
154
|
+
);
|
|
155
|
+
return useMemo(() => ({ list, save, syncNow, remove }), [list, save, syncNow, remove]);
|
|
156
|
+
}
|
|
104
157
|
export {
|
|
105
158
|
BulkStreamProvider,
|
|
106
159
|
data_table_default as DataTable,
|
|
@@ -129,6 +182,7 @@ export {
|
|
|
129
182
|
useBulkRowCounts,
|
|
130
183
|
useBulkSubscription,
|
|
131
184
|
useDevToolsConfig,
|
|
185
|
+
useGsheetsApi,
|
|
132
186
|
useLiveBulkRegistry,
|
|
133
187
|
wsAuthProtocols,
|
|
134
188
|
wsBaseFrom
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/core/use-cartridge-info.ts"],
|
|
4
|
-
"sourcesContent": ["import { useEffect, useState } from \"react\";\nimport { useDevToolsConfig } from \"./config\";\nimport { authHeaders } from \"./auth\";\n\n// Live cartridge/entity registry for the Data tab. Instead of the build-time\n// `BULK_REGISTRY` (every cart in the repo, generated by `nbt generate`), we\n// read the daemon's `/_console/contracts` \u2014 which only lists *running*\n// cartridges and carries each entity's `searchFields`. Bulk WS routes are\n// derived live (`/_ws/bulk/<cart>/<entity-lower>`) and fed straight into\n// useBulkStream. So the tab reflects what is actually installed right now.\n\nexport type BulkEntity = {\n name: string;\n route: string;\n searchFields: readonly string[];\n};\nexport type BulkRegistry = Record<string, BulkEntity[]>;\n\ntype Contract = {\n cartridge?: string;\n core?: boolean;\n owns?: Record<string, { searchFields?: string[] }>;\n};\n\nfunction buildRegistry(contracts: Contract[]): { reg: BulkRegistry; core: Set<string> } {\n const reg: BulkRegistry = {};\n const core = new Set<string>();\n for (const c of contracts) {\n const cart = c.cartridge;\n if (!cart || !c.owns) continue;\n const entities: BulkEntity[] = [];\n for (const [name, ent] of Object.entries(c.owns)) {\n const sf = Array.isArray(ent?.searchFields) ? ent.searchFields : [];\n entities.push({\n name,\n route: `/_ws/bulk/${cart}/${name.toLowerCase()}`,\n searchFields: sf,\n });\n }\n if (entities.length > 0) {\n reg[cart] = entities;\n if (c.core) core.add(cart);\n }\n }\n return { reg, core };\n}\n\nexport type LiveRegistryState = {\n registry: BulkRegistry;\n carts: string[];\n // Cartridge slugs flagged `core` by the daemon. The Data tab keeps `auth`\n // visible but tucks the rest of these behind a \"more cartridges\" menu so\n // deployed (user) cartridges are what's shown by default.\n coreCarts: Set<string>;\n loading: boolean;\n error: string | null;\n};\n\nexport function useLiveBulkRegistry(): LiveRegistryState {\n const { apiBaseUrl } = useDevToolsConfig();\n const [registry, setRegistry] = useState<BulkRegistry>({});\n const [coreCarts, setCoreCarts] = useState<Set<string>>(new Set());\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const ac = new AbortController();\n let cancelled = false;\n setLoading(true);\n setError(null);\n (async () => {\n try {\n const r = await fetch(`${apiBaseUrl}/_console/contracts`, {\n signal: ac.signal,\n credentials: \"include\",\n headers: authHeaders(),\n });\n if (!r.ok) throw new Error(`HTTP ${r.status}`);\n const data = (await r.json()) as Contract[];\n if (!Array.isArray(data)) throw new Error(\"malformed contracts response\");\n if (cancelled) return;\n const { reg, core } = buildRegistry(data);\n setRegistry(reg);\n setCoreCarts(core);\n } catch (e) {\n if (cancelled || (e as { name?: string }).name === \"AbortError\") return;\n setError(e instanceof Error ? e.message : String(e));\n } finally {\n if (!cancelled) setLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n ac.abort();\n };\n }, [apiBaseUrl]);\n\n const carts = Object.keys(registry).sort();\n return { registry, carts, coreCarts, loading, error };\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,WAAW,gBAAgB;AAwBpC,SAAS,cAAc,WAAiE;AACtF,QAAM,MAAoB,CAAC;AAC3B,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,KAAK,WAAW;AACzB,UAAM,OAAO,EAAE;AACf,QAAI,CAAC,QAAQ,CAAC,EAAE,KAAM;AACtB,UAAM,WAAyB,CAAC;AAChC,eAAW,CAAC,MAAM,GAAG,KAAK,OAAO,QAAQ,EAAE,IAAI,GAAG;AAChD,YAAM,KAAK,MAAM,QAAQ,KAAK,YAAY,IAAI,IAAI,eAAe,CAAC;AAClE,eAAS,KAAK;AAAA,QACZ;AAAA,QACA,OAAO,aAAa,IAAI,IAAI,KAAK,YAAY,CAAC;AAAA,QAC9C,cAAc;AAAA,MAChB,CAAC;AAAA,IACH;AACA,QAAI,SAAS,SAAS,GAAG;AACvB,UAAI,IAAI,IAAI;AACZ,UAAI,EAAE,KAAM,MAAK,IAAI,IAAI;AAAA,IAC3B;AAAA,EACF;AACA,SAAO,EAAE,KAAK,KAAK;AACrB;AAaO,SAAS,sBAAyC;AACvD,QAAM,EAAE,WAAW,IAAI,kBAAkB;AACzC,QAAM,CAAC,UAAU,WAAW,IAAI,SAAuB,CAAC,CAAC;AACzD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAsB,oBAAI,IAAI,CAAC;AACjE,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AAEtD,YAAU,MAAM;AACd,UAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAI,YAAY;AAChB,eAAW,IAAI;AACf,aAAS,IAAI;AACb,KAAC,YAAY;AACX,UAAI;AACF,cAAM,IAAI,MAAM,MAAM,GAAG,UAAU,uBAAuB;AAAA,UACxD,QAAQ,GAAG;AAAA,UACX,aAAa;AAAA,UACb,SAAS,YAAY;AAAA,QACvB,CAAC;AACD,YAAI,CAAC,EAAE,GAAI,OAAM,IAAI,MAAM,QAAQ,EAAE,MAAM,EAAE;AAC7C,cAAM,OAAQ,MAAM,EAAE,KAAK;AAC3B,YAAI,CAAC,MAAM,QAAQ,IAAI,EAAG,OAAM,IAAI,MAAM,8BAA8B;AACxE,YAAI,UAAW;AACf,cAAM,EAAE,KAAK,KAAK,IAAI,cAAc,IAAI;AACxC,oBAAY,GAAG;AACf,qBAAa,IAAI;AAAA,MACnB,SAAS,GAAG;AACV,YAAI,aAAc,EAAwB,SAAS,aAAc;AACjE,iBAAS,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,MACrD,UAAE;AACA,YAAI,CAAC,UAAW,YAAW,KAAK;AAAA,MAClC;AAAA,IACF,GAAG;AACH,WAAO,MAAM;AACX,kBAAY;AACZ,SAAG,MAAM;AAAA,IACX;AAAA,EACF,GAAG,CAAC,UAAU,CAAC;AAEf,QAAM,QAAQ,OAAO,KAAK,QAAQ,EAAE,KAAK;AACzC,SAAO,EAAE,UAAU,OAAO,WAAW,SAAS,MAAM;AACtD;",
|
|
3
|
+
"sources": ["../src/core/use-cartridge-info.ts", "../src/core/use-gsheets.ts"],
|
|
4
|
+
"sourcesContent": ["import { useEffect, useState } from \"react\";\nimport { useDevToolsConfig } from \"./config\";\nimport { authHeaders } from \"./auth\";\n\n// Live cartridge/entity registry for the Data tab. Instead of the build-time\n// `BULK_REGISTRY` (every cart in the repo, generated by `nbt generate`), we\n// read the daemon's `/_console/contracts` \u2014 which only lists *running*\n// cartridges and carries each entity's `searchFields`. Bulk WS routes are\n// derived live (`/_ws/bulk/<cart>/<entity-lower>`) and fed straight into\n// useBulkStream. So the tab reflects what is actually installed right now.\n\nexport type BulkEntity = {\n name: string;\n route: string;\n searchFields: readonly string[];\n};\nexport type BulkRegistry = Record<string, BulkEntity[]>;\n\ntype Contract = {\n cartridge?: string;\n core?: boolean;\n owns?: Record<string, { searchFields?: string[] }>;\n};\n\nfunction buildRegistry(contracts: Contract[]): { reg: BulkRegistry; core: Set<string> } {\n const reg: BulkRegistry = {};\n const core = new Set<string>();\n for (const c of contracts) {\n const cart = c.cartridge;\n if (!cart || !c.owns) continue;\n const entities: BulkEntity[] = [];\n for (const [name, ent] of Object.entries(c.owns)) {\n const sf = Array.isArray(ent?.searchFields) ? ent.searchFields : [];\n entities.push({\n name,\n route: `/_ws/bulk/${cart}/${name.toLowerCase()}`,\n searchFields: sf,\n });\n }\n if (entities.length > 0) {\n reg[cart] = entities;\n if (c.core) core.add(cart);\n }\n }\n return { reg, core };\n}\n\nexport type LiveRegistryState = {\n registry: BulkRegistry;\n carts: string[];\n // Cartridge slugs flagged `core` by the daemon. The Data tab keeps `auth`\n // visible but tucks the rest of these behind a \"more cartridges\" menu so\n // deployed (user) cartridges are what's shown by default.\n coreCarts: Set<string>;\n loading: boolean;\n error: string | null;\n};\n\nexport function useLiveBulkRegistry(): LiveRegistryState {\n const { apiBaseUrl } = useDevToolsConfig();\n const [registry, setRegistry] = useState<BulkRegistry>({});\n const [coreCarts, setCoreCarts] = useState<Set<string>>(new Set());\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const ac = new AbortController();\n let cancelled = false;\n setLoading(true);\n setError(null);\n (async () => {\n try {\n const r = await fetch(`${apiBaseUrl}/_console/contracts`, {\n signal: ac.signal,\n credentials: \"include\",\n headers: authHeaders(),\n });\n if (!r.ok) throw new Error(`HTTP ${r.status}`);\n const data = (await r.json()) as Contract[];\n if (!Array.isArray(data)) throw new Error(\"malformed contracts response\");\n if (cancelled) return;\n const { reg, core } = buildRegistry(data);\n setRegistry(reg);\n setCoreCarts(core);\n } catch (e) {\n if (cancelled || (e as { name?: string }).name === \"AbortError\") return;\n setError(e instanceof Error ? e.message : String(e));\n } finally {\n if (!cancelled) setLoading(false);\n }\n })();\n return () => {\n cancelled = true;\n ac.abort();\n };\n }, [apiBaseUrl]);\n\n const carts = Object.keys(registry).sort();\n return { registry, carts, coreCarts, loading, error };\n}\n", "// Fetchers + hook for the Google Sheets cartridge-sync admin endpoints\n// (/_console/integrations/gsheets/*). Admin-gated server-side \u2014 the requests\n// carry the devtools Bearer token (authHeaders) like the contracts probe.\n\nimport { useCallback, useMemo } from \"react\";\nimport { useDevToolsConfig } from \"./config\";\nimport { authHeaders } from \"./auth\";\n\nexport type GsheetSyncConfig = {\n cartridge: string;\n spreadsheetId: string;\n saEmail: string;\n hasServiceAccount: boolean;\n intervalSeconds: number;\n enabled: boolean;\n lastSyncAt: number; // ms epoch, 0 = never\n status: string; // \"ok\" | \"error\" | \"\"\n lastError: string;\n};\n\nexport type GsheetSaveInput = {\n cartridge: string;\n spreadsheetUrl: string;\n serviceAccountJson?: string; // omit to keep the stored key\n intervalSeconds: number;\n enabled: boolean;\n};\n\nasync function jsonOrThrow(r: Response) {\n const body = await r.json().catch(() => ({}));\n if (!r.ok) throw new Error((body as { error?: string })?.error ?? `HTTP ${r.status}`);\n return body;\n}\n\nexport function useGsheetsApi() {\n const { apiBaseUrl } = useDevToolsConfig();\n\n const list = useCallback(\n async (): Promise<GsheetSyncConfig[]> =>\n jsonOrThrow(\n await fetch(`${apiBaseUrl}/_console/integrations/gsheets`, {\n credentials: \"include\",\n headers: authHeaders(),\n }),\n ),\n [apiBaseUrl],\n );\n\n const save = useCallback(\n async (input: GsheetSaveInput): Promise<GsheetSyncConfig> =>\n jsonOrThrow(\n await fetch(`${apiBaseUrl}/_console/integrations/gsheets`, {\n method: \"POST\",\n credentials: \"include\",\n headers: authHeaders({ \"content-type\": \"application/json\" }),\n body: JSON.stringify(input),\n }),\n ),\n [apiBaseUrl],\n );\n\n const syncNow = useCallback(\n async (cartridge: string): Promise<{ ok: boolean; error: string }> =>\n jsonOrThrow(\n await fetch(`${apiBaseUrl}/_console/integrations/gsheets/sync`, {\n method: \"POST\",\n credentials: \"include\",\n headers: authHeaders({ \"content-type\": \"application/json\" }),\n body: JSON.stringify({ cartridge }),\n }),\n ),\n [apiBaseUrl],\n );\n\n const remove = useCallback(\n async (cartridge: string): Promise<{ ok: boolean }> =>\n jsonOrThrow(\n await fetch(`${apiBaseUrl}/_console/integrations/gsheets/${encodeURIComponent(cartridge)}`, {\n method: \"DELETE\",\n credentials: \"include\",\n headers: authHeaders(),\n }),\n ),\n [apiBaseUrl],\n );\n\n return useMemo(() => ({ list, save, syncNow, remove }), [list, save, syncNow, remove]);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nbt-dev/components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "Reusable React building blocks for NBT-console apps: the CodeMirror NBT editor (+LSP), the entity graph, and the live data table.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
package/src/core/index.ts
CHANGED
|
@@ -21,6 +21,8 @@ export type {
|
|
|
21
21
|
BulkRegistry,
|
|
22
22
|
LiveRegistryState,
|
|
23
23
|
} from "./use-cartridge-info";
|
|
24
|
+
export { useGsheetsApi } from "./use-gsheets";
|
|
25
|
+
export type { GsheetSyncConfig, GsheetSaveInput } from "./use-gsheets";
|
|
24
26
|
export {
|
|
25
27
|
getDevToolsToken,
|
|
26
28
|
setDevToolsToken,
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Fetchers + hook for the Google Sheets cartridge-sync admin endpoints
|
|
2
|
+
// (/_console/integrations/gsheets/*). Admin-gated server-side — the requests
|
|
3
|
+
// carry the devtools Bearer token (authHeaders) like the contracts probe.
|
|
4
|
+
|
|
5
|
+
import { useCallback, useMemo } from "react";
|
|
6
|
+
import { useDevToolsConfig } from "./config";
|
|
7
|
+
import { authHeaders } from "./auth";
|
|
8
|
+
|
|
9
|
+
export type GsheetSyncConfig = {
|
|
10
|
+
cartridge: string;
|
|
11
|
+
spreadsheetId: string;
|
|
12
|
+
saEmail: string;
|
|
13
|
+
hasServiceAccount: boolean;
|
|
14
|
+
intervalSeconds: number;
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
lastSyncAt: number; // ms epoch, 0 = never
|
|
17
|
+
status: string; // "ok" | "error" | ""
|
|
18
|
+
lastError: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type GsheetSaveInput = {
|
|
22
|
+
cartridge: string;
|
|
23
|
+
spreadsheetUrl: string;
|
|
24
|
+
serviceAccountJson?: string; // omit to keep the stored key
|
|
25
|
+
intervalSeconds: number;
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
async function jsonOrThrow(r: Response) {
|
|
30
|
+
const body = await r.json().catch(() => ({}));
|
|
31
|
+
if (!r.ok) throw new Error((body as { error?: string })?.error ?? `HTTP ${r.status}`);
|
|
32
|
+
return body;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useGsheetsApi() {
|
|
36
|
+
const { apiBaseUrl } = useDevToolsConfig();
|
|
37
|
+
|
|
38
|
+
const list = useCallback(
|
|
39
|
+
async (): Promise<GsheetSyncConfig[]> =>
|
|
40
|
+
jsonOrThrow(
|
|
41
|
+
await fetch(`${apiBaseUrl}/_console/integrations/gsheets`, {
|
|
42
|
+
credentials: "include",
|
|
43
|
+
headers: authHeaders(),
|
|
44
|
+
}),
|
|
45
|
+
),
|
|
46
|
+
[apiBaseUrl],
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const save = useCallback(
|
|
50
|
+
async (input: GsheetSaveInput): Promise<GsheetSyncConfig> =>
|
|
51
|
+
jsonOrThrow(
|
|
52
|
+
await fetch(`${apiBaseUrl}/_console/integrations/gsheets`, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
credentials: "include",
|
|
55
|
+
headers: authHeaders({ "content-type": "application/json" }),
|
|
56
|
+
body: JSON.stringify(input),
|
|
57
|
+
}),
|
|
58
|
+
),
|
|
59
|
+
[apiBaseUrl],
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const syncNow = useCallback(
|
|
63
|
+
async (cartridge: string): Promise<{ ok: boolean; error: string }> =>
|
|
64
|
+
jsonOrThrow(
|
|
65
|
+
await fetch(`${apiBaseUrl}/_console/integrations/gsheets/sync`, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
credentials: "include",
|
|
68
|
+
headers: authHeaders({ "content-type": "application/json" }),
|
|
69
|
+
body: JSON.stringify({ cartridge }),
|
|
70
|
+
}),
|
|
71
|
+
),
|
|
72
|
+
[apiBaseUrl],
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const remove = useCallback(
|
|
76
|
+
async (cartridge: string): Promise<{ ok: boolean }> =>
|
|
77
|
+
jsonOrThrow(
|
|
78
|
+
await fetch(`${apiBaseUrl}/_console/integrations/gsheets/${encodeURIComponent(cartridge)}`, {
|
|
79
|
+
method: "DELETE",
|
|
80
|
+
credentials: "include",
|
|
81
|
+
headers: authHeaders(),
|
|
82
|
+
}),
|
|
83
|
+
),
|
|
84
|
+
[apiBaseUrl],
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return useMemo(() => ({ list, save, syncNow, remove }), [list, save, syncNow, remove]);
|
|
88
|
+
}
|