├── .editorconfig ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── angular.json ├── contentful ├── export.json └── images.ctfassets.net │ └── 5u403xny70b7 │ ├── 10TkaLheGeQG6qQGqWYqUI │ └── e6485b04c5f6991cfbae6ef8e332976f │ │ └── ryugj83mqwa1asojwtwb.jpg │ ├── 1MgbdJNTsMWKI0W68oYqkU │ └── f409b10201b5a2e12409c6a3a2a30033 │ │ └── 9ef190c59f0d375c0dea58b58a4bc1f0.jpeg │ ├── 2Y8LhXLnYAYqKCGEWG4EKI │ └── 94b1546dbbfc121c91b074b53fa5b3bc │ │ └── lemnos-logo.jpg │ ├── 3wtvPBbBjiMKqKKga8I2Cu │ └── 013db30808b5426c8c84434317653a1a │ │ └── zJYzDlGk.jpeg │ ├── 4zj1ZOfHgQ8oqgaSKm4Qo2 │ └── eef852bd9504ab06abd5b3c7523ab9ed │ │ └── playsam.jpg │ ├── 6m5AJ9vMPKc8OUoQeoCS4o │ └── 34f04735f1d86f7b89080752c7505afc │ │ └── 1418244847_Streamline-18-256.png │ ├── 6s3iG2OVmoUcosmA8ocqsG │ └── d576e30a70d1a105ec1966ba033c0d27 │ │ └── 1418244847_Streamline-18-256__1_.png │ ├── 6t4HKjytPi0mYgs240wkG │ └── 765f475f435fbee1d31637b38814124b │ │ └── toys_512pxGREY.png │ ├── 6wDAXbzkqKRAUWO3u8sh2z │ └── 1d77f55c0fe375a862ef553a914bf500 │ │ └── photo-1568901346375-23c9450c58cd │ ├── KTRF62Q4gg60q6WCsWKw8 │ └── 00a22ab2a0577b991f560a4b12f43b35 │ │ └── soso.clock.jpg │ ├── Xc0ny7GWsMEMCeASWO2um │ └── 386ee028262461f49449a3dcde7712ba │ │ └── jqvtazcyfwseah9fmysz.jpg │ └── wtrHxeu3zEoEce2MokCSi │ └── a781b57aaa586539b9fe847b155a68c9 │ └── quwowooybuqbl6ntboz3.jpg ├── e2e ├── app.e2e-spec.ts ├── app.po.ts └── tsconfig.e2e.json ├── karma.conf.js ├── netlify.toml ├── package.json ├── protractor.conf.js ├── sample.env ├── screenshot.png ├── set-env.ts ├── src ├── app │ ├── app.component.css │ ├── app.component.html │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── category-list │ │ ├── category-list.component.css │ │ ├── category-list.component.html │ │ ├── category-list.component.spec.ts │ │ └── category-list.component.ts │ ├── contentful.service.ts │ ├── drupal.service.ts │ ├── product-detail │ │ ├── product-detail.component.css │ │ ├── product-detail.component.html │ │ ├── product-detail.component.spec.ts │ │ └── product-detail.component.ts │ ├── product-list │ │ ├── product-list.component.css │ │ ├── product-list.component.html │ │ ├── product-list.component.spec.ts │ │ └── product-list.component.ts │ └── stackbit.service.ts ├── assets │ └── .gitkeep ├── environments │ └── environment.prod.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.css ├── test.ts ├── tsconfig.app.json ├── tsconfig.spec.json └── typings.d.ts ├── stackbit.config.js ├── stackbit.yaml ├── tsconfig.json ├── tslint.json └── tutorial.md /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | testem.log 34 | /typings 35 | 36 | # e2e 37 | /e2e/*.js 38 | /e2e/*.map 39 | 40 | # System Files 41 | .DS_Store 42 | Thumbs.db 43 | 44 | /.angular 45 | 46 | # Ignoring environment file to make it auto generated based on .env file 47 | /src/environments/environment.ts 48 | 49 | .env -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "activityBar.background": "#043330", 4 | "titleBar.activeBackground": "#064844", 5 | "titleBar.activeForeground": "#EDFEFD" 6 | } 7 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2016 Contentful GmbH 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Product catalogue web application 2 | 3 | This is an example application built in [Angular](https://angular.io/). In the default setup the app gets content from the Contentful read-only [Product Catalogue Space Template](https://www.contentful.com/blog/2015/01/30/introducing-space-templates/). Our example applications for [iOS](https://github.com/contentful/product-catalogue-ios) and [Android](https://github.com/contentful/product-catalogue-android) happen to use the same space template as well. 4 | 5 | This repository is the base for the [Using Contentful in an Angular project](https://www.contentful.com/developers/docs/javascript/tutorials/using-contentful-in-an-angular-project/) tutorial. 6 | 7 | ## What is Contentful 8 | 9 | [Contentful](https://www.contentful.com) is a content management platform for web applications, mobile apps and connected devices. It allows you to create, edit & manage content in the cloud and publish it anywhere via powerful API. Contentful offers tools for managing editorial teams and enabling cooperation between organizations. 10 | 11 | ![Screenshots of Product Catalogue Web demo App](./screenshot.png?raw=true "Screenshots") 12 | 13 | ## Live Demo 14 | 15 | The real benefit of the app is the capability to connect it to any space which uses the [Product Catalogue Space Template](https://www.contentful.com/blog/2015/01/30/introducing-space-templates/). Once the app is connected to a user-controlled version of the [Product Catalogue Space Template](https://www.contentful.com/blog/2015/01/30/introducing-space-templates/), all changes to the space in [the Contentful UI](https://app.contentful.com) will be reflected in the app. 16 | 17 | 1. Prepare a Contentful demo space 18 | - Create a new space in https://app.contentful.com 19 | - **IMPORTANT**: make sure to create it from the [Product Catalogue Space Template](https://www.contentful.com/blog/2015/01/30/introducing-space-templates/) 20 | 2. Connect the space to the demo application 21 | - Open the hosted version of the demo application [https://contentful-labs.github.io/product-catalogue-web.ts](https://contentful-labs.github.io/product-catalogue-web.ts) 22 | - Open settings 23 | - Paste in your API key and space ID 24 | - Save session and/or save the deep link for your convenience 25 | - **Optional**: bookmark the deep link for later usage 26 | 27 | ## Getting started 28 | 29 | - Clone or fork this repository 30 | - run `npm install` to install dependencies 31 | - run `npm start` to fire up dev server 32 | - open browser to `http://localhost:4200` 33 | 34 | ### Links 35 | 36 | This repository is the base for the [Using Contentful in an Angular project](https://www.contentful.com/developers/docs/javascript/tutorials/using-contentful-in-an-angular-project/). 37 | 38 | There are also other implementations of the product catalogue demo available for iOS and Android 39 | 40 | - [Product Catalogue for iOS](https://github.com/contentful/product-catalogue-ios) 41 | - [Product Catalogue for Android](https://github.com/contentful/product-catalogue-android) 42 | 43 | This is a project created for tutorial purposes and not officially supported. Report problems via the issues page but please don't expect a quick and prompt response. 44 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "stackbit-angular-contentful": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "architect": { 11 | "build": { 12 | "builder": "@angular-devkit/build-angular:browser", 13 | "options": { 14 | "outputPath": "dist", 15 | "index": "src/index.html", 16 | "main": "src/main.ts", 17 | "tsConfig": "src/tsconfig.app.json", 18 | "polyfills": "src/polyfills.ts", 19 | "assets": [ 20 | "src/assets", 21 | "src/favicon.ico", 22 | "src/_redirects" 23 | ], 24 | "styles": [ 25 | "src/styles.css" 26 | ], 27 | "scripts": [] 28 | }, 29 | "configurations": { 30 | "production": { 31 | "optimization": true, 32 | "outputHashing": "all", 33 | "sourceMap": false, 34 | "extractCss": true, 35 | "namedChunks": false, 36 | "aot": true, 37 | "extractLicenses": true, 38 | "vendorChunk": false, 39 | "buildOptimizer": true, 40 | "fileReplacements": [ 41 | { 42 | "replace": "src/environments/environment.ts", 43 | "with": "src/environments/environment.prod.ts" 44 | } 45 | ] 46 | } 47 | } 48 | }, 49 | "serve": { 50 | "builder": "@angular-devkit/build-angular:dev-server", 51 | "options": { 52 | "browserTarget": "stackbit-angular-contentful:build" 53 | }, 54 | "configurations": { 55 | "production": { 56 | "browserTarget": "stackbit-angular-contentful:build:production" 57 | } 58 | } 59 | }, 60 | "extract-i18n": { 61 | "builder": "@angular-devkit/build-angular:extract-i18n", 62 | "options": { 63 | "browserTarget": "stackbit-angular-contentful:build" 64 | } 65 | }, 66 | "test": { 67 | "builder": "@angular-devkit/build-angular:karma", 68 | "options": { 69 | "main": "src/test.ts", 70 | "karmaConfig": "./karma.conf.js", 71 | "polyfills": "src/polyfills.ts", 72 | "tsConfig": "src/tsconfig.spec.json", 73 | "scripts": [], 74 | "styles": [ 75 | "src/styles.css" 76 | ], 77 | "assets": [ 78 | "src/assets", 79 | "src/favicon.ico", 80 | "src/_redirects" 81 | ] 82 | } 83 | }, 84 | "lint": { 85 | "builder": "@angular-devkit/build-angular:tslint", 86 | "options": { 87 | "tsConfig": [ 88 | "src/tsconfig.app.json", 89 | "src/tsconfig.spec.json" 90 | ], 91 | "exclude": [ 92 | "**/node_modules/**" 93 | ] 94 | } 95 | } 96 | } 97 | }, 98 | "stackbit-angular-contentful-e2e": { 99 | "root": "", 100 | "sourceRoot": "e2e", 101 | "projectType": "application", 102 | "architect": { 103 | "e2e": { 104 | "builder": "@angular-devkit/build-angular:protractor", 105 | "options": { 106 | "protractorConfig": "./protractor.conf.js", 107 | "devServerTarget": "stackbit-angular-contentful:serve" 108 | } 109 | }, 110 | "lint": { 111 | "builder": "@angular-devkit/build-angular:tslint", 112 | "options": { 113 | "tsConfig": [ 114 | "e2e/tsconfig.e2e.json" 115 | ], 116 | "exclude": [ 117 | "**/node_modules/**" 118 | ] 119 | } 120 | } 121 | } 122 | } 123 | }, 124 | "defaultProject": "stackbit-angular-contentful", 125 | "schematics": { 126 | "@schematics/angular:component": { 127 | "prefix": "app", 128 | "styleext": "css" 129 | }, 130 | "@schematics/angular:directive": { 131 | "prefix": "app" 132 | } 133 | }, 134 | "cli": { 135 | "analytics": false 136 | } 137 | } -------------------------------------------------------------------------------- /contentful/export.json: -------------------------------------------------------------------------------- 1 | { 2 | "contentTypes": [ 3 | { 4 | "sys": { 5 | "space": { 6 | "sys": { 7 | "type": "Link", 8 | "linkType": "Space", 9 | "id": "5u403xny70b7" 10 | } 11 | }, 12 | "id": "brand", 13 | "type": "ContentType", 14 | "createdAt": "2022-06-15T14:02:42.438Z", 15 | "updatedAt": "2022-06-15T14:02:42.912Z", 16 | "environment": { 17 | "sys": { 18 | "id": "master", 19 | "type": "Link", 20 | "linkType": "Environment" 21 | } 22 | }, 23 | "publishedVersion": 1, 24 | "publishedAt": "2022-06-15T14:02:42.912Z", 25 | "firstPublishedAt": "2022-06-15T14:02:42.912Z", 26 | "createdBy": { 27 | "sys": { 28 | "type": "Link", 29 | "linkType": "User", 30 | "id": "5JyrJiiomUI3mj8CrwPKe9" 31 | } 32 | }, 33 | "updatedBy": { 34 | "sys": { 35 | "type": "Link", 36 | "linkType": "User", 37 | "id": "5JyrJiiomUI3mj8CrwPKe9" 38 | } 39 | }, 40 | "publishedCounter": 1, 41 | "version": 2, 42 | "publishedBy": { 43 | "sys": { 44 | "type": "Link", 45 | "linkType": "User", 46 | "id": "5JyrJiiomUI3mj8CrwPKe9" 47 | } 48 | } 49 | }, 50 | "displayField": "companyName", 51 | "name": "Brand", 52 | "description": null, 53 | "fields": [ 54 | { 55 | "id": "companyName", 56 | "name": "Company name", 57 | "type": "Text", 58 | "localized": false, 59 | "required": true, 60 | "validations": [ 61 | ], 62 | "disabled": false, 63 | "omitted": false 64 | }, 65 | { 66 | "id": "logo", 67 | "name": "Logo", 68 | "type": "Link", 69 | "localized": false, 70 | "required": false, 71 | "validations": [ 72 | ], 73 | "disabled": false, 74 | "omitted": false, 75 | "linkType": "Asset" 76 | }, 77 | { 78 | "id": "companyDescription", 79 | "name": "Description", 80 | "type": "Text", 81 | "localized": false, 82 | "required": false, 83 | "validations": [ 84 | ], 85 | "disabled": false, 86 | "omitted": false 87 | }, 88 | { 89 | "id": "website", 90 | "name": "Website", 91 | "type": "Symbol", 92 | "localized": false, 93 | "required": false, 94 | "validations": [ 95 | ], 96 | "disabled": false, 97 | "omitted": false 98 | }, 99 | { 100 | "id": "twitter", 101 | "name": "Twitter", 102 | "type": "Symbol", 103 | "localized": false, 104 | "required": false, 105 | "validations": [ 106 | ], 107 | "disabled": false, 108 | "omitted": false 109 | }, 110 | { 111 | "id": "email", 112 | "name": "Email", 113 | "type": "Symbol", 114 | "localized": false, 115 | "required": false, 116 | "validations": [ 117 | ], 118 | "disabled": false, 119 | "omitted": false 120 | }, 121 | { 122 | "id": "phone", 123 | "name": "Phone #", 124 | "type": "Array", 125 | "localized": false, 126 | "required": false, 127 | "validations": [ 128 | ], 129 | "disabled": false, 130 | "omitted": false, 131 | "items": { 132 | "type": "Symbol", 133 | "validations": [ 134 | ] 135 | } 136 | } 137 | ] 138 | }, 139 | { 140 | "sys": { 141 | "space": { 142 | "sys": { 143 | "type": "Link", 144 | "linkType": "Space", 145 | "id": "5u403xny70b7" 146 | } 147 | }, 148 | "id": "product", 149 | "type": "ContentType", 150 | "createdAt": "2022-06-15T14:02:42.516Z", 151 | "updatedAt": "2022-06-15T14:02:42.942Z", 152 | "environment": { 153 | "sys": { 154 | "id": "master", 155 | "type": "Link", 156 | "linkType": "Environment" 157 | } 158 | }, 159 | "publishedVersion": 1, 160 | "publishedAt": "2022-06-15T14:02:42.942Z", 161 | "firstPublishedAt": "2022-06-15T14:02:42.942Z", 162 | "createdBy": { 163 | "sys": { 164 | "type": "Link", 165 | "linkType": "User", 166 | "id": "5JyrJiiomUI3mj8CrwPKe9" 167 | } 168 | }, 169 | "updatedBy": { 170 | "sys": { 171 | "type": "Link", 172 | "linkType": "User", 173 | "id": "5JyrJiiomUI3mj8CrwPKe9" 174 | } 175 | }, 176 | "publishedCounter": 1, 177 | "version": 2, 178 | "publishedBy": { 179 | "sys": { 180 | "type": "Link", 181 | "linkType": "User", 182 | "id": "5JyrJiiomUI3mj8CrwPKe9" 183 | } 184 | } 185 | }, 186 | "displayField": "productName", 187 | "name": "Product", 188 | "description": null, 189 | "fields": [ 190 | { 191 | "id": "productName", 192 | "name": "Product name", 193 | "type": "Text", 194 | "localized": false, 195 | "required": true, 196 | "validations": [ 197 | ], 198 | "disabled": false, 199 | "omitted": false 200 | }, 201 | { 202 | "id": "slug", 203 | "name": "Slug", 204 | "type": "Symbol", 205 | "localized": false, 206 | "required": false, 207 | "validations": [ 208 | ], 209 | "disabled": false, 210 | "omitted": false 211 | }, 212 | { 213 | "id": "productDescription", 214 | "name": "Description", 215 | "type": "Text", 216 | "localized": false, 217 | "required": false, 218 | "validations": [ 219 | ], 220 | "disabled": false, 221 | "omitted": false 222 | }, 223 | { 224 | "id": "sizetypecolor", 225 | "name": "Size/Type/Color", 226 | "type": "Symbol", 227 | "localized": false, 228 | "required": false, 229 | "validations": [ 230 | ], 231 | "disabled": false, 232 | "omitted": false 233 | }, 234 | { 235 | "id": "image", 236 | "name": "Image", 237 | "type": "Array", 238 | "localized": false, 239 | "required": false, 240 | "validations": [ 241 | ], 242 | "disabled": false, 243 | "omitted": false, 244 | "items": { 245 | "type": "Link", 246 | "validations": [ 247 | ], 248 | "linkType": "Asset" 249 | } 250 | }, 251 | { 252 | "id": "tags", 253 | "name": "Tags", 254 | "type": "Array", 255 | "localized": false, 256 | "required": false, 257 | "validations": [ 258 | ], 259 | "disabled": false, 260 | "omitted": false, 261 | "items": { 262 | "type": "Symbol", 263 | "validations": [ 264 | ] 265 | } 266 | }, 267 | { 268 | "id": "categories", 269 | "name": "Categories", 270 | "type": "Array", 271 | "localized": false, 272 | "required": false, 273 | "validations": [ 274 | ], 275 | "disabled": false, 276 | "omitted": false, 277 | "items": { 278 | "type": "Link", 279 | "validations": [ 280 | { 281 | "linkContentType": [ 282 | "category" 283 | ] 284 | } 285 | ], 286 | "linkType": "Entry" 287 | } 288 | }, 289 | { 290 | "id": "price", 291 | "name": "Price", 292 | "type": "Number", 293 | "localized": false, 294 | "required": false, 295 | "validations": [ 296 | ], 297 | "disabled": false, 298 | "omitted": false 299 | }, 300 | { 301 | "id": "brand", 302 | "name": "Brand", 303 | "type": "Link", 304 | "localized": false, 305 | "required": false, 306 | "validations": [ 307 | { 308 | "linkContentType": [ 309 | "brand" 310 | ] 311 | } 312 | ], 313 | "disabled": false, 314 | "omitted": false, 315 | "linkType": "Entry" 316 | }, 317 | { 318 | "id": "quantity", 319 | "name": "Quantity", 320 | "type": "Integer", 321 | "localized": false, 322 | "required": false, 323 | "validations": [ 324 | ], 325 | "disabled": false, 326 | "omitted": false 327 | }, 328 | { 329 | "id": "sku", 330 | "name": "SKU", 331 | "type": "Symbol", 332 | "localized": false, 333 | "required": false, 334 | "validations": [ 335 | ], 336 | "disabled": false, 337 | "omitted": false 338 | }, 339 | { 340 | "id": "website", 341 | "name": "Available at", 342 | "type": "Symbol", 343 | "localized": false, 344 | "required": false, 345 | "validations": [ 346 | ], 347 | "disabled": false, 348 | "omitted": false 349 | } 350 | ] 351 | }, 352 | { 353 | "sys": { 354 | "space": { 355 | "sys": { 356 | "type": "Link", 357 | "linkType": "Space", 358 | "id": "5u403xny70b7" 359 | } 360 | }, 361 | "id": "category", 362 | "type": "ContentType", 363 | "createdAt": "2022-06-15T14:02:42.732Z", 364 | "updatedAt": "2022-06-15T14:02:42.907Z", 365 | "environment": { 366 | "sys": { 367 | "id": "master", 368 | "type": "Link", 369 | "linkType": "Environment" 370 | } 371 | }, 372 | "publishedVersion": 1, 373 | "publishedAt": "2022-06-15T14:02:42.907Z", 374 | "firstPublishedAt": "2022-06-15T14:02:42.907Z", 375 | "createdBy": { 376 | "sys": { 377 | "type": "Link", 378 | "linkType": "User", 379 | "id": "5JyrJiiomUI3mj8CrwPKe9" 380 | } 381 | }, 382 | "updatedBy": { 383 | "sys": { 384 | "type": "Link", 385 | "linkType": "User", 386 | "id": "5JyrJiiomUI3mj8CrwPKe9" 387 | } 388 | }, 389 | "publishedCounter": 1, 390 | "version": 2, 391 | "publishedBy": { 392 | "sys": { 393 | "type": "Link", 394 | "linkType": "User", 395 | "id": "5JyrJiiomUI3mj8CrwPKe9" 396 | } 397 | } 398 | }, 399 | "displayField": "title", 400 | "name": "Category", 401 | "description": null, 402 | "fields": [ 403 | { 404 | "id": "title", 405 | "name": "Title", 406 | "type": "Text", 407 | "localized": false, 408 | "required": true, 409 | "validations": [ 410 | ], 411 | "disabled": false, 412 | "omitted": false 413 | }, 414 | { 415 | "id": "icon", 416 | "name": "Icon", 417 | "type": "Link", 418 | "localized": false, 419 | "required": false, 420 | "validations": [ 421 | ], 422 | "disabled": false, 423 | "omitted": false, 424 | "linkType": "Asset" 425 | }, 426 | { 427 | "id": "categoryDescription", 428 | "name": "Description", 429 | "type": "Text", 430 | "localized": false, 431 | "required": false, 432 | "validations": [ 433 | ], 434 | "disabled": false, 435 | "omitted": false 436 | } 437 | ] 438 | } 439 | ], 440 | "editorInterfaces": [ 441 | { 442 | "sys": { 443 | "id": "default", 444 | "type": "EditorInterface", 445 | "space": { 446 | "sys": { 447 | "id": "5u403xny70b7", 448 | "type": "Link", 449 | "linkType": "Space" 450 | } 451 | }, 452 | "version": 1, 453 | "createdAt": "2022-06-15T14:02:43.034Z", 454 | "createdBy": { 455 | "sys": { 456 | "id": "5JyrJiiomUI3mj8CrwPKe9", 457 | "type": "Link", 458 | "linkType": "User" 459 | } 460 | }, 461 | "updatedAt": "2022-06-15T14:02:43.034Z", 462 | "updatedBy": { 463 | "sys": { 464 | "id": "5JyrJiiomUI3mj8CrwPKe9", 465 | "type": "Link", 466 | "linkType": "User" 467 | } 468 | }, 469 | "contentType": { 470 | "sys": { 471 | "id": "brand", 472 | "type": "Link", 473 | "linkType": "ContentType" 474 | } 475 | }, 476 | "environment": { 477 | "sys": { 478 | "id": "master", 479 | "type": "Link", 480 | "linkType": "Environment" 481 | } 482 | } 483 | }, 484 | "controls": [ 485 | { 486 | "fieldId": "companyName" 487 | }, 488 | { 489 | "fieldId": "logo" 490 | }, 491 | { 492 | "fieldId": "companyDescription" 493 | }, 494 | { 495 | "fieldId": "website" 496 | }, 497 | { 498 | "fieldId": "twitter" 499 | }, 500 | { 501 | "fieldId": "email" 502 | }, 503 | { 504 | "fieldId": "phone" 505 | } 506 | ] 507 | }, 508 | { 509 | "sys": { 510 | "id": "default", 511 | "type": "EditorInterface", 512 | "space": { 513 | "sys": { 514 | "id": "5u403xny70b7", 515 | "type": "Link", 516 | "linkType": "Space" 517 | } 518 | }, 519 | "version": 1, 520 | "createdAt": "2022-06-15T14:02:43.033Z", 521 | "createdBy": { 522 | "sys": { 523 | "id": "5JyrJiiomUI3mj8CrwPKe9", 524 | "type": "Link", 525 | "linkType": "User" 526 | } 527 | }, 528 | "updatedAt": "2022-06-15T14:02:43.033Z", 529 | "updatedBy": { 530 | "sys": { 531 | "id": "5JyrJiiomUI3mj8CrwPKe9", 532 | "type": "Link", 533 | "linkType": "User" 534 | } 535 | }, 536 | "contentType": { 537 | "sys": { 538 | "id": "product", 539 | "type": "Link", 540 | "linkType": "ContentType" 541 | } 542 | }, 543 | "environment": { 544 | "sys": { 545 | "id": "master", 546 | "type": "Link", 547 | "linkType": "Environment" 548 | } 549 | } 550 | }, 551 | "controls": [ 552 | { 553 | "fieldId": "productName" 554 | }, 555 | { 556 | "fieldId": "slug" 557 | }, 558 | { 559 | "fieldId": "productDescription" 560 | }, 561 | { 562 | "fieldId": "sizetypecolor" 563 | }, 564 | { 565 | "fieldId": "image" 566 | }, 567 | { 568 | "fieldId": "tags" 569 | }, 570 | { 571 | "fieldId": "categories" 572 | }, 573 | { 574 | "fieldId": "price" 575 | }, 576 | { 577 | "fieldId": "brand" 578 | }, 579 | { 580 | "fieldId": "quantity" 581 | }, 582 | { 583 | "fieldId": "sku" 584 | }, 585 | { 586 | "fieldId": "website" 587 | } 588 | ] 589 | }, 590 | { 591 | "sys": { 592 | "id": "default", 593 | "type": "EditorInterface", 594 | "space": { 595 | "sys": { 596 | "id": "5u403xny70b7", 597 | "type": "Link", 598 | "linkType": "Space" 599 | } 600 | }, 601 | "version": 1, 602 | "createdAt": "2022-06-15T14:02:42.955Z", 603 | "createdBy": { 604 | "sys": { 605 | "id": "5JyrJiiomUI3mj8CrwPKe9", 606 | "type": "Link", 607 | "linkType": "User" 608 | } 609 | }, 610 | "updatedAt": "2022-06-15T14:02:42.955Z", 611 | "updatedBy": { 612 | "sys": { 613 | "id": "5JyrJiiomUI3mj8CrwPKe9", 614 | "type": "Link", 615 | "linkType": "User" 616 | } 617 | }, 618 | "contentType": { 619 | "sys": { 620 | "id": "category", 621 | "type": "Link", 622 | "linkType": "ContentType" 623 | } 624 | }, 625 | "environment": { 626 | "sys": { 627 | "id": "master", 628 | "type": "Link", 629 | "linkType": "Environment" 630 | } 631 | } 632 | }, 633 | "controls": [ 634 | { 635 | "fieldId": "title" 636 | }, 637 | { 638 | "fieldId": "icon" 639 | }, 640 | { 641 | "fieldId": "categoryDescription" 642 | } 643 | ] 644 | } 645 | ], 646 | "entries": [ 647 | { 648 | "metadata": { 649 | "tags": [ 650 | ] 651 | }, 652 | "sys": { 653 | "space": { 654 | "sys": { 655 | "type": "Link", 656 | "linkType": "Space", 657 | "id": "5u403xny70b7" 658 | } 659 | }, 660 | "id": "4BqrajvA8E6qwgkieoqmqO", 661 | "type": "Entry", 662 | "createdAt": "2022-06-15T14:02:53.168Z", 663 | "updatedAt": "2022-06-15T14:02:53.168Z", 664 | "environment": { 665 | "sys": { 666 | "id": "master", 667 | "type": "Link", 668 | "linkType": "Environment" 669 | } 670 | }, 671 | "revision": 1, 672 | "contentType": { 673 | "sys": { 674 | "type": "Link", 675 | "linkType": "ContentType", 676 | "id": "product" 677 | } 678 | } 679 | }, 680 | "fields": { 681 | "productName": { 682 | "en-US": "SoSo Wall Clock" 683 | }, 684 | "slug": { 685 | "en-US": "soso-wall-clock" 686 | }, 687 | "productDescription": { 688 | "en-US": "The newly released SoSo Clock from Lemnos marries simple, clean design and bold, striking features. Its saturated marigold face is a lively pop of color to white or grey walls, but would also pair nicely with navy and maroon. Where most clocks feature numbers at the border of the clock, the SoSo brings them in tight to the middle, leaving a wide space between the numbers and the slight frame. The hour hand provides a nice interruption to the black and yellow of the clock - it is featured in a brilliant white. Despite its bold color and contrast, the SoSo maintains a clean, pure aesthetic that is suitable to a variety of contemporary interiors." 689 | }, 690 | "sizetypecolor": { 691 | "en-US": "10\" x 2.2\"" 692 | }, 693 | "image": { 694 | "en-US": [ 695 | { 696 | "sys": { 697 | "type": "Link", 698 | "linkType": "Asset", 699 | "id": "KTRF62Q4gg60q6WCsWKw8" 700 | } 701 | } 702 | ] 703 | }, 704 | "tags": { 705 | "en-US": [ 706 | "home décor", 707 | "clocks", 708 | "interior design", 709 | "yellow", 710 | "gifts" 711 | ] 712 | }, 713 | "categories": { 714 | "en-US": [ 715 | { 716 | "sys": { 717 | "type": "Link", 718 | "linkType": "Entry", 719 | "id": "7LAnCobuuWYSqks6wAwY2a" 720 | } 721 | } 722 | ] 723 | }, 724 | "price": { 725 | "en-US": 120 726 | }, 727 | "brand": { 728 | "en-US": { 729 | "sys": { 730 | "type": "Link", 731 | "linkType": "Entry", 732 | "id": "4LgMotpNF6W20YKmuemW0a" 733 | } 734 | } 735 | }, 736 | "quantity": { 737 | "en-US": 3 738 | }, 739 | "sku": { 740 | "en-US": "B00MG4ULK2" 741 | }, 742 | "website": { 743 | "en-US": "http://store.dwell.com/soso-wall-clock.html" 744 | } 745 | } 746 | }, 747 | { 748 | "metadata": { 749 | "tags": [ 750 | ] 751 | }, 752 | "sys": { 753 | "space": { 754 | "sys": { 755 | "type": "Link", 756 | "linkType": "Space", 757 | "id": "5u403xny70b7" 758 | } 759 | }, 760 | "id": "24DPGBDeGEaYy8ms4Y8QMQ", 761 | "type": "Entry", 762 | "createdAt": "2022-06-15T14:02:53.957Z", 763 | "updatedAt": "2022-06-15T14:02:53.957Z", 764 | "environment": { 765 | "sys": { 766 | "id": "master", 767 | "type": "Link", 768 | "linkType": "Environment" 769 | } 770 | }, 771 | "revision": 1, 772 | "contentType": { 773 | "sys": { 774 | "type": "Link", 775 | "linkType": "ContentType", 776 | "id": "category" 777 | } 778 | } 779 | }, 780 | "fields": { 781 | "title": { 782 | "en-US": "Toys" 783 | }, 784 | "icon": { 785 | "en-US": { 786 | "sys": { 787 | "type": "Link", 788 | "linkType": "Asset", 789 | "id": "6t4HKjytPi0mYgs240wkG" 790 | } 791 | } 792 | }, 793 | "categoryDescription": { 794 | "en-US": "Shop for toys, games, educational aids" 795 | } 796 | } 797 | }, 798 | { 799 | "metadata": { 800 | "tags": [ 801 | ] 802 | }, 803 | "sys": { 804 | "space": { 805 | "sys": { 806 | "type": "Link", 807 | "linkType": "Space", 808 | "id": "5u403xny70b7" 809 | } 810 | }, 811 | "id": "JrePkDVYomE8AwcuCUyMi", 812 | "type": "Entry", 813 | "createdAt": "2022-06-15T14:02:53.959Z", 814 | "updatedAt": "2022-06-15T14:02:53.959Z", 815 | "environment": { 816 | "sys": { 817 | "id": "master", 818 | "type": "Link", 819 | "linkType": "Environment" 820 | } 821 | }, 822 | "revision": 1, 823 | "contentType": { 824 | "sys": { 825 | "type": "Link", 826 | "linkType": "ContentType", 827 | "id": "brand" 828 | } 829 | } 830 | }, 831 | "fields": { 832 | "companyName": { 833 | "en-US": "Playsam" 834 | }, 835 | "logo": { 836 | "en-US": { 837 | "sys": { 838 | "type": "Link", 839 | "linkType": "Asset", 840 | "id": "4zj1ZOfHgQ8oqgaSKm4Qo2" 841 | } 842 | } 843 | }, 844 | "companyDescription": { 845 | "en-US": "Playsam is the leading Scandinavian design company for executive wooden toy gift. Scandinavian design playful creativity, integrity and sophistication are Playsam. Scandinavian design and wooden toy makes Playsam gift lovely to the world of design since 1984." 846 | }, 847 | "website": { 848 | "en-US": "http://playsam.com/" 849 | } 850 | } 851 | }, 852 | { 853 | "metadata": { 854 | "tags": [ 855 | ] 856 | }, 857 | "sys": { 858 | "space": { 859 | "sys": { 860 | "type": "Link", 861 | "linkType": "Space", 862 | "id": "5u403xny70b7" 863 | } 864 | }, 865 | "id": "6dbjWqNd9SqccegcqYq224", 866 | "type": "Entry", 867 | "createdAt": "2022-06-15T14:02:53.963Z", 868 | "updatedAt": "2022-06-15T14:02:53.963Z", 869 | "environment": { 870 | "sys": { 871 | "id": "master", 872 | "type": "Link", 873 | "linkType": "Environment" 874 | } 875 | }, 876 | "revision": 1, 877 | "contentType": { 878 | "sys": { 879 | "type": "Link", 880 | "linkType": "ContentType", 881 | "id": "product" 882 | } 883 | } 884 | }, 885 | "fields": { 886 | "productName": { 887 | "en-US": "Whisk Beater" 888 | }, 889 | "slug": { 890 | "en-US": "whisk-beater" 891 | }, 892 | "productDescription": { 893 | "en-US": "A creative little whisk that comes in 8 different colors. Handy and easy to clean after use. A great gift idea." 894 | }, 895 | "sizetypecolor": { 896 | "en-US": "0.8 x 0.8 x 11.2 inches; 1.6 ounces" 897 | }, 898 | "image": { 899 | "en-US": [ 900 | { 901 | "sys": { 902 | "type": "Link", 903 | "linkType": "Asset", 904 | "id": "10TkaLheGeQG6qQGqWYqUI" 905 | } 906 | } 907 | ] 908 | }, 909 | "tags": { 910 | "en-US": [ 911 | "kitchen", 912 | "accessories", 913 | "whisk", 914 | "scandinavia", 915 | "design" 916 | ] 917 | }, 918 | "categories": { 919 | "en-US": [ 920 | { 921 | "sys": { 922 | "type": "Link", 923 | "linkType": "Entry", 924 | "id": "7LAnCobuuWYSqks6wAwY2a" 925 | } 926 | } 927 | ] 928 | }, 929 | "price": { 930 | "en-US": 22 931 | }, 932 | "brand": { 933 | "en-US": { 934 | "sys": { 935 | "type": "Link", 936 | "linkType": "Entry", 937 | "id": "651CQ8rLoIYCeY6G0QG22q" 938 | } 939 | } 940 | }, 941 | "quantity": { 942 | "en-US": 89 943 | }, 944 | "sku": { 945 | "en-US": "B0081F2CCK" 946 | }, 947 | "website": { 948 | "en-US": "http://www.amazon.com/dp/B0081F2CCK/" 949 | } 950 | } 951 | }, 952 | { 953 | "metadata": { 954 | "tags": [ 955 | ] 956 | }, 957 | "sys": { 958 | "space": { 959 | "sys": { 960 | "type": "Link", 961 | "linkType": "Space", 962 | "id": "5u403xny70b7" 963 | } 964 | }, 965 | "id": "4LgMotpNF6W20YKmuemW0a", 966 | "type": "Entry", 967 | "createdAt": "2022-06-15T14:02:53.965Z", 968 | "updatedAt": "2022-06-15T14:02:53.965Z", 969 | "environment": { 970 | "sys": { 971 | "id": "master", 972 | "type": "Link", 973 | "linkType": "Environment" 974 | } 975 | }, 976 | "revision": 1, 977 | "contentType": { 978 | "sys": { 979 | "type": "Link", 980 | "linkType": "ContentType", 981 | "id": "brand" 982 | } 983 | } 984 | }, 985 | "fields": { 986 | "companyName": { 987 | "en-US": "Lemnos" 988 | }, 989 | "logo": { 990 | "en-US": { 991 | "sys": { 992 | "type": "Link", 993 | "linkType": "Asset", 994 | "id": "2Y8LhXLnYAYqKCGEWG4EKI" 995 | } 996 | } 997 | }, 998 | "companyDescription": { 999 | "en-US": "TAKATA Lemnos Inc. was founded in 1947 as a brass casting manufacturing industry in Takaoka-city, Toyama Prefecture, Japan and we launched out into the full-scale business trade with Seiko Clock Co., Ltd. since 1966.\n\nWe entered into the development for the original planning from late 1980 and \"Lemnos Brand\" recognized as the global design clock by a masterpiece \"HOLA\" designed by Kazuo KAWASAKI which released in 1989.\n\nAfterwards, we made a lot of projects with well-known designers who took in active in Japan and overseas such as Riki WATANABE, Kazuo KAWASAKI, Shin AZUMI, Tomoko AZUMI, Kanae TSUKAMOTO etc. and we made announcement of their fine works abounding in artistry and prominent designs. In addition, we realized to make a special project by the collaboration with Andrea Branzi, a well-known architect in the world.\n\nLemnos brand products are now highly praised from the design shops and the interior shops all over the world.\n\nIn recent years, we also have been given high priority to develop interior accessories making full use of our traditional techniques by the founding manufacturer and we always focus our minds on the development for the new Lemnos products in the new market.\n\nOur Lemnos products are made carefully by our craftsmen finely honed skillful techniques in Japan. They surely bring out the attractiveness of the materials to the maximum and create fine products not being influenced on the fashion trend accordingly. TAKATA Lemnos Inc. definitely would like to be innovative and continuously propose the beauty lasts forever." 1000 | }, 1001 | "website": { 1002 | "en-US": "http://www.lemnos.jp/en/" 1003 | }, 1004 | "email": { 1005 | "en-US": "info@acgears.com" 1006 | }, 1007 | "phone": { 1008 | "en-US": [ 1009 | "+1 212 260 2269" 1010 | ] 1011 | } 1012 | } 1013 | }, 1014 | { 1015 | "metadata": { 1016 | "tags": [ 1017 | ] 1018 | }, 1019 | "sys": { 1020 | "space": { 1021 | "sys": { 1022 | "type": "Link", 1023 | "linkType": "Space", 1024 | "id": "5u403xny70b7" 1025 | } 1026 | }, 1027 | "id": "3DVqIYj4dOwwcKu6sgqOgg", 1028 | "type": "Entry", 1029 | "createdAt": "2022-06-15T14:02:55.038Z", 1030 | "updatedAt": "2022-06-15T14:02:55.038Z", 1031 | "environment": { 1032 | "sys": { 1033 | "id": "master", 1034 | "type": "Link", 1035 | "linkType": "Environment" 1036 | } 1037 | }, 1038 | "revision": 1, 1039 | "contentType": { 1040 | "sys": { 1041 | "type": "Link", 1042 | "linkType": "ContentType", 1043 | "id": "product" 1044 | } 1045 | } 1046 | }, 1047 | "fields": { 1048 | "productName": { 1049 | "en-US": "Hudson Wall Cup" 1050 | }, 1051 | "slug": { 1052 | "en-US": "hudson-wall-cup" 1053 | }, 1054 | "productDescription": { 1055 | "en-US": "Wall Hanging Glass Flower Vase and Terrarium" 1056 | }, 1057 | "sizetypecolor": { 1058 | "en-US": "3 x 3 x 5 inches; 5.3 ounces" 1059 | }, 1060 | "image": { 1061 | "en-US": [ 1062 | { 1063 | "sys": { 1064 | "type": "Link", 1065 | "linkType": "Asset", 1066 | "id": "Xc0ny7GWsMEMCeASWO2um" 1067 | } 1068 | } 1069 | ] 1070 | }, 1071 | "tags": { 1072 | "en-US": [ 1073 | "vase", 1074 | "flowers", 1075 | "accessories" 1076 | ] 1077 | }, 1078 | "categories": { 1079 | "en-US": [ 1080 | { 1081 | "sys": { 1082 | "type": "Link", 1083 | "linkType": "Entry", 1084 | "id": "7LAnCobuuWYSqks6wAwY2a" 1085 | } 1086 | } 1087 | ] 1088 | }, 1089 | "price": { 1090 | "en-US": 11 1091 | }, 1092 | "brand": { 1093 | "en-US": { 1094 | "sys": { 1095 | "type": "Link", 1096 | "linkType": "Entry", 1097 | "id": "651CQ8rLoIYCeY6G0QG22q" 1098 | } 1099 | } 1100 | }, 1101 | "quantity": { 1102 | "en-US": 101 1103 | }, 1104 | "sku": { 1105 | "en-US": "B00E82D7I8" 1106 | }, 1107 | "website": { 1108 | "en-US": "http://www.amazon.com/dp/B00E82D7I8/" 1109 | } 1110 | } 1111 | }, 1112 | { 1113 | "metadata": { 1114 | "tags": [ 1115 | ] 1116 | }, 1117 | "sys": { 1118 | "space": { 1119 | "sys": { 1120 | "type": "Link", 1121 | "linkType": "Space", 1122 | "id": "5u403xny70b7" 1123 | } 1124 | }, 1125 | "id": "651CQ8rLoIYCeY6G0QG22q", 1126 | "type": "Entry", 1127 | "createdAt": "2022-06-15T14:02:55.047Z", 1128 | "updatedAt": "2022-06-15T14:02:55.047Z", 1129 | "environment": { 1130 | "sys": { 1131 | "id": "master", 1132 | "type": "Link", 1133 | "linkType": "Environment" 1134 | } 1135 | }, 1136 | "revision": 1, 1137 | "contentType": { 1138 | "sys": { 1139 | "type": "Link", 1140 | "linkType": "ContentType", 1141 | "id": "brand" 1142 | } 1143 | } 1144 | }, 1145 | "fields": { 1146 | "companyName": { 1147 | "en-US": "Normann Copenhagen" 1148 | }, 1149 | "logo": { 1150 | "en-US": { 1151 | "sys": { 1152 | "type": "Link", 1153 | "linkType": "Asset", 1154 | "id": "3wtvPBbBjiMKqKKga8I2Cu" 1155 | } 1156 | } 1157 | }, 1158 | "companyDescription": { 1159 | "en-US": "Normann Copenhagen is a way of living - a mindset. We love to challenge the conventional design rules. This is why you will find traditional materials put into untraditional use such as a Stone Hook made of Icelandic stones, a vase made out of silicon and last but not least a dog made out of plastic." 1160 | }, 1161 | "website": { 1162 | "en-US": "http://www.normann-copenhagen.com/" 1163 | }, 1164 | "twitter": { 1165 | "en-US": "https://twitter.com/NormannCPH" 1166 | }, 1167 | "email": { 1168 | "en-US": "normann@normann-copenhagen.com" 1169 | }, 1170 | "phone": { 1171 | "en-US": [ 1172 | "+45 35 55 44 59" 1173 | ] 1174 | } 1175 | } 1176 | }, 1177 | { 1178 | "metadata": { 1179 | "tags": [ 1180 | ] 1181 | }, 1182 | "sys": { 1183 | "space": { 1184 | "sys": { 1185 | "type": "Link", 1186 | "linkType": "Space", 1187 | "id": "5u403xny70b7" 1188 | } 1189 | }, 1190 | "id": "7LAnCobuuWYSqks6wAwY2a", 1191 | "type": "Entry", 1192 | "createdAt": "2022-06-15T14:02:55.119Z", 1193 | "updatedAt": "2022-06-15T14:02:55.119Z", 1194 | "environment": { 1195 | "sys": { 1196 | "id": "master", 1197 | "type": "Link", 1198 | "linkType": "Environment" 1199 | } 1200 | }, 1201 | "revision": 1, 1202 | "contentType": { 1203 | "sys": { 1204 | "type": "Link", 1205 | "linkType": "ContentType", 1206 | "id": "category" 1207 | } 1208 | } 1209 | }, 1210 | "fields": { 1211 | "title": { 1212 | "en-US": "Home & Kitchen" 1213 | }, 1214 | "icon": { 1215 | "en-US": { 1216 | "sys": { 1217 | "type": "Link", 1218 | "linkType": "Asset", 1219 | "id": "6m5AJ9vMPKc8OUoQeoCS4o" 1220 | } 1221 | } 1222 | }, 1223 | "categoryDescription": { 1224 | "en-US": "Shop for furniture, bedding, bath, vacuums, kitchen products, and more" 1225 | } 1226 | } 1227 | }, 1228 | { 1229 | "metadata": { 1230 | "tags": [ 1231 | ] 1232 | }, 1233 | "sys": { 1234 | "space": { 1235 | "sys": { 1236 | "type": "Link", 1237 | "linkType": "Space", 1238 | "id": "5u403xny70b7" 1239 | } 1240 | }, 1241 | "id": "5KsDBWseXY6QegucYAoacS", 1242 | "type": "Entry", 1243 | "createdAt": "2022-06-15T14:02:55.121Z", 1244 | "updatedAt": "2022-06-15T14:02:55.121Z", 1245 | "environment": { 1246 | "sys": { 1247 | "id": "master", 1248 | "type": "Link", 1249 | "linkType": "Environment" 1250 | } 1251 | }, 1252 | "revision": 1, 1253 | "contentType": { 1254 | "sys": { 1255 | "type": "Link", 1256 | "linkType": "ContentType", 1257 | "id": "product" 1258 | } 1259 | } 1260 | }, 1261 | "fields": { 1262 | "productName": { 1263 | "en-US": "Playsam Streamliner Classic Car, Espresso" 1264 | }, 1265 | "slug": { 1266 | "en-US": "playsam-streamliner-classic-car-espresso" 1267 | }, 1268 | "productDescription": { 1269 | "en-US": "A classic Playsam design, the Streamliner Classic Car has been selected as Swedish Design Classic by the Swedish National Museum for its inventive style and sleek surface. It's no wonder that this wooden car has also been a long-standing favorite for children both big and small!" 1270 | }, 1271 | "sizetypecolor": { 1272 | "en-US": "Length: 135 mm | color: espresso, green, or icar (white)" 1273 | }, 1274 | "image": { 1275 | "en-US": [ 1276 | { 1277 | "sys": { 1278 | "type": "Link", 1279 | "linkType": "Asset", 1280 | "id": "wtrHxeu3zEoEce2MokCSi" 1281 | } 1282 | } 1283 | ] 1284 | }, 1285 | "tags": { 1286 | "en-US": [ 1287 | "wood", 1288 | "toy", 1289 | "car", 1290 | "sweden", 1291 | "design" 1292 | ] 1293 | }, 1294 | "categories": { 1295 | "en-US": [ 1296 | { 1297 | "sys": { 1298 | "type": "Link", 1299 | "linkType": "Entry", 1300 | "id": "24DPGBDeGEaYy8ms4Y8QMQ" 1301 | } 1302 | } 1303 | ] 1304 | }, 1305 | "price": { 1306 | "en-US": 44 1307 | }, 1308 | "brand": { 1309 | "en-US": { 1310 | "sys": { 1311 | "type": "Link", 1312 | "linkType": "Entry", 1313 | "id": "JrePkDVYomE8AwcuCUyMi" 1314 | } 1315 | } 1316 | }, 1317 | "quantity": { 1318 | "en-US": 56 1319 | }, 1320 | "sku": { 1321 | "en-US": "B001R6JUZ2" 1322 | }, 1323 | "website": { 1324 | "en-US": "http://www.amazon.com/dp/B001R6JUZ2/" 1325 | } 1326 | } 1327 | } 1328 | ], 1329 | "assets": [ 1330 | { 1331 | "metadata": { 1332 | "tags": [ 1333 | ] 1334 | }, 1335 | "sys": { 1336 | "space": { 1337 | "sys": { 1338 | "type": "Link", 1339 | "linkType": "Space", 1340 | "id": "5u403xny70b7" 1341 | } 1342 | }, 1343 | "id": "Xc0ny7GWsMEMCeASWO2um", 1344 | "type": "Asset", 1345 | "createdAt": "2022-06-15T14:02:52.139Z", 1346 | "updatedAt": "2022-06-15T14:02:52.139Z", 1347 | "environment": { 1348 | "sys": { 1349 | "id": "master", 1350 | "type": "Link", 1351 | "linkType": "Environment" 1352 | } 1353 | }, 1354 | "revision": 1 1355 | }, 1356 | "fields": { 1357 | "title": { 1358 | "en-US": "Hudson Wall Cup " 1359 | }, 1360 | "description": { 1361 | "en-US": "Merchandise image" 1362 | }, 1363 | "file": { 1364 | "en-US": { 1365 | "url": "//images.ctfassets.net/5u403xny70b7/Xc0ny7GWsMEMCeASWO2um/386ee028262461f49449a3dcde7712ba/jqvtazcyfwseah9fmysz.jpg", 1366 | "details": { 1367 | "size": 48751, 1368 | "image": { 1369 | "width": 600, 1370 | "height": 600 1371 | } 1372 | }, 1373 | "fileName": "jqvtazcyfwseah9fmysz.jpg", 1374 | "contentType": "image/jpeg" 1375 | } 1376 | } 1377 | } 1378 | }, 1379 | { 1380 | "metadata": { 1381 | "tags": [ 1382 | ] 1383 | }, 1384 | "sys": { 1385 | "space": { 1386 | "sys": { 1387 | "type": "Link", 1388 | "linkType": "Space", 1389 | "id": "5u403xny70b7" 1390 | } 1391 | }, 1392 | "id": "10TkaLheGeQG6qQGqWYqUI", 1393 | "type": "Asset", 1394 | "createdAt": "2022-06-15T14:02:52.152Z", 1395 | "updatedAt": "2022-06-15T14:02:52.152Z", 1396 | "environment": { 1397 | "sys": { 1398 | "id": "master", 1399 | "type": "Link", 1400 | "linkType": "Environment" 1401 | } 1402 | }, 1403 | "revision": 1 1404 | }, 1405 | "fields": { 1406 | "title": { 1407 | "en-US": "Whisk beaters" 1408 | }, 1409 | "description": { 1410 | "en-US": "Merchandise photo" 1411 | }, 1412 | "file": { 1413 | "en-US": { 1414 | "url": "//images.ctfassets.net/5u403xny70b7/10TkaLheGeQG6qQGqWYqUI/e6485b04c5f6991cfbae6ef8e332976f/ryugj83mqwa1asojwtwb.jpg", 1415 | "details": { 1416 | "size": 28435, 1417 | "image": { 1418 | "width": 450, 1419 | "height": 600 1420 | } 1421 | }, 1422 | "fileName": "ryugj83mqwa1asojwtwb.jpg", 1423 | "contentType": "image/jpeg" 1424 | } 1425 | } 1426 | } 1427 | }, 1428 | { 1429 | "metadata": { 1430 | "tags": [ 1431 | ] 1432 | }, 1433 | "sys": { 1434 | "space": { 1435 | "sys": { 1436 | "type": "Link", 1437 | "linkType": "Space", 1438 | "id": "5u403xny70b7" 1439 | } 1440 | }, 1441 | "id": "6s3iG2OVmoUcosmA8ocqsG", 1442 | "type": "Asset", 1443 | "createdAt": "2022-06-15T14:02:52.157Z", 1444 | "updatedAt": "2022-06-15T14:02:52.157Z", 1445 | "environment": { 1446 | "sys": { 1447 | "id": "master", 1448 | "type": "Link", 1449 | "linkType": "Environment" 1450 | } 1451 | }, 1452 | "revision": 1 1453 | }, 1454 | "fields": { 1455 | "title": { 1456 | "en-US": "House icon" 1457 | }, 1458 | "description": { 1459 | "en-US": "Category icon set" 1460 | }, 1461 | "file": { 1462 | "en-US": { 1463 | "url": "//images.ctfassets.net/5u403xny70b7/6s3iG2OVmoUcosmA8ocqsG/d576e30a70d1a105ec1966ba033c0d27/1418244847_Streamline-18-256__1_.png", 1464 | "details": { 1465 | "size": 4244, 1466 | "image": { 1467 | "width": 250, 1468 | "height": 250 1469 | } 1470 | }, 1471 | "fileName": "1418244847_Streamline-18-256 (1).png", 1472 | "contentType": "image/png" 1473 | } 1474 | } 1475 | } 1476 | }, 1477 | { 1478 | "metadata": { 1479 | "tags": [ 1480 | ] 1481 | }, 1482 | "sys": { 1483 | "space": { 1484 | "sys": { 1485 | "type": "Link", 1486 | "linkType": "Space", 1487 | "id": "5u403xny70b7" 1488 | } 1489 | }, 1490 | "id": "2Y8LhXLnYAYqKCGEWG4EKI", 1491 | "type": "Asset", 1492 | "createdAt": "2022-06-15T14:02:52.184Z", 1493 | "updatedAt": "2022-06-15T14:02:52.184Z", 1494 | "environment": { 1495 | "sys": { 1496 | "id": "master", 1497 | "type": "Link", 1498 | "linkType": "Environment" 1499 | } 1500 | }, 1501 | "revision": 1 1502 | }, 1503 | "fields": { 1504 | "title": { 1505 | "en-US": "Lemnos" 1506 | }, 1507 | "description": { 1508 | "en-US": "company logo" 1509 | }, 1510 | "file": { 1511 | "en-US": { 1512 | "url": "//images.ctfassets.net/5u403xny70b7/2Y8LhXLnYAYqKCGEWG4EKI/94b1546dbbfc121c91b074b53fa5b3bc/lemnos-logo.jpg", 1513 | "details": { 1514 | "size": 7149, 1515 | "image": { 1516 | "width": 175, 1517 | "height": 32 1518 | } 1519 | }, 1520 | "fileName": "lemnos-logo.jpg", 1521 | "contentType": "image/jpeg" 1522 | } 1523 | } 1524 | } 1525 | }, 1526 | { 1527 | "metadata": { 1528 | "tags": [ 1529 | ] 1530 | }, 1531 | "sys": { 1532 | "space": { 1533 | "sys": { 1534 | "type": "Link", 1535 | "linkType": "Space", 1536 | "id": "5u403xny70b7" 1537 | } 1538 | }, 1539 | "id": "6m5AJ9vMPKc8OUoQeoCS4o", 1540 | "type": "Asset", 1541 | "createdAt": "2022-06-15T14:02:52.189Z", 1542 | "updatedAt": "2022-06-15T14:02:52.189Z", 1543 | "environment": { 1544 | "sys": { 1545 | "id": "master", 1546 | "type": "Link", 1547 | "linkType": "Environment" 1548 | } 1549 | }, 1550 | "revision": 1 1551 | }, 1552 | "fields": { 1553 | "title": { 1554 | "en-US": "Home and Kitchen" 1555 | }, 1556 | "description": { 1557 | "en-US": "category icon" 1558 | }, 1559 | "file": { 1560 | "en-US": { 1561 | "url": "//images.ctfassets.net/5u403xny70b7/6m5AJ9vMPKc8OUoQeoCS4o/34f04735f1d86f7b89080752c7505afc/1418244847_Streamline-18-256.png", 1562 | "details": { 1563 | "size": 2977, 1564 | "image": { 1565 | "width": 256, 1566 | "height": 256 1567 | } 1568 | }, 1569 | "fileName": "1418244847_Streamline-18-256.png", 1570 | "contentType": "image/png" 1571 | } 1572 | } 1573 | } 1574 | }, 1575 | { 1576 | "metadata": { 1577 | "tags": [ 1578 | ] 1579 | }, 1580 | "sys": { 1581 | "space": { 1582 | "sys": { 1583 | "type": "Link", 1584 | "linkType": "Space", 1585 | "id": "5u403xny70b7" 1586 | } 1587 | }, 1588 | "id": "1MgbdJNTsMWKI0W68oYqkU", 1589 | "type": "Asset", 1590 | "createdAt": "2022-06-15T14:02:52.194Z", 1591 | "updatedAt": "2022-06-15T14:02:52.194Z", 1592 | "environment": { 1593 | "sys": { 1594 | "id": "master", 1595 | "type": "Link", 1596 | "linkType": "Environment" 1597 | } 1598 | }, 1599 | "revision": 1 1600 | }, 1601 | "fields": { 1602 | "title": { 1603 | "en-US": "Chive logo" 1604 | }, 1605 | "description": { 1606 | "en-US": "Brand logo" 1607 | }, 1608 | "file": { 1609 | "en-US": { 1610 | "url": "//images.ctfassets.net/5u403xny70b7/1MgbdJNTsMWKI0W68oYqkU/f409b10201b5a2e12409c6a3a2a30033/9ef190c59f0d375c0dea58b58a4bc1f0.jpeg", 1611 | "details": { 1612 | "size": 44089, 1613 | "image": { 1614 | "width": 500, 1615 | "height": 500 1616 | } 1617 | }, 1618 | "fileName": "9ef190c59f0d375c0dea58b58a4bc1f0.jpeg", 1619 | "contentType": "image/jpeg" 1620 | } 1621 | } 1622 | } 1623 | }, 1624 | { 1625 | "metadata": { 1626 | "tags": [ 1627 | ] 1628 | }, 1629 | "sys": { 1630 | "space": { 1631 | "sys": { 1632 | "type": "Link", 1633 | "linkType": "Space", 1634 | "id": "5u403xny70b7" 1635 | } 1636 | }, 1637 | "id": "KTRF62Q4gg60q6WCsWKw8", 1638 | "type": "Asset", 1639 | "createdAt": "2022-06-15T14:02:53.150Z", 1640 | "updatedAt": "2022-06-15T14:02:53.150Z", 1641 | "environment": { 1642 | "sys": { 1643 | "id": "master", 1644 | "type": "Link", 1645 | "linkType": "Environment" 1646 | } 1647 | }, 1648 | "revision": 1 1649 | }, 1650 | "fields": { 1651 | "title": { 1652 | "en-US": "SoSo Wall Clock" 1653 | }, 1654 | "description": { 1655 | "en-US": "by Lemnos" 1656 | }, 1657 | "file": { 1658 | "en-US": { 1659 | "url": "//images.ctfassets.net/5u403xny70b7/KTRF62Q4gg60q6WCsWKw8/00a22ab2a0577b991f560a4b12f43b35/soso.clock.jpg", 1660 | "details": { 1661 | "size": 66927, 1662 | "image": { 1663 | "width": 1000, 1664 | "height": 1000 1665 | } 1666 | }, 1667 | "fileName": "soso.clock.jpg", 1668 | "contentType": "image/jpeg" 1669 | } 1670 | } 1671 | } 1672 | }, 1673 | { 1674 | "metadata": { 1675 | "tags": [ 1676 | ] 1677 | }, 1678 | "sys": { 1679 | "space": { 1680 | "sys": { 1681 | "type": "Link", 1682 | "linkType": "Space", 1683 | "id": "5u403xny70b7" 1684 | } 1685 | }, 1686 | "id": "3wtvPBbBjiMKqKKga8I2Cu", 1687 | "type": "Asset", 1688 | "createdAt": "2022-06-15T14:02:53.157Z", 1689 | "updatedAt": "2022-06-15T14:02:53.157Z", 1690 | "environment": { 1691 | "sys": { 1692 | "id": "master", 1693 | "type": "Link", 1694 | "linkType": "Environment" 1695 | } 1696 | }, 1697 | "revision": 1 1698 | }, 1699 | "fields": { 1700 | "title": { 1701 | "en-US": "Normann Copenhagen" 1702 | }, 1703 | "description": { 1704 | "en-US": "Brand logo" 1705 | }, 1706 | "file": { 1707 | "en-US": { 1708 | "url": "//images.ctfassets.net/5u403xny70b7/3wtvPBbBjiMKqKKga8I2Cu/013db30808b5426c8c84434317653a1a/zJYzDlGk.jpeg", 1709 | "details": { 1710 | "size": 12302, 1711 | "image": { 1712 | "width": 353, 1713 | "height": 353 1714 | } 1715 | }, 1716 | "fileName": "zJYzDlGk.jpeg", 1717 | "contentType": "image/jpeg" 1718 | } 1719 | } 1720 | } 1721 | }, 1722 | { 1723 | "metadata": { 1724 | "tags": [ 1725 | ] 1726 | }, 1727 | "sys": { 1728 | "space": { 1729 | "sys": { 1730 | "type": "Link", 1731 | "linkType": "Space", 1732 | "id": "5u403xny70b7" 1733 | } 1734 | }, 1735 | "id": "wtrHxeu3zEoEce2MokCSi", 1736 | "type": "Asset", 1737 | "createdAt": "2022-06-15T14:02:53.164Z", 1738 | "updatedAt": "2022-06-15T14:02:53.164Z", 1739 | "environment": { 1740 | "sys": { 1741 | "id": "master", 1742 | "type": "Link", 1743 | "linkType": "Environment" 1744 | } 1745 | }, 1746 | "revision": 1 1747 | }, 1748 | "fields": { 1749 | "title": { 1750 | "en-US": "Playsam Streamliner" 1751 | }, 1752 | "description": { 1753 | "en-US": "Merchandise photo" 1754 | }, 1755 | "file": { 1756 | "en-US": { 1757 | "url": "//images.ctfassets.net/5u403xny70b7/wtrHxeu3zEoEce2MokCSi/a781b57aaa586539b9fe847b155a68c9/quwowooybuqbl6ntboz3.jpg", 1758 | "details": { 1759 | "size": 27187, 1760 | "image": { 1761 | "width": 600, 1762 | "height": 446 1763 | } 1764 | }, 1765 | "fileName": "quwowooybuqbl6ntboz3.jpg", 1766 | "contentType": "image/jpeg" 1767 | } 1768 | } 1769 | } 1770 | }, 1771 | { 1772 | "metadata": { 1773 | "tags": [ 1774 | ] 1775 | }, 1776 | "sys": { 1777 | "space": { 1778 | "sys": { 1779 | "type": "Link", 1780 | "linkType": "Space", 1781 | "id": "5u403xny70b7" 1782 | } 1783 | }, 1784 | "id": "4zj1ZOfHgQ8oqgaSKm4Qo2", 1785 | "type": "Asset", 1786 | "createdAt": "2022-06-15T14:02:53.173Z", 1787 | "updatedAt": "2022-06-15T14:02:53.173Z", 1788 | "environment": { 1789 | "sys": { 1790 | "id": "master", 1791 | "type": "Link", 1792 | "linkType": "Environment" 1793 | } 1794 | }, 1795 | "revision": 1 1796 | }, 1797 | "fields": { 1798 | "title": { 1799 | "en-US": "Playsam" 1800 | }, 1801 | "description": { 1802 | "en-US": "Brand logo" 1803 | }, 1804 | "file": { 1805 | "en-US": { 1806 | "url": "//images.ctfassets.net/5u403xny70b7/4zj1ZOfHgQ8oqgaSKm4Qo2/eef852bd9504ab06abd5b3c7523ab9ed/playsam.jpg", 1807 | "details": { 1808 | "size": 7003, 1809 | "image": { 1810 | "width": 100, 1811 | "height": 100 1812 | } 1813 | }, 1814 | "fileName": "playsam.jpg", 1815 | "contentType": "image/jpeg" 1816 | } 1817 | } 1818 | } 1819 | }, 1820 | { 1821 | "metadata": { 1822 | "tags": [ 1823 | ] 1824 | }, 1825 | "sys": { 1826 | "space": { 1827 | "sys": { 1828 | "type": "Link", 1829 | "linkType": "Space", 1830 | "id": "5u403xny70b7" 1831 | } 1832 | }, 1833 | "id": "6t4HKjytPi0mYgs240wkG", 1834 | "type": "Asset", 1835 | "createdAt": "2022-06-15T14:02:53.184Z", 1836 | "updatedAt": "2022-06-15T14:02:53.184Z", 1837 | "environment": { 1838 | "sys": { 1839 | "id": "master", 1840 | "type": "Link", 1841 | "linkType": "Environment" 1842 | } 1843 | }, 1844 | "revision": 1 1845 | }, 1846 | "fields": { 1847 | "title": { 1848 | "en-US": "Toys" 1849 | }, 1850 | "description": { 1851 | "en-US": "Category icon set" 1852 | }, 1853 | "file": { 1854 | "en-US": { 1855 | "url": "//images.ctfassets.net/5u403xny70b7/6t4HKjytPi0mYgs240wkG/765f475f435fbee1d31637b38814124b/toys_512pxGREY.png", 1856 | "details": { 1857 | "size": 6744, 1858 | "image": { 1859 | "width": 128, 1860 | "height": 128 1861 | } 1862 | }, 1863 | "fileName": "toys_512pxGREY.png", 1864 | "contentType": "image/png" 1865 | } 1866 | } 1867 | } 1868 | }, 1869 | { 1870 | "metadata": { 1871 | "tags": [ 1872 | ] 1873 | }, 1874 | "sys": { 1875 | "space": { 1876 | "sys": { 1877 | "type": "Link", 1878 | "linkType": "Space", 1879 | "id": "5u403xny70b7" 1880 | } 1881 | }, 1882 | "id": "6wDAXbzkqKRAUWO3u8sh2z", 1883 | "type": "Asset", 1884 | "createdAt": "2022-06-15T14:07:54.114Z", 1885 | "updatedAt": "2022-06-15T14:07:54.114Z", 1886 | "environment": { 1887 | "sys": { 1888 | "id": "master", 1889 | "type": "Link", 1890 | "linkType": "Environment" 1891 | } 1892 | }, 1893 | "revision": 1 1894 | }, 1895 | "fields": { 1896 | "title": { 1897 | "en-US": "Hamburger" 1898 | }, 1899 | "file": { 1900 | "en-US": { 1901 | "url": "//images.ctfassets.net/5u403xny70b7/6wDAXbzkqKRAUWO3u8sh2z/1d77f55c0fe375a862ef553a914bf500/photo-1568901346375-23c9450c58cd", 1902 | "details": { 1903 | "size": 336689, 1904 | "image": { 1905 | "width": 1998, 1906 | "height": 1660 1907 | } 1908 | }, 1909 | "fileName": "photo-1568901346375-23c9450c58cd", 1910 | "contentType": "image/jpeg" 1911 | } 1912 | } 1913 | } 1914 | } 1915 | ], 1916 | "locales": [ 1917 | { 1918 | "name": "English (United States)", 1919 | "code": "en-US", 1920 | "fallbackCode": null, 1921 | "default": true, 1922 | "contentManagementApi": true, 1923 | "contentDeliveryApi": true, 1924 | "optional": false, 1925 | "sys": { 1926 | "type": "Locale", 1927 | "id": "5yG2d6reNlQ1kr3c0qTqb7", 1928 | "version": 1, 1929 | "space": { 1930 | "sys": { 1931 | "type": "Link", 1932 | "linkType": "Space", 1933 | "id": "5u403xny70b7" 1934 | } 1935 | }, 1936 | "environment": { 1937 | "sys": { 1938 | "type": "Link", 1939 | "linkType": "Environment", 1940 | "id": "master", 1941 | "uuid": "6cd53f0f-fcf5-40ee-98cd-3a030594d0bf" 1942 | } 1943 | }, 1944 | "createdBy": { 1945 | "sys": { 1946 | "type": "Link", 1947 | "linkType": "User", 1948 | "id": "5JyrJiiomUI3mj8CrwPKe9" 1949 | } 1950 | }, 1951 | "createdAt": "2022-06-15T14:02:27Z", 1952 | "updatedBy": { 1953 | "sys": { 1954 | "type": "Link", 1955 | "linkType": "User", 1956 | "id": "5JyrJiiomUI3mj8CrwPKe9" 1957 | } 1958 | }, 1959 | "updatedAt": "2022-06-15T14:02:27Z" 1960 | } 1961 | } 1962 | ], 1963 | "webhooks": [ 1964 | ], 1965 | "roles": [ 1966 | { 1967 | "name": "Author", 1968 | "description": "Allows editing of content", 1969 | "policies": [ 1970 | { 1971 | "effect": "allow", 1972 | "actions": [ 1973 | "create" 1974 | ], 1975 | "constraint": { 1976 | "and": [ 1977 | { 1978 | "equals": [ 1979 | { 1980 | "doc": "sys.type" 1981 | }, 1982 | "Entry" 1983 | ] 1984 | } 1985 | ] 1986 | } 1987 | }, 1988 | { 1989 | "effect": "allow", 1990 | "actions": [ 1991 | "read" 1992 | ], 1993 | "constraint": { 1994 | "and": [ 1995 | { 1996 | "equals": [ 1997 | { 1998 | "doc": "sys.type" 1999 | }, 2000 | "Entry" 2001 | ] 2002 | } 2003 | ] 2004 | } 2005 | }, 2006 | { 2007 | "effect": "allow", 2008 | "actions": [ 2009 | "update" 2010 | ], 2011 | "constraint": { 2012 | "and": [ 2013 | { 2014 | "equals": [ 2015 | { 2016 | "doc": "sys.type" 2017 | }, 2018 | "Entry" 2019 | ] 2020 | } 2021 | ] 2022 | } 2023 | }, 2024 | { 2025 | "effect": "allow", 2026 | "actions": [ 2027 | "create" 2028 | ], 2029 | "constraint": { 2030 | "and": [ 2031 | { 2032 | "equals": [ 2033 | { 2034 | "doc": "sys.type" 2035 | }, 2036 | "Asset" 2037 | ] 2038 | } 2039 | ] 2040 | } 2041 | }, 2042 | { 2043 | "effect": "allow", 2044 | "actions": [ 2045 | "read" 2046 | ], 2047 | "constraint": { 2048 | "and": [ 2049 | { 2050 | "equals": [ 2051 | { 2052 | "doc": "sys.type" 2053 | }, 2054 | "Asset" 2055 | ] 2056 | } 2057 | ] 2058 | } 2059 | }, 2060 | { 2061 | "effect": "allow", 2062 | "actions": [ 2063 | "update" 2064 | ], 2065 | "constraint": { 2066 | "and": [ 2067 | { 2068 | "equals": [ 2069 | { 2070 | "doc": "sys.type" 2071 | }, 2072 | "Asset" 2073 | ] 2074 | } 2075 | ] 2076 | } 2077 | } 2078 | ], 2079 | "permissions": { 2080 | "ContentModel": [ 2081 | "read" 2082 | ], 2083 | "Settings": [ 2084 | ], 2085 | "ContentDelivery": [ 2086 | ], 2087 | "Environments": [ 2088 | ], 2089 | "EnvironmentAliases": [ 2090 | ], 2091 | "Tags": [ 2092 | ] 2093 | }, 2094 | "sys": { 2095 | "type": "Role", 2096 | "id": "5NLZ0sicyMuU2zhIur9R29", 2097 | "version": 0, 2098 | "space": { 2099 | "sys": { 2100 | "type": "Link", 2101 | "linkType": "Space", 2102 | "id": "5u403xny70b7" 2103 | } 2104 | }, 2105 | "createdBy": { 2106 | "sys": { 2107 | "type": "Link", 2108 | "linkType": "User", 2109 | "id": "5JyrJiiomUI3mj8CrwPKe9" 2110 | } 2111 | }, 2112 | "createdAt": "2022-06-15T14:02:41Z", 2113 | "updatedBy": { 2114 | "sys": { 2115 | "type": "Link", 2116 | "linkType": "User", 2117 | "id": "5JyrJiiomUI3mj8CrwPKe9" 2118 | } 2119 | }, 2120 | "updatedAt": "2022-06-15T14:02:41Z" 2121 | } 2122 | }, 2123 | { 2124 | "name": "Editor", 2125 | "description": "Allows editing, publishing and archiving of content", 2126 | "policies": [ 2127 | { 2128 | "effect": "allow", 2129 | "actions": "all", 2130 | "constraint": { 2131 | "and": [ 2132 | { 2133 | "equals": [ 2134 | { 2135 | "doc": "sys.type" 2136 | }, 2137 | "Entry" 2138 | ] 2139 | } 2140 | ] 2141 | } 2142 | }, 2143 | { 2144 | "effect": "allow", 2145 | "actions": "all", 2146 | "constraint": { 2147 | "and": [ 2148 | { 2149 | "equals": [ 2150 | { 2151 | "doc": "sys.type" 2152 | }, 2153 | "Asset" 2154 | ] 2155 | } 2156 | ] 2157 | } 2158 | } 2159 | ], 2160 | "permissions": { 2161 | "ContentModel": [ 2162 | "read" 2163 | ], 2164 | "Settings": [ 2165 | ], 2166 | "ContentDelivery": [ 2167 | ], 2168 | "Environments": [ 2169 | ], 2170 | "EnvironmentAliases": [ 2171 | ], 2172 | "Tags": [ 2173 | ] 2174 | }, 2175 | "sys": { 2176 | "type": "Role", 2177 | "id": "5NMHmsWIUNUhlPX4nAFtTz", 2178 | "version": 0, 2179 | "space": { 2180 | "sys": { 2181 | "type": "Link", 2182 | "linkType": "Space", 2183 | "id": "5u403xny70b7" 2184 | } 2185 | }, 2186 | "createdBy": { 2187 | "sys": { 2188 | "type": "Link", 2189 | "linkType": "User", 2190 | "id": "5JyrJiiomUI3mj8CrwPKe9" 2191 | } 2192 | }, 2193 | "createdAt": "2022-06-15T14:02:41Z", 2194 | "updatedBy": { 2195 | "sys": { 2196 | "type": "Link", 2197 | "linkType": "User", 2198 | "id": "5JyrJiiomUI3mj8CrwPKe9" 2199 | } 2200 | }, 2201 | "updatedAt": "2022-06-15T14:02:41Z" 2202 | } 2203 | }, 2204 | { 2205 | "name": "Freelancer", 2206 | "description": "Allows only editing of content they created themselves", 2207 | "policies": [ 2208 | { 2209 | "effect": "allow", 2210 | "actions": [ 2211 | "create" 2212 | ], 2213 | "constraint": { 2214 | "and": [ 2215 | { 2216 | "equals": [ 2217 | { 2218 | "doc": "sys.type" 2219 | }, 2220 | "Entry" 2221 | ] 2222 | } 2223 | ] 2224 | } 2225 | }, 2226 | { 2227 | "effect": "allow", 2228 | "actions": [ 2229 | "create" 2230 | ], 2231 | "constraint": { 2232 | "and": [ 2233 | { 2234 | "equals": [ 2235 | { 2236 | "doc": "sys.type" 2237 | }, 2238 | "Asset" 2239 | ] 2240 | } 2241 | ] 2242 | } 2243 | }, 2244 | { 2245 | "effect": "allow", 2246 | "actions": [ 2247 | "read" 2248 | ], 2249 | "constraint": { 2250 | "and": [ 2251 | { 2252 | "equals": [ 2253 | { 2254 | "doc": "sys.type" 2255 | }, 2256 | "Entry" 2257 | ] 2258 | }, 2259 | { 2260 | "equals": [ 2261 | { 2262 | "doc": "sys.createdBy.sys.id" 2263 | }, 2264 | "User.current()" 2265 | ] 2266 | } 2267 | ] 2268 | } 2269 | }, 2270 | { 2271 | "effect": "allow", 2272 | "actions": [ 2273 | "update" 2274 | ], 2275 | "constraint": { 2276 | "and": [ 2277 | { 2278 | "equals": [ 2279 | { 2280 | "doc": "sys.type" 2281 | }, 2282 | "Entry" 2283 | ] 2284 | }, 2285 | { 2286 | "equals": [ 2287 | { 2288 | "doc": "sys.createdBy.sys.id" 2289 | }, 2290 | "User.current()" 2291 | ] 2292 | }, 2293 | { 2294 | "paths": [ 2295 | { 2296 | "doc": "fields.%.%" 2297 | } 2298 | ] 2299 | } 2300 | ] 2301 | } 2302 | }, 2303 | { 2304 | "effect": "allow", 2305 | "actions": [ 2306 | "delete" 2307 | ], 2308 | "constraint": { 2309 | "and": [ 2310 | { 2311 | "equals": [ 2312 | { 2313 | "doc": "sys.type" 2314 | }, 2315 | "Entry" 2316 | ] 2317 | }, 2318 | { 2319 | "equals": [ 2320 | { 2321 | "doc": "sys.createdBy.sys.id" 2322 | }, 2323 | "User.current()" 2324 | ] 2325 | } 2326 | ] 2327 | } 2328 | }, 2329 | { 2330 | "effect": "allow", 2331 | "actions": [ 2332 | "read" 2333 | ], 2334 | "constraint": { 2335 | "and": [ 2336 | { 2337 | "equals": [ 2338 | { 2339 | "doc": "sys.type" 2340 | }, 2341 | "Asset" 2342 | ] 2343 | }, 2344 | { 2345 | "equals": [ 2346 | { 2347 | "doc": "sys.createdBy.sys.id" 2348 | }, 2349 | "User.current()" 2350 | ] 2351 | } 2352 | ] 2353 | } 2354 | }, 2355 | { 2356 | "effect": "allow", 2357 | "actions": [ 2358 | "update" 2359 | ], 2360 | "constraint": { 2361 | "and": [ 2362 | { 2363 | "equals": [ 2364 | { 2365 | "doc": "sys.type" 2366 | }, 2367 | "Asset" 2368 | ] 2369 | }, 2370 | { 2371 | "equals": [ 2372 | { 2373 | "doc": "sys.createdBy.sys.id" 2374 | }, 2375 | "User.current()" 2376 | ] 2377 | }, 2378 | { 2379 | "paths": [ 2380 | { 2381 | "doc": "fields.%.%" 2382 | } 2383 | ] 2384 | } 2385 | ] 2386 | } 2387 | }, 2388 | { 2389 | "effect": "allow", 2390 | "actions": [ 2391 | "delete" 2392 | ], 2393 | "constraint": { 2394 | "and": [ 2395 | { 2396 | "equals": [ 2397 | { 2398 | "doc": "sys.type" 2399 | }, 2400 | "Asset" 2401 | ] 2402 | }, 2403 | { 2404 | "equals": [ 2405 | { 2406 | "doc": "sys.createdBy.sys.id" 2407 | }, 2408 | "User.current()" 2409 | ] 2410 | } 2411 | ] 2412 | } 2413 | } 2414 | ], 2415 | "permissions": { 2416 | "ContentModel": [ 2417 | "read" 2418 | ], 2419 | "Settings": [ 2420 | ], 2421 | "ContentDelivery": [ 2422 | ], 2423 | "Environments": [ 2424 | ], 2425 | "EnvironmentAliases": [ 2426 | ], 2427 | "Tags": [ 2428 | ] 2429 | }, 2430 | "sys": { 2431 | "type": "Role", 2432 | "id": "5NNCoCFaFNSJbM8wXGUzD3", 2433 | "version": 0, 2434 | "space": { 2435 | "sys": { 2436 | "type": "Link", 2437 | "linkType": "Space", 2438 | "id": "5u403xny70b7" 2439 | } 2440 | }, 2441 | "createdBy": { 2442 | "sys": { 2443 | "type": "Link", 2444 | "linkType": "User", 2445 | "id": "5JyrJiiomUI3mj8CrwPKe9" 2446 | } 2447 | }, 2448 | "createdAt": "2022-06-15T14:02:41Z", 2449 | "updatedBy": { 2450 | "sys": { 2451 | "type": "Link", 2452 | "linkType": "User", 2453 | "id": "5JyrJiiomUI3mj8CrwPKe9" 2454 | } 2455 | }, 2456 | "updatedAt": "2022-06-15T14:02:41Z" 2457 | } 2458 | }, 2459 | { 2460 | "name": "Translator", 2461 | "description": "Allows editing of localized fields in the specified language", 2462 | "policies": [ 2463 | { 2464 | "effect": "allow", 2465 | "actions": [ 2466 | "read" 2467 | ], 2468 | "constraint": { 2469 | "and": [ 2470 | { 2471 | "equals": [ 2472 | { 2473 | "doc": "sys.type" 2474 | }, 2475 | "Entry" 2476 | ] 2477 | } 2478 | ] 2479 | } 2480 | }, 2481 | { 2482 | "effect": "allow", 2483 | "actions": [ 2484 | "read" 2485 | ], 2486 | "constraint": { 2487 | "and": [ 2488 | { 2489 | "equals": [ 2490 | { 2491 | "doc": "sys.type" 2492 | }, 2493 | "Asset" 2494 | ] 2495 | } 2496 | ] 2497 | } 2498 | }, 2499 | { 2500 | "effect": "allow", 2501 | "actions": [ 2502 | "update" 2503 | ], 2504 | "constraint": { 2505 | "and": [ 2506 | { 2507 | "equals": [ 2508 | { 2509 | "doc": "sys.type" 2510 | }, 2511 | "Entry" 2512 | ] 2513 | }, 2514 | { 2515 | "paths": [ 2516 | { 2517 | "doc": "fields.%.%" 2518 | } 2519 | ] 2520 | } 2521 | ] 2522 | } 2523 | }, 2524 | { 2525 | "effect": "allow", 2526 | "actions": [ 2527 | "update" 2528 | ], 2529 | "constraint": { 2530 | "and": [ 2531 | { 2532 | "equals": [ 2533 | { 2534 | "doc": "sys.type" 2535 | }, 2536 | "Asset" 2537 | ] 2538 | }, 2539 | { 2540 | "paths": [ 2541 | { 2542 | "doc": "fields.%.%" 2543 | } 2544 | ] 2545 | } 2546 | ] 2547 | } 2548 | } 2549 | ], 2550 | "permissions": { 2551 | "ContentModel": [ 2552 | "read" 2553 | ], 2554 | "Settings": [ 2555 | ], 2556 | "ContentDelivery": [ 2557 | ], 2558 | "Environments": [ 2559 | ], 2560 | "EnvironmentAliases": [ 2561 | ], 2562 | "Tags": [ 2563 | ] 2564 | }, 2565 | "sys": { 2566 | "type": "Role", 2567 | "id": "5NOiHGq3PaszFz44L7VEFH", 2568 | "version": 0, 2569 | "space": { 2570 | "sys": { 2571 | "type": "Link", 2572 | "linkType": "Space", 2573 | "id": "5u403xny70b7" 2574 | } 2575 | }, 2576 | "createdBy": { 2577 | "sys": { 2578 | "type": "Link", 2579 | "linkType": "User", 2580 | "id": "5JyrJiiomUI3mj8CrwPKe9" 2581 | } 2582 | }, 2583 | "createdAt": "2022-06-15T14:02:41Z", 2584 | "updatedBy": { 2585 | "sys": { 2586 | "type": "Link", 2587 | "linkType": "User", 2588 | "id": "5JyrJiiomUI3mj8CrwPKe9" 2589 | } 2590 | }, 2591 | "updatedAt": "2022-06-15T14:02:41Z" 2592 | } 2593 | } 2594 | ] 2595 | } -------------------------------------------------------------------------------- /contentful/images.ctfassets.net/5u403xny70b7/10TkaLheGeQG6qQGqWYqUI/e6485b04c5f6991cfbae6ef8e332976f/ryugj83mqwa1asojwtwb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoenixdev0117/Contentful-Angular/58e5018fc427d9087b0a65e64ecf90ddab800e89/contentful/images.ctfassets.net/5u403xny70b7/10TkaLheGeQG6qQGqWYqUI/e6485b04c5f6991cfbae6ef8e332976f/ryugj83mqwa1asojwtwb.jpg -------------------------------------------------------------------------------- /contentful/images.ctfassets.net/5u403xny70b7/1MgbdJNTsMWKI0W68oYqkU/f409b10201b5a2e12409c6a3a2a30033/9ef190c59f0d375c0dea58b58a4bc1f0.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoenixdev0117/Contentful-Angular/58e5018fc427d9087b0a65e64ecf90ddab800e89/contentful/images.ctfassets.net/5u403xny70b7/1MgbdJNTsMWKI0W68oYqkU/f409b10201b5a2e12409c6a3a2a30033/9ef190c59f0d375c0dea58b58a4bc1f0.jpeg -------------------------------------------------------------------------------- /contentful/images.ctfassets.net/5u403xny70b7/2Y8LhXLnYAYqKCGEWG4EKI/94b1546dbbfc121c91b074b53fa5b3bc/lemnos-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoenixdev0117/Contentful-Angular/58e5018fc427d9087b0a65e64ecf90ddab800e89/contentful/images.ctfassets.net/5u403xny70b7/2Y8LhXLnYAYqKCGEWG4EKI/94b1546dbbfc121c91b074b53fa5b3bc/lemnos-logo.jpg -------------------------------------------------------------------------------- /contentful/images.ctfassets.net/5u403xny70b7/3wtvPBbBjiMKqKKga8I2Cu/013db30808b5426c8c84434317653a1a/zJYzDlGk.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoenixdev0117/Contentful-Angular/58e5018fc427d9087b0a65e64ecf90ddab800e89/contentful/images.ctfassets.net/5u403xny70b7/3wtvPBbBjiMKqKKga8I2Cu/013db30808b5426c8c84434317653a1a/zJYzDlGk.jpeg -------------------------------------------------------------------------------- /contentful/images.ctfassets.net/5u403xny70b7/4zj1ZOfHgQ8oqgaSKm4Qo2/eef852bd9504ab06abd5b3c7523ab9ed/playsam.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoenixdev0117/Contentful-Angular/58e5018fc427d9087b0a65e64ecf90ddab800e89/contentful/images.ctfassets.net/5u403xny70b7/4zj1ZOfHgQ8oqgaSKm4Qo2/eef852bd9504ab06abd5b3c7523ab9ed/playsam.jpg -------------------------------------------------------------------------------- /contentful/images.ctfassets.net/5u403xny70b7/6m5AJ9vMPKc8OUoQeoCS4o/34f04735f1d86f7b89080752c7505afc/1418244847_Streamline-18-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoenixdev0117/Contentful-Angular/58e5018fc427d9087b0a65e64ecf90ddab800e89/contentful/images.ctfassets.net/5u403xny70b7/6m5AJ9vMPKc8OUoQeoCS4o/34f04735f1d86f7b89080752c7505afc/1418244847_Streamline-18-256.png -------------------------------------------------------------------------------- /contentful/images.ctfassets.net/5u403xny70b7/6s3iG2OVmoUcosmA8ocqsG/d576e30a70d1a105ec1966ba033c0d27/1418244847_Streamline-18-256__1_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoenixdev0117/Contentful-Angular/58e5018fc427d9087b0a65e64ecf90ddab800e89/contentful/images.ctfassets.net/5u403xny70b7/6s3iG2OVmoUcosmA8ocqsG/d576e30a70d1a105ec1966ba033c0d27/1418244847_Streamline-18-256__1_.png -------------------------------------------------------------------------------- /contentful/images.ctfassets.net/5u403xny70b7/6t4HKjytPi0mYgs240wkG/765f475f435fbee1d31637b38814124b/toys_512pxGREY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoenixdev0117/Contentful-Angular/58e5018fc427d9087b0a65e64ecf90ddab800e89/contentful/images.ctfassets.net/5u403xny70b7/6t4HKjytPi0mYgs240wkG/765f475f435fbee1d31637b38814124b/toys_512pxGREY.png -------------------------------------------------------------------------------- /contentful/images.ctfassets.net/5u403xny70b7/6wDAXbzkqKRAUWO3u8sh2z/1d77f55c0fe375a862ef553a914bf500/photo-1568901346375-23c9450c58cd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoenixdev0117/Contentful-Angular/58e5018fc427d9087b0a65e64ecf90ddab800e89/contentful/images.ctfassets.net/5u403xny70b7/6wDAXbzkqKRAUWO3u8sh2z/1d77f55c0fe375a862ef553a914bf500/photo-1568901346375-23c9450c58cd -------------------------------------------------------------------------------- /contentful/images.ctfassets.net/5u403xny70b7/KTRF62Q4gg60q6WCsWKw8/00a22ab2a0577b991f560a4b12f43b35/soso.clock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoenixdev0117/Contentful-Angular/58e5018fc427d9087b0a65e64ecf90ddab800e89/contentful/images.ctfassets.net/5u403xny70b7/KTRF62Q4gg60q6WCsWKw8/00a22ab2a0577b991f560a4b12f43b35/soso.clock.jpg -------------------------------------------------------------------------------- /contentful/images.ctfassets.net/5u403xny70b7/Xc0ny7GWsMEMCeASWO2um/386ee028262461f49449a3dcde7712ba/jqvtazcyfwseah9fmysz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoenixdev0117/Contentful-Angular/58e5018fc427d9087b0a65e64ecf90ddab800e89/contentful/images.ctfassets.net/5u403xny70b7/Xc0ny7GWsMEMCeASWO2um/386ee028262461f49449a3dcde7712ba/jqvtazcyfwseah9fmysz.jpg -------------------------------------------------------------------------------- /contentful/images.ctfassets.net/5u403xny70b7/wtrHxeu3zEoEce2MokCSi/a781b57aaa586539b9fe847b155a68c9/quwowooybuqbl6ntboz3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoenixdev0117/Contentful-Angular/58e5018fc427d9087b0a65e64ecf90ddab800e89/contentful/images.ctfassets.net/5u403xny70b7/wtrHxeu3zEoEce2MokCSi/a781b57aaa586539b9fe847b155a68c9/quwowooybuqbl6ntboz3.jpg -------------------------------------------------------------------------------- /e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { CfAngularTutorialPage } from './app.po'; 2 | 3 | describe('cf-angular-tutorial App', () => { 4 | let page: CfAngularTutorialPage; 5 | 6 | beforeEach(() => { 7 | page = new CfAngularTutorialPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to app!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class CfAngularTutorialPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "target": "es2019", 8 | "types": [ 9 | "jasmine", 10 | "jasminewd2", 11 | "node" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client:{ 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ], 20 | fixWebpackSourcePaths: true 21 | }, 22 | 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "dist" 3 | command = "npm run build" 4 | [[redirects]] 5 | from = "/*" 6 | to = "/index.html" 7 | status = 200 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stackbit-angular-contentful", 3 | "version": "0.1.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "config": "ts-node set-env.ts", 8 | "dev": "npm run config && ng serve --port 3000 --disable-host-check", 9 | "build": "npm run config && ng build", 10 | "test": "npm run config && ng test", 11 | "lint": "ng lint", 12 | "e2e": "ng e2e" 13 | }, 14 | "dependencies": { 15 | "@angular/animations": "^14.0.2", 16 | "@angular/cdk": "^14.0.1", 17 | "@angular/common": "^14.0.2", 18 | "@angular/compiler": "^14.0.2", 19 | "@angular/core": "^14.0.2", 20 | "@angular/forms": "^14.0.2", 21 | "@angular/material": "^14.0.1", 22 | "@angular/platform-browser": "^14.0.2", 23 | "@angular/platform-browser-dynamic": "^14.0.2", 24 | "@angular/router": "^14.0.2", 25 | "contentful": "^9.1.32", 26 | "core-js": "^2.6.12", 27 | "rxjs": "^7.5.5", 28 | "zone.js": "^0.11.6" 29 | }, 30 | "devDependencies": { 31 | "@angular-devkit/build-angular": "^14.0.2", 32 | "@angular/cli": "^14.0.2", 33 | "@angular/compiler-cli": "^14.0.2", 34 | "@angular/language-service": "^14.0.2", 35 | "@stackbit/cms-contentful": "^0.4.56", 36 | "@types/jasmine": "~4.0.3", 37 | "@types/jasminewd2": "~2.0.10", 38 | "@types/node": "~18.0.0", 39 | "codelyzer": "~6.0.2", 40 | "dotenv": "^16.0.1", 41 | "drupal-content-connector": "https://6ea7f19e-4742-49a2-9cff-5a80a3d433d4.netlify.app/packages/connector.tgz", 42 | "jasmine-core": "~4.2.0", 43 | "jasmine-spec-reporter": "~7.0.0", 44 | "karma": "~6.4.0", 45 | "karma-chrome-launcher": "~3.1.1", 46 | "karma-cli": "~2.0.0", 47 | "karma-coverage-istanbul-reporter": "^3.0.3", 48 | "karma-jasmine": "~5.1.0", 49 | "karma-jasmine-html-reporter": "^2.0.0", 50 | "protractor": "~7.0.0", 51 | "ts-node": "~10.8.1", 52 | "tslint": "~6.1.3", 53 | "typescript": "^4.7.3" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: 'e2e/tsconfig.e2e.json' 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /sample.env: -------------------------------------------------------------------------------- 1 | CONTENTFUL_SPACE_ID= 2 | CONTENTFUL_PREVIEW_TOKEN= 3 | CONTENTFUL_DELIVERY_TOKEN= 4 | PRODUCTION=false -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoenixdev0117/Contentful-Angular/58e5018fc427d9087b0a65e64ecf90ddab800e89/screenshot.png -------------------------------------------------------------------------------- /set-env.ts: -------------------------------------------------------------------------------- 1 | import { writeFile } from 'fs'; 2 | // Configure Angular `environment.ts` file path 3 | const targetPath = './src/environments/environment.ts'; 4 | // Load node modules 5 | require('dotenv').config(); 6 | // `environment.ts` file structure 7 | const envConfigFile = `export const environment = { 8 | contentfulSpaceId: '${process.env.CONTENTFUL_SPACE_ID}', 9 | contentfulPreviewToken: '${process.env.CONTENTFUL_PREVIEW_TOKEN}', 10 | contentfulDeliveryToken: '${process.env.CONTENTFUL_DELIVERY_TOKEN}', 11 | production: '${process.env.PRODUCTION}' 12 | }; 13 | `; 14 | 15 | console.log('HELLO FROM THE SET ENV'); 16 | writeFile(targetPath, envConfigFile, function (err) { 17 | if (err) { 18 | throw console.error(err); 19 | } else { 20 | console.log(`Angular environment.ts file generated correctly at ${targetPath} \n`); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- 1 | .logo { 2 | width: 1em; 3 | height: 1em; 4 | margin-right: .5em; 5 | display: inline-block; 6 | vertical-align: middle; 7 | } 8 | 9 | .container { 10 | display: block; 11 | max-width: 50em; 12 | margin: 0 auto; 13 | padding: 1em; 14 | } 15 | 16 | .app-title { 17 | margin-left: auto; 18 | font-size: .8em; 19 | } 20 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Products 7 | 8 | Categories 9 | 10 | {{ title }} 11 | 12 | 13 |
14 | 15 |
16 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async(() => { 7 | TestBed.configureTestingModule({ 8 | declarations: [ 9 | AppComponent 10 | ], 11 | }).compileComponents(); 12 | })); 13 | 14 | it('should create the app', async(() => { 15 | const fixture = TestBed.createComponent(AppComponent); 16 | const app = fixture.debugElement.componentInstance; 17 | expect(app).toBeTruthy(); 18 | })); 19 | 20 | it(`should have as title 'app'`, async(() => { 21 | const fixture = TestBed.createComponent(AppComponent); 22 | const app = fixture.debugElement.componentInstance; 23 | expect(app.title).toEqual('app'); 24 | })); 25 | 26 | it('should render title in a h1 tag', async(() => { 27 | const fixture = TestBed.createComponent(AppComponent); 28 | fixture.detectChanges(); 29 | const compiled = fixture.debugElement.nativeElement; 30 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!'); 31 | })); 32 | }); 33 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ContentfulService } from './contentful.service'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | templateUrl: './app.component.html', 7 | styleUrls: ['./app.component.css'] 8 | }) 9 | export class AppComponent implements OnInit { 10 | title: string; 11 | 12 | constructor( 13 | private ContentfulService: ContentfulService 14 | ) {} 15 | 16 | ngOnInit() { 17 | this.ContentfulService.onTitleChange(title => this.title = title) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { HttpClientModule } from '@angular/common/http'; 5 | import { RouterModule, Routes } from '@angular/router'; 6 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 7 | import { MatListModule } from '@angular/material/list'; 8 | import { MatInputModule} from '@angular/material/input'; 9 | import { MatToolbarModule} from '@angular/material/toolbar'; 10 | import { MatButtonModule } from '@angular/material/button'; 11 | import { MatCardModule } from '@angular/material/card'; 12 | import { MatTabsModule } from '@angular/material/tabs'; 13 | 14 | import { AppComponent } from './app.component'; 15 | import { ProductListComponent } from './product-list/product-list.component'; 16 | import { ProductDetailComponent } from './product-detail/product-detail.component'; 17 | import { CategoryListComponent } from './category-list/category-list.component'; 18 | 19 | import { ContentfulService } from './contentful.service'; 20 | import { StackbitService } from './stackbit.service'; 21 | import { DrupalService } from './drupal.service'; 22 | 23 | // check this and make a module out of it 24 | const routes: Routes = [ 25 | { path: '', redirectTo: '/products', pathMatch: 'full' }, 26 | { path: 'products', component: ProductListComponent }, 27 | { path: 'products/:slug', component: ProductDetailComponent }, 28 | { path: 'categories', component: CategoryListComponent } 29 | ]; 30 | 31 | @NgModule({ 32 | declarations: [ 33 | AppComponent, 34 | ProductListComponent, 35 | ProductDetailComponent, 36 | CategoryListComponent 37 | ], 38 | imports: [ 39 | BrowserModule, 40 | BrowserAnimationsModule, 41 | RouterModule.forRoot(routes), 42 | FormsModule, 43 | MatListModule, 44 | MatToolbarModule, 45 | MatCardModule, 46 | MatButtonModule, 47 | MatTabsModule, 48 | MatInputModule, 49 | HttpClientModule 50 | ], 51 | exports: [ 52 | MatListModule, 53 | MatToolbarModule, 54 | MatCardModule, 55 | MatButtonModule, 56 | MatTabsModule, 57 | MatInputModule 58 | ], 59 | providers: [ContentfulService, StackbitService, DrupalService], 60 | bootstrap: [AppComponent] 61 | }) 62 | export class AppModule { } 63 | -------------------------------------------------------------------------------- /src/app/category-list/category-list.component.css: -------------------------------------------------------------------------------- 1 | .category-grid { 2 | display: flex; 3 | flex-wrap: wrap; 4 | margin: -0.5em; 5 | } 6 | 7 | .category-grid > * { 8 | flex: 1 0 5em; 9 | margin: 0.5em; 10 | 11 | min-width: 20em; 12 | max-width: 37.5em; 13 | } 14 | 15 | .category-card { 16 | height: 100%; 17 | } 18 | 19 | .category-image { 20 | width: 3em; 21 | height: 3em; 22 | 23 | margin-left: auto; 24 | } 25 | -------------------------------------------------------------------------------- /src/app/category-list/category-list.component.html: -------------------------------------------------------------------------------- 1 |
2 | 20 |
21 | 22 | -------------------------------------------------------------------------------- /src/app/category-list/category-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CategoryListComponent } from './category-list.component'; 4 | 5 | describe('CategoryListComponent', () => { 6 | let component: CategoryListComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ CategoryListComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(CategoryListComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/category-list/category-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | // import { MdList } from '@angular/material'; 3 | import { ContentfulService } from '../contentful.service'; 4 | import { Entry } from 'contentful'; 5 | 6 | 7 | @Component({ 8 | selector: 'app-category-list', 9 | templateUrl: './category-list.component.html', 10 | styleUrls: ['./category-list.component.css'], 11 | // entryComponents: [ MdList ] 12 | }) 13 | export class CategoryListComponent implements OnInit { 14 | categories: Entry[]; 15 | productsForCategories: {} = {}; 16 | 17 | constructor(private contentfulService: ContentfulService) { } 18 | 19 | ngOnInit() { 20 | this.contentfulService.getCategories() 21 | .then(categories => { 22 | this.categories = categories; 23 | 24 | return Promise.all(this.categories.map( 25 | category => this.contentfulService.getProductsFromContentful({ 26 | 'fields.categories.sys.id': category.sys.id 27 | }) 28 | )) 29 | }) 30 | .then(productListings => { 31 | this.categories.forEach((cat, i) => { 32 | this.productsForCategories[cat.sys.id] = productListings[i]; 33 | }); 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/app/contentful.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, isDevMode, EventEmitter } from '@angular/core'; 2 | import { BehaviorSubject } from 'rxjs'; 3 | import { createClient, Entry, Space, ContentfulClientApi } from 'contentful'; 4 | import { StackbitEvent, StackbitService } from './stackbit.service'; 5 | import { environment } from './../environments/environment'; 6 | 7 | // change these to include your own settings 8 | const DEFAULT_CONFIG = { 9 | credentials: { 10 | space: environment.contentfulSpaceId, 11 | accessToken: isDevMode ? environment.contentfulPreviewToken : environment.contentfulDeliveryToken 12 | }, 13 | 14 | contentTypeIds: { 15 | product: 'product', 16 | category: 'category' 17 | } 18 | } 19 | 20 | @Injectable() 21 | export class ContentfulService { 22 | cdaClient: ContentfulClientApi; 23 | config: { 24 | space: string, 25 | accessToken: string, 26 | }; 27 | titleHandlers: Function[]; 28 | constructor(private stackbitService: StackbitService) { 29 | try { 30 | this.config = JSON.parse(localStorage.catalogConfig); 31 | } catch (e) { 32 | this.config = DEFAULT_CONFIG.credentials; 33 | } 34 | 35 | this.titleHandlers = []; 36 | this._createClient(); 37 | this.getSpace(); 38 | } 39 | 40 | onTitleChange(fn): void { 41 | this.titleHandlers.push(fn) 42 | } 43 | 44 | // get the current space 45 | getSpace(): Promise { 46 | return this.cdaClient.getSpace() 47 | .then(space => { 48 | this.titleHandlers.forEach(handler => handler(space.name)) 49 | 50 | return space; 51 | }) 52 | } 53 | 54 | // fetch products 55 | getProductsFromContentful(query?: object): Promise[]> { 56 | return this.cdaClient.getEntries(Object.assign({ 57 | content_type: DEFAULT_CONFIG.contentTypeIds.product 58 | }, query)) 59 | .then(res => res.items); 60 | } 61 | 62 | getProducts(query?: object): BehaviorSubject[]>> { 63 | const productsSubject = new BehaviorSubject(this.getProductsFromContentful(query)); 64 | this.stackbitService.contentChanged.subscribe({ 65 | next: (event: StackbitEvent) => { 66 | if (event.changedContentTypes.includes(DEFAULT_CONFIG.contentTypeIds.product)) { 67 | productsSubject.next(this.getProductsFromContentful(query)); 68 | } 69 | } 70 | }) 71 | 72 | return productsSubject; 73 | } 74 | 75 | // fetch products with a given slug 76 | // and return one of them 77 | getProductFromContentful(slug: string): Promise> { 78 | return this.getProductsFromContentful({ 'fields.slug': slug }) 79 | .then(items => items[0]) 80 | } 81 | 82 | getProduct(slug: string): BehaviorSubject>> { 83 | const productSubject = new BehaviorSubject(this.getProductFromContentful(slug)); 84 | this.stackbitService.contentChanged.subscribe({ 85 | next: (event: StackbitEvent) => { 86 | if (event.changedContentTypes.includes(DEFAULT_CONFIG.contentTypeIds.product)) { 87 | productSubject.next(this.getProductFromContentful(slug)); 88 | } 89 | } 90 | }) 91 | return productSubject; 92 | } 93 | 94 | // fetch categories 95 | getCategories(): Promise[]> { 96 | return this.cdaClient.getEntries({ 97 | content_type: DEFAULT_CONFIG.contentTypeIds.category 98 | }) 99 | .then(res => res.items); 100 | } 101 | 102 | // return a custom config if available 103 | getConfig(): { space: string, accessToken: string } { 104 | return this.config !== DEFAULT_CONFIG.credentials ? 105 | Object.assign({}, this.config) : 106 | { space: '', accessToken: '' }; 107 | } 108 | 109 | // set a new config and store it in localStorage 110 | setConfig(config: { space: string, accessToken: string }) { 111 | localStorage.setItem('catalogConfig', JSON.stringify(config)); 112 | this.config = config; 113 | 114 | this._createClient(); 115 | this.getSpace(); 116 | 117 | return Object.assign({}, this.config); 118 | } 119 | 120 | // set config back to default values 121 | resetConfig() { 122 | localStorage.removeItem('catalogConfig'); 123 | this.config = DEFAULT_CONFIG.credentials; 124 | 125 | this._createClient(); 126 | this.getSpace(); 127 | 128 | return Object.assign({}, this.config); 129 | } 130 | 131 | _createClient() { 132 | this.cdaClient = createClient({ 133 | space: this.config.space, 134 | accessToken: this.config.accessToken, 135 | host: isDevMode ? 'preview.contentful.com' : 'cdn.contentful.com' 136 | }); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/app/drupal.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient, HttpHeaders } from '@angular/common/http' 3 | import { environment } from './../environments/environment'; 4 | 5 | const DEFAULT_CONFIG = { 6 | credentials: { 7 | accessToken: environment.drupalAuthToken 8 | }, 9 | } 10 | 11 | @Injectable({ 12 | providedIn: 'root' 13 | }) 14 | export class DrupalService { 15 | 16 | constructor(private http: HttpClient) { } 17 | 18 | httpOptions = { 19 | headers: new HttpHeaders({ 20 | 'Content-Type': 'application/json', 21 | 'Authorization': DEFAULT_CONFIG.credentials.accessToken, 22 | 'Access-Control-Allow-Origin':'*', 23 | 'Target-URL': 'https://dev-fkd10.pantheonsite.io/jsonapi/node/page/478ecbff-11fe-4532-854e-2b899720ba29' 24 | }) 25 | } 26 | 27 | getPage(blog: any) { 28 | let url = "http://localhost:4000"; 29 | return this.http.get(url, this.httpOptions); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/app/product-detail/product-detail.component.css: -------------------------------------------------------------------------------- 1 | .tab-container { 2 | padding: 2em 0; 3 | } 4 | 5 | .brand-panel { 6 | display: flex; 7 | } 8 | 9 | .brand-panel-img { 10 | flex: 0 0 10em; 11 | margin-right: 2em; 12 | 13 | display: flex; 14 | justify-content: center; 15 | align-items: center; 16 | } 17 | -------------------------------------------------------------------------------- /src/app/product-detail/product-detail.component.html: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | {{ product.fields.productName }} 5 | {{ product.fields.sizetypecolor }} 6 | Price: ${{ product.fields.price }} 7 | 8 | 9 | 10 | 11 |

12 | 13 |

{{ product.fields.image[0].fields.description }}

14 |
15 | 16 | 17 |
18 |

{{ product.fields.productDescription }}

19 |

SKU: {{ product.fields.sku || 'SKU not available' }}

20 |
21 |
22 | 23 |
24 |
25 |
26 | 27 |
28 |
29 |

{{ product.fields.brand.fields.companyName }}

30 |

{{ product.fields.brand.fields.companyDescription }}

31 |
32 |
33 |
34 |
35 | 36 | 37 | 38 |

39 | -------------------------------------------------------------------------------- /src/app/product-detail/product-detail.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProductDetailComponent } from './product-detail.component'; 4 | 5 | describe('ProductDetailComponent', () => { 6 | let component: ProductDetailComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ProductDetailComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ProductDetailComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/product-detail/product-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { combineLatest, switchMap } from 'rxjs/operators'; 2 | import { BehaviorSubject, Observable } from 'rxjs'; 3 | import { Component, OnInit } from '@angular/core'; 4 | import { ContentfulService } from '../contentful.service'; 5 | import { ActivatedRoute, ParamMap } from '@angular/router'; 6 | import { StackbitEvent, StackbitService } from '../stackbit.service'; 7 | import { Entry } from 'contentful'; 8 | 9 | @Component({ 10 | selector: 'app-product-detail', 11 | templateUrl: './product-detail.component.html', 12 | styleUrls: ['./product-detail.component.css'] 13 | }) 14 | export class ProductDetailComponent implements OnInit { 15 | product: Entry; 16 | 17 | constructor( 18 | private contentfulService: ContentfulService, 19 | private route: ActivatedRoute 20 | ) {} 21 | 22 | 23 | ngOnInit() { 24 | this.route.paramMap 25 | .pipe(switchMap((params: ParamMap) => this.contentfulService.getProduct(params.get('slug')))) 26 | .subscribe(value => value.then(product => this.product = product)); 27 | } 28 | } -------------------------------------------------------------------------------- /src/app/product-list/product-list.component.css: -------------------------------------------------------------------------------- 1 | .product-grid { 2 | display: flex; 3 | flex-wrap: wrap; 4 | margin: -0.5em; 5 | } 6 | 7 | .product-grid > * { 8 | flex: 1 0 5em; 9 | margin: 0.5em; 10 | 11 | min-width: 20em; 12 | max-width: 37.5em; 13 | } 14 | 15 | .product-card { 16 | height: 100%; 17 | display: flex; 18 | flex-direction: column; 19 | } 20 | 21 | .product-image { 22 | align-self: center; 23 | } 24 | -------------------------------------------------------------------------------- /src/app/product-list/product-list.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
  • 3 | 4 | 5 | {{ product.fields.productName }} 6 | Price: ${{ product.fields.price 7 | }} 8 | 9 | 11 | 12 | View details 13 | 14 | 15 |
  • 16 |
17 | 18 |
19 |

{{page.data.attributes.body.value}}

20 |
-------------------------------------------------------------------------------- /src/app/product-list/product-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProductListComponent } from './product-list.component'; 4 | 5 | describe('ProductListComponent', () => { 6 | let component: ProductListComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ProductListComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ProductListComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/product-list/product-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ContentfulService } from '../contentful.service'; 3 | import { DrupalService } from '../drupal.service' 4 | import { Entry } from 'contentful'; 5 | 6 | @Component({ 7 | selector: 'app-product-list', 8 | templateUrl: './product-list.component.html', 9 | styleUrls: ['./product-list.component.css'] 10 | }) 11 | export class ProductListComponent implements OnInit { 12 | products: Entry[]; 13 | page: any; 14 | 15 | constructor(private contentfulService: ContentfulService, private drupalService: DrupalService) { } 16 | 17 | ngOnInit() { 18 | let pageSubject = this.drupalService.getPage('blog'); 19 | let productsSubject = this.contentfulService.getProducts(); 20 | 21 | productsSubject.subscribe({ 22 | next: (value) => value.then(products => this.products = products) 23 | }); 24 | 25 | pageSubject.subscribe( 26 | (response) => { console.log(response); this.page = response; }, 27 | (error) => { console.log(error); }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/stackbit.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Subject } from 'rxjs'; 3 | 4 | export class StackbitEvent { 5 | changedContentTypes: string[]; 6 | changedObjectIds: []; 7 | currentPageObjectId: string; 8 | currentUrl: string; 9 | visibleObjectIds: []; 10 | } 11 | 12 | @Injectable() 13 | export class StackbitService { 14 | public contentChanged = new Subject(); 15 | 16 | constructor() { 17 | window.addEventListener('stackbitObjectsChanged', (event: any) => { 18 | this.contentChanged.next({ 19 | changedContentTypes: event.detail.changedContentTypes, 20 | changedObjectIds: event.detail.changedObjectIds, 21 | currentPageObjectId: event.detail.currentPageObjectId, 22 | currentUrl: event.detail.currentUrl, 23 | visibleObjectIds: event.detail.visibleObjectIds 24 | }); 25 | 26 | event.preventDefault(); 27 | }, { passive: false }); 28 | } 29 | } -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoenixdev0117/Contentful-Angular/58e5018fc427d9087b0a65e64ecf90ddab800e89/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phoenixdev0117/Contentful-Angular/58e5018fc427d9087b0a65e64ecf90ddab800e89/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CfAngularTutorial 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule); 12 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** Evergreen browsers require these. **/ 41 | import 'core-js/es6/reflect'; 42 | import 'core-js/es7/reflect'; 43 | 44 | 45 | /** 46 | * Required to support Web Animations `@angular/animation`. 47 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation 48 | **/ 49 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 50 | 51 | 52 | 53 | /*************************************************************************************************** 54 | * Zone JS is required by Angular itself. 55 | */ 56 | import 'zone.js/dist/zone'; // Included with Angular CLI. 57 | 58 | 59 | 60 | /*************************************************************************************************** 61 | * APPLICATION IMPORTS 62 | */ 63 | 64 | /** 65 | * Date, currency, decimal and percent pipes. 66 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 67 | */ 68 | // import 'intl'; // Run `npm install --save intl`. 69 | /** 70 | * Need to import at least one locale-data with intl. 71 | */ 72 | // import 'intl/locale-data/jsonp/en'; 73 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | @import "~@angular/material/prebuilt-themes/indigo-pink.css"; 2 | 3 | * { 4 | box-sizing: border-box; 5 | } 6 | 7 | a { 8 | color: inherit; 9 | } 10 | 11 | img { 12 | max-width: 100%; 13 | } 14 | 15 | body { 16 | margin: 0; 17 | padding: 0; 18 | } 19 | 20 | .u-listReset { 21 | margin: 0; 22 | padding: 0; 23 | list-style: none; 24 | } 25 | 26 | .u-listReset li { 27 | margin: 0; 28 | } 29 | 30 | .u-marginBottom { 31 | margin-bottom: 2em !important; 32 | } 33 | 34 | .u-marginTopAuto { 35 | margin-top: auto; 36 | } 37 | 38 | .u-marginAutoHorizontal { 39 | display: block; 40 | margin-left: auto; 41 | margin-right: auto; 42 | } 43 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 16 | declare const __karma__: any; 17 | declare const require: any; 18 | 19 | // Prevent Karma from running prematurely. 20 | __karma__.loaded = function () {}; 21 | 22 | // First, initialize the Angular testing environment. 23 | getTestBed().initTestEnvironment( 24 | BrowserDynamicTestingModule, 25 | platformBrowserDynamicTesting() 26 | ); 27 | // Then we find all the tests. 28 | const context = require.context('./', true, /\.spec\.ts$/); 29 | // And load the modules. 30 | context.keys().map(context); 31 | // Finally, start Karma to run the tests. 32 | __karma__.start(); 33 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "baseUrl": "./", 6 | "module": "es2015", 7 | "types": [] 8 | }, 9 | "exclude": [ 10 | "test.ts", 11 | "**/*.spec.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "target": "es2019", 8 | "types": [ 9 | "jasmine", 10 | "node" 11 | ] 12 | }, 13 | "files": [ 14 | "test.ts", 15 | "polyfills.ts" 16 | ], 17 | "include": [ 18 | "**/*.spec.ts", 19 | "**/*.d.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /stackbit.config.js: -------------------------------------------------------------------------------- 1 | import "drupal-content-connector/0.9810097649035752-index.js" 2 | import { defineStackbitConfig } from "@stackbit/types"; 3 | import { ContentfulContentSource } from '@stackbit/cms-contentful'; 4 | import DrupalContentSource from "drupal-content-connector/netlify-create.js"; 5 | 6 | const config = defineStackbitConfig({ 7 | stackbitVersion: "0.6.0", 8 | nodeVersion: "18", 9 | useESM: true, 10 | contentSources: [ 11 | new ContentfulContentSource({ 12 | spaceId: process.env.CONTENTFUL_SPACE_ID, 13 | environment: process.env.CONTENTFUL_ENVIRONMENT || 'master', 14 | previewToken: process.env.CONTENTFUL_PREVIEW_TOKEN, 15 | accessToken: process.env.CONTENTFUL_MANAGEMENT_TOKEN, 16 | }), 17 | new DrupalContentSource({ 18 | options: { 19 | instanceID: 'drupal-content-connector', 20 | siteUrl: process.env.DRUPAL_BASE_URL, 21 | pathPrefix: process.env.DRUPAL_API_BASE, 22 | basicAuthUsername: process.env.DRUPAL_BASIC_AUTH_USERNAME, 23 | basicAuthPassword: process.env.DRUPAL_BASIC_AUTH_PASSWORD, 24 | }, 25 | }), 26 | ], 27 | }); 28 | 29 | export default config; -------------------------------------------------------------------------------- /stackbit.yaml: -------------------------------------------------------------------------------- 1 | stackbitVersion: ~0.5.0 2 | ssgName: custom 3 | cmsName: contentful 4 | nodeVersion: '14' 5 | 6 | customContentReload: true 7 | 8 | buildCommand: npm run build 9 | devCommand: npm run start 10 | publishDir: dist 11 | 12 | modelsSource: 13 | type: contentful 14 | 15 | import: 16 | type: contentful 17 | contentFile: contentful/export.json 18 | uploadAssets: true 19 | assetsDirectory: contentful 20 | spaceIdEnvVar: CONTENTFUL_SPACE_ID 21 | deliveryTokenEnvVar: CONTENTFUL_DELIVERY_TOKEN 22 | previewTokenEnvVar: CONTENTFUL_PREVIEW_TOKEN 23 | 24 | __unsafe_internal_stackbitRunnerOptions: 25 | displayName: Angular 26 | triggerInstallFiles: 27 | - package.json 28 | - package-lock.json 29 | directPaths: 30 | - '/ng-cli-ws/**' 31 | patterns: 32 | errorState: 33 | - 'Failed to compile' 34 | doneStart: 35 | - 'Angular Live Development Server is listening' -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "target": "es2019", 11 | "typeRoots": [ 12 | "node_modules/@types" 13 | ], 14 | "lib": [ 15 | "es2019", 16 | "dom" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "eofline": true, 15 | "forin": true, 16 | "import-blacklist": [ 17 | true 18 | ], 19 | "import-spacing": true, 20 | "indent": [ 21 | true, 22 | "spaces" 23 | ], 24 | "interface-over-type-literal": true, 25 | "label-position": true, 26 | "max-line-length": [ 27 | true, 28 | 140 29 | ], 30 | "member-access": false, 31 | "member-ordering": [ 32 | true, 33 | { 34 | "order": [ 35 | "static-field", 36 | "instance-field", 37 | "static-method", 38 | "instance-method" 39 | ] 40 | } 41 | ], 42 | "no-arg": true, 43 | "no-bitwise": true, 44 | "no-console": [ 45 | true, 46 | "debug", 47 | "info", 48 | "time", 49 | "timeEnd", 50 | "trace" 51 | ], 52 | "no-construct": true, 53 | "no-debugger": true, 54 | "no-duplicate-super": true, 55 | "no-empty": false, 56 | "no-empty-interface": true, 57 | "no-eval": true, 58 | "no-inferrable-types": [ 59 | true, 60 | "ignore-params" 61 | ], 62 | "no-misused-new": true, 63 | "no-non-null-assertion": true, 64 | "no-shadowed-variable": true, 65 | "no-string-literal": false, 66 | "no-string-throw": true, 67 | "no-switch-case-fall-through": true, 68 | "no-trailing-whitespace": true, 69 | "no-unnecessary-initializer": true, 70 | "no-unused-expression": true, 71 | "no-use-before-declare": true, 72 | "no-var-keyword": true, 73 | "object-literal-sort-keys": false, 74 | "one-line": [ 75 | true, 76 | "check-open-brace", 77 | "check-catch", 78 | "check-else", 79 | "check-whitespace" 80 | ], 81 | "prefer-const": true, 82 | "quotemark": [ 83 | true, 84 | "single" 85 | ], 86 | "radix": true, 87 | "semicolon": [ 88 | true, 89 | "always" 90 | ], 91 | "triple-equals": [ 92 | true, 93 | "allow-null-check" 94 | ], 95 | "typedef-whitespace": [ 96 | true, 97 | { 98 | "call-signature": "nospace", 99 | "index-signature": "nospace", 100 | "parameter": "nospace", 101 | "property-declaration": "nospace", 102 | "variable-declaration": "nospace" 103 | } 104 | ], 105 | "typeof-compare": true, 106 | "unified-signatures": true, 107 | "variable-name": false, 108 | "whitespace": [ 109 | true, 110 | "check-branch", 111 | "check-decl", 112 | "check-operator", 113 | "check-separator", 114 | "check-type" 115 | ], 116 | "directive-selector": [ 117 | true, 118 | "attribute", 119 | "app", 120 | "camelCase" 121 | ], 122 | "component-selector": [ 123 | true, 124 | "element", 125 | "app", 126 | "kebab-case" 127 | ], 128 | "use-input-property-decorator": true, 129 | "use-output-property-decorator": true, 130 | "use-host-property-decorator": true, 131 | "no-input-rename": true, 132 | "no-output-rename": true, 133 | "use-life-cycle-interface": true, 134 | "use-pipe-transform-interface": true, 135 | "component-class-suffix": true, 136 | "directive-class-suffix": true, 137 | "no-access-missing-member": true, 138 | "templates-use-public": true, 139 | "invoke-injectable": true 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /tutorial.md: -------------------------------------------------------------------------------- 1 | ng new cf-angular-tutorial 2 | 3 | ng g component product-list 4 | ng g component product-detail 5 | --------------------------------------------------------------------------------