gatsby-attainlabs-cms 1.0.15 → 1.0.18

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 CHANGED
@@ -57,6 +57,7 @@ module.exports = {
57
57
  options: {
58
58
  brand: "Cash Money", // LendDirect | Cash Money | Heights Finance adding-puck-for-visual-code-editing
59
59
  // personalAccessToken: "optional-fallback", // not recommended, but supported
60
+ environment: "production", // production | dev
60
61
  },
61
62
  },
62
63
  ],
@@ -3,6 +3,8 @@ interface SliceWrapperProps {
3
3
  componentPath: string;
4
4
  [key: string]: any;
5
5
  };
6
+ data: any;
6
7
  }
7
- export default function SliceWrapper({ sliceContext }: SliceWrapperProps): import("react/jsx-runtime").JSX.Element | null;
8
+ export default function SliceWrapper({ sliceContext, data, }: SliceWrapperProps): import("react/jsx-runtime").JSX.Element | null;
9
+ export declare const TrustPilotQuery: import("gatsby").StaticQueryDocument;
8
10
  export {};
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { graphql } from "gatsby";
2
3
  import { CMS_COMPONENTS } from "./map";
3
- export default function SliceWrapper({ sliceContext }) {
4
+ export default function SliceWrapper({ sliceContext, data, }) {
4
5
  const { componentPath, ...props } = sliceContext;
5
6
  const pathFormatted = componentPath
6
7
  .replace("src/cms/components//", "")
@@ -11,5 +12,40 @@ export default function SliceWrapper({ sliceContext }) {
11
12
  console.warn(`Component "${pathFormatted}" not found in CMS_COMPONENTS`);
12
13
  return null;
13
14
  }
14
- return _jsx(Component, { ...props });
15
+ return (_jsx(Component, { ...props, trustPilotData: data.allTrustPilotReviews.edges[0].node }));
15
16
  }
17
+ export const TrustPilotQuery = graphql `
18
+ query {
19
+ allTrustPilotReviews {
20
+ edges {
21
+ node {
22
+ recentReviews {
23
+ reviews {
24
+ numberOfLikes
25
+ stars
26
+ text
27
+ title
28
+ createdAt
29
+ isVerified
30
+ reviewVerificationLevel
31
+ consumer {
32
+ displayName
33
+ }
34
+ }
35
+ }
36
+ businessData {
37
+ score {
38
+ stars
39
+ trustScore
40
+ }
41
+ displayName
42
+ numberOfReviews {
43
+ total
44
+ usedForTrustScoreCalculation
45
+ }
46
+ }
47
+ }
48
+ }
49
+ }
50
+ }
51
+ `;
@@ -0,0 +1,16 @@
1
+ module.exports = ({ brand }) => ({
2
+ plugins: [
3
+ `gatsby-plugin-image`,
4
+ {
5
+ resolve: `gatsby-plugin-sharp`,
6
+ options: {
7
+ defaults: {
8
+ quality: 100,
9
+ placeholder: "none",
10
+ formats: [`webp`, "avif"],
11
+ },
12
+ },
13
+ },
14
+ `gatsby-transformer-sharp`,
15
+ ],
16
+ });
package/gatsby-node.js CHANGED
@@ -11,18 +11,18 @@ const brands = {
11
11
  "Attain Finance": "attainfinance",
12
12
  };
13
13
 
14
- const createPage = async (blocks, pageUrl, id) => {
14
+ const createPage = async (blocks, pageUrl) => {
15
15
  // Validate input parameters
16
- if (!Array.isArray(blocks) || !pageUrl || !id) {
16
+ if (!Array.isArray(blocks) || !pageUrl) {
17
17
  throw new Error("Invalid parameters passed to createPage.");
18
18
  }
19
19
 
20
20
  // Helper function to generate Slice components
21
- const generateSlices = (blocks, pageUrl, id) => {
21
+ const generateSlices = (blocks, id) => {
22
22
  return blocks
23
23
  .map(
24
24
  (b) =>
25
- `<Slice alias="block--${pageUrl}--${b.props.component.name}--${id}" />`
25
+ `<Slice alias="${b.sliceId}" />`
26
26
  )
27
27
  .join("\n");
28
28
  };
@@ -44,7 +44,7 @@ const createPage = async (blocks, pageUrl, id) => {
44
44
  const Page = () => {
45
45
  return (
46
46
  <Layout>
47
- ${generateSlices(blocks, pageUrl, id)}
47
+ ${generateSlices(blocks, pageUrl)}
48
48
  </Layout>
49
49
  );
50
50
  };
@@ -59,11 +59,23 @@ const createPage = async (blocks, pageUrl, id) => {
59
59
  require("dotenv").config();
60
60
  // Load PAT from env instead of hardcoding (fallback to old const for now but warn)
61
61
  exports.onPreInit = async (_, pluginOptions) => {
62
- const { brand, azureBranch, personalAccessToken: patOption } = pluginOptions;
62
+ const {
63
+ brand,
64
+ azureBranch,
65
+ personalAccessToken: patOption,
66
+ environment,
67
+ } = pluginOptions;
63
68
 
64
69
  // Try env first, then plugin option fallback
65
70
  const pat = process.env.PERSONAL_ACCESS_TOKEN || patOption;
66
71
 
72
+ if (environment === "dev") {
73
+ console.log(
74
+ "ℹ️ [gatsby-attainlabs-cms] Running in 'dev' environment mode. Skipping component sync."
75
+ );
76
+ return;
77
+ }
78
+
67
79
  if (!pat) {
68
80
  console.warn(
69
81
  "⚠️ [gatsby-attainlabs-cms] No PERSONAL_ACCESS_TOKEN found. " +
@@ -106,13 +118,12 @@ exports.onPreInit = async (_, pluginOptions) => {
106
118
  // List of folders to download from
107
119
  const targets = [
108
120
  {
109
- repoPath: `/apps/cms/src/cms/components/editor/visual-block-editor/components/brands/${brands[brand]}`,
110
- localBasePath: path.resolve("./src/cms/components/"),
121
+ repoPath: `/apps/cms/src/cms/editors/visual-block-editor/brands/${brands[brand]}/`,
122
+ localBasePath: path.resolve("./src/cms/"),
111
123
  },
112
124
  {
113
- repoPath:
114
- "/apps/cms/src/cms/components/editor/visual-block-editor/components/global/blocks",
115
- localBasePath: path.resolve("./src/cms/components/"),
125
+ repoPath: "/apps/cms/src/cms/editors/visual-block-editor/core/",
126
+ localBasePath: path.resolve("./src/cms/"),
116
127
  },
117
128
  {
118
129
  repoPath: `/apps/cms/src/gatsby-plugin-theme-ui/${brands[brand]}`,
@@ -165,13 +176,12 @@ exports.onPreInit = async (_, pluginOptions) => {
165
176
  const result = JSON.parse(data);
166
177
  const posix = path.posix;
167
178
 
168
- // Keep only index.tsx files
179
+ // Exclude files named config.tsx
169
180
  const items = (result.value || []).filter(
170
181
  (i) =>
171
182
  !i.isFolder &&
172
183
  i.gitObjectType === "blob" &&
173
- (posix.basename(i.path) === "index.tsx" ||
174
- posix.extname(i.path) === ".ts")
184
+ posix.basename(i.path) !== "config.tsx"
175
185
  );
176
186
 
177
187
  console.log(`Found ${items.length} files in ${repoPath}`);
@@ -240,8 +250,12 @@ exports.onPreInit = async (_, pluginOptions) => {
240
250
  res.pipe(fileStream);
241
251
  fileStream.on("finish", () => {
242
252
  fileStream.close(() => {
243
- const generatedComment =
244
- "// GENERATED FILE // This file is automatically generated by the AttainLabs CMS plugin. Do not edit this file directly.\n";
253
+ const ext = path.extname(destFile).toLowerCase();
254
+ let generatedComment = "";
255
+ if (ext === ".ts" || ext === ".tsx" || ext === ".js" || ext === ".jsx") {
256
+ generatedComment =
257
+ "// GENERATED FILE // This file is automatically generated by the AttainLabs CMS plugin. Do not edit this file directly.\n";
258
+ }
245
259
  const fileContent = fs.readFileSync(tmpFile, "utf8");
246
260
  fs.writeFileSync(destFile, generatedComment + fileContent);
247
261
 
@@ -259,11 +273,96 @@ exports.onPreInit = async (_, pluginOptions) => {
259
273
  doRequest(fileUrl);
260
274
  }
261
275
  };
276
+ exports.sourceNodes = async (
277
+ { actions, createNodeId, createContentDigest },
278
+ pluginOptions
279
+ ) => {
280
+ const { createNode } = actions;
281
+ const { trustpilotApiKey, brand } = pluginOptions;
282
+ const apiKey = process.env.TRUSTPILOT_API_KEY || trustpilotApiKey;
283
+ console.log(
284
+ `ℹ️ [gatsby-attainlabs-cms] Fetching Trustpilot data for brand: ${brand}`
285
+ );
286
+ // // Map brand names to Trustpilot business unit IDs
287
+ const businessIds = {
288
+ LendDirect: "599affea0000ff0005a95acd",
289
+ "Cash Money": "599afd420000ff0005a95a9d",
290
+ "Heights Finance": "5e72238d600d1a0001be01eb",
291
+ };
292
+ const businessUnitId = businessIds[brand];
293
+ if (!apiKey) {
294
+ console.warn(
295
+ "⚠️ [gatsby-attainlabs-cms] No TRUSTPILOT_API_KEY found. " +
296
+ "Set it in your project's .env file (recommended) or pass via plugin options.\n" +
297
+ "Example .env:\n" +
298
+ "TRUSTPILOT_API_KEY=xxxxxxx\n"
299
+ );
300
+ return; // stop execution early
301
+ }
302
+ if (!brand || !businessUnitId) {
303
+ console.warn(
304
+ "⚠️ [gatsby-attainlabs-cms] Invalid or missing 'brand' option for Trustpilot integration. " +
305
+ `Current value: '${brand}'. Supported brands: ${Object.keys(businessIds).join(", ")}. ` +
306
+ "Trustpilot data will not be fetched."
307
+ );
308
+ return; // stop execution early
309
+ }
310
+
311
+ const fetchTrustpilotData = async (endpoint) => {
312
+ try {
313
+ const response = await fetch(
314
+ `https://api.trustpilot.com/v1/business-units/${businessUnitId}/${endpoint}`,
315
+ {
316
+ headers: { ApiKey: apiKey },
317
+ }
318
+ );
319
+
320
+ if (!response.ok) {
321
+ throw new Error(`HTTP error! status: ${response.status}`);
322
+ }
262
323
 
324
+ return await response.json();
325
+ } catch (error) {
326
+ console.error(
327
+ `Failed to fetch data from Trustpilot (${endpoint}):`,
328
+ error.message
329
+ );
330
+ return null;
331
+ }
332
+ };
333
+
334
+ const businessData = await fetchTrustpilotData("");
335
+ const recentReviewsData = await fetchTrustpilotData(
336
+ "reviews?stars=5&includeReportedReviews=true"
337
+ );
338
+
339
+ if (businessData || recentReviewsData) {
340
+ const combinedData = {
341
+ businessData: businessData || {},
342
+ recentReviews: recentReviewsData || {},
343
+ };
344
+
345
+ return createNode({
346
+ ...combinedData,
347
+ id: createNodeId("trustPilotReviews"),
348
+ parent: null,
349
+ internal: {
350
+ type: "TrustPilotReviews",
351
+ contentDigest: createContentDigest(combinedData),
352
+ },
353
+ });
354
+ }
355
+ };
263
356
  exports.createPages = async ({ actions, store }, pluginOptions) => {
264
- const { brand } = pluginOptions;
357
+ const { brand, environment } = pluginOptions;
265
358
  const { createSlice } = actions;
266
359
  const siteRoot = store.getState().program.directory;
360
+ if (environment === "dev") {
361
+ console.log(
362
+ "ℹ️ [gatsby-attainlabs-cms] Running in 'dev' environment mode. Skipping component sync."
363
+ );
364
+ return;
365
+ }
267
366
  // 🚨 Validate brand option
268
367
  if (!brand || !brands[brand]) {
269
368
  throw new Error(
@@ -292,34 +391,40 @@ exports.createPages = async ({ actions, store }, pluginOptions) => {
292
391
  },
293
392
  },
294
393
  id,
394
+ published,
295
395
  } = page;
296
396
 
297
- //Generate page's blocks slices
298
- await Promise.all(
299
- content.map(async (b) => {
300
- const name = b.props.component.name;
301
- const blockId = `block--${pageUrl}--${name}--${id}`;
302
- console.log(`Creating slice: ${blockId}`);
303
- // here we could await something per slice if needed later
304
- createSlice({
305
- id: blockId,
306
- component: path.resolve(
307
- siteRoot,
308
- "src/cms/components/sliceWrapper.tsx"
309
- ),
310
- context: {
311
- componentPath: `./src/cms/components/${b.props.component.path}/index.tsx`,
312
- ...b.props,
313
- },
314
- });
315
- })
316
- );
317
-
318
- const pageSource = await createPage(content, pageUrl, id);
319
- const outPath = path.join(siteRoot, "src/pages", `${pageUrl}.tsx`);
320
- // Generate the page itself
321
- await fse.outputFile(outPath, pageSource);
322
- console.log(`✅ Wrote page to ${outPath}`);
397
+ if (published) {
398
+ //Generate page's blocks slices
399
+ await Promise.all(
400
+ content.map(async (b) => {
401
+ const name = b.props.component.name;
402
+ const sliceId = `block--${pageUrl}--${name}--${id}--${crypto.randomUUID()}`;
403
+ console.log(`Creating slice: ${sliceId}`);
404
+ // here we could await something per slice if needed later
405
+ createSlice({
406
+ id: sliceId,
407
+ component: path.resolve(
408
+ siteRoot,
409
+ "src/cms/components/sliceWrapper.tsx"
410
+ ),
411
+ context: {
412
+ componentPath: `./src/cms/components${b.props.component.path}/index.tsx`,
413
+ ...b.props,
414
+ },
415
+ });
416
+ b["sliceId"] = sliceId; // attach sliceId to block for later use
417
+ return b;
418
+ })
419
+ );
420
+
421
+ const pageSource = await createPage(content, pageUrl);
422
+ const outPath = path.join(siteRoot, "src/pages", `${pageUrl}.tsx`);
423
+ // Generate the page itself
424
+ await fse.outputFile(outPath, pageSource);
425
+ console.log(`✅ Wrote page to ${outPath}`);
426
+ }
323
427
  }
324
428
  }
325
429
  };
430
+
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "gatsby-attainlabs-cms",
3
- "version": "1.0.15",
3
+ "version": "1.0.18",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
- "types": "dist/index.d.ts",
6
+ "types": "dist/index.d.ts",
7
7
  "scripts": {
8
8
  "test": "echo \"Error: no test specified\" && exit 1",
9
9
  "build": "tsc"
@@ -16,6 +16,11 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "dotenv": "^17.2.1",
19
+ "gatsby-background-image": "^1.6.0",
20
+ "gatsby-plugin-image": "^3.15.0",
21
+ "gatsby-plugin-sharp": "^5.15.0",
22
+ "gatsby-source-filesystem": "^5.15.0",
23
+ "gatsby-transformer-sharp": "^5.15.0",
19
24
  "prettier": "^3.6.2",
20
25
  "react-slick": "^0.31.0"
21
26
  },
@@ -1,7 +1,7 @@
1
1
  // GENERATED FILE // This file is automatically generated by the AttainLabs CMS plugin. Do not edit this file directly.
2
2
 
3
3
  import React from "react";
4
- import type { SliceComponentProps } from "gatsby";
4
+ import { graphql, type SliceComponentProps } from "gatsby";
5
5
  import { CMS_COMPONENTS } from "./map";
6
6
 
7
7
  interface SliceWrapperProps {
@@ -9,11 +9,14 @@ interface SliceWrapperProps {
9
9
  componentPath: string;
10
10
  [key: string]: any;
11
11
  };
12
+ data: any;
12
13
  }
13
14
 
14
- export default function SliceWrapper({ sliceContext }: SliceWrapperProps) {
15
+ export default function SliceWrapper({
16
+ sliceContext,
17
+ data,
18
+ }: SliceWrapperProps) {
15
19
  const { componentPath, ...props } = sliceContext;
16
-
17
20
  const pathFormatted = componentPath
18
21
  .replace("src/cms/components//", "")
19
22
  .replace(/^.\//, "")
@@ -24,5 +27,46 @@ export default function SliceWrapper({ sliceContext }: SliceWrapperProps) {
24
27
  return null;
25
28
  }
26
29
 
27
- return <Component {...props} />;
30
+ return (
31
+ <Component
32
+ {...props}
33
+ trustPilotData={data.allTrustPilotReviews.edges[0].node}
34
+ />
35
+ );
28
36
  }
37
+
38
+ export const TrustPilotQuery = graphql`
39
+ query {
40
+ allTrustPilotReviews {
41
+ edges {
42
+ node {
43
+ recentReviews {
44
+ reviews {
45
+ numberOfLikes
46
+ stars
47
+ text
48
+ title
49
+ createdAt
50
+ isVerified
51
+ reviewVerificationLevel
52
+ consumer {
53
+ displayName
54
+ }
55
+ }
56
+ }
57
+ businessData {
58
+ score {
59
+ stars
60
+ trustScore
61
+ }
62
+ displayName
63
+ numberOfReviews {
64
+ total
65
+ usedForTrustScoreCalculation
66
+ }
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ `;