diff --git a/.gitignore b/.gitignore
index a8f86bc..0f5b2ba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,3 +40,5 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
+
+.vscode
diff --git a/package-lock.json b/package-lock.json
index efef1d8..d814ba3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,8 @@
"@contentstack/live-preview-utils": "^4.2.1",
"@contentstack/personalize-edge-sdk": "^1.0.16",
"@contentstack/utils": "^1.4.4",
+ "@headlessui/react": "^2.2.9",
+ "@heroicons/react": "^2.2.0",
"contentstack": "^3.26.2",
"next": "15.5.4",
"next-intl": "^4.3.9",
@@ -285,28 +287,56 @@
}
},
"node_modules/@floating-ui/core": {
- "version": "1.7.4",
- "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz",
- "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==",
+ "version": "1.7.5",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz",
+ "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==",
"license": "MIT",
"dependencies": {
- "@floating-ui/utils": "^0.2.10"
+ "@floating-ui/utils": "^0.2.11"
}
},
"node_modules/@floating-ui/dom": {
- "version": "1.7.5",
- "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz",
- "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==",
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
+ "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.5",
+ "@floating-ui/utils": "^0.2.11"
+ }
+ },
+ "node_modules/@floating-ui/react": {
+ "version": "0.26.28",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz",
+ "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==",
"license": "MIT",
"dependencies": {
- "@floating-ui/core": "^1.7.4",
- "@floating-ui/utils": "^0.2.10"
+ "@floating-ui/react-dom": "^2.1.2",
+ "@floating-ui/utils": "^0.2.8",
+ "tabbable": "^6.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/react-dom": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz",
+ "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.7.6"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
}
},
"node_modules/@floating-ui/utils": {
- "version": "0.2.10",
- "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
- "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
+ "version": "0.2.11",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz",
+ "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
"license": "MIT"
},
"node_modules/@formatjs/ecma402-abstract": {
@@ -370,6 +400,35 @@
"tslib": "2"
}
},
+ "node_modules/@headlessui/react": {
+ "version": "2.2.9",
+ "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.9.tgz",
+ "integrity": "sha512-Mb+Un58gwBn0/yWZfyrCh0TJyurtT+dETj7YHleylHk5od3dv2XqETPGWMyQ5/7sYN7oWdyM1u9MvC0OC8UmzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react": "^0.26.16",
+ "@react-aria/focus": "^3.20.2",
+ "@react-aria/interactions": "^3.25.0",
+ "@tanstack/react-virtual": "^3.13.9",
+ "use-sync-external-store": "^1.5.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^18 || ^19 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/@heroicons/react": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz",
+ "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">= 16 || ^19.0.0-rc"
+ }
+ },
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -1464,6 +1523,7 @@
"resolved": "https://registry.npmjs.org/@preact/signals/-/signals-2.6.1.tgz",
"integrity": "sha512-Gp3DI1T/0YyirwJnImR8l9xyVJgKiVzJXmEhic1/7SPw3zStrsvuBpwKnD609CzsIdzxprWa6yTNXN+VLLZPGQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@preact/signals-core": "^1.12.2"
},
@@ -1485,6 +1545,103 @@
"url": "https://opencollective.com/preact"
}
},
+ "node_modules/@react-aria/focus": {
+ "version": "3.21.5",
+ "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.21.5.tgz",
+ "integrity": "sha512-V18fwCyf8zqgJdpLQeDU5ZRNd9TeOfBbhLgmX77Zr5ae9XwaoJ1R3SFJG1wCJX60t34AW+aLZSEEK+saQElf3Q==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-aria/interactions": "^3.27.1",
+ "@react-aria/utils": "^3.33.1",
+ "@react-types/shared": "^3.33.1",
+ "@swc/helpers": "^0.5.0",
+ "clsx": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
+ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-aria/interactions": {
+ "version": "3.27.1",
+ "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.27.1.tgz",
+ "integrity": "sha512-M3wLpTTmDflI0QGNK0PJNUaBXXfeBXue8ZxLMngfc1piHNiH4G5lUvWd9W14XVbqrSCVY8i8DfGrNYpyyZu0tw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-aria/ssr": "^3.9.10",
+ "@react-aria/utils": "^3.33.1",
+ "@react-stately/flags": "^3.1.2",
+ "@react-types/shared": "^3.33.1",
+ "@swc/helpers": "^0.5.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
+ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-aria/ssr": {
+ "version": "3.9.10",
+ "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz",
+ "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ },
+ "engines": {
+ "node": ">= 12"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-aria/utils": {
+ "version": "3.33.1",
+ "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.33.1.tgz",
+ "integrity": "sha512-kIx1Sj6bbAT0pdqCegHuPanR9zrLn5zMRiM7LN12rgRf55S19ptd9g3ncahArifYTRkfEU9VIn+q0HjfMqS9/w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-aria/ssr": "^3.9.10",
+ "@react-stately/flags": "^3.1.2",
+ "@react-stately/utils": "^3.11.0",
+ "@react-types/shared": "^3.33.1",
+ "@swc/helpers": "^0.5.0",
+ "clsx": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
+ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-stately/flags": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.2.tgz",
+ "integrity": "sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ }
+ },
+ "node_modules/@react-stately/utils": {
+ "version": "3.11.0",
+ "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.11.0.tgz",
+ "integrity": "sha512-8LZpYowJ9eZmmYLpudbo/eclIRnbhWIJZ994ncmlKlouNzKohtM8qTC6B1w1pwUbiwGdUoyzLuQbeaIor5Dvcw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-types/shared": {
+ "version": "3.33.1",
+ "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.33.1.tgz",
+ "integrity": "sha512-oJHtjvLG43VjwemQDadlR5g/8VepK56B/xKO2XORPHt9zlW6IZs3tZrYlvH29BMvoqC7RtE7E5UjgbnbFtDGag==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.5.tgz",
@@ -1973,6 +2130,33 @@
"tailwindcss": "4.1.18"
}
},
+ "node_modules/@tanstack/react-virtual": {
+ "version": "3.13.23",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.23.tgz",
+ "integrity": "sha512-XnMRnHQ23piOVj2bzJqHrRrLg4r+F86fuBcwteKfbIjJrtGxb4z7tIvPVAe4B+4UVwo9G4Giuz5fmapcrnZ0OQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/virtual-core": "3.13.23"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@tanstack/virtual-core": {
+ "version": "3.13.23",
+ "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.23.tgz",
+ "integrity": "sha512-zSz2Z2HNyLjCplANTDyl3BcdQJc2k1+yyFoKhNRmCr7V7dY8o8q5m8uFTI1/Pg1kL+Hgrz6u3Xo6eFUB7l66cg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
"node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
@@ -2021,6 +2205,7 @@
"integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -2087,6 +2272,7 @@
"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.54.0",
"@typescript-eslint/types": "8.54.0",
@@ -2573,6 +2759,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2854,6 +3041,7 @@
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz",
"integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
@@ -3021,6 +3209,15 @@
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT"
},
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -3103,7 +3300,8 @@
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
@@ -3538,6 +3736,7 @@
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -3711,6 +3910,7 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -5698,17 +5898,6 @@
}
}
},
- "node_modules/next-intl/node_modules/@swc/helpers": {
- "version": "0.5.18",
- "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz",
- "integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==",
- "license": "Apache-2.0",
- "optional": true,
- "peer": true,
- "dependencies": {
- "tslib": "^2.8.0"
- }
- },
"node_modules/next/node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@@ -6062,6 +6251,7 @@
"resolved": "https://registry.npmjs.org/preact/-/preact-10.28.2.tgz",
"integrity": "sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==",
"license": "MIT",
+ "peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
@@ -6146,6 +6336,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -6155,6 +6346,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"scheduler": "^0.26.0"
},
@@ -6767,6 +6959,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/tabbable": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz",
+ "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==",
+ "license": "MIT"
+ },
"node_modules/tailwindcss": {
"version": "4.1.18",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
@@ -6829,6 +7027,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -6984,6 +7183,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -7084,6 +7284,15 @@
"react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0"
}
},
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/uuid": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
diff --git a/package.json b/package.json
index d14ecc3..57e3fd4 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,8 @@
"@contentstack/live-preview-utils": "^4.2.1",
"@contentstack/personalize-edge-sdk": "^1.0.16",
"@contentstack/utils": "^1.4.4",
+ "@headlessui/react": "^2.2.9",
+ "@heroicons/react": "^2.2.0",
"contentstack": "^3.26.2",
"next": "15.5.4",
"next-intl": "^4.3.9",
diff --git a/public/fonts/250e8e4b496e4dc5220091e3deb9695d.woff2 b/public/fonts/250e8e4b496e4dc5220091e3deb9695d.woff2
new file mode 100644
index 0000000..e6d1844
Binary files /dev/null and b/public/fonts/250e8e4b496e4dc5220091e3deb9695d.woff2 differ
diff --git a/public/fonts/68f02d7f0c95808a4553b17278e213cd.woff b/public/fonts/68f02d7f0c95808a4553b17278e213cd.woff
new file mode 100644
index 0000000..da0a9ab
Binary files /dev/null and b/public/fonts/68f02d7f0c95808a4553b17278e213cd.woff differ
diff --git a/public/fonts/7893d414982a0e78c26139ee5b97b2de.woff b/public/fonts/7893d414982a0e78c26139ee5b97b2de.woff
new file mode 100644
index 0000000..a76e7c6
Binary files /dev/null and b/public/fonts/7893d414982a0e78c26139ee5b97b2de.woff differ
diff --git a/public/fonts/acf09532871d2823b12362210faf5b46.woff b/public/fonts/acf09532871d2823b12362210faf5b46.woff
new file mode 100644
index 0000000..90a634c
Binary files /dev/null and b/public/fonts/acf09532871d2823b12362210faf5b46.woff differ
diff --git a/public/fonts/cec2d493e9b85b4b4e19838cd9579fd8.woff2 b/public/fonts/cec2d493e9b85b4b4e19838cd9579fd8.woff2
new file mode 100644
index 0000000..929daa4
Binary files /dev/null and b/public/fonts/cec2d493e9b85b4b4e19838cd9579fd8.woff2 differ
diff --git a/public/fonts/f4a729dac7ca5c2ee60fcccc3547954b.woff2 b/public/fonts/f4a729dac7ca5c2ee60fcccc3547954b.woff2
new file mode 100644
index 0000000..30eb5fc
Binary files /dev/null and b/public/fonts/f4a729dac7ca5c2ee60fcccc3547954b.woff2 differ
diff --git a/public/fonts/rift/524071a6787baf1dcac8a09b88a39672.woff b/public/fonts/rift/524071a6787baf1dcac8a09b88a39672.woff
new file mode 100644
index 0000000..7f85179
Binary files /dev/null and b/public/fonts/rift/524071a6787baf1dcac8a09b88a39672.woff differ
diff --git a/public/fonts/rift/a b/public/fonts/rift/a
new file mode 100644
index 0000000..2ebadf1
Binary files /dev/null and b/public/fonts/rift/a differ
diff --git a/public/fonts/rift/cf8df83caa33a220e6abb7190465ebc3.woff2 b/public/fonts/rift/cf8df83caa33a220e6abb7190465ebc3.woff2
new file mode 100644
index 0000000..8f20eaf
Binary files /dev/null and b/public/fonts/rift/cf8df83caa33a220e6abb7190465ebc3.woff2 differ
diff --git a/public/fonts/rift/d.woff b/public/fonts/rift/d.woff
new file mode 100644
index 0000000..53148e2
Binary files /dev/null and b/public/fonts/rift/d.woff differ
diff --git a/public/fonts/rift/l.woff2 b/public/fonts/rift/l.woff2
new file mode 100644
index 0000000..632f207
Binary files /dev/null and b/public/fonts/rift/l.woff2 differ
diff --git a/src/app/[locale]/[...slug]/layout.jsx b/src/app/[locale]/[...slug]/layout.jsx
new file mode 100644
index 0000000..79667bd
--- /dev/null
+++ b/src/app/[locale]/[...slug]/layout.jsx
@@ -0,0 +1,47 @@
+
+import { cache } from "react";
+import { headers } from "next/headers";
+import ContentstackServer from "@/lib/cstack";
+import DataContextProvider from "@/context/data.context";
+
+const fetchData = cache(async (locale) => {
+ const headersList = await headers();
+ const variantParam = headersList.get('x-personalize-variants');
+ // example of how to fetch seo metadata from contentstack, replace "homepage" with the content type which contains the seo metadata
+ const data = await ContentstackServer.getElementByType("landing_pages", locale, {}, variantParam);
+ return data;
+});
+
+export const generateMetadata = async ({ params }) => {
+ const { locale } = await params;
+ const data = await fetchData(locale);
+ const entry = data?.[0];
+
+ return {
+ title: entry?.seo?.title,
+ description: entry?.seo?.description,
+ robots: {
+ index: entry?.seo?.no_index || false,
+ follow: entry?.seo?.no_follow || false,
+ },
+ openGraph: {
+ title: entry?.seo?.og_meta_tags?.title,
+ description: entry?.seo?.og_meta_tags?.description,
+ images: entry?.seo?.og_meta_tags?.image,
+ },
+ }
+};
+
+export default async function RootLayout({
+ children,
+ params,
+}) {
+ const { locale } = await params;
+ const data = await fetchData(locale);
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/app/[locale]/[...slug]/page.jsx b/src/app/[locale]/[...slug]/page.jsx
new file mode 100644
index 0000000..7cf1845
--- /dev/null
+++ b/src/app/[locale]/[...slug]/page.jsx
@@ -0,0 +1,96 @@
+"use client";
+import { useDataContext } from "@/context/data.context";
+import { ContentstackClient } from "@/lib/contentstack-client";
+import UnlockAdventureSection from "@/components/UnlockAdventureSection";
+import PreviewVehicle from "@/components/PreviewVehicle";
+import HeroBanner from "@/components/HeroBanner";
+import { useState, useEffect, use } from "react";
+import TextAndImage from "@/components/TextAndImage";
+import CardsCollection from "@/components/CardsCollection";
+import BuyingTools from "@/components/BuyingTools";
+import Navbar from "@/components/Navbar";
+
+export default function Home({ params }) {
+ const { locale } = use(params);
+ const initialData = useDataContext();
+ const pageUrl = use(params).slug?.length > 0 ? "/"+use(params).slug.join('/') : null;
+
+ const [entry, setEntry] = useState(null);
+
+ const getContent = async () => {
+ const data = await ContentstackClient.getElementByUrlWithRefs(
+ "landing_pages",
+ pageUrl, locale,
+ [
+ 'hero_banner',
+ 'hero_banner.cta.internal_link',
+ 'modular_blocks.unlock_adventure_section.reference',
+ 'modular_blocks.unlock_adventure_section.reference.vehicles.internal_url',
+ 'modular_blocks.preview_vehicle.vehicle_preview_reference',
+ 'modular_blocks.preview_vehicle.vehicle_preview_reference.vehicle_models',
+ 'modular_blocks.preview_vehicle.vehicle_preview_reference.link.internal_link',
+ 'modular_blocks.buying_tools.reference',
+ 'modular_blocks.buying_tools.reference.icon_list.internal_url',
+ ],
+ // initialData
+ )
+
+ setEntry(data[0]);
+ };
+
+ useEffect(() => {
+ ContentstackClient.onEntryChange(() => {
+ getContent();
+ });
+ }, []);
+
+ return (
+
+
+
+
+
+ {entry?.modular_blocks.map((block, index) => (
+
+ {block.hasOwnProperty("unlock_adventure_section") && (
+
+ )}
+ {block.hasOwnProperty("preview_vehicle") && (
+
+ )}
+ {block.hasOwnProperty("text_and_image") && (
+
+ )}
+ {block.hasOwnProperty("card_collection") && (
+
+ )}
+ {block.hasOwnProperty("buying_tools") && (
+
+ )}
+
+ ))}
+
+
+
+ );
+}
+
diff --git a/src/app/[locale]/page.jsx b/src/app/[locale]/page.jsx
index 7c6cc3b..0f6f3df 100644
--- a/src/app/[locale]/page.jsx
+++ b/src/app/[locale]/page.jsx
@@ -1,7 +1,13 @@
"use client";
import { useDataContext } from "@/context/data.context";
import { ContentstackClient } from "@/lib/contentstack-client";
+import HeroBanner from "@/components/HeroBanner";
+import UnlockAdventureSection from "@/components/UnlockAdventureSection";
import { useState, useEffect, use } from "react";
+import TextAndImage from "@/components/TextAndImage";
+import CardsCollection from "@/components/CardsCollection";
+import Navbar from "@/components/Navbar";
+import BuyingTools from "@/components/BuyingTools";
export default function Home({ params }) {
const { locale } = use(params);
@@ -9,23 +15,74 @@ export default function Home({ params }) {
const [entry, setEntry] = useState(null);
- useEffect(() => {
- const fetchData = async () => {
- // example of how to fetch data from contentstack, replace "homepage" with the content type you want to fetch
- const data = await ContentstackClient.getElementByType("homepage", locale, initialData);
- if(data) {
- setEntry(data[0]);
- } else {
- setEntry(null);
- }
- }
+ const getContent = async () => {
+ const data = await ContentstackClient.getElementByTypeWithRefs(
+ "homepage",
+ locale,
+ [
+ 'hero_carousel',
+ 'hero_carousel.cta.internal_link',
+ 'modular_blocks.unlock_adventure_section.reference',
+ 'modular_blocks.unlock_adventure_section.reference.vehicles.internal_url',
+ 'modular_blocks.buying_tools.reference',
+ 'modular_blocks.buying_tools.reference.icon_list.internal_url',
+ ],
+ // initialData
+ );
+ // console.log(data);
+
+ setEntry(data[0]);
+ };
- ContentstackClient.onEntryChange(fetchData);
- }, [locale, initialData]);
+ useEffect(() => {
+ ContentstackClient.onEntryChange(() => {
+ getContent();
+ });
+ }, []);
return (
-
{entry?.title}
+
+
+
+
+ {entry?.modular_blocks.map((block, index) => (
+
+ {block.hasOwnProperty("unlock_adventure_section") && (
+
+ )}
+ {block.hasOwnProperty("text_and_image") && (
+
+ )}
+ {block.hasOwnProperty("card_collection") && (
+
+ )}
+ {block.hasOwnProperty("buying_tools") && (
+
+ )}
+
+ ))}
+
+
);
}
diff --git a/src/app/api/contentstack/getElementByReference/route.js b/src/app/api/contentstack/getElementByReference/route.js
new file mode 100644
index 0000000..bb105a0
--- /dev/null
+++ b/src/app/api/contentstack/getElementByReference/route.js
@@ -0,0 +1,13 @@
+import ContentstackServer from "@/lib/cstack";
+
+export async function POST(request) {
+ try {
+ const variantParam = request.headers.get('x-personalize-variants');
+ const { type, locale, referenceUids, live_preview } = await request.json();
+ const res = await ContentstackServer.getElementByReference(type, locale, referenceUids, live_preview, variantParam);
+ return Response.json(res || {});
+ } catch (error) {
+ console.error(error);
+ return Response.json({ error: "Failed to fetch data" }, { status: 500 });
+ }
+}
diff --git a/src/app/globals.css b/src/app/globals.css
index a461c50..7399cb1 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1 +1,139 @@
-@import "tailwindcss";
\ No newline at end of file
+@import "tailwindcss";
+
+@font-face {
+ font-family: Montserrat;
+ src: url(/fonts/68f02d7f0c95808a4553b17278e213cd.woff) format("woff2"), url(/fonts/68f02d7f0c95808a4553b17278e213cd.woff) format("woff");
+ font-weight: 400;
+ font-style: normal;
+}
+@font-face {
+ font-family: riftdemi;
+ src: url(/fonts/f4a729dac7ca5c2ee60fcccc3547954b.woff2) format("woff2"), url(/fonts/7893d414982a0e78c26139ee5b97b2de.woff) format("woff");
+ font-weight: 400;
+ font-style: normal;
+}
+@font-face {
+ font-family: aktiv_grotesk;
+ src: url(/fonts/250e8e4b496e4dc5220091e3deb9695d.woff) format("woff2"), url(/fonts/acf09532871d2823b12362210faf5b46.woff) format("woff");
+ font-weight: 400;
+ font-style: normal;
+}
+/* @font-face {
+ font-family: rift;
+ src: url(/fonts/rift/l.woff2) format("woff2"), url(fonts/rift/d.woff) format("woff"), url(fonts/rift/a) format("opentype");
+ font-display: auto;
+ font-style: normal;
+ font-weight: 600;
+ font-stretch: normal;
+} */
+@font-face {
+ font-family: riftbold;
+ src: url(/fonts/524071a6787baf1dcac8a09b88a39672.woff2) format("woff2"), url(/fonts/cf8df83caa33a220e6abb7190465ebc3.woff) format("woff");
+ font-weight: 400;
+ font-style: normal;
+}
+
+@theme {
+ --color-isuzu-red: #e30613;
+ --font-montserrat: Montserrat;
+ --font-riftdemi: riftdemi;
+ --font-aktiv_grotesk: aktiv_grotesk;
+ --font-rift: rift;
+ --font-riftbold: riftbold;
+}
+
+@keyframes hero-banner-content-in {
+ from {
+ opacity: 0;
+ transform: translate3d(0, 10%, 0);
+ }
+ to {
+ opacity: 1;
+ transform: translate3d(0, 0, 0);
+ }
+}
+
+.hero-banner-content {
+ animation: hero-banner-content-in 0.50s ease-out forwards;
+}
+
+@keyframes slideinvehicle {
+ 0% {
+ opacity: 0;
+ margin-left: -20px;
+ }
+ 100% {
+ opacity: 1;
+ margin-left: 0;
+ }
+}
+
+.sub-nav-mega-item__image {
+ animation: slideinvehicle 0.5s ease-out;
+}
+
+@keyframes slidetexticoncontainer{
+ 0% {
+ opacity: 0;
+ margin-top: 60px;
+ }
+ 100% {
+ opacity: 1;
+ margin-top: 0;
+ }
+}
+
+.sub-nav-mega-item__text-icon-container {
+ animation: slidetexticoncontainer 0.3s ease-out;
+}
+
+/* @keyframes cards-collection-slide-next {
+ from {
+ transform: translate3d(80px, 0, 0);
+ }
+ to {
+ transform: translate3d(0, 0, 0);
+ }
+}
+
+@keyframes cards-collection-slide-prev {
+ from {
+ transform: translate3d(-80px, 0, 0);
+ }
+ to {
+ transform: translate3d(0, 0, 0);
+ }
+}
+
+.cards-collection-slide-next {
+ animation: cards-collection-slide-next 500ms ease-in-out forwards;
+}
+
+.cards-collection-slide-prev {
+ animation: cards-collection-slide-prev 500ms ease-in-out forwards;
+} */
+
+@media (prefers-reduced-motion: reduce) {
+ .hero-banner-content {
+ animation: none;
+ opacity: 1;
+ transform: translate3d(0, 0, 0);
+ }
+
+ .sub-nav-mega-item__image {
+ animation: none;
+ opacity: 1;
+ margin-left: 0;
+ }
+
+ .sub-nav-mega-item__text-icon-container {
+ animation: none;
+ opacity: 1;
+ margin-top: 0;
+ }
+
+ /* .cards-collection-slide-next,
+ .cards-collection-slide-prev {
+ animation: none;
+ } */
+}
\ No newline at end of file
diff --git a/src/components/BuyingTools.jsx b/src/components/BuyingTools.jsx
new file mode 100644
index 0000000..85ed61c
--- /dev/null
+++ b/src/components/BuyingTools.jsx
@@ -0,0 +1,141 @@
+"use client";
+
+function getToolHref(item) {
+ if (!item) return "";
+ const url = item?.internal_url?.[0]?.url;
+ return url || null;
+}
+
+function isFullWidthFlag(value) {
+ if (value === true) return true;
+ if (typeof value === "string") {
+ const v = value.trim().toLowerCase();
+ return v === "true" || v === "yes" || v === "1";
+ }
+ return false;
+}
+
+function BuyingToolItem({ item, index, count, barLayout, content, groupUid, fullWidth }) {
+ const href = getToolHref(item);
+ const iconUrl = item?.icon?.url || "";
+ const IconWrapper = href ? "a" : "div";
+ const label = item?.text || "";
+
+
+ const borders = barLayout
+ ? [
+ index % 2 === 0 ? "max-md:border-r max-md:border-neutral-300/80" : "",
+ index < 2 ? "max-md:border-b max-md:border-neutral-300/80 md:border-b-0" : "",
+ index < count - 1 ? "md:border-r md:border-neutral-300/80" : "",
+ ]
+ .filter(Boolean)
+ .join(" ")
+ : "";
+
+ return (
+
+
+ {iconUrl ? (
+

+ ) : (
+
+ Icon
+
+ )}
+
+ {label ? (
+
+
+ {label}
+
+
+
+ ) : null}
+
+ );
+}
+
+export default function BuyingTools({ content }) {
+ const items = Array.isArray(content?.icon_list)
+ ? content.icon_list
+ : Array.isArray(content?.icons)
+ ? content.icons
+ : [];
+ const list = items.filter(Boolean);
+ const groupUid = Array.isArray(content?.icon_list)
+ ? "icon_list"
+ : "icons";
+ const fullWidth = isFullWidthFlag(
+ content?.full_width ?? content?.fullWidth,
+ );
+ const bgColor = content?.backgound_color?.hex
+
+ if (list.length === 0) return null;
+
+ return (
+
+
+ {fullWidth && content?.title ? (
+
+ ) : null}
+
+
+ {list.map((item, index) => (
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/components/CardsCollection.jsx b/src/components/CardsCollection.jsx
new file mode 100644
index 0000000..1e4e67d
--- /dev/null
+++ b/src/components/CardsCollection.jsx
@@ -0,0 +1,251 @@
+"use client";
+
+import { useEffect, useMemo, useState } from "react";
+
+function getStartIndexes(totalCards, visibleCount, step) {
+ if (totalCards <= visibleCount) return [0];
+
+ const indexes = [];
+ const maxStart = totalCards - visibleCount;
+
+ for (let index = 0; index <= maxStart; index += step) {
+ indexes.push(index);
+ }
+
+ if (indexes[indexes.length - 1] !== maxStart) {
+ indexes.push(maxStart);
+ }
+
+ return indexes;
+}
+
+function getCtaData(cta) {
+ const item = Array.isArray(cta) ? cta[0] : cta;
+ if (!item) return { label: "", href: "#" };
+
+ return {
+ label: item.link_text,
+ href: item.internal_link?.[0]?.url || item.external_link || "#",
+ };
+}
+
+function CarouselArrow({ direction, onClick }) {
+ const isPrev = direction === "prev";
+
+ return (
+
+ );
+}
+
+function CollectionCta({ cta, editableAttrs, compact = false }) {
+ const { label, href } = getCtaData(cta);
+
+ if (!label) return null;
+
+ return (
+
+
+ {label}
+
+
+
+
+ );
+}
+
+function CollectionCard({ card, gridType }) {
+ const imageUrl = card?.image?.url || "";
+ const ctaAttrs = card?.link?.$?.link_text || {};
+ const isTwoByTwo = gridType === "2x2";
+ return (
+
+
+ {imageUrl ? (
+ // eslint-disable-next-line @next/next/no-img-element
+

+ ) : (
+
+ Image
+
+ )}
+
+
+
+
+ {card?.heading ? (
+
+ {card.heading}
+
+ ) : null}
+
+ {card?.description ? (
+
+ ) : null}
+
+
+
+
+
+ );
+}
+
+export default function CardsCollection({ content }) {
+ const gridValue = String(content?.grid || "4x1").trim().toLowerCase();
+ const gridType = gridValue === "2x2" ? "2x2" : "4x1";
+ const cards = Array.isArray(content?.cards) ? content.cards.filter(Boolean) : [];
+ const cardsPerPage = 4;
+ const stepSize = 2;
+ const startIndexes = useMemo(
+ () => getStartIndexes(cards.length, cardsPerPage, stepSize),
+ [cards.length],
+ );
+ const [stepIndex, setStepIndex] = useState(0);
+ // const [slideDirection, setSlideDirection] = useState("next");
+
+ useEffect(() => {
+ setStepIndex((current) => Math.min(current, startIndexes.length - 1));
+ }, [startIndexes]);
+
+ const visibleCards = useMemo(() => {
+ const start = startIndexes[stepIndex] ?? 0;
+ return cards.slice(start, start + cardsPerPage);
+ }, [cards, startIndexes, stepIndex]);
+
+ const showArrows = cards.length > cardsPerPage;
+
+ const gridClass =
+ gridType === "2x2"
+ ? "grid-cols-1 gap-6 sm:grid-cols-2"
+ : "grid-cols-1 gap-6 sm:grid-cols-2 xl:grid-cols-4 px-[40px]";
+ const sectionMaxWidth =
+ gridType === "2x2" ? "max-w-[1630px]" : "max-w-[1550px]";
+
+ return (
+
+ {content?.title ? (
+
+ ) : null}
+
+
+ {showArrows ? (
+ <>
+
{
+ // setSlideDirection("prev");
+ setStepIndex((current) =>
+ current === 0 ? startIndexes.length - 1 : current - 1,
+ );
+ }}
+ />
+ {
+ // setSlideDirection("next");
+ setStepIndex((current) =>
+ current === startIndexes.length - 1 ? 0 : current + 1,
+ );
+ }}
+ />
+ >
+ ) : null}
+
+
+ {visibleCards.map((card, index) => (
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/components/HeroBanner.jsx b/src/components/HeroBanner.jsx
new file mode 100644
index 0000000..76ca656
--- /dev/null
+++ b/src/components/HeroBanner.jsx
@@ -0,0 +1,182 @@
+"use client";
+
+import { useState, useEffect } from "react";
+
+export default function HeroBanner({ content = [] }) {
+ const [currentIndex, setCurrentIndex] = useState(0);
+ const totalSlides = content?.length;
+ const currentSlide = content?.[currentIndex] || {};
+ const hasHeading = Boolean(currentSlide?.heading);
+ const hasSubheading = Boolean(currentSlide?.sub_heading);
+ const isButtonOnly = !hasHeading && !hasSubheading && currentSlide?.cta?.link_text;
+
+ // Auto slide (optional - can remove if not needed)
+ useEffect(() => {
+ if (totalSlides <= 1) return;
+
+ const interval = setInterval(() => {
+ setCurrentIndex((prev) => (prev + 1) % totalSlides);
+ }, 8000);
+
+ return () => clearInterval(interval);
+ }, [totalSlides]);
+
+ const getTextAlignment = () => {
+ switch (currentSlide?.text_position) {
+ case "Left":
+ return "items-start text-left";
+ case "Center":
+ return "items-center text-center";
+ case "Right":
+ default:
+ return "items-end text-right";
+ }
+ };
+
+ const goToPrev = () => {
+ if (totalSlides <= 1) return;
+ setCurrentIndex((prev) => (prev - 1 + totalSlides) % totalSlides);
+ };
+
+ const goToNext = () => {
+ if (totalSlides <= 1) return;
+ setCurrentIndex((prev) => (prev + 1) % totalSlides);
+ };
+
+ return (
+
+ {/* Background media: video first, image fallback */}
+ {currentSlide?.video?.url ? (
+
+ ) : currentSlide?.background_image?.url ? (
+

+ ) : null}
+
+ {/* Overlay (for readability like your image) */}
+ {/*
*/}
+
+ {/* Content */}
+
+ {/* Heading */}
+ {currentSlide?.heading && (
+
+ {currentSlide?.heading}
+
+ )}
+
+ {/* Subheading */}
+ {currentSlide?.sub_heading && (
+
+ {currentSlide.sub_heading}
+
+ )}
+
+ {/* CTA */}
+ {currentSlide?.cta?.link_text && (
+
+ {currentSlide.cta.link_text}
+
+
+ )}
+
+
+ {/* Carousel arrows (only if more than 1 slide) */}
+ {totalSlides > 1 && (
+ <>
+
+
+ >
+ )}
+
+ {/* Dots Navigation (ONLY if more than 1 slide) */}
+ {totalSlides > 1 && (
+
+ {content.map((_, index) => (
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/Navbar/SecondaryNavBar.jsx b/src/components/Navbar/SecondaryNavBar.jsx
new file mode 100644
index 0000000..e63b267
--- /dev/null
+++ b/src/components/Navbar/SecondaryNavBar.jsx
@@ -0,0 +1,263 @@
+"use client";
+
+import Link from "next/link";
+import { useEffect, useId, useMemo, useState } from "react";
+
+// Renders either next/link or a plain so menu URLs work for internal paths and absolute external URLs.
+function NavAnchor({ link, className, children, ...rest }) {
+ const href = link;
+ const external = /^https?:\/\//i.test(String(href).trim());
+
+ if (external) {
+ return (
+
+ {children}
+
+ );
+ }
+
+ return (
+
+ {children}
+
+ );
+}
+
+// Collects all column links from every menu_item so mega + secondary bar can render one combined list.
+export function flattenSubNavMenuLinks(sub) {
+ const items = sub?.menu_items || sub?.menu_item || [];
+ return items.flatMap((mi) => mi?.links || mi?.link || []);
+}
+
+// Returns the URL path stored on a sub-nav link’s internal_link entry for route matching and active states.
+function getSubNavLinkInternalPath(link) {
+ const url = link?.internal_link?.[0]?.url;
+ if (url == null ) return null;
+ const t = url.trim();
+ const clean = t.startsWith("/") ? t : `/${t}`;
+ return clean;
+}
+
+// Compares slugParam to a sub-nav link’s internal path so the secondary bar can track the current page.
+function slugParamMatchesSubNavLink(slugParam, link) {
+ const internalPath = getSubNavLinkInternalPath(link);
+ if (internalPath == null || slugParam == null) return false;
+ const slugStr = Array.isArray(slugParam)
+ ? slugParam.filter(Boolean).join("/")
+ : String(slugParam).trim();
+ if (!slugStr) return false;
+ const pathNorm = internalPath.replace(/^\/+|\/+$/g, "");
+ const slugNorm = slugStr.replace(/^\/+|\/+$/g, "");
+ return slugNorm === pathNorm;
+}
+
+// True when the active route matches this sub-nav link (used for secondary bar visibility and link highlighting).
+export function subNavLinkMatchesCurrentRoute(link, slugParam) {
+ return (
+ slugParamMatchesSubNavLink(slugParam, link)
+ );
+}
+
+function getSecondaryNavLogoUrl(sub) {
+ if (!sub) return null;
+ const img = sub?.secondary_nav_logo;
+ if (img?.url) return img.url;
+ if (Array.isArray(img) && img[0]?.url) return img[0].url;
+ return null;
+}
+
+function getSecondaryNavCta(sub) {
+ if (!sub) return { text: null, link: null };
+ return {
+ text: sub?.secondary_nav_cta?.text,
+ link: {
+ internal_link: sub?.secondary_nav_cta?.internal_link
+ },
+ };
+}
+
+function secondaryNavLinkClass(active) {
+ return `font-riftdemi pb-0.5 text-[15px] uppercase tracking-wide transition-colors ${
+ active
+ ? "border-b-1 border-isuzu-red text-white/40"
+ : "text-white/85 hover:text-white hover:border-b-1 hover:border-isuzu-red"
+ }`;
+}
+
+export default function SecondaryNavBar({ sub, slugParam, pathname }) {
+ const [mobileExpanded, setMobileExpanded] = useState(false);
+ const mobilePanelId = useId();
+ const flatLinks = useMemo(() => flattenSubNavMenuLinks(sub), [sub]);
+ const logoUrl = getSecondaryNavLogoUrl(sub);
+ const cta = getSecondaryNavCta(sub);
+ const logoLive =
+ sub?.$?.secondary_nav_logo ||
+ sub?.$?.secondary_navigation_logo ||
+ sub?.$?.secondary_nav_logo_image ||
+ {};
+
+ const activeLink = useMemo(
+ () => flatLinks.find((link) => subNavLinkMatchesCurrentRoute(link, slugParam)),
+ [flatLinks, slugParam],
+ );
+
+ const collapsedLabel =
+ activeLink?.link_text ||
+ activeLink?.title ||
+ activeLink?.text ||
+ flatLinks[0]?.link_text ||
+ "";
+
+ useEffect(() => {
+ setMobileExpanded(false);
+ }, [pathname, slugParam]);
+
+ return (
+
+ {/* Below lg: collapsed row (active label + chevron only); tap expands links + CTA. */}
+
+
+ {mobileExpanded ? (
+
+
+ {flatLinks.map((link, idx) => {
+ const href =
+ link?.internal_link?.[0]?.url || link?.external_link || "#";
+ const active = subNavLinkMatchesCurrentRoute(link, slugParam);
+ return (
+ -
+ setMobileExpanded(false)}
+ >
+ {link?.link_text}
+
+
+ );
+ })}
+
+ {cta.text ? (
+
+
setMobileExpanded(false)}
+ >
+ {cta.text}
+
+
+
+ ) : null}
+
+ ) : null}
+
+
+ {/* lg+: logo, full link row, CTA */}
+
+
+ );
+}
diff --git a/src/components/Navbar/index.jsx b/src/components/Navbar/index.jsx
new file mode 100644
index 0000000..9e1f396
--- /dev/null
+++ b/src/components/Navbar/index.jsx
@@ -0,0 +1,671 @@
+"use client";
+
+import Link from "next/link";
+import { useParams, usePathname } from "next/navigation";
+import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
+import { ContentstackClient } from "@/lib/contentstack-client";
+import SecondaryNavBar, { flattenSubNavMenuLinks, subNavLinkMatchesCurrentRoute} from "./SecondaryNavBar";
+
+function getSubNavEntry(item) {
+ const ref = item?.sub_nav;
+ if (!ref) return null;
+ return Array.isArray(ref) ? ref[0] : ref;
+}
+
+function showSecondaryNavBar(sub) {
+ if (!sub) return false;
+ const v = sub?.show_secondary_navigation_bar;
+ if (v === true || v === "true") return true;
+ return Boolean(v);
+}
+
+// Decides if a top-level item should show a chevron and mega/accordion (any columns, images, or tiles in sub_nav).
+function hasSubNavigation(item) {
+ const sub = getSubNavEntry(item);
+ if (!sub) return false;
+ const items = sub.menu_items || sub.menu_item || [];
+ const hasMenuItems =
+ Array.isArray(items) &&
+ items.length > 0 &&
+ Boolean(
+ items[0] &&
+ (items[0].title ||
+ items[0].links?.length ||
+ items[0].image?.url ||
+ items[0].tiles?.length > 0),
+ );
+ const anyItemImage = Array.isArray(items) && items.some((mi) => mi?.image?.url);
+ const anyItemTiles =
+ Array.isArray(items) &&
+ items.some((mi) => mi?.tiles?.length > 0);
+ return hasMenuItems || anyItemImage || anyItemTiles;
+}
+
+// Finds which sub_nav (if any) should drive the secondary bar when the current slug matches one of its links.
+function findSecondaryNavContext(menu, slugParam) {
+ if (!Array.isArray(menu)) return null;
+ for (let i = 0; i < menu.length; i++) {
+ const item = menu[i];
+ if (!hasSubNavigation(item)) continue;
+ const sub = getSubNavEntry(item);
+ if (!sub || !showSecondaryNavBar(sub)) continue;
+ const links = flattenSubNavMenuLinks(sub);
+ for (const link of links) {
+ if (subNavLinkMatchesCurrentRoute(link, slugParam)) {
+ return { sub };
+ }
+ }
+ }
+ return null;
+}
+
+// Renders either next/link or a plain so menu URLs work for internal paths and absolute external URLs.
+function NavAnchor({ link, className, children, ...rest }) {
+ const href = link;
+ const external = /^https?:\/\//i.test(String(href).trim());
+
+ if (external) {
+ return (
+
+ {children}
+
+ );
+ }
+ return (
+
+ {children}
+
+ );
+}
+
+function Chevron({ open, active }) {
+ return (
+
+ );
+}
+
+function ChevronRightMobile({ expanded }) {
+ return (
+
+ );
+}
+
+function subNavMegaFlexClass(count, plainMobile) {
+ if (plainMobile) {
+ return "flex flex-col gap-8";
+ }
+ if (count <= 1) {
+ return "flex flex-col items-center gap-10 md:gap-12 lg:gap-16";
+ }
+ if (count === 2 || count === 3) {
+ return "flex flex-col gap-10 md:flex-row md:items-start md:gap-12 lg:gap-16";
+ }
+ if (count === 4 || count === 5) {
+ return "flex flex-col gap-10 sm:flex-row sm:flex-wrap sm:items-start sm:gap-x-10 sm:gap-y-10 lg:flex-nowrap lg:gap-12";
+ }
+ return "flex flex-col gap-10 sm:flex-row sm:flex-wrap sm:items-start sm:gap-x-10 sm:gap-y-10 xl:flex-nowrap xl:gap-12 xl:gap-16";
+}
+
+function subNavMegaItemFlexClass(count, plainMobile) {
+ if (plainMobile) return "w-full min-w-0";
+ if (count <= 1) return "w-full max-w-3xl";
+ if (count === 2 || count === 3) {
+ return "w-full min-w-0 md:flex-1 md:basis-0";
+ }
+ if (count === 4 || count === 5) {
+ return "w-full min-w-0 sm:flex-1 sm:basis-1/2 lg:basis-0 lg:flex-1";
+ }
+ return "w-full min-w-0 sm:flex-1 sm:basis-1/2 md:basis-1/3 xl:basis-0 xl:flex-1";
+}
+
+function SubNavMegaItem({ item, locale, count, plainMobile, onMegaLinkClick }) {
+ const iconUrl = plainMobile ? null : item?.icon?.url;
+ const title = item?.title;
+ const links = item?.links || item?.link || [];
+ const imageUrl = plainMobile ? null : item?.image?.url;
+ const tiles = item?.tiles || item?.tile || [];
+ const centerTextOnlyColumn = !imageUrl;
+
+ const titleClass = plainMobile
+ ? "font-riftdemi text-sm uppercase tracking-wide text-neutral-900"
+ : count === 1
+ ? "font-riftdemi font-bold text-[18px] md:text-5xl uppercase tracking-wide text-neutral-900"
+ : "font-riftdemi font-bold text-[18px] md:text-5xl uppercase tracking-wide text-neutral-900";
+
+ const linkClass = plainMobile
+ ? "font-riftdemi text-[16px] uppercase tracking-wide text-neutral-900 transition-colors group-hover:text-isuzu-red"
+ : "font-riftdemi text-[18px] uppercase tracking-wide text-neutral-900 transition-colors group-hover:text-isuzu-red";
+
+ const textColumn = (
+
+ {iconUrl ? (
+ // eslint-disable-next-line @next/next/no-img-element
+

+ ) : null}
+
+ {title ?
{title}
: null}
+
+ {links.map((link, idx) => (
+ -
+ onMegaLinkClick?.()}
+ >
+
+ {link?.link_text}
+
+
+
+
+ ))}
+
+
+
+ );
+
+ const imageBlock = imageUrl ? (
+
+
+ {/* eslint-disable-next-line @next/next/no-img-element */}
+

+
+
+ ) : null;
+
+ return (
+
+ {imageUrl ? (
+
+ {textColumn}
+ {imageBlock}
+
+ ) : (
+ textColumn
+ )}
+ {tiles.length > 0 ? (
+
+ ) : null}
+
+ );
+}
+
+function SubNavMega({sub, locale, panelId, plainMobile = false, onSubNavLinkActivate}) {
+ const menuItems = sub?.menu_items || sub?.menu_item || [];
+ const count = menuItems.length;
+ const flexClass = subNavMegaFlexClass(count, plainMobile);
+ const megaLinkClick =
+ typeof onSubNavLinkActivate === "function"
+ ? () => onSubNavLinkActivate()
+ : undefined;
+
+ return (
+
+
+
+ {menuItems.map((item, idx) => (
+
+ ))}
+
+
+
+ );
+}
+
+export default function Navbar({ navigation }) {
+ const params = useParams();
+ const pathname = usePathname();
+ const locale = params?.locale || "en";
+ const slugParam = params?.slug;
+ const baseId = useId();
+ const headerRef = useRef(null);
+ const [openIndex, setOpenIndex] = useState(null);
+ const [mobileNavOpen, setMobileNavOpen] = useState(false);
+ const [mobileSubIndex, setMobileSubIndex] = useState(null);
+
+ // Runs when a user follows a link inside the mega / mobile sub-nav so overlays close after navigation.
+ const handleSubNavLinkActivate = useCallback(() => {
+ setOpenIndex(null);
+ setMobileNavOpen(false);
+ setMobileSubIndex(null);
+ }, []);
+
+ // Collapses primary mega, mobile drawer, and accordions (route change, Escape, click outside header).
+ const closeMenus = useCallback(() => {
+ setOpenIndex(null);
+ setMobileNavOpen(false);
+ setMobileSubIndex(null);
+ }, []);
+
+ const [entry, setEntry] = useState(null);
+
+ useEffect(() => {
+ const getContent = async () => {
+ const data = await ContentstackClient.getElementByTypeWithRefs(
+ "navigation",
+ locale,
+ [ 'menu.sub_nav', 'menu.internal_link', 'menu.sub_nav.menu_items.links.internal_link', 'menu.sub_nav.secondary_nav_cta.internal_link', 'menu.sub_nav.menu_items.tiles.internal_url', 'cta.internal_link'],
+ );
+
+ setEntry(data[0]);
+ };
+ getContent();
+ ContentstackClient.onEntryChange(() => {getContent()});
+ }, [locale]);
+
+ useEffect(() => {
+ closeMenus();
+ }, [pathname, closeMenus]);
+
+ useEffect(() => {
+ function onKey(e) {
+ if (e.key === "Escape") closeMenus();
+ }
+ window.addEventListener("keydown", onKey);
+ return () => window.removeEventListener("keydown", onKey);
+ }, [closeMenus]);
+
+ useEffect(() => {
+ function onPointerDown(e) {
+ if (!headerRef.current?.contains(e.target)) closeMenus();
+ }
+ document.addEventListener("mousedown", onPointerDown);
+ document.addEventListener("touchstart", onPointerDown);
+ return () => {
+ document.removeEventListener("mousedown", onPointerDown);
+ document.removeEventListener("touchstart", onPointerDown);
+ };
+ }, [closeMenus]);
+
+ useEffect(() => {
+ if (!mobileNavOpen) return;
+ const prev = document.body.style.overflow;
+ document.body.style.overflow = "hidden";
+ return () => {
+ document.body.style.overflow = prev;
+ };
+ }, [mobileNavOpen]);
+
+ useEffect(() => {
+ if (mobileNavOpen) setOpenIndex(null);
+ }, [mobileNavOpen]);
+
+
+ const menu = useMemo(() => (Array.isArray(entry?.menu) ? entry?.menu : [entry?.menu]), [entry?.menu]);
+ const secondaryNavContext = useMemo(
+ () => findSecondaryNavContext(menu, slugParam),
+ [menu, slugParam],
+ );
+ const logoUrl = entry?.logo?.url;
+ const cta = entry?.cta;
+ const ctaItem = Array.isArray(cta) ? cta[0] : cta;
+ const ctaLabel =
+ ctaItem?.link_text || "Find a dealer";
+
+ const ctaLink = ctaItem?.internal_link?.[0]?.url || ctaItem?.external_link || '#'
+
+ if (!entry) return null;
+
+ const desktopSubNavOpen =
+ openIndex !== null &&
+ Boolean(menu[openIndex] && hasSubNavigation(menu[openIndex]));
+ const mobileSubNavOpen = mobileSubIndex !== null;
+
+ const showSecondaryNavUi =
+ secondaryNavContext &&
+ showSecondaryNavBar(secondaryNavContext.sub) &&
+ flattenSubNavMenuLinks(secondaryNavContext.sub).length > 0 &&
+ !desktopSubNavOpen &&
+ !mobileSubNavOpen;
+
+ return (
+
+
+
+ {showSecondaryNavUi ? (
+
+ ) : null}
+
+ {openIndex !== null && menu[openIndex] && hasSubNavigation(menu[openIndex]) ? (
+
+
+
+ ) : null}
+
+ {mobileNavOpen ? (
+
+
+ {menu.map((item, index) => {
+ const expandable = hasSubNavigation(item);
+ const title = item?.title;
+ const topHref = item?.internal_link[0]?.url || item?.external_link || '#';
+ const subOpen = mobileSubIndex === index;
+ const mobilePanelId = `${baseId}-mobile-sub-${index}`;
+
+ return (
+ -
+ {expandable ? (
+ <>
+
+ {subOpen ? (
+
+
+
+ ) : null}
+ >
+ ) : (
+ closeMenus()}
+ >
+ {title}
+
+ )}
+
+ );
+ })}
+
+
+ ) : null}
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/PreviewVehicle/index.jsx b/src/components/PreviewVehicle/index.jsx
new file mode 100644
index 0000000..9fc5f2d
--- /dev/null
+++ b/src/components/PreviewVehicle/index.jsx
@@ -0,0 +1,239 @@
+"use client";
+import { ChevronRightIcon, ChevronDownIcon, CheckIcon } from "@heroicons/react/24/outline";
+import { useState, useEffect } from "react";
+import { useParams } from "next/navigation";
+import { ContentstackClient } from "@/lib/contentstack-client";
+import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from "@headlessui/react";
+
+export default function PreviewVehicle({ content }) {
+ const models = Array.isArray(content?.vehicle_models) ? content.vehicle_models : [];
+ const modelUids = models.map(model => model.uid);
+ const [activeIndex, setActiveIndex] = useState(0);
+ const { locale } = useParams();
+ const [vehicleVariations, setVehicleVariations] = useState({});
+ const [selectedVariation, setSelectedVariation] = useState(null);
+ console.log("🚀 ~ PreviewVehicle ~ selectedVariation:", selectedVariation)
+ const [selectedColour, setSelectedColour] = useState(null);
+ const activeModel = models[activeIndex];
+
+
+ const fetchMuxProductData = async () => {
+ const data = await ContentstackClient.getElementByReference('vehicle_variation', locale, [...modelUids])
+ const groupedData = data.reduce((acc, item) => {
+ // vehicle_model is a Contentstack reference array
+ const uid = item.vehicle_model?.[0]?.uid ?? item.vehicle_model?.uid;
+ if (!uid) return acc;
+ if (!acc[uid]) acc[uid] = [];
+ acc[uid].push(item);
+ return acc;
+ }, {});
+ setVehicleVariations(groupedData);
+ // pre-select first variation of the initially active model
+ const firstModelUid = models[activeIndex]?.uid;
+ const firstVariation = groupedData[firstModelUid]?.[0] ?? null;
+ setSelectedVariation(firstVariation);
+ setSelectedColour(firstVariation?.available_colours?.[0] ?? null);
+ }
+
+ useEffect(() => {
+ if (window.location.pathname.includes('/mu-x')) {
+ fetchMuxProductData();
+ }
+ }, [])
+
+ return (
+
+
+
+ {/* Title */}
+
+ {content?.title}
+
+
+ {/* Model tabs */}
+
+
+ {models.map((model, index) => (
+
+ ))}
+
+
+
+ {/* Active model panel */}
+ {activeModel && (
+
+
+ {vehicleVariations?.[activeModel.uid]?.length > 1 ? (
+
{
+ setSelectedVariation(v);
+ setSelectedColour(v?.available_colours?.[0] ?? null);
+ }}
+ />
+ ):
+
+ {selectedVariation?.heading}
+
+
}
+
+ {/* Vehicle image — swaps when a colour swatch is clicked */}
+ {(selectedColour?.vehicle_image?.url || activeModel.image?.url) && (
+
+ {/* eslint-disable-next-line @next/next/no-img-element */}
+
![{selectedColour?.colour?.[0]?.title]({selectedColour?.vehicle_image?.url)
+
+ )}
+
+ {/* Colour swatches — always visible when colours exist */}
+ {selectedVariation?.available_colours?.length > 0 && (
+
+
+ {selectedVariation.available_colours.map((entry) => {
+ const colour = entry.colour?.[0];
+ if (!colour) return null;
+ const isActive = entry._metadata?.uid === selectedColour?._metadata?.uid;
+ return (
+
+ {/* Colour name label below swatches */}
+ {selectedColour?.colour?.[0]?.title && (
+
+ {selectedColour.colour[0].title}
+
+ )}
+
+ )}
+
+ {/* CTA */}
+ {content?.link?.link_text && (
+
+ )}
+
+ )}
+
+
+ );
+}
+
+function groupByDriveType(variations) {
+ const groups = [];
+ const seen = new Map();
+ for (const v of variations) {
+ const driveType = v.drive_type ?? extractDriveType(v.title);
+ if (!seen.has(driveType)) {
+ seen.set(driveType, []);
+ groups.push({ label: driveType, items: seen.get(driveType) });
+ }
+ seen.get(driveType).push(v);
+ }
+ return groups;
+}
+
+function extractDriveType(title = "") {
+ const match = title.match(/^(4x[24])/i);
+ return match ? match[1].toUpperCase() : "Other";
+}
+
+function VariationDropdown({ variations, selected, onChange }) {
+ const groups = groupByDriveType(variations);
+
+ return (
+
+
+
+ {selected?.title ?? "Select variant"}
+
+
+
+
+
+
+ {groups.map((group) => (
+
+ {/* Non-interactive drive type group header */}
+
+ {group.label}
+
+ {group.items.map((v) => (
+
+
+ {v.heading || v.title}
+
+ ))}
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/components/TextAndImage.jsx b/src/components/TextAndImage.jsx
new file mode 100644
index 0000000..4908744
--- /dev/null
+++ b/src/components/TextAndImage.jsx
@@ -0,0 +1,190 @@
+"use client";
+
+function getCtaData(cta) {
+ const item = Array.isArray(cta) ? cta[0] : cta;
+ if (!item) return { label: "", href: "" };
+
+ return {
+ label: item.link_text || "",
+ href: item.internal_link?.[0]?.url || item.external_link|| "#",
+ };
+}
+
+function CtaLink({ cta, editableAttrs, textColor }) {
+ const { label, href } = getCtaData(cta);
+
+ if (!label) return null;
+
+ return (
+
+
+ {label}
+
+
+
+
+ );
+}
+
+function TextPanel({ content, backgroundColor, textColor, className = "", splitLayout = true }) {
+ return (
+
+
+ {content?.heading ? (
+
+ {content.heading}
+
+ ) : null}
+
+ {content?.content ? (
+
+ ) : null}
+
+
+
+
+ );
+}
+
+function SplitLayout({ content, textSide, backgroundColor, textColor, imageUrl }) {
+ const textFirst = textSide === "left";
+ const splitlayout = true ;
+
+ return (
+
+
+ {textFirst ? (
+
+ ) : (
+
+ )}
+
+ {textFirst ? (
+
+ ) : (
+
+ )}
+
+
+ );
+}
+
+function ImagePanel({ imageUrl, editableAttrs }) {
+ return imageUrl ? (
+
+ ) : (
+
+ Image
+
+ );
+}
+
+function OverlayLayout({ content, backgroundColor, textColor, imageUrl }) {
+ const splitlayout = false ;
+ return (
+
+
+ {imageUrl ? (
+

+ ) : (
+
+ )}
+
+
+
+
+
+
+ );
+}
+
+export default function TextAndImage({ content }) {
+ const layoutValue = String(content?.layout || "split").trim().toLowerCase();
+ const layout =
+ layoutValue === "text-overlay" || layoutValue === "overlay"
+ ? "overlay"
+ : "split";
+ const textAlignValue = String(content?.text_align || "left")
+ .trim()
+ .toLowerCase();
+ const textSide = textAlignValue === "right" ? "right" : "left";
+ const backgroundColor = content?.background_color?.hex;
+ const textColor = content?.text_color?.hex;
+ const imageUrl = content?.image?.url || "";
+
+ if (layout === "overlay") {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/src/components/UnlockAdventureSection.jsx b/src/components/UnlockAdventureSection.jsx
new file mode 100644
index 0000000..371ae2b
--- /dev/null
+++ b/src/components/UnlockAdventureSection.jsx
@@ -0,0 +1,115 @@
+"use client";
+import { ChevronRightIcon } from "@heroicons/react/24/outline";
+
+export default function UnlockAdventureSection({ content }) {
+ const vehicles = Array.isArray(content?.vehicles) ? content.vehicles : [];
+ return (
+
+
+
+
+
+
+ {vehicles.map((vehicle, index) => (
+
+ ))}
+
+
+
+ );
+}
+
+function VehicleColumn({ vehicle, content, vehicleIndex }) {
+ const imageSrc = vehicle?.vehicle_image?.url;
+ const outlineSrc = vehicle?.hover_background?.url;
+ const alt =
+ vehicle?.vehicle_image?.title ||
+ vehicle?.vehicle_image?.filename ||
+ "Isuzu vehicle";
+
+ const blockAttrs = content?.$?.[`vehicles__${vehicleIndex}`] || {};
+
+ return (
+
+
+ {/* Outline watermark — rendered first so it sits behind the vehicle (z-0) */}
+ {outlineSrc ? (
+ // eslint-disable-next-line @next/next/no-img-element
+

+ ) : null}
+
+
+
+ {imageSrc ? (
+ // eslint-disable-next-line @next/next/no-img-element
+

+ ) : (
+
+ Vehicle image
+
+ )}
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/lib/contentstack-client.js b/src/lib/contentstack-client.js
index 8632e4d..4536587 100644
--- a/src/lib/contentstack-client.js
+++ b/src/lib/contentstack-client.js
@@ -347,6 +347,27 @@ export const ContentstackClient = {
}
}
return data;
- }
+ },
+
+ getElementByReference: async function (type, locale, referenceUids) {
+ const searchQueryParams = getSearchQueryParams();
+ if (inLivePreview() && !(searchQueryParams.live_preview || searchQueryParams.hash)) {
+ while (!Stack.live_preview?.hash) {
+ await new Promise(resolve => setTimeout(resolve, 100));
+ }
+ }
+ let data = null;
+ const res = await fetch(`/api/contentstack/getElementByReference`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ type, locale, referenceUids, live_preview: (searchQueryParams.live_preview || searchQueryParams.hash) ? searchQueryParams : (Stack.live_preview.hash) ? Stack.live_preview : null })
+ });
+ if(res.ok) {
+ data = await res.json();
+ } else {
+ data = null;
+ }
+ return data;
+ },
}
\ No newline at end of file
diff --git a/src/lib/cstack.js b/src/lib/cstack.js
index cb171df..d9f25bc 100644
--- a/src/lib/cstack.js
+++ b/src/lib/cstack.js
@@ -1,4 +1,5 @@
import contentstack from '@contentstack/delivery-sdk';
+import { QueryOperation } from '@contentstack/delivery-sdk';
function deserializeVariantIds (variantsQueryParam) {
if(!variantsQueryParam) return '';
@@ -281,6 +282,33 @@ const ContentstackServer = {
});
},
+ getElementByReference: async function (type, locale, referenceUids, live_preview, variantParam) {
+ const query = stack.contentType('vehicle_model').entry().query().where('uid', QueryOperation.INCLUDES, referenceUids);
+
+ stack.livePreviewQuery(live_preview ?? {});
+
+ return new Promise((resolve, reject) => {
+ stack.contentType(type)
+ .entry()
+ .locale(locale ? locale : "en")
+ .includeReference('available_colours.colour')
+ .query().referenceIn('vehicle_model', query)
+ .find()
+ .then(
+ function success(data) {
+ resolve(data.entries);
+ },
+ function empty() {
+ resolve(null);
+ },
+ function error(err) {
+ console.error("error", err);
+ reject(err);
+ }
+ );
+ });
+ },
+
getStack() {
return stack;
},