gatsby-attainlabs-cms 1.1.0 → 1.1.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/gatsby-browser.js +1 -1
- package/gatsby-node.js +295 -213
- package/package.json +1 -1
- package/tsconfig.json +1 -2
- package/dist/index.d.ts +0 -9
- package/dist/index.js +0 -8
- package/dist/map.d.ts +0 -3
- package/dist/map.js +0 -9
- package/dist/sliceWrapper.d.ts +0 -10
- package/dist/sliceWrapper.js +0 -139
- package/src/index.ts +0 -18
- package/src/map.ts +0 -16
- package/src/sliceWrapper.tsx +0 -161
package/gatsby-browser.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
exports.onClientEntry = (_, pluginOptions) => {
|
|
2
2
|
const { brand } = pluginOptions;
|
|
3
3
|
|
|
4
|
-
if (brand === "LendDirect") {
|
|
4
|
+
if (brand === "LendDirect" || brand === "Cash Money") {
|
|
5
5
|
import("@fontsource/open-sans/300.css");
|
|
6
6
|
import("@fontsource/open-sans/400.css");
|
|
7
7
|
import("@fontsource/open-sans/500.css");
|
package/gatsby-node.js
CHANGED
|
@@ -4,7 +4,7 @@ const path = require("path");
|
|
|
4
4
|
const prettier = require("prettier");
|
|
5
5
|
const fse = require("fs-extra");
|
|
6
6
|
const crypto = require("crypto");
|
|
7
|
-
const { graphql } = require("gatsby");
|
|
7
|
+
const { graphql } = require("gatsby"); // kept (even if unused here) to match your existing file
|
|
8
8
|
|
|
9
9
|
const brands = {
|
|
10
10
|
LendDirect: "lenddirect",
|
|
@@ -15,7 +15,227 @@ const brands = {
|
|
|
15
15
|
|
|
16
16
|
const generateSliceWrapper = () => {};
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* ----------------------------
|
|
20
|
+
* Promise-based download helpers
|
|
21
|
+
* ----------------------------
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
function httpsGet(url, options) {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
https
|
|
27
|
+
.get(url, options, (res) => {
|
|
28
|
+
let data = "";
|
|
29
|
+
res.on("data", (chunk) => (data += chunk));
|
|
30
|
+
res.on("end", () => resolve({ res, data }));
|
|
31
|
+
})
|
|
32
|
+
.on("error", reject);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function listItems({ org, project, repo, repoPath, branch, options }) {
|
|
37
|
+
const listUrl = `https://dev.azure.com/${org}/${project}/_apis/git/repositories/${encodeURIComponent(
|
|
38
|
+
repo
|
|
39
|
+
)}/items?scopePath=${encodeURIComponent(
|
|
40
|
+
repoPath
|
|
41
|
+
)}&recursionLevel=full&includeContentMetadata=true&versionDescriptor.version=${encodeURIComponent(
|
|
42
|
+
branch
|
|
43
|
+
)}&versionDescriptor.versionType=branch&api-version=7.0`;
|
|
44
|
+
|
|
45
|
+
const { res, data } = await httpsGet(listUrl, options);
|
|
46
|
+
|
|
47
|
+
if (res.statusCode !== 200) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Failed to list items for ${repoPath}: ${res.statusCode} ${res.statusMessage}\n${data}`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const result = JSON.parse(data);
|
|
54
|
+
const posix = path.posix;
|
|
55
|
+
|
|
56
|
+
// Exclude files named config.tsx and preview.tsx (same as your original logic)
|
|
57
|
+
return (result.value || []).filter(
|
|
58
|
+
(i) =>
|
|
59
|
+
!i.isFolder &&
|
|
60
|
+
i.gitObjectType === "blob" &&
|
|
61
|
+
posix.basename(i.path) !== "config.tsx" &&
|
|
62
|
+
posix.basename(i.path) !== "preview.tsx"
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function downloadFileAsync({
|
|
67
|
+
org,
|
|
68
|
+
project,
|
|
69
|
+
repo,
|
|
70
|
+
repoPath,
|
|
71
|
+
filePath,
|
|
72
|
+
localBasePath,
|
|
73
|
+
branch,
|
|
74
|
+
options,
|
|
75
|
+
}) {
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
const fileUrl = `https://dev.azure.com/${org}/${project}/_apis/git/repositories/${encodeURIComponent(
|
|
78
|
+
repo
|
|
79
|
+
)}/items?path=${encodeURIComponent(
|
|
80
|
+
filePath
|
|
81
|
+
)}&versionDescriptor.version=${encodeURIComponent(
|
|
82
|
+
branch
|
|
83
|
+
)}&versionDescriptor.versionType=branch&api-version=7.0&download=true`;
|
|
84
|
+
|
|
85
|
+
const posix = path.posix;
|
|
86
|
+
const relativePath = posix.relative(repoPath, filePath);
|
|
87
|
+
const destFile = path.join(localBasePath, ...relativePath.split("/"));
|
|
88
|
+
const destDir = path.dirname(destFile);
|
|
89
|
+
|
|
90
|
+
if (!fs.existsSync(destDir)) {
|
|
91
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const doRequest = (url) => {
|
|
95
|
+
https
|
|
96
|
+
.get(url, options, (res) => {
|
|
97
|
+
// Follow redirects
|
|
98
|
+
if (
|
|
99
|
+
res.statusCode &&
|
|
100
|
+
res.statusCode >= 300 &&
|
|
101
|
+
res.statusCode < 400 &&
|
|
102
|
+
res.headers.location
|
|
103
|
+
) {
|
|
104
|
+
res.resume();
|
|
105
|
+
return doRequest(res.headers.location);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (res.statusCode !== 200) {
|
|
109
|
+
res.resume();
|
|
110
|
+
return reject(
|
|
111
|
+
new Error(
|
|
112
|
+
`Failed to download ${filePath}: ${res.statusCode} ${res.statusMessage}`
|
|
113
|
+
)
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const uniqueId = Math.random().toString(36).substring(7);
|
|
118
|
+
const tmpFile = destFile + "." + uniqueId + ".part";
|
|
119
|
+
const fileStream = fs.createWriteStream(tmpFile);
|
|
120
|
+
|
|
121
|
+
res.pipe(fileStream);
|
|
122
|
+
|
|
123
|
+
fileStream.on("error", reject);
|
|
124
|
+
res.on("error", reject);
|
|
125
|
+
|
|
126
|
+
fileStream.on("finish", () => {
|
|
127
|
+
fileStream.close(() => {
|
|
128
|
+
try {
|
|
129
|
+
const ext = path.extname(destFile).toLowerCase();
|
|
130
|
+
let generatedComment = "";
|
|
131
|
+
if ([".ts", ".tsx", ".js", ".jsx"].includes(ext)) {
|
|
132
|
+
generatedComment =
|
|
133
|
+
"// GENERATED FILE // This file is automatically generated by the AttainLabs CMS plugin. Do not edit this file directly.\n";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const fileContent = fs.readFileSync(tmpFile, "utf8");
|
|
137
|
+
fs.writeFileSync(destFile, generatedComment + fileContent);
|
|
138
|
+
fs.unlinkSync(tmpFile);
|
|
139
|
+
|
|
140
|
+
resolve(destFile);
|
|
141
|
+
} catch (e) {
|
|
142
|
+
reject(e);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
})
|
|
147
|
+
.on("error", reject);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
doRequest(fileUrl);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function downloadTarget({
|
|
155
|
+
org,
|
|
156
|
+
project,
|
|
157
|
+
repo,
|
|
158
|
+
repoPath,
|
|
159
|
+
localBasePath,
|
|
160
|
+
branch,
|
|
161
|
+
options,
|
|
162
|
+
}) {
|
|
163
|
+
const items = await listItems({
|
|
164
|
+
org,
|
|
165
|
+
project,
|
|
166
|
+
repo,
|
|
167
|
+
repoPath,
|
|
168
|
+
branch,
|
|
169
|
+
options,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
console.log(`Found ${items.length} files in ${repoPath}`);
|
|
173
|
+
|
|
174
|
+
const results = await Promise.all(
|
|
175
|
+
items.map((item) =>
|
|
176
|
+
downloadFileAsync({
|
|
177
|
+
org,
|
|
178
|
+
project,
|
|
179
|
+
repo,
|
|
180
|
+
repoPath,
|
|
181
|
+
filePath: item.path,
|
|
182
|
+
localBasePath,
|
|
183
|
+
branch,
|
|
184
|
+
options,
|
|
185
|
+
})
|
|
186
|
+
)
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
console.log(`✅ Completed downloading all files for target: ${repoPath}`);
|
|
190
|
+
return results;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function rewriteSliceWrapper(fetchConfig) {
|
|
194
|
+
const sliceWrapperPath = path.resolve(
|
|
195
|
+
"./src/cms/components/sliceWrapper.tsx"
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
if (!fs.existsSync(sliceWrapperPath)) {
|
|
199
|
+
console.warn(
|
|
200
|
+
`⚠️ [gatsby-attainlabs-cms] sliceWrapper.tsx not found at ${sliceWrapperPath}. Skipping rewrite.`
|
|
201
|
+
);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let content = fs.readFileSync(sliceWrapperPath, "utf8");
|
|
206
|
+
|
|
207
|
+
if (!fetchConfig || !fetchConfig.includes("blogs")) {
|
|
208
|
+
content = content.replace(
|
|
209
|
+
/# CHECK_BLOGS_START[\s\S]*?# CHECK_BLOGS_END/g,
|
|
210
|
+
""
|
|
211
|
+
);
|
|
212
|
+
content = content.replace(
|
|
213
|
+
/\/\* CHECK_BLOGS_START \*\/[\s\S]*?\/\* CHECK_BLOGS_END \*\//g,
|
|
214
|
+
""
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (!fetchConfig || !fetchConfig.includes("disclaimers")) {
|
|
219
|
+
content = content.replace(
|
|
220
|
+
/# CHECK_DISCLAIMERS_START[\s\S]*?# CHECK_DISCLAIMERS_END/g,
|
|
221
|
+
""
|
|
222
|
+
);
|
|
223
|
+
content = content.replace(
|
|
224
|
+
/\/\* CHECK_DISCLAIMERS_START \*\/[\s\S]*?\/\* CHECK_DISCLAIMERS_END \*\//g,
|
|
225
|
+
""
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
fs.writeFileSync(sliceWrapperPath, content);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* ----------------------------
|
|
234
|
+
* Existing code (mostly unchanged)
|
|
235
|
+
* ----------------------------
|
|
236
|
+
*/
|
|
237
|
+
|
|
238
|
+
const generatePage = async (blocks, layout, isNested, environment, page) => {
|
|
19
239
|
// Validate input parameters
|
|
20
240
|
if (!Array.isArray(blocks)) {
|
|
21
241
|
throw new Error("Invalid parameters passed to createPage.");
|
|
@@ -33,8 +253,8 @@ const generatePage = async (blocks, layout, isNested) => {
|
|
|
33
253
|
import SEO from "../${isNested && "../"}../cms/components/SEO";
|
|
34
254
|
|
|
35
255
|
export const Head = ({ pageContext }: any) => {
|
|
36
|
-
const blocks = pageContext.blocks
|
|
37
|
-
const meta = blocks.root.props.meta
|
|
256
|
+
const blocks = ${environment === "staging" ? (page.draft ? "pageContext.draft.blocks" : "pageContext.blocks") : "pageContext.blocks"}
|
|
257
|
+
const meta = blocks.root.props.meta
|
|
38
258
|
return (
|
|
39
259
|
<SEO
|
|
40
260
|
title={meta ? meta.title : ""}
|
|
@@ -90,6 +310,7 @@ const getAllBlogs = (data) => {
|
|
|
90
310
|
};
|
|
91
311
|
|
|
92
312
|
require("dotenv").config();
|
|
313
|
+
|
|
93
314
|
// Load PAT from env instead of hardcoding (fallback to old const for now but warn)
|
|
94
315
|
exports.onPreInit = async (_, pluginOptions) => {
|
|
95
316
|
const {
|
|
@@ -135,7 +356,7 @@ exports.onPreInit = async (_, pluginOptions) => {
|
|
|
135
356
|
|
|
136
357
|
// Helper to fetch latest PR branch
|
|
137
358
|
const getLatestPullRequestBranch = (org, project, repo, pat) => {
|
|
138
|
-
return new Promise((resolve
|
|
359
|
+
return new Promise((resolve) => {
|
|
139
360
|
const options = {
|
|
140
361
|
headers: {
|
|
141
362
|
Authorization: `Basic ${Buffer.from(`:${pat}`).toString("base64")}`,
|
|
@@ -143,8 +364,7 @@ exports.onPreInit = async (_, pluginOptions) => {
|
|
|
143
364
|
},
|
|
144
365
|
};
|
|
145
366
|
|
|
146
|
-
// Fetch top 10 active PRs
|
|
147
|
-
// Note: Azure DevOps usually sorts by ID desc (creation time), but fetching a batch is safer.
|
|
367
|
+
// Fetch top 10 active PRs
|
|
148
368
|
const url = `https://dev.azure.com/${org}/${project}/_apis/git/repositories/${encodeURIComponent(
|
|
149
369
|
repo
|
|
150
370
|
)}/pullrequests?searchCriteria.status=active&top=10&api-version=7.0`;
|
|
@@ -164,8 +384,6 @@ exports.onPreInit = async (_, pluginOptions) => {
|
|
|
164
384
|
try {
|
|
165
385
|
const result = JSON.parse(data);
|
|
166
386
|
if (result.value && result.value.length > 0) {
|
|
167
|
-
// Sort by creationDate descending to get the latest created PR
|
|
168
|
-
// (Since we can't easily get 'last updated/pushed' without extra calls, creation is the standard proxy)
|
|
169
387
|
const latestPr = result.value.sort(
|
|
170
388
|
(a, b) =>
|
|
171
389
|
new Date(b.creationDate).getTime() -
|
|
@@ -230,18 +448,6 @@ exports.onPreInit = async (_, pluginOptions) => {
|
|
|
230
448
|
}
|
|
231
449
|
}
|
|
232
450
|
|
|
233
|
-
const localTargets = [
|
|
234
|
-
{
|
|
235
|
-
localPath: path.resolve(__dirname, "src/sliceWrapper.tsx"),
|
|
236
|
-
targetPath: path.resolve("./src/cms/components/"),
|
|
237
|
-
},
|
|
238
|
-
{
|
|
239
|
-
localPath: path.resolve(__dirname, "src/map.ts"),
|
|
240
|
-
targetPath: path.resolve("./src/cms/components/"),
|
|
241
|
-
},
|
|
242
|
-
];
|
|
243
|
-
|
|
244
|
-
// List of folders to download from
|
|
245
451
|
const targets = [
|
|
246
452
|
{
|
|
247
453
|
repoPath: `/apps/cms/src/cms/editors/visual-block-editor/brands/${brands[brand]}/`,
|
|
@@ -264,179 +470,30 @@ exports.onPreInit = async (_, pluginOptions) => {
|
|
|
264
470
|
},
|
|
265
471
|
};
|
|
266
472
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
);
|
|
284
|
-
content = content.replace(
|
|
285
|
-
/\/\* CHECK_BLOGS_START \*\/[\s\S]*?\/\* CHECK_BLOGS_END \*\//g,
|
|
286
|
-
""
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (!fetchConfig || !fetchConfig.includes("disclaimers")) {
|
|
291
|
-
content = content.replace(
|
|
292
|
-
/# CHECK_DISCLAIMERS_START[\s\S]*?# CHECK_DISCLAIMERS_END/g,
|
|
293
|
-
""
|
|
294
|
-
);
|
|
295
|
-
content = content.replace(
|
|
296
|
-
/\/\* CHECK_DISCLAIMERS_START \*\/[\s\S]*?\/\* CHECK_DISCLAIMERS_END \*\//g,
|
|
297
|
-
""
|
|
298
|
-
);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
fs.writeFileSync(localPath, content);
|
|
302
|
-
fs.writeFileSync(destFile, content);
|
|
303
|
-
console.log(`✅ Copied and transformed ${destFile}`);
|
|
304
|
-
} else {
|
|
305
|
-
fs.copyFileSync(localPath, destFile);
|
|
306
|
-
console.log(`✅ Copied ${destFile}`);
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
// Loop through targets
|
|
311
|
-
targets.forEach(({ repoPath, localBasePath }) => {
|
|
312
|
-
const listUrl = `https://dev.azure.com/${org}/${project}/_apis/git/repositories/${encodeURIComponent(
|
|
313
|
-
repo
|
|
314
|
-
)}/items?scopePath=${encodeURIComponent(
|
|
315
|
-
repoPath
|
|
316
|
-
)}&recursionLevel=full&includeContentMetadata=true&versionDescriptor.version=${encodeURIComponent(
|
|
317
|
-
branch
|
|
318
|
-
)}&versionDescriptor.versionType=branch&api-version=7.0`;
|
|
319
|
-
|
|
320
|
-
https
|
|
321
|
-
.get(listUrl, options, (res) => {
|
|
322
|
-
let data = "";
|
|
323
|
-
res.on("data", (chunk) => (data += chunk));
|
|
324
|
-
res.on("end", () => {
|
|
325
|
-
if (res.statusCode !== 200) {
|
|
326
|
-
console.error(
|
|
327
|
-
`Failed to list items for ${repoPath}: ${res.statusCode} ${res.statusMessage}`
|
|
328
|
-
);
|
|
329
|
-
console.error(data);
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const result = JSON.parse(data);
|
|
334
|
-
const posix = path.posix;
|
|
335
|
-
|
|
336
|
-
// Exclude files named config.tsx
|
|
337
|
-
const items = (result.value || []).filter(
|
|
338
|
-
(i) =>
|
|
339
|
-
!i.isFolder &&
|
|
340
|
-
i.gitObjectType === "blob" &&
|
|
341
|
-
posix.basename(i.path) !== "config.tsx" &&
|
|
342
|
-
posix.basename(i.path) !== "preview.tsx"
|
|
343
|
-
);
|
|
344
|
-
|
|
345
|
-
console.log(`Found ${items.length} files in ${repoPath}`);
|
|
346
|
-
let completedDownloads = 0;
|
|
347
|
-
|
|
348
|
-
items.forEach((item) => {
|
|
349
|
-
downloadFile(item.path, repoPath, localBasePath, () => {
|
|
350
|
-
completedDownloads++;
|
|
351
|
-
if (completedDownloads === items.length) {
|
|
352
|
-
console.log(
|
|
353
|
-
`✅ Completed downloading all files for target: ${repoPath}`
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
});
|
|
357
|
-
});
|
|
358
|
-
});
|
|
473
|
+
/**
|
|
474
|
+
* ✅ Awaitable download pipeline:
|
|
475
|
+
* - list items
|
|
476
|
+
* - download all files
|
|
477
|
+
* - only after all targets complete, rewrite sliceWrapper.tsx
|
|
478
|
+
*/
|
|
479
|
+
await Promise.all(
|
|
480
|
+
targets.map((t) =>
|
|
481
|
+
downloadTarget({
|
|
482
|
+
org,
|
|
483
|
+
project,
|
|
484
|
+
repo,
|
|
485
|
+
repoPath: t.repoPath,
|
|
486
|
+
localBasePath: t.localBasePath,
|
|
487
|
+
branch,
|
|
488
|
+
options,
|
|
359
489
|
})
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
});
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
// Download a single file preserving folder structure under localBasePath
|
|
366
|
-
function downloadFile(filePath, repoPath, localBasePath, callback) {
|
|
367
|
-
const fileUrl = `https://dev.azure.com/${org}/${project}/_apis/git/repositories/${encodeURIComponent(
|
|
368
|
-
repo
|
|
369
|
-
)}/items?path=${encodeURIComponent(
|
|
370
|
-
filePath
|
|
371
|
-
)}&versionDescriptor.version=${encodeURIComponent(
|
|
372
|
-
branch
|
|
373
|
-
)}&versionDescriptor.versionType=branch&api-version=7.0&download=true`;
|
|
374
|
-
|
|
375
|
-
const posix = path.posix;
|
|
376
|
-
const relativePath = posix.relative(repoPath, filePath); // structure under repoPath
|
|
377
|
-
const destFile = path.join(localBasePath, ...relativePath.split("/"));
|
|
378
|
-
const destDir = path.dirname(destFile);
|
|
379
|
-
|
|
380
|
-
if (!fs.existsSync(destDir)) {
|
|
381
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
382
|
-
// console.log(`📂 Created directory: ${destDir}`);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
const doRequest = (url) => {
|
|
386
|
-
https
|
|
387
|
-
.get(url, options, (res) => {
|
|
388
|
-
if (
|
|
389
|
-
res.statusCode &&
|
|
390
|
-
res.statusCode >= 300 &&
|
|
391
|
-
res.statusCode < 400 &&
|
|
392
|
-
res.headers.location
|
|
393
|
-
) {
|
|
394
|
-
return doRequest(res.headers.location);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if (res.statusCode !== 200) {
|
|
398
|
-
console.error(
|
|
399
|
-
`Failed to download ${filePath}: ${res.statusCode} ${res.statusMessage}`
|
|
400
|
-
);
|
|
401
|
-
res.resume();
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
const uniqueId = Math.random().toString(36).substring(7);
|
|
406
|
-
const tmpFile = destFile + "." + uniqueId + ".part";
|
|
407
|
-
const fileStream = fs.createWriteStream(tmpFile);
|
|
408
|
-
|
|
409
|
-
res.pipe(fileStream);
|
|
410
|
-
fileStream.on("finish", () => {
|
|
411
|
-
fileStream.close(() => {
|
|
412
|
-
const ext = path.extname(destFile).toLowerCase();
|
|
413
|
-
let generatedComment = "";
|
|
414
|
-
if (
|
|
415
|
-
ext === ".ts" ||
|
|
416
|
-
ext === ".tsx" ||
|
|
417
|
-
ext === ".js" ||
|
|
418
|
-
ext === ".jsx"
|
|
419
|
-
) {
|
|
420
|
-
generatedComment =
|
|
421
|
-
"// GENERATED FILE // This file is automatically generated by the AttainLabs CMS plugin. Do not edit this file directly.\n";
|
|
422
|
-
}
|
|
423
|
-
const fileContent = fs.readFileSync(tmpFile, "utf8");
|
|
424
|
-
fs.writeFileSync(destFile, generatedComment + fileContent);
|
|
425
|
-
|
|
426
|
-
// Remove the temporary file
|
|
427
|
-
fs.unlinkSync(tmpFile);
|
|
428
|
-
if (callback) callback();
|
|
429
|
-
});
|
|
430
|
-
});
|
|
431
|
-
})
|
|
432
|
-
.on("error", (err) => {
|
|
433
|
-
console.error("Request error:", err.message);
|
|
434
|
-
});
|
|
435
|
-
};
|
|
490
|
+
)
|
|
491
|
+
);
|
|
436
492
|
|
|
437
|
-
|
|
438
|
-
|
|
493
|
+
// ✅ Now safe: no later download will overwrite your changes.
|
|
494
|
+
rewriteSliceWrapper(fetchConfig);
|
|
439
495
|
};
|
|
496
|
+
|
|
440
497
|
exports.sourceNodes = async (
|
|
441
498
|
{ actions, createNodeId, createContentDigest },
|
|
442
499
|
pluginOptions
|
|
@@ -460,13 +517,15 @@ exports.sourceNodes = async (
|
|
|
460
517
|
console.log(
|
|
461
518
|
`ℹ️ [gatsby-attainlabs-cms] Fetching Trustpilot data for brand: ${brand}`
|
|
462
519
|
);
|
|
463
|
-
|
|
520
|
+
|
|
464
521
|
const businessIds = {
|
|
465
522
|
LendDirect: "599affea0000ff0005a95acd",
|
|
466
523
|
"Cash Money": "599afd420000ff0005a95a9d",
|
|
467
524
|
"Heights Finance": "5e72238d600d1a0001be01eb",
|
|
468
525
|
};
|
|
526
|
+
|
|
469
527
|
const businessUnitId = businessIds[brand];
|
|
528
|
+
|
|
470
529
|
if (!apiKey) {
|
|
471
530
|
console.warn(
|
|
472
531
|
"⚠️ [gatsby-attainlabs-cms] No TRUSTPILOT_API_KEY found. " +
|
|
@@ -474,7 +533,7 @@ exports.sourceNodes = async (
|
|
|
474
533
|
"Example .env:\n" +
|
|
475
534
|
"TRUSTPILOT_API_KEY=xxxxxxx\n"
|
|
476
535
|
);
|
|
477
|
-
return;
|
|
536
|
+
return;
|
|
478
537
|
}
|
|
479
538
|
|
|
480
539
|
if (!brand || !businessUnitId) {
|
|
@@ -483,13 +542,9 @@ exports.sourceNodes = async (
|
|
|
483
542
|
`Current value: '${brand}'. Supported brands: ${Object.keys(businessIds).join(", ")}. ` +
|
|
484
543
|
"Trustpilot data will not be fetched."
|
|
485
544
|
);
|
|
486
|
-
return;
|
|
545
|
+
return;
|
|
487
546
|
}
|
|
488
547
|
|
|
489
|
-
console.log(
|
|
490
|
-
`ℹ️ [gatsby-attainlabs-cms] Fetching Firebase Blogs Data brand: ${brand}`
|
|
491
|
-
);
|
|
492
|
-
|
|
493
548
|
const fetchTrustpilotData = async (endpoint) => {
|
|
494
549
|
try {
|
|
495
550
|
const response = await fetch(
|
|
@@ -554,7 +609,12 @@ exports.sourceNodes = async (
|
|
|
554
609
|
|
|
555
610
|
if (fetchConfig) {
|
|
556
611
|
const firebaseData = await fetchData();
|
|
612
|
+
|
|
557
613
|
if (fetchConfig.includes("blogs")) {
|
|
614
|
+
console.log(
|
|
615
|
+
`ℹ️ [gatsby-attainlabs-cms] Fetching Firebase Blogs Data brand: ${brand}`
|
|
616
|
+
);
|
|
617
|
+
|
|
558
618
|
const allBlogs = getAllBlogs(firebaseData.pages);
|
|
559
619
|
|
|
560
620
|
for (const blog of allBlogs) {
|
|
@@ -562,6 +622,7 @@ exports.sourceNodes = async (
|
|
|
562
622
|
blog.blocks.root.props.datePublished = new Date(
|
|
563
623
|
blog.blocks.root.props.datePublished
|
|
564
624
|
);
|
|
625
|
+
|
|
565
626
|
const node = {
|
|
566
627
|
...blog,
|
|
567
628
|
id,
|
|
@@ -573,12 +634,17 @@ exports.sourceNodes = async (
|
|
|
573
634
|
contentDigest: createContentDigest(blog),
|
|
574
635
|
},
|
|
575
636
|
};
|
|
637
|
+
|
|
576
638
|
createNode(node);
|
|
577
639
|
}
|
|
578
640
|
}
|
|
579
641
|
|
|
580
642
|
// Disclaimers Source Node
|
|
581
643
|
if (fetchConfig.includes("disclaimers")) {
|
|
644
|
+
console.log(
|
|
645
|
+
`ℹ️ [gatsby-attainlabs-cms] Fetching Firebase Disclaimers Data brand: ${brand}`
|
|
646
|
+
);
|
|
647
|
+
|
|
582
648
|
const allDisclaimers = Object.values(firebaseData.disclaimers);
|
|
583
649
|
for (const disclaimer of allDisclaimers) {
|
|
584
650
|
const id = createNodeId(`attain-cms-disclaimer-${disclaimer.id}`);
|
|
@@ -598,16 +664,19 @@ exports.sourceNodes = async (
|
|
|
598
664
|
}
|
|
599
665
|
}
|
|
600
666
|
};
|
|
667
|
+
|
|
601
668
|
exports.createPages = async ({ actions, store }, pluginOptions) => {
|
|
602
669
|
const { brand, environment, debug } = pluginOptions;
|
|
603
670
|
const { createSlice, createPage } = actions;
|
|
604
671
|
const siteRoot = store.getState().program.directory;
|
|
672
|
+
|
|
605
673
|
if (environment === "dev") {
|
|
606
674
|
console.log(
|
|
607
675
|
"ℹ️ [gatsby-attainlabs-cms] Running in 'dev' environment mode. Skipping page creation."
|
|
608
676
|
);
|
|
609
677
|
return;
|
|
610
678
|
}
|
|
679
|
+
|
|
611
680
|
// 🚨 Validate brand option
|
|
612
681
|
if (!brand || !brands[brand]) {
|
|
613
682
|
throw new Error(
|
|
@@ -620,6 +689,7 @@ exports.createPages = async ({ actions, store }, pluginOptions) => {
|
|
|
620
689
|
`}`
|
|
621
690
|
);
|
|
622
691
|
}
|
|
692
|
+
|
|
623
693
|
const firebaseData = await fetch(
|
|
624
694
|
`https://attain-finance-cms-default-rtdb.firebaseio.com/cms/brands/${brands[brand]}/pages.json`
|
|
625
695
|
);
|
|
@@ -634,44 +704,50 @@ exports.createPages = async ({ actions, store }, pluginOptions) => {
|
|
|
634
704
|
});
|
|
635
705
|
|
|
636
706
|
async function processPage(page, parentPath = "") {
|
|
637
|
-
//
|
|
707
|
+
// Handle folders
|
|
638
708
|
if (
|
|
639
709
|
page.folderUrl &&
|
|
640
710
|
page.children &&
|
|
641
711
|
Object.keys(page.children).length > 0
|
|
642
712
|
) {
|
|
643
|
-
// use folderUrl instead of pageUrl for folder structure
|
|
644
713
|
const folderPath = parentPath
|
|
645
714
|
? path.join(parentPath, page.folderUrl)
|
|
646
715
|
: page.folderUrl;
|
|
647
|
-
const folderFullPath = path.join(siteRoot, "src/cms/pages", folderPath);
|
|
648
716
|
|
|
717
|
+
const folderFullPath = path.join(siteRoot, "src/cms/pages", folderPath);
|
|
649
718
|
await fse.ensureDir(folderFullPath);
|
|
650
719
|
|
|
651
720
|
for (const child of Object.values(page.children)) {
|
|
652
721
|
await processPage(child, folderPath);
|
|
653
722
|
}
|
|
654
723
|
|
|
655
|
-
return;
|
|
724
|
+
return;
|
|
656
725
|
}
|
|
657
726
|
|
|
658
|
-
//
|
|
659
|
-
if (
|
|
727
|
+
// Pages
|
|
728
|
+
if (
|
|
729
|
+
(page.published && environment === "production") ||
|
|
730
|
+
(environment === "staging" && page.hasOwnProperty("type"))
|
|
731
|
+
) {
|
|
732
|
+
let blocks =
|
|
733
|
+
environment === "staging"
|
|
734
|
+
? page.draft
|
|
735
|
+
? page.draft.blocks
|
|
736
|
+
: page.blocks
|
|
737
|
+
: page.blocks;
|
|
738
|
+
|
|
660
739
|
const {
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
props: { pageUrl, layout },
|
|
665
|
-
},
|
|
740
|
+
content,
|
|
741
|
+
root: {
|
|
742
|
+
props: { pageUrl, layout },
|
|
666
743
|
},
|
|
667
|
-
|
|
668
|
-
} = page;
|
|
744
|
+
} = blocks;
|
|
669
745
|
|
|
670
746
|
// Create slice for each block
|
|
671
747
|
await Promise.all(
|
|
672
748
|
content.map(async (b) => {
|
|
673
749
|
const name = b.props.component.name;
|
|
674
|
-
const sliceId = `block--${pageUrl}--${name}--${id}--${crypto.randomUUID()}`;
|
|
750
|
+
const sliceId = `block--${pageUrl}--${name}--${page.id}--${crypto.randomUUID()}`;
|
|
675
751
|
|
|
676
752
|
createSlice({
|
|
677
753
|
id: sliceId,
|
|
@@ -692,7 +768,13 @@ exports.createPages = async ({ actions, store }, pluginOptions) => {
|
|
|
692
768
|
);
|
|
693
769
|
|
|
694
770
|
// Generate page file
|
|
695
|
-
const pageSource = await generatePage(
|
|
771
|
+
const pageSource = await generatePage(
|
|
772
|
+
content,
|
|
773
|
+
layout,
|
|
774
|
+
parentPath,
|
|
775
|
+
environment,
|
|
776
|
+
page
|
|
777
|
+
);
|
|
696
778
|
const folderPath = parentPath ? path.join(parentPath, pageUrl) : pageUrl;
|
|
697
779
|
const outPath = path.join(siteRoot, "src/cms/pages", `${folderPath}.tsx`);
|
|
698
780
|
const gatsbyPagePath = path.join(
|
package/package.json
CHANGED
package/tsconfig.json
CHANGED
package/dist/index.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import type { SliceComponentProps } from "gatsby";
|
|
3
|
-
interface SliceWrapperProps extends SliceComponentProps {
|
|
4
|
-
sliceContext: {
|
|
5
|
-
componentPath: string;
|
|
6
|
-
};
|
|
7
|
-
}
|
|
8
|
-
declare function SliceWrapper({ sliceContext }: SliceWrapperProps): React.DetailedReactHTMLElement<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>;
|
|
9
|
-
export { SliceWrapper };
|
package/dist/index.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
function SliceWrapper({ sliceContext }) {
|
|
3
|
-
const { componentPath, ...props } = sliceContext;
|
|
4
|
-
// Dynamically import your component
|
|
5
|
-
const Component = require(componentPath).default;
|
|
6
|
-
return React.createElement(Component, props);
|
|
7
|
-
}
|
|
8
|
-
export { SliceWrapper };
|
package/dist/map.d.ts
DELETED
package/dist/map.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
// This requires Webpack to include all .tsx files in the folder
|
|
2
|
-
// @ts-ignore
|
|
3
|
-
const context = require.context("./", true, /\.tsx$/);
|
|
4
|
-
export const CMS_COMPONENTS = {};
|
|
5
|
-
context.keys().forEach((key) => {
|
|
6
|
-
// Remove "./" and ".tsx" from the path to get the component name
|
|
7
|
-
const name = key.replace(/^.\//, "").replace(/\.tsx$/, "");
|
|
8
|
-
CMS_COMPONENTS[name] = context(key).default;
|
|
9
|
-
});
|
package/dist/sliceWrapper.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
interface SliceWrapperProps {
|
|
2
|
-
sliceContext: {
|
|
3
|
-
componentPath: string;
|
|
4
|
-
[key: string]: any;
|
|
5
|
-
};
|
|
6
|
-
data: any;
|
|
7
|
-
}
|
|
8
|
-
export default function SliceWrapper({ sliceContext, data, }: SliceWrapperProps): import("react/jsx-runtime").JSX.Element | null;
|
|
9
|
-
export declare const TrustPilotQuery: import("gatsby").StaticQueryDocument;
|
|
10
|
-
export {};
|
package/dist/sliceWrapper.js
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { graphql } from "gatsby";
|
|
3
|
-
import { CMS_COMPONENTS } from "./map";
|
|
4
|
-
export default function SliceWrapper({ sliceContext, data, }) {
|
|
5
|
-
const { componentPath, ...props } = sliceContext;
|
|
6
|
-
const pathFormatted = componentPath
|
|
7
|
-
.replace("src/cms/components/", "")
|
|
8
|
-
.replace(/^.\//, "")
|
|
9
|
-
.replace(/\.tsx$/, "");
|
|
10
|
-
const Component = CMS_COMPONENTS[pathFormatted];
|
|
11
|
-
if (!Component) {
|
|
12
|
-
console.warn(`Component "${pathFormatted}" not found in CMS_COMPONENTS`);
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
return (_jsx(Component, { ...props, trustPilotData: data.allTrustPilotReviews.edges[0].node,
|
|
16
|
-
/* CHECK_DISCLAIMERS_START */
|
|
17
|
-
disclaimers: data.allDisclaimers,
|
|
18
|
-
/* CHECK_DISCLAIMERS_END */
|
|
19
|
-
/* CHECK_BLOGS_START */
|
|
20
|
-
blogs: {
|
|
21
|
-
sliderBlogs: {
|
|
22
|
-
...data.sliderBlogs,
|
|
23
|
-
},
|
|
24
|
-
allBlogs: {
|
|
25
|
-
...data.allBlogs,
|
|
26
|
-
},
|
|
27
|
-
} }));
|
|
28
|
-
}
|
|
29
|
-
export const TrustPilotQuery = graphql `
|
|
30
|
-
query {
|
|
31
|
-
allTrustPilotReviews {
|
|
32
|
-
edges {
|
|
33
|
-
node {
|
|
34
|
-
recentReviews {
|
|
35
|
-
reviews {
|
|
36
|
-
numberOfLikes
|
|
37
|
-
stars
|
|
38
|
-
text
|
|
39
|
-
title
|
|
40
|
-
createdAt
|
|
41
|
-
isVerified
|
|
42
|
-
reviewVerificationLevel
|
|
43
|
-
consumer {
|
|
44
|
-
displayName
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
businessData {
|
|
49
|
-
score {
|
|
50
|
-
stars
|
|
51
|
-
trustScore
|
|
52
|
-
}
|
|
53
|
-
displayName
|
|
54
|
-
numberOfReviews {
|
|
55
|
-
total
|
|
56
|
-
usedForTrustScoreCalculation
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
# CHECK_DISCLAIMERS_START
|
|
63
|
-
allDisclaimers: allAttainLabsCmsDisclaimers {
|
|
64
|
-
edges {
|
|
65
|
-
node {
|
|
66
|
-
content
|
|
67
|
-
published
|
|
68
|
-
order
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
# CHECK_DISCLAIMERS_END
|
|
73
|
-
# CHECK_BLOGS_START
|
|
74
|
-
allBlogs: allAttainLabsCmsBlogs {
|
|
75
|
-
edges {
|
|
76
|
-
node {
|
|
77
|
-
blocks {
|
|
78
|
-
content {
|
|
79
|
-
props {
|
|
80
|
-
content {
|
|
81
|
-
props {
|
|
82
|
-
text
|
|
83
|
-
image {
|
|
84
|
-
desktop {
|
|
85
|
-
url
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
# type
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
root {
|
|
94
|
-
props {
|
|
95
|
-
author
|
|
96
|
-
datePublished
|
|
97
|
-
pageUrl
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
sliderBlogs: allAttainLabsCmsBlogs(
|
|
105
|
-
limit: 9
|
|
106
|
-
sort: { blocks: { root: { props: { datePublished: DESC } } } }
|
|
107
|
-
) {
|
|
108
|
-
edges {
|
|
109
|
-
node {
|
|
110
|
-
blocks {
|
|
111
|
-
content {
|
|
112
|
-
props {
|
|
113
|
-
content {
|
|
114
|
-
props {
|
|
115
|
-
text
|
|
116
|
-
image {
|
|
117
|
-
desktop {
|
|
118
|
-
url
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
# type
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
root {
|
|
127
|
-
props {
|
|
128
|
-
author
|
|
129
|
-
datePublished
|
|
130
|
-
pageUrl
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
# CHECK_BLOGS_END
|
|
138
|
-
}
|
|
139
|
-
`;
|
package/src/index.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import type { SliceComponentProps } from "gatsby";
|
|
3
|
-
|
|
4
|
-
interface SliceWrapperProps extends SliceComponentProps {
|
|
5
|
-
sliceContext: {
|
|
6
|
-
componentPath: string;
|
|
7
|
-
};
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function SliceWrapper({ sliceContext }: SliceWrapperProps) {
|
|
11
|
-
const { componentPath, ...props } = sliceContext;
|
|
12
|
-
|
|
13
|
-
// Dynamically import your component
|
|
14
|
-
const Component = require(componentPath).default;
|
|
15
|
-
return React.createElement(Component, props);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export { SliceWrapper };
|
package/src/map.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
// GENERATED FILE // This file is automatically generated by the AttainLabs CMS plugin. Do not edit this file directly.
|
|
2
|
-
import React from "react";
|
|
3
|
-
|
|
4
|
-
// This requires Webpack to include all .tsx files in the folder
|
|
5
|
-
// @ts-ignore
|
|
6
|
-
const context = require.context("./", true, /\.tsx$/);
|
|
7
|
-
|
|
8
|
-
export const CMS_COMPONENTS: Record<string, React.ComponentType<any>> = {};
|
|
9
|
-
|
|
10
|
-
context.keys().forEach((key: string) => {
|
|
11
|
-
// Remove "./" and ".tsx" from the path to get the component name
|
|
12
|
-
const name = key.replace(/^.\//, "").replace(/\.tsx$/, "");
|
|
13
|
-
CMS_COMPONENTS[name] = context(key).default;
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
export type CMSComponentName = keyof typeof CMS_COMPONENTS;
|
package/src/sliceWrapper.tsx
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
// GENERATED FILE // This file is automatically generated by the AttainLabs CMS plugin. Do not edit this file directly.
|
|
2
|
-
|
|
3
|
-
import React from "react";
|
|
4
|
-
import { graphql, type SliceComponentProps } from "gatsby";
|
|
5
|
-
import { CMS_COMPONENTS } from "./map";
|
|
6
|
-
|
|
7
|
-
interface SliceWrapperProps {
|
|
8
|
-
sliceContext: {
|
|
9
|
-
componentPath: string;
|
|
10
|
-
[key: string]: any;
|
|
11
|
-
};
|
|
12
|
-
data: any;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export default function SliceWrapper({
|
|
16
|
-
sliceContext,
|
|
17
|
-
data,
|
|
18
|
-
}: SliceWrapperProps) {
|
|
19
|
-
const { componentPath, ...props } = sliceContext;
|
|
20
|
-
const pathFormatted = componentPath
|
|
21
|
-
.replace("src/cms/components/", "")
|
|
22
|
-
.replace(/^.\//, "")
|
|
23
|
-
.replace(/\.tsx$/, "");
|
|
24
|
-
const Component = CMS_COMPONENTS[pathFormatted];
|
|
25
|
-
if (!Component) {
|
|
26
|
-
console.warn(`Component "${pathFormatted}" not found in CMS_COMPONENTS`);
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<Component
|
|
32
|
-
{...props}
|
|
33
|
-
trustPilotData={data.allTrustPilotReviews.edges[0].node}
|
|
34
|
-
/* CHECK_DISCLAIMERS_START */
|
|
35
|
-
disclaimers={data.allDisclaimers}
|
|
36
|
-
/* CHECK_DISCLAIMERS_END */
|
|
37
|
-
/* CHECK_BLOGS_START */
|
|
38
|
-
blogs={{
|
|
39
|
-
sliderBlogs: {
|
|
40
|
-
...data.sliderBlogs,
|
|
41
|
-
},
|
|
42
|
-
allBlogs: {
|
|
43
|
-
...data.allBlogs,
|
|
44
|
-
},
|
|
45
|
-
}}
|
|
46
|
-
/* CHECK_BLOGS_END */
|
|
47
|
-
/>
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export const TrustPilotQuery = graphql`
|
|
52
|
-
query {
|
|
53
|
-
allTrustPilotReviews {
|
|
54
|
-
edges {
|
|
55
|
-
node {
|
|
56
|
-
recentReviews {
|
|
57
|
-
reviews {
|
|
58
|
-
numberOfLikes
|
|
59
|
-
stars
|
|
60
|
-
text
|
|
61
|
-
title
|
|
62
|
-
createdAt
|
|
63
|
-
isVerified
|
|
64
|
-
reviewVerificationLevel
|
|
65
|
-
consumer {
|
|
66
|
-
displayName
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
businessData {
|
|
71
|
-
score {
|
|
72
|
-
stars
|
|
73
|
-
trustScore
|
|
74
|
-
}
|
|
75
|
-
displayName
|
|
76
|
-
numberOfReviews {
|
|
77
|
-
total
|
|
78
|
-
usedForTrustScoreCalculation
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
# CHECK_DISCLAIMERS_START
|
|
85
|
-
allDisclaimers: allAttainLabsCmsDisclaimers {
|
|
86
|
-
edges {
|
|
87
|
-
node {
|
|
88
|
-
content
|
|
89
|
-
published
|
|
90
|
-
order
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
# CHECK_DISCLAIMERS_END
|
|
95
|
-
# CHECK_BLOGS_START
|
|
96
|
-
allBlogs: allAttainLabsCmsBlogs {
|
|
97
|
-
edges {
|
|
98
|
-
node {
|
|
99
|
-
blocks {
|
|
100
|
-
content {
|
|
101
|
-
props {
|
|
102
|
-
content {
|
|
103
|
-
props {
|
|
104
|
-
text
|
|
105
|
-
image {
|
|
106
|
-
desktop {
|
|
107
|
-
url
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
# type
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
root {
|
|
116
|
-
props {
|
|
117
|
-
author
|
|
118
|
-
datePublished
|
|
119
|
-
pageUrl
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
sliderBlogs: allAttainLabsCmsBlogs(
|
|
127
|
-
limit: 9
|
|
128
|
-
sort: { blocks: { root: { props: { datePublished: DESC } } } }
|
|
129
|
-
) {
|
|
130
|
-
edges {
|
|
131
|
-
node {
|
|
132
|
-
blocks {
|
|
133
|
-
content {
|
|
134
|
-
props {
|
|
135
|
-
content {
|
|
136
|
-
props {
|
|
137
|
-
text
|
|
138
|
-
image {
|
|
139
|
-
desktop {
|
|
140
|
-
url
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
# type
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
root {
|
|
149
|
-
props {
|
|
150
|
-
author
|
|
151
|
-
datePublished
|
|
152
|
-
pageUrl
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
# CHECK_BLOGS_END
|
|
160
|
-
}
|
|
161
|
-
`;
|