gatsby-attainlabs-cms 1.0.49 → 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/README.md +21 -6
- package/gatsby-browser.js +1 -1
- package/gatsby-node.js +382 -208
- 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/README.md
CHANGED
|
@@ -55,17 +55,27 @@ module.exports = {
|
|
|
55
55
|
{
|
|
56
56
|
resolve: "gatsby-attainlabs-cms",
|
|
57
57
|
options: {
|
|
58
|
-
brand: "Cash Money", // LendDirect | Cash Money | Heights Finance
|
|
58
|
+
brand: "Cash Money", // LendDirect | Cash Money | Heights Finance | Attain Finance
|
|
59
59
|
// personalAccessToken: "optional-fallback", // not recommended, but supported
|
|
60
|
-
environment: "production", // production | dev
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
environment: "production", // production | staging | dev
|
|
61
|
+
// azureBranch: "my-feature-branch", // Optional: Override branch for non-production environments
|
|
62
|
+
fetch: ["blogs", "disclaimers", "faqs"], // Array of data to fetch from database
|
|
63
|
+
debug: false, // Console logs blocks created
|
|
63
64
|
},
|
|
64
65
|
},
|
|
65
66
|
],
|
|
66
67
|
};
|
|
67
68
|
```
|
|
68
69
|
|
|
70
|
+
### Environment Options
|
|
71
|
+
|
|
72
|
+
- **`production`**: Forces content sync from the `master` branch.
|
|
73
|
+
- **`staging`**: Automatically fetches content from the **latest active Pull Request** branch in Azure DevOps. Falls back to `master` if no PR is active.
|
|
74
|
+
- **`dev`**: Skips component sync and data fetching. Useful for local development speed.
|
|
75
|
+
- **Default behavior**: If `environment` is unspecified or other values, it defaults to the `azureBranch` option or `master`.
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
|
|
69
79
|
---
|
|
70
80
|
|
|
71
81
|
## Behavior
|
|
@@ -79,7 +89,7 @@ module.exports = {
|
|
|
79
89
|
|
|
80
90
|
- If the PAT is missing, the plugin will **warn and skip execution** instead of failing the build.
|
|
81
91
|
|
|
82
|
-
- If the `brand` option is missing or invalid, the plugin will **throw an error** and stop the build.
|
|
92
|
+
- If the `brand` option is missing or invalid, the plugin will **throw an error** and stop the build.
|
|
83
93
|
Valid values are:
|
|
84
94
|
- `LendDirect`
|
|
85
95
|
- `Cash Money`
|
|
@@ -95,7 +105,9 @@ module.exports = {
|
|
|
95
105
|
If you see:
|
|
96
106
|
|
|
97
107
|
```
|
|
108
|
+
|
|
98
109
|
⚠️ [gatsby-attainlabs-cms] No PERSONAL_ACCESS_TOKEN found...
|
|
110
|
+
|
|
99
111
|
```
|
|
100
112
|
|
|
101
113
|
➡️ Double-check that `.env` exists and is loaded in `gatsby-config.js`.
|
|
@@ -107,8 +119,10 @@ If you see:
|
|
|
107
119
|
If you see:
|
|
108
120
|
|
|
109
121
|
```
|
|
122
|
+
|
|
110
123
|
[gatsby-attainlabs-cms] Invalid or missing "brand" option.
|
|
111
124
|
You must specify one of: LendDirect, Cash Money, Heights Finance
|
|
125
|
+
|
|
112
126
|
```
|
|
113
127
|
|
|
114
128
|
➡️ Make sure you pass a valid brand in `gatsby-config.js`.
|
|
@@ -117,5 +131,6 @@ You must specify one of: LendDirect, Cash Money, Heights Finance
|
|
|
117
131
|
|
|
118
132
|
### Token in client code
|
|
119
133
|
|
|
120
|
-
Don’t use `GATSBY_PERSONAL_ACCESS_TOKEN`. That will leak your secret into the browser bundle.
|
|
134
|
+
Don’t use `GATSBY_PERSONAL_ACCESS_TOKEN`. That will leak your secret into the browser bundle.
|
|
121
135
|
Always use `PERSONAL_ACCESS_TOKEN`.
|
|
136
|
+
```
|
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 {
|
|
@@ -133,23 +354,100 @@ exports.onPreInit = async (_, pluginOptions) => {
|
|
|
133
354
|
);
|
|
134
355
|
}
|
|
135
356
|
|
|
357
|
+
// Helper to fetch latest PR branch
|
|
358
|
+
const getLatestPullRequestBranch = (org, project, repo, pat) => {
|
|
359
|
+
return new Promise((resolve) => {
|
|
360
|
+
const options = {
|
|
361
|
+
headers: {
|
|
362
|
+
Authorization: `Basic ${Buffer.from(`:${pat}`).toString("base64")}`,
|
|
363
|
+
"User-Agent": "gatsby-attainlabs-cms",
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// Fetch top 10 active PRs
|
|
368
|
+
const url = `https://dev.azure.com/${org}/${project}/_apis/git/repositories/${encodeURIComponent(
|
|
369
|
+
repo
|
|
370
|
+
)}/pullrequests?searchCriteria.status=active&top=10&api-version=7.0`;
|
|
371
|
+
|
|
372
|
+
https
|
|
373
|
+
.get(url, options, (res) => {
|
|
374
|
+
let data = "";
|
|
375
|
+
res.on("data", (chunk) => (data += chunk));
|
|
376
|
+
res.on("end", () => {
|
|
377
|
+
if (res.statusCode !== 200) {
|
|
378
|
+
console.error(
|
|
379
|
+
`Failed to fetch PRs: ${res.statusCode} ${res.statusMessage}`
|
|
380
|
+
);
|
|
381
|
+
resolve(null);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
try {
|
|
385
|
+
const result = JSON.parse(data);
|
|
386
|
+
if (result.value && result.value.length > 0) {
|
|
387
|
+
const latestPr = result.value.sort(
|
|
388
|
+
(a, b) =>
|
|
389
|
+
new Date(b.creationDate).getTime() -
|
|
390
|
+
new Date(a.creationDate).getTime()
|
|
391
|
+
)[0];
|
|
392
|
+
|
|
393
|
+
const sourceRef = latestPr.sourceRefName;
|
|
394
|
+
const branchName = sourceRef.replace("refs/heads/", "");
|
|
395
|
+
resolve(branchName);
|
|
396
|
+
} else {
|
|
397
|
+
resolve(null);
|
|
398
|
+
}
|
|
399
|
+
} catch (e) {
|
|
400
|
+
console.error("Error parsing PR response:", e);
|
|
401
|
+
resolve(null);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
})
|
|
405
|
+
.on("error", (e) => {
|
|
406
|
+
console.error("Request error fetching PRs:", e);
|
|
407
|
+
resolve(null);
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
};
|
|
411
|
+
|
|
136
412
|
const org = "CuroFinTech";
|
|
137
413
|
const project = "Marketing";
|
|
138
414
|
const repo = "Attain Labs";
|
|
139
|
-
const branch = azureBranch || "master";
|
|
140
415
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
{
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
416
|
+
let branch = azureBranch || "master";
|
|
417
|
+
|
|
418
|
+
if (environment === "production") {
|
|
419
|
+
branch = "master";
|
|
420
|
+
} else if (environment === "staging") {
|
|
421
|
+
try {
|
|
422
|
+
console.log(
|
|
423
|
+
"ℹ️ [gatsby-attainlabs-cms] Staging mode: Checking for active PRs..."
|
|
424
|
+
);
|
|
425
|
+
const latestPrBranch = await getLatestPullRequestBranch(
|
|
426
|
+
org,
|
|
427
|
+
project,
|
|
428
|
+
repo,
|
|
429
|
+
pat
|
|
430
|
+
);
|
|
431
|
+
if (latestPrBranch) {
|
|
432
|
+
branch = latestPrBranch;
|
|
433
|
+
console.log(
|
|
434
|
+
`ℹ️ [gatsby-attainlabs-cms] Staging mode: Using latest PR branch '${branch}'`
|
|
435
|
+
);
|
|
436
|
+
} else {
|
|
437
|
+
console.warn(
|
|
438
|
+
"⚠️ [gatsby-attainlabs-cms] Staging mode: No active PRs found. Falling back to 'master'."
|
|
439
|
+
);
|
|
440
|
+
branch = "master";
|
|
441
|
+
}
|
|
442
|
+
} catch (e) {
|
|
443
|
+
console.error(
|
|
444
|
+
"⚠️ [gatsby-attainlabs-cms] Failed to fetch latest PR branch, falling back to 'master':",
|
|
445
|
+
e
|
|
446
|
+
);
|
|
447
|
+
branch = "master";
|
|
448
|
+
}
|
|
449
|
+
}
|
|
151
450
|
|
|
152
|
-
// List of folders to download from
|
|
153
451
|
const targets = [
|
|
154
452
|
{
|
|
155
453
|
repoPath: `/apps/cms/src/cms/editors/visual-block-editor/brands/${brands[brand]}/`,
|
|
@@ -172,179 +470,30 @@ exports.onPreInit = async (_, pluginOptions) => {
|
|
|
172
470
|
},
|
|
173
471
|
};
|
|
174
472
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
);
|
|
192
|
-
content = content.replace(
|
|
193
|
-
/\/\* CHECK_BLOGS_START \*\/[\s\S]*?\/\* CHECK_BLOGS_END \*\//g,
|
|
194
|
-
""
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (!fetchConfig || !fetchConfig.includes("disclaimers")) {
|
|
199
|
-
content = content.replace(
|
|
200
|
-
/# CHECK_DISCLAIMERS_START[\s\S]*?# CHECK_DISCLAIMERS_END/g,
|
|
201
|
-
""
|
|
202
|
-
);
|
|
203
|
-
content = content.replace(
|
|
204
|
-
/\/\* CHECK_DISCLAIMERS_START \*\/[\s\S]*?\/\* CHECK_DISCLAIMERS_END \*\//g,
|
|
205
|
-
""
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
fs.writeFileSync(localPath, content);
|
|
210
|
-
fs.writeFileSync(destFile, content);
|
|
211
|
-
console.log(`✅ Copied and transformed ${destFile}`);
|
|
212
|
-
} else {
|
|
213
|
-
fs.copyFileSync(localPath, destFile);
|
|
214
|
-
console.log(`✅ Copied ${destFile}`);
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
// Loop through targets
|
|
219
|
-
targets.forEach(({ repoPath, localBasePath }) => {
|
|
220
|
-
const listUrl = `https://dev.azure.com/${org}/${project}/_apis/git/repositories/${encodeURIComponent(
|
|
221
|
-
repo
|
|
222
|
-
)}/items?scopePath=${encodeURIComponent(
|
|
223
|
-
repoPath
|
|
224
|
-
)}&recursionLevel=full&includeContentMetadata=true&versionDescriptor.version=${encodeURIComponent(
|
|
225
|
-
branch
|
|
226
|
-
)}&versionDescriptor.versionType=branch&api-version=7.0`;
|
|
227
|
-
|
|
228
|
-
https
|
|
229
|
-
.get(listUrl, options, (res) => {
|
|
230
|
-
let data = "";
|
|
231
|
-
res.on("data", (chunk) => (data += chunk));
|
|
232
|
-
res.on("end", () => {
|
|
233
|
-
if (res.statusCode !== 200) {
|
|
234
|
-
console.error(
|
|
235
|
-
`Failed to list items for ${repoPath}: ${res.statusCode} ${res.statusMessage}`
|
|
236
|
-
);
|
|
237
|
-
console.error(data);
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const result = JSON.parse(data);
|
|
242
|
-
const posix = path.posix;
|
|
243
|
-
|
|
244
|
-
// Exclude files named config.tsx
|
|
245
|
-
const items = (result.value || []).filter(
|
|
246
|
-
(i) =>
|
|
247
|
-
!i.isFolder &&
|
|
248
|
-
i.gitObjectType === "blob" &&
|
|
249
|
-
posix.basename(i.path) !== "config.tsx" &&
|
|
250
|
-
posix.basename(i.path) !== "preview.tsx"
|
|
251
|
-
);
|
|
252
|
-
|
|
253
|
-
console.log(`Found ${items.length} files in ${repoPath}`);
|
|
254
|
-
let completedDownloads = 0;
|
|
255
|
-
|
|
256
|
-
items.forEach((item) => {
|
|
257
|
-
downloadFile(item.path, repoPath, localBasePath, () => {
|
|
258
|
-
completedDownloads++;
|
|
259
|
-
if (completedDownloads === items.length) {
|
|
260
|
-
console.log(
|
|
261
|
-
`✅ Completed downloading all files for target: ${repoPath}`
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
});
|
|
266
|
-
});
|
|
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,
|
|
267
489
|
})
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
});
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
// Download a single file preserving folder structure under localBasePath
|
|
274
|
-
function downloadFile(filePath, repoPath, localBasePath, callback) {
|
|
275
|
-
const fileUrl = `https://dev.azure.com/${org}/${project}/_apis/git/repositories/${encodeURIComponent(
|
|
276
|
-
repo
|
|
277
|
-
)}/items?path=${encodeURIComponent(
|
|
278
|
-
filePath
|
|
279
|
-
)}&versionDescriptor.version=${encodeURIComponent(
|
|
280
|
-
branch
|
|
281
|
-
)}&versionDescriptor.versionType=branch&api-version=7.0&download=true`;
|
|
282
|
-
|
|
283
|
-
const posix = path.posix;
|
|
284
|
-
const relativePath = posix.relative(repoPath, filePath); // structure under repoPath
|
|
285
|
-
const destFile = path.join(localBasePath, ...relativePath.split("/"));
|
|
286
|
-
const destDir = path.dirname(destFile);
|
|
287
|
-
|
|
288
|
-
if (!fs.existsSync(destDir)) {
|
|
289
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
290
|
-
// console.log(`📂 Created directory: ${destDir}`);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
const doRequest = (url) => {
|
|
294
|
-
https
|
|
295
|
-
.get(url, options, (res) => {
|
|
296
|
-
if (
|
|
297
|
-
res.statusCode &&
|
|
298
|
-
res.statusCode >= 300 &&
|
|
299
|
-
res.statusCode < 400 &&
|
|
300
|
-
res.headers.location
|
|
301
|
-
) {
|
|
302
|
-
return doRequest(res.headers.location);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (res.statusCode !== 200) {
|
|
306
|
-
console.error(
|
|
307
|
-
`Failed to download ${filePath}: ${res.statusCode} ${res.statusMessage}`
|
|
308
|
-
);
|
|
309
|
-
res.resume();
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const uniqueId = Math.random().toString(36).substring(7);
|
|
314
|
-
const tmpFile = destFile + "." + uniqueId + ".part";
|
|
315
|
-
const fileStream = fs.createWriteStream(tmpFile);
|
|
316
|
-
|
|
317
|
-
res.pipe(fileStream);
|
|
318
|
-
fileStream.on("finish", () => {
|
|
319
|
-
fileStream.close(() => {
|
|
320
|
-
const ext = path.extname(destFile).toLowerCase();
|
|
321
|
-
let generatedComment = "";
|
|
322
|
-
if (
|
|
323
|
-
ext === ".ts" ||
|
|
324
|
-
ext === ".tsx" ||
|
|
325
|
-
ext === ".js" ||
|
|
326
|
-
ext === ".jsx"
|
|
327
|
-
) {
|
|
328
|
-
generatedComment =
|
|
329
|
-
"// GENERATED FILE // This file is automatically generated by the AttainLabs CMS plugin. Do not edit this file directly.\n";
|
|
330
|
-
}
|
|
331
|
-
const fileContent = fs.readFileSync(tmpFile, "utf8");
|
|
332
|
-
fs.writeFileSync(destFile, generatedComment + fileContent);
|
|
333
|
-
|
|
334
|
-
// Remove the temporary file
|
|
335
|
-
fs.unlinkSync(tmpFile);
|
|
336
|
-
if (callback) callback();
|
|
337
|
-
});
|
|
338
|
-
});
|
|
339
|
-
})
|
|
340
|
-
.on("error", (err) => {
|
|
341
|
-
console.error("Request error:", err.message);
|
|
342
|
-
});
|
|
343
|
-
};
|
|
490
|
+
)
|
|
491
|
+
);
|
|
344
492
|
|
|
345
|
-
|
|
346
|
-
|
|
493
|
+
// ✅ Now safe: no later download will overwrite your changes.
|
|
494
|
+
rewriteSliceWrapper(fetchConfig);
|
|
347
495
|
};
|
|
496
|
+
|
|
348
497
|
exports.sourceNodes = async (
|
|
349
498
|
{ actions, createNodeId, createContentDigest },
|
|
350
499
|
pluginOptions
|
|
@@ -368,13 +517,15 @@ exports.sourceNodes = async (
|
|
|
368
517
|
console.log(
|
|
369
518
|
`ℹ️ [gatsby-attainlabs-cms] Fetching Trustpilot data for brand: ${brand}`
|
|
370
519
|
);
|
|
371
|
-
|
|
520
|
+
|
|
372
521
|
const businessIds = {
|
|
373
522
|
LendDirect: "599affea0000ff0005a95acd",
|
|
374
523
|
"Cash Money": "599afd420000ff0005a95a9d",
|
|
375
524
|
"Heights Finance": "5e72238d600d1a0001be01eb",
|
|
376
525
|
};
|
|
526
|
+
|
|
377
527
|
const businessUnitId = businessIds[brand];
|
|
528
|
+
|
|
378
529
|
if (!apiKey) {
|
|
379
530
|
console.warn(
|
|
380
531
|
"⚠️ [gatsby-attainlabs-cms] No TRUSTPILOT_API_KEY found. " +
|
|
@@ -382,7 +533,7 @@ exports.sourceNodes = async (
|
|
|
382
533
|
"Example .env:\n" +
|
|
383
534
|
"TRUSTPILOT_API_KEY=xxxxxxx\n"
|
|
384
535
|
);
|
|
385
|
-
return;
|
|
536
|
+
return;
|
|
386
537
|
}
|
|
387
538
|
|
|
388
539
|
if (!brand || !businessUnitId) {
|
|
@@ -391,13 +542,9 @@ exports.sourceNodes = async (
|
|
|
391
542
|
`Current value: '${brand}'. Supported brands: ${Object.keys(businessIds).join(", ")}. ` +
|
|
392
543
|
"Trustpilot data will not be fetched."
|
|
393
544
|
);
|
|
394
|
-
return;
|
|
545
|
+
return;
|
|
395
546
|
}
|
|
396
547
|
|
|
397
|
-
console.log(
|
|
398
|
-
`ℹ️ [gatsby-attainlabs-cms] Fetching Firebase Blogs Data brand: ${brand}`
|
|
399
|
-
);
|
|
400
|
-
|
|
401
548
|
const fetchTrustpilotData = async (endpoint) => {
|
|
402
549
|
try {
|
|
403
550
|
const response = await fetch(
|
|
@@ -462,7 +609,12 @@ exports.sourceNodes = async (
|
|
|
462
609
|
|
|
463
610
|
if (fetchConfig) {
|
|
464
611
|
const firebaseData = await fetchData();
|
|
612
|
+
|
|
465
613
|
if (fetchConfig.includes("blogs")) {
|
|
614
|
+
console.log(
|
|
615
|
+
`ℹ️ [gatsby-attainlabs-cms] Fetching Firebase Blogs Data brand: ${brand}`
|
|
616
|
+
);
|
|
617
|
+
|
|
466
618
|
const allBlogs = getAllBlogs(firebaseData.pages);
|
|
467
619
|
|
|
468
620
|
for (const blog of allBlogs) {
|
|
@@ -470,6 +622,7 @@ exports.sourceNodes = async (
|
|
|
470
622
|
blog.blocks.root.props.datePublished = new Date(
|
|
471
623
|
blog.blocks.root.props.datePublished
|
|
472
624
|
);
|
|
625
|
+
|
|
473
626
|
const node = {
|
|
474
627
|
...blog,
|
|
475
628
|
id,
|
|
@@ -481,12 +634,17 @@ exports.sourceNodes = async (
|
|
|
481
634
|
contentDigest: createContentDigest(blog),
|
|
482
635
|
},
|
|
483
636
|
};
|
|
637
|
+
|
|
484
638
|
createNode(node);
|
|
485
639
|
}
|
|
486
640
|
}
|
|
487
641
|
|
|
488
642
|
// Disclaimers Source Node
|
|
489
643
|
if (fetchConfig.includes("disclaimers")) {
|
|
644
|
+
console.log(
|
|
645
|
+
`ℹ️ [gatsby-attainlabs-cms] Fetching Firebase Disclaimers Data brand: ${brand}`
|
|
646
|
+
);
|
|
647
|
+
|
|
490
648
|
const allDisclaimers = Object.values(firebaseData.disclaimers);
|
|
491
649
|
for (const disclaimer of allDisclaimers) {
|
|
492
650
|
const id = createNodeId(`attain-cms-disclaimer-${disclaimer.id}`);
|
|
@@ -506,16 +664,19 @@ exports.sourceNodes = async (
|
|
|
506
664
|
}
|
|
507
665
|
}
|
|
508
666
|
};
|
|
667
|
+
|
|
509
668
|
exports.createPages = async ({ actions, store }, pluginOptions) => {
|
|
510
669
|
const { brand, environment, debug } = pluginOptions;
|
|
511
670
|
const { createSlice, createPage } = actions;
|
|
512
671
|
const siteRoot = store.getState().program.directory;
|
|
672
|
+
|
|
513
673
|
if (environment === "dev") {
|
|
514
674
|
console.log(
|
|
515
675
|
"ℹ️ [gatsby-attainlabs-cms] Running in 'dev' environment mode. Skipping page creation."
|
|
516
676
|
);
|
|
517
677
|
return;
|
|
518
678
|
}
|
|
679
|
+
|
|
519
680
|
// 🚨 Validate brand option
|
|
520
681
|
if (!brand || !brands[brand]) {
|
|
521
682
|
throw new Error(
|
|
@@ -528,6 +689,7 @@ exports.createPages = async ({ actions, store }, pluginOptions) => {
|
|
|
528
689
|
`}`
|
|
529
690
|
);
|
|
530
691
|
}
|
|
692
|
+
|
|
531
693
|
const firebaseData = await fetch(
|
|
532
694
|
`https://attain-finance-cms-default-rtdb.firebaseio.com/cms/brands/${brands[brand]}/pages.json`
|
|
533
695
|
);
|
|
@@ -542,44 +704,50 @@ exports.createPages = async ({ actions, store }, pluginOptions) => {
|
|
|
542
704
|
});
|
|
543
705
|
|
|
544
706
|
async function processPage(page, parentPath = "") {
|
|
545
|
-
//
|
|
707
|
+
// Handle folders
|
|
546
708
|
if (
|
|
547
709
|
page.folderUrl &&
|
|
548
710
|
page.children &&
|
|
549
711
|
Object.keys(page.children).length > 0
|
|
550
712
|
) {
|
|
551
|
-
// use folderUrl instead of pageUrl for folder structure
|
|
552
713
|
const folderPath = parentPath
|
|
553
714
|
? path.join(parentPath, page.folderUrl)
|
|
554
715
|
: page.folderUrl;
|
|
555
|
-
const folderFullPath = path.join(siteRoot, "src/cms/pages", folderPath);
|
|
556
716
|
|
|
717
|
+
const folderFullPath = path.join(siteRoot, "src/cms/pages", folderPath);
|
|
557
718
|
await fse.ensureDir(folderFullPath);
|
|
558
719
|
|
|
559
720
|
for (const child of Object.values(page.children)) {
|
|
560
721
|
await processPage(child, folderPath);
|
|
561
722
|
}
|
|
562
723
|
|
|
563
|
-
return;
|
|
724
|
+
return;
|
|
564
725
|
}
|
|
565
726
|
|
|
566
|
-
//
|
|
567
|
-
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
|
+
|
|
568
739
|
const {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
props: { pageUrl, layout },
|
|
573
|
-
},
|
|
740
|
+
content,
|
|
741
|
+
root: {
|
|
742
|
+
props: { pageUrl, layout },
|
|
574
743
|
},
|
|
575
|
-
|
|
576
|
-
} = page;
|
|
744
|
+
} = blocks;
|
|
577
745
|
|
|
578
746
|
// Create slice for each block
|
|
579
747
|
await Promise.all(
|
|
580
748
|
content.map(async (b) => {
|
|
581
749
|
const name = b.props.component.name;
|
|
582
|
-
const sliceId = `block--${pageUrl}--${name}--${id}--${crypto.randomUUID()}`;
|
|
750
|
+
const sliceId = `block--${pageUrl}--${name}--${page.id}--${crypto.randomUUID()}`;
|
|
583
751
|
|
|
584
752
|
createSlice({
|
|
585
753
|
id: sliceId,
|
|
@@ -600,7 +768,13 @@ exports.createPages = async ({ actions, store }, pluginOptions) => {
|
|
|
600
768
|
);
|
|
601
769
|
|
|
602
770
|
// Generate page file
|
|
603
|
-
const pageSource = await generatePage(
|
|
771
|
+
const pageSource = await generatePage(
|
|
772
|
+
content,
|
|
773
|
+
layout,
|
|
774
|
+
parentPath,
|
|
775
|
+
environment,
|
|
776
|
+
page
|
|
777
|
+
);
|
|
604
778
|
const folderPath = parentPath ? path.join(parentPath, pageUrl) : pageUrl;
|
|
605
779
|
const outPath = path.join(siteRoot, "src/cms/pages", `${folderPath}.tsx`);
|
|
606
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
|
-
`;
|