├── .babelrc ├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── bin └── publish.sh ├── config.js ├── config └── analytics.json ├── contentful_logo_120x90@2x.png ├── index.html ├── npm-shrinkwrap.json ├── package.json ├── screenshot.png ├── src ├── actions │ └── actionCreators.js ├── components │ ├── App.css │ ├── App.js │ ├── Author.css │ ├── Author.js │ ├── Date.css │ ├── Date.js │ ├── Gallery.css │ ├── Gallery.js │ ├── GalleryList.js │ ├── GalleryThumb.css │ ├── GalleryThumb.js │ ├── Location.css │ ├── Location.js │ ├── Main.js │ ├── NoMatch.js │ ├── ResponsiveImage.css │ └── ResponsiveImage.js ├── main.js ├── reducers │ ├── app.js │ ├── galleries.js │ ├── index.js │ └── util.js ├── services │ ├── contentfulClient.js │ └── galleryStore.js ├── store.js └── styles │ ├── _base.scss │ ├── _util.scss │ ├── main.scss │ ├── objects │ ├── _btn.scss │ ├── _container.scss │ ├── _keyframes.scss │ ├── _list.scss │ ├── _tag.scss │ └── _warning.scss │ └── plugins │ ├── _reactImageGallery.scss │ └── _reactModal.scss └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["standard", "plugin:react/recommended"], 3 | "plugins": [ 4 | "react" 5 | ], 6 | "rules": { 7 | "no-return-assign": "off" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # publish 40 | dist 41 | gh-pages -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Contentful 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gallery 2 | 3 | This is a React application example for the [Contentful][1] gallery space template. 4 | 5 | ![The gallery App](./screenshot.png) 6 | 7 | 8 | [Contentful](https://www.contentful.com) provides a content infrastructure for digital teams to power content in websites, apps, and devices. Unlike a CMS, Contentful was built to integrate with the modern software stack. It offers a central hub for structured content, powerful management and delivery APIs, and a customizable web app that enable developers and content creators to ship digital products faster. 9 | 10 | ## Usage 11 | 12 | - create a space with the "Gallery" space template on [Contentful][1] 13 | - clone this repo and run `npm i` 14 | - edit `deliveryAccessToken`, `spaceId` and `galleryTypeId` (id of the content type `Photo Gallery`) in `config.js` included in the project root 15 | - run `npm start` 16 | - open `localhost:9020/` to see it in action 17 | 18 | ## License 19 | 20 | Copyright (c) 2016 Contentful GmbH. See LICENSE for further details. 21 | 22 | [1]: https://www.contentful.com 23 | -------------------------------------------------------------------------------- /bin/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | PAGES_DIR=./gh-pages 5 | REPO="git@github.com:contentful/gallery-app-react.git" 6 | 7 | echo "Publishing" 8 | 9 | # get the gh-pages branch of the repo 10 | if [ ! -d $PAGES_DIR ] ; then 11 | git clone --single-branch --branch gh-pages $REPO $PAGES_DIR 12 | fi 13 | 14 | cp *.png dist/*.js $PAGES_DIR 15 | 16 | # Setup base path and analytics tag 17 | cat index.html | \ 18 | sed -e "s//{{{ANALYTICS}}}/g' > \ 20 | $PAGES_DIR/index.mustache 21 | 22 | ./node_modules/.bin/mustache ./config/analytics.json $PAGES_DIR/index.mustache > $PAGES_DIR/index.html 23 | rm -f $PAGES_DIR/index.mustache 24 | 25 | cp $PAGES_DIR/index.html $PAGES_DIR/404.html 26 | 27 | pushd $PAGES_DIR 28 | git add . 29 | git commit -a -m "Docs update" 30 | if [ $? -eq 1 ] ; then 31 | echo "Nothing to update" 32 | else 33 | git push origin gh-pages 34 | fi 35 | popd 36 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | export const deliveryAccessToken = 'fa8bac8fe7a47402e1412b831e49ddeb9b41e7a1cd32ab742dbf88804a6da697'; 2 | export const spaceId = '3qjjkhhegtjs'; 3 | 4 | export const galleryTypeId = 'photoGallery'; 5 | -------------------------------------------------------------------------------- /config/analytics.json: -------------------------------------------------------------------------------- 1 | { 2 | "ANALYTICS": " " 3 | } 4 | -------------------------------------------------------------------------------- /contentful_logo_120x90@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/gallery-app-react/0686f01a2d7e911b733ffb7bc4dce398ad876fd8/contentful_logo_120x90@2x.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Contentful Gallery App 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /npm-shrinkwrap.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cf-gallery-app-react", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "asap": { 6 | "version": "2.0.4", 7 | "from": "asap@>=2.0.3 <2.1.0", 8 | "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.4.tgz" 9 | }, 10 | "babel-polyfill": { 11 | "version": "6.13.0", 12 | "from": "babel-polyfill@>=6.13.0 <7.0.0", 13 | "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.13.0.tgz" 14 | }, 15 | "babel-runtime": { 16 | "version": "6.11.6", 17 | "from": "babel-runtime@>=6.9.1 <7.0.0", 18 | "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.11.6.tgz" 19 | }, 20 | "contentful": { 21 | "version": "3.5.0", 22 | "from": "contentful@>=3.5.0 <4.0.0", 23 | "resolved": "https://registry.npmjs.org/contentful/-/contentful-3.5.0.tgz" 24 | }, 25 | "contentful-sdk-core": { 26 | "version": "2.3.4", 27 | "from": "contentful-sdk-core@>=2.3.0 <3.0.0", 28 | "resolved": "https://registry.npmjs.org/contentful-sdk-core/-/contentful-sdk-core-2.3.4.tgz" 29 | }, 30 | "core-js": { 31 | "version": "2.4.1", 32 | "from": "core-js@>=2.4.0 <3.0.0", 33 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz" 34 | }, 35 | "debug": { 36 | "version": "2.2.0", 37 | "from": "debug@>=2.2.0 <3.0.0", 38 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz" 39 | }, 40 | "deep-equal": { 41 | "version": "1.0.1", 42 | "from": "deep-equal@>=1.0.0 <2.0.0", 43 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz" 44 | }, 45 | "deep-extend": { 46 | "version": "0.4.1", 47 | "from": "deep-extend@>=0.4.1 <0.5.0", 48 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz" 49 | }, 50 | "element-class": { 51 | "version": "0.2.2", 52 | "from": "element-class@>=0.2.0 <0.3.0", 53 | "resolved": "https://registry.npmjs.org/element-class/-/element-class-0.2.2.tgz" 54 | }, 55 | "encoding": { 56 | "version": "0.1.12", 57 | "from": "encoding@>=0.1.11 <0.2.0", 58 | "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz" 59 | }, 60 | "exenv": { 61 | "version": "1.2.0", 62 | "from": "exenv@1.2.0", 63 | "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.0.tgz" 64 | }, 65 | "fbjs": { 66 | "version": "0.8.3", 67 | "from": "fbjs@>=0.8.1 <0.9.0", 68 | "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.3.tgz", 69 | "dependencies": { 70 | "core-js": { 71 | "version": "1.2.7", 72 | "from": "core-js@>=1.0.0 <2.0.0", 73 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz" 74 | } 75 | } 76 | }, 77 | "follow-redirects": { 78 | "version": "0.0.7", 79 | "from": "follow-redirects@0.0.7", 80 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-0.0.7.tgz" 81 | }, 82 | "history": { 83 | "version": "2.1.2", 84 | "from": "history@>=2.0.0 <3.0.0", 85 | "resolved": "https://registry.npmjs.org/history/-/history-2.1.2.tgz" 86 | }, 87 | "hoist-non-react-statics": { 88 | "version": "1.2.0", 89 | "from": "hoist-non-react-statics@>=1.0.5 <2.0.0", 90 | "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz" 91 | }, 92 | "iconv-lite": { 93 | "version": "0.4.13", 94 | "from": "iconv-lite@>=0.4.13 <0.5.0", 95 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz" 96 | }, 97 | "immutable": { 98 | "version": "3.8.1", 99 | "from": "immutable@>=3.7.6 <4.0.0", 100 | "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.1.tgz" 101 | }, 102 | "invariant": { 103 | "version": "2.2.1", 104 | "from": "invariant@>=2.0.0 <3.0.0", 105 | "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.1.tgz" 106 | }, 107 | "is-stream": { 108 | "version": "1.1.0", 109 | "from": "is-stream@>=1.0.1 <2.0.0", 110 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz" 111 | }, 112 | "isomorphic-fetch": { 113 | "version": "2.2.1", 114 | "from": "isomorphic-fetch@>=2.1.1 <3.0.0", 115 | "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz" 116 | }, 117 | "js-tokens": { 118 | "version": "1.0.3", 119 | "from": "js-tokens@>=1.0.1 <2.0.0", 120 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-1.0.3.tgz" 121 | }, 122 | "json-stringify-safe": { 123 | "version": "5.0.1", 124 | "from": "json-stringify-safe@>=5.0.1 <6.0.0", 125 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" 126 | }, 127 | "lodash": { 128 | "version": "4.15.0", 129 | "from": "lodash@>=4.2.0 <5.0.0", 130 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.15.0.tgz" 131 | }, 132 | "lodash-es": { 133 | "version": "4.15.0", 134 | "from": "lodash-es@>=4.2.1 <5.0.0", 135 | "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.15.0.tgz" 136 | }, 137 | "lodash._baseassign": { 138 | "version": "3.2.0", 139 | "from": "lodash._baseassign@>=3.0.0 <4.0.0", 140 | "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz" 141 | }, 142 | "lodash._basecopy": { 143 | "version": "3.0.1", 144 | "from": "lodash._basecopy@>=3.0.0 <4.0.0", 145 | "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz" 146 | }, 147 | "lodash._bindcallback": { 148 | "version": "3.0.1", 149 | "from": "lodash._bindcallback@>=3.0.0 <4.0.0", 150 | "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz" 151 | }, 152 | "lodash._createassigner": { 153 | "version": "3.1.1", 154 | "from": "lodash._createassigner@>=3.0.0 <4.0.0", 155 | "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz" 156 | }, 157 | "lodash._getnative": { 158 | "version": "3.9.1", 159 | "from": "lodash._getnative@>=3.0.0 <4.0.0", 160 | "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz" 161 | }, 162 | "lodash._isiterateecall": { 163 | "version": "3.0.9", 164 | "from": "lodash._isiterateecall@>=3.0.0 <4.0.0", 165 | "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz" 166 | }, 167 | "lodash.assign": { 168 | "version": "3.2.0", 169 | "from": "lodash.assign@>=3.2.0 <4.0.0", 170 | "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz" 171 | }, 172 | "lodash.isarguments": { 173 | "version": "3.1.0", 174 | "from": "lodash.isarguments@>=3.0.0 <4.0.0", 175 | "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz" 176 | }, 177 | "lodash.isarray": { 178 | "version": "3.0.4", 179 | "from": "lodash.isarray@>=3.0.0 <4.0.0", 180 | "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz" 181 | }, 182 | "lodash.keys": { 183 | "version": "3.1.2", 184 | "from": "lodash.keys@>=3.0.0 <4.0.0", 185 | "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz" 186 | }, 187 | "lodash.restparam": { 188 | "version": "3.6.1", 189 | "from": "lodash.restparam@>=3.0.0 <4.0.0", 190 | "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz" 191 | }, 192 | "loose-envify": { 193 | "version": "1.2.0", 194 | "from": "loose-envify@>=1.0.0 <2.0.0", 195 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.2.0.tgz" 196 | }, 197 | "ms": { 198 | "version": "0.7.1", 199 | "from": "ms@0.7.1", 200 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" 201 | }, 202 | "node-fetch": { 203 | "version": "1.6.0", 204 | "from": "node-fetch@>=1.0.1 <2.0.0", 205 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.6.0.tgz" 206 | }, 207 | "object-assign": { 208 | "version": "4.1.0", 209 | "from": "object-assign@>=4.1.0 <5.0.0", 210 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz" 211 | }, 212 | "object-unfreeze": { 213 | "version": "1.1.0", 214 | "from": "object-unfreeze@>=1.0.2 <2.0.0", 215 | "resolved": "https://registry.npmjs.org/object-unfreeze/-/object-unfreeze-1.1.0.tgz" 216 | }, 217 | "promise": { 218 | "version": "7.1.1", 219 | "from": "promise@>=7.1.1 <8.0.0", 220 | "resolved": "https://registry.npmjs.org/promise/-/promise-7.1.1.tgz" 221 | }, 222 | "qs": { 223 | "version": "6.2.1", 224 | "from": "qs@>=6.1.0 <7.0.0", 225 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.1.tgz" 226 | }, 227 | "query-string": { 228 | "version": "3.0.3", 229 | "from": "query-string@>=3.0.0 <4.0.0", 230 | "resolved": "https://registry.npmjs.org/query-string/-/query-string-3.0.3.tgz" 231 | }, 232 | "react": { 233 | "version": "15.3.0", 234 | "from": "react@>=15.3.0 <16.0.0", 235 | "resolved": "https://registry.npmjs.org/react/-/react-15.3.0.tgz" 236 | }, 237 | "react-css-modules": { 238 | "version": "3.7.10", 239 | "from": "react-css-modules@>=3.7.9 <4.0.0", 240 | "resolved": "https://registry.npmjs.org/react-css-modules/-/react-css-modules-3.7.10.tgz" 241 | }, 242 | "react-dom": { 243 | "version": "15.3.0", 244 | "from": "react-dom@>=15.3.0 <16.0.0", 245 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.3.0.tgz" 246 | }, 247 | "react-image-gallery": { 248 | "version": "0.6.7", 249 | "from": "react-image-gallery@>=0.6.7 <0.7.0", 250 | "resolved": "https://registry.npmjs.org/react-image-gallery/-/react-image-gallery-0.6.7.tgz" 251 | }, 252 | "react-modal": { 253 | "version": "1.4.0", 254 | "from": "react-modal@>=1.4.0 <2.0.0", 255 | "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-1.4.0.tgz" 256 | }, 257 | "react-redux": { 258 | "version": "4.4.5", 259 | "from": "react-redux@>=4.4.5 <5.0.0", 260 | "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-4.4.5.tgz" 261 | }, 262 | "react-router": { 263 | "version": "2.6.1", 264 | "from": "react-router@>=2.6.1 <3.0.0", 265 | "resolved": "https://registry.npmjs.org/react-router/-/react-router-2.6.1.tgz", 266 | "dependencies": { 267 | "warning": { 268 | "version": "3.0.0", 269 | "from": "warning@>=3.0.0 <4.0.0", 270 | "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz" 271 | } 272 | } 273 | }, 274 | "react-swipeable": { 275 | "version": "3.5.1", 276 | "from": "react-swipeable@>=3.5.1 <4.0.0", 277 | "resolved": "https://registry.npmjs.org/react-swipeable/-/react-swipeable-3.5.1.tgz" 278 | }, 279 | "redux": { 280 | "version": "3.5.2", 281 | "from": "redux@>=3.5.2 <4.0.0", 282 | "resolved": "https://registry.npmjs.org/redux/-/redux-3.5.2.tgz" 283 | }, 284 | "redux-logger": { 285 | "version": "2.6.1", 286 | "from": "redux-logger@>=2.6.1 <3.0.0", 287 | "resolved": "https://registry.npmjs.org/redux-logger/-/redux-logger-2.6.1.tgz" 288 | }, 289 | "redux-promise-middleware": { 290 | "version": "3.3.2", 291 | "from": "redux-promise-middleware@>=3.3.2 <4.0.0", 292 | "resolved": "https://registry.npmjs.org/redux-promise-middleware/-/redux-promise-middleware-3.3.2.tgz" 293 | }, 294 | "redux-thunk": { 295 | "version": "2.1.0", 296 | "from": "redux-thunk@>=2.1.0 <3.0.0", 297 | "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.1.0.tgz" 298 | }, 299 | "regenerator-runtime": { 300 | "version": "0.9.5", 301 | "from": "regenerator-runtime@>=0.9.5 <0.10.0", 302 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.9.5.tgz" 303 | }, 304 | "stream-consume": { 305 | "version": "0.1.0", 306 | "from": "stream-consume@>=0.1.0 <0.2.0", 307 | "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.0.tgz" 308 | }, 309 | "strict-uri-encode": { 310 | "version": "1.1.0", 311 | "from": "strict-uri-encode@>=1.0.0 <2.0.0", 312 | "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz" 313 | }, 314 | "symbol-observable": { 315 | "version": "0.2.4", 316 | "from": "symbol-observable@>=0.2.3 <0.3.0", 317 | "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-0.2.4.tgz" 318 | }, 319 | "ua-parser-js": { 320 | "version": "0.7.10", 321 | "from": "ua-parser-js@>=0.7.9 <0.8.0", 322 | "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.10.tgz" 323 | }, 324 | "warning": { 325 | "version": "2.1.0", 326 | "from": "warning@>=2.0.0 <3.0.0", 327 | "resolved": "https://registry.npmjs.org/warning/-/warning-2.1.0.tgz" 328 | }, 329 | "whatwg-fetch": { 330 | "version": "1.0.0", 331 | "from": "whatwg-fetch@>=0.10.0", 332 | "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-1.0.0.tgz" 333 | } 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cf-gallery-app-react", 3 | "version": "1.0.0", 4 | "description": "A React application example for the gallery space template. ", 5 | "main": "index.js", 6 | "dependencies": { 7 | "babel-polyfill": "^6.13.0", 8 | "contentful": "^3.5.0", 9 | "deep-extend": "^0.4.1", 10 | "history": "^2.0.0", 11 | "react": "^15.3.0", 12 | "react-css-modules": "^3.7.9", 13 | "react-dom": "^15.3.0", 14 | "react-image-gallery": "^0.6.7", 15 | "react-modal": "^1.4.0", 16 | "react-redux": "^4.4.5", 17 | "react-router": "^2.6.1", 18 | "redux": "^3.5.2", 19 | "redux-logger": "^2.6.1", 20 | "redux-promise-middleware": "^3.3.2", 21 | "redux-thunk": "^2.1.0" 22 | }, 23 | "devDependencies": { 24 | "autoprefixer": "^6.4.0", 25 | "babel-eslint": "^6.1.2", 26 | "babel-loader": "^6.2.4", 27 | "babel-plugin-transform-object-assign": "^6.8.0", 28 | "babel-preset-es2015": "^6.9.0", 29 | "babel-preset-react": "^6.11.1", 30 | "css-loader": "^0.23.1", 31 | "eslint": "^3.2.2", 32 | "eslint-config-standard": "^5.3.5", 33 | "eslint-plugin-promise": "^2.0.1", 34 | "eslint-plugin-react": "^6.0.0", 35 | "eslint-plugin-standard": "^2.0.0", 36 | "mustache": "^2.2.1", 37 | "node-sass": "^3.8.0", 38 | "postcss-loader": "^0.9.1", 39 | "rimraf": "^2.5.4", 40 | "sass-loader": "^4.0.0", 41 | "style-loader": "^0.13.1", 42 | "webpack": "^1.13.1", 43 | "webpack-dev-server": "^1.14.1" 44 | }, 45 | "scripts": { 46 | "build": "npm run clean && NODE_ENV=production webpack -p", 47 | "clean": "rimraf dist && rimraf gh-pages", 48 | "lint": "eslint \"src/**/*.js\"", 49 | "start": "NODE_ENV=development webpack-dev-server --host=0.0.0.0 --port=9020 --inline --hot", 50 | "test": "npm run lint", 51 | "publish": "npm run build && ./bin/publish.sh" 52 | }, 53 | "repository": "contentful/gallery-app-react", 54 | "license": "MIT", 55 | "bugs": { 56 | "url": "https://github.com/contentful/gallery-app-react/issues" 57 | }, 58 | "homepage": "https://github.com/contentful/gallery-app-react#readme" 59 | } 60 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/gallery-app-react/0686f01a2d7e911b733ffb7bc4dce398ad876fd8/screenshot.png -------------------------------------------------------------------------------- /src/actions/actionCreators.js: -------------------------------------------------------------------------------- 1 | import * as galleryService from '../services/galleryStore' 2 | 3 | export function setAppClientState (authState) { 4 | return { 5 | type: 'LOADED_CLIENT', 6 | authState 7 | } 8 | } 9 | 10 | export function loadGalleries ({contentTypeId}) { 11 | return { 12 | type: 'LOAD_GALLERIES', 13 | payload: galleryService.loadGalleries(contentTypeId) 14 | } 15 | } 16 | 17 | export function loadGallery (id) { 18 | return { 19 | type: 'LOAD_GALLERY', 20 | payload: galleryService.loadGallery(id), 21 | meta: { 22 | id 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/App.css: -------------------------------------------------------------------------------- 1 | .c-nav { 2 | position : relative; 3 | 4 | padding : 2em 0; 5 | } 6 | .c-nav__cfImage { 7 | display: block; 8 | 9 | width: 2.5em; 10 | } 11 | 12 | .c-nav__logo { 13 | display: flex; 14 | 15 | justify-content: center; 16 | align-items: center; 17 | 18 | font-size: 1.5em; 19 | letter-spacing: 1px; 20 | } 21 | 22 | .c-nav__spinner { 23 | position: absolute; 24 | 25 | top: 50%; 26 | right: 1em; 27 | 28 | transform: translate(0, -50%); 29 | } 30 | -------------------------------------------------------------------------------- /src/components/App.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import { Link } from 'react-router' 3 | import CSSModules from 'react-css-modules' 4 | import styles from './App.css' 5 | import { connectComponent } from '../store' 6 | import { initClient } from '../services/contentfulClient' 7 | 8 | /*eslint-disable no-unused-vars*/ 9 | import Main from '../styles/main.scss' 10 | 11 | class App extends React.Component { 12 | componentWillMount () { 13 | const { spaceId, deliveryAccessToken } = this.props.app 14 | 15 | initClient(spaceId, deliveryAccessToken) 16 | .then( 17 | () => this.props.setAppClientState('success'), 18 | () => this.props.setAppClientState('error') 19 | ) 20 | } 21 | 22 | render () { 23 | return ( 24 |
25 | 47 | { (() => { 48 | if (this.props.app.authState === 'success') { 49 | return ( 50 |
51 | {this.props.children} 52 |
53 | ) 54 | } 55 | 56 | if (this.props.app.authState === 'error') { 57 | return ( 58 |
59 |

The connection to contenful could not be established.

60 |

Please check your delivery access token and space id

61 |
62 | ) 63 | } 64 | })() } 65 |
66 | ) 67 | } 68 | } 69 | App.contextTypes = { 70 | router: PropTypes.object.isRequired 71 | } 72 | 73 | App.propTypes = { 74 | app: PropTypes.object, 75 | children: PropTypes.object, 76 | setAppClientState: PropTypes.func 77 | } 78 | 79 | export default connectComponent(CSSModules(App, styles)) 80 | -------------------------------------------------------------------------------- /src/components/Author.css: -------------------------------------------------------------------------------- 1 | .c-author { 2 | text-align: center; 3 | padding: .5em 0; 4 | } 5 | 6 | .c-author::before, .c-author::after { 7 | content: '—'; 8 | 9 | display : inline; 10 | } 11 | 12 | .c-author::before { 13 | margin-right: 1em; 14 | } 15 | 16 | .c-author::after { 17 | margin-left: 1em; 18 | } 19 | -------------------------------------------------------------------------------- /src/components/Author.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import CSSModules from 'react-css-modules' 3 | import styles from './Author.css' 4 | 5 | function Author ({ author }) { 6 | return ( 7 |
8 | { author.fields.name } 9 |
10 | ) 11 | } 12 | 13 | Author.propTypes = { author: PropTypes.object } 14 | 15 | export default CSSModules(Author, styles) 16 | -------------------------------------------------------------------------------- /src/components/Date.css: -------------------------------------------------------------------------------- 1 | .c-date { 2 | padding : 1em .5em; 3 | 4 | display: flex; 5 | 6 | justify-content: center; 7 | align-items: center; 8 | } 9 | 10 | .c-date svg { 11 | margin-right: .5em; 12 | } 13 | -------------------------------------------------------------------------------- /src/components/Date.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import CSSModules from 'react-css-modules' 3 | import styles from './Date.css' 4 | 5 | function Date ({ date }) { 6 | if (date) { 7 | return ( 8 |
9 | 10 | 11 | 12 | 13 | { date } 14 |
15 | ) 16 | } 17 | } 18 | 19 | Date.propTypes = { date: PropTypes.string } 20 | 21 | export default CSSModules(Date, styles) 22 | -------------------------------------------------------------------------------- /src/components/Gallery.css: -------------------------------------------------------------------------------- 1 | .c-gallery__close { 2 | position: absolute; 3 | 4 | right: 1em; 5 | top: 1.75em; 6 | } 7 | 8 | .c-gallery__header { 9 | position: relative; 10 | padding : 1em 4em; 11 | 12 | text-align: center; 13 | } 14 | 15 | .c-gallery__headline { 16 | font-weight: normal; 17 | text-align: center; 18 | } 19 | 20 | .c-gallery__modalClose { 21 | position: absolute; 22 | 23 | top: -1.5em; 24 | right: .5em; 25 | 26 | z-index: 100; 27 | 28 | } 29 | 30 | @media (orientation:landscape) { 31 | .c-gallery__modalClose { 32 | top: 3em; 33 | } 34 | } 35 | 36 | @media (min-width:45em) { 37 | .c-gallery__modalClose { 38 | top: 0; 39 | right: -1em; 40 | } 41 | } 42 | 43 | 44 | .c-gallery__modalOpenTitle { 45 | font-size: 1.25em; 46 | 47 | position: absolute; 48 | 49 | bottom: 1em; 50 | left: 0; 51 | z-index: 0; 52 | 53 | padding: .5em; 54 | color: #fff; 55 | 56 | background: rgba(0,0,0,0.5); 57 | } 58 | 59 | .c-gallery__modalOpenLink { 60 | display: block; 61 | position: relative; 62 | 63 | overflow: hidden; 64 | 65 | height: 100%; 66 | 67 | } 68 | 69 | .c-gallery__modalOpenLink a { 70 | display: flex; 71 | justify-content: center; 72 | align-items: center; 73 | 74 | height: 100%; 75 | } 76 | 77 | .c-gallery__modalOpenLink img { 78 | // work around transform and scale rendering issues 79 | margin: -1px; 80 | transition: transform 0.25s ease-in-out; 81 | } 82 | 83 | .c-gallery__modalOpenLink:hover img { 84 | transform: scale(1.03125); 85 | } 86 | -------------------------------------------------------------------------------- /src/components/Gallery.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import CSSModules from 'react-css-modules' 3 | import styles from './Gallery.css' 4 | import ResponsiveImage from './ResponsiveImage' 5 | import { Link, browserHistory } from 'react-router' 6 | import Modal from 'react-modal' 7 | import Author from './Author' 8 | import Date from './Date' 9 | import Location from './Location' 10 | import ImageGallery from 'react-image-gallery' 11 | import { connectComponent } from '../store' 12 | 13 | const customStyles = { 14 | content: { 15 | position: 'static', 16 | left: 'auto', 17 | padding: 0, 18 | 19 | border: 'none', 20 | borderRadius: 0, 21 | overflow: 'visible', 22 | 23 | boxShadow: '0 .5em 3em #ddd' 24 | } 25 | } 26 | 27 | class Gallery extends React.Component { 28 | componentDidMount () { 29 | this.props.loadGallery(this.props.params.galleryId) 30 | } 31 | 32 | closeModal () { 33 | browserHistory.goBack() 34 | } 35 | 36 | getIndexOfImage (imageCollection, id) { 37 | let foundIndex = -1 38 | 39 | imageCollection.some((item, index) => { 40 | if (item.fields.photo.sys.id === id) { 41 | foundIndex = index 42 | 43 | return true 44 | } 45 | }) 46 | 47 | return foundIndex 48 | } 49 | 50 | componentWillReceiveProps () { 51 | let gallery = this.props.galleries.entries[ this.props.params.galleryId ] 52 | 53 | if (gallery && gallery.error) { 54 | browserHistory.push('/not-found') 55 | } 56 | } 57 | 58 | renderImageEntry (entry) { 59 | return ( 60 |
61 | 65 | 66 |
67 | { 68 | entry.fields.title && 69 |
70 | {entry.fields.title} 71 |
72 | } 73 | { 74 | entry.fields.imageCaption && 75 |
76 | {entry.fields.imageCaption} 77 |
78 | } 79 | { 80 | entry.fields.imageCredits && 81 |
82 | {entry.fields.imageCredits} 83 |
84 | } 85 |
86 |
87 | ) 88 | } 89 | 90 | render () { 91 | const gallery = this.props.galleries.entries[ this.props.params.galleryId ] 92 | 93 | if (gallery && gallery.fields) { 94 | return ( 95 |
96 |
97 |

{ gallery.fields.title }

98 | 99 | ✕ 100 | 101 | 102 | 103 | { this.renderTags(gallery) } 104 | 105 |
106 | 107 | 108 |
109 |
110 | 111 | 127 | 131 | 132 | 133 | this._imageGallery = i} 135 | items={ gallery.fields.images } 136 | slideInterval={2000} 137 | startIndex={ this.props.params.imageId ? this.getIndexOfImage(gallery.fields.images, this.props.params.imageId) : -1} 138 | onImageLoad={this.handleImageLoad} 139 | renderItem={this.renderImageEntry}/> 140 | 141 |
142 | ) 143 | } 144 | } 145 | 146 | renderTags (gallery) { 147 | if (gallery.fields.tags) { 148 | return ( 149 | 156 | ) 157 | } 158 | } 159 | } 160 | 161 | Gallery.propTypes = { 162 | app: PropTypes.object, 163 | galleries: PropTypes.object, 164 | loadGallery: PropTypes.func, 165 | loadGalleries: PropTypes.func, 166 | params: PropTypes.object 167 | } 168 | 169 | export default connectComponent(CSSModules(Gallery, styles)) 170 | -------------------------------------------------------------------------------- /src/components/GalleryList.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import GalleryThumb from './GalleryThumb' 3 | import { connectComponent } from '../store' 4 | 5 | class GalleryList extends React.Component { 6 | componentWillMount () { 7 | const { galleryTypeId } = this.props.app 8 | 9 | this.props.loadGalleries({contentTypeId: galleryTypeId}) 10 | } 11 | 12 | render () { 13 | return ( 14 |
15 | 26 | 27 | { this.maybeRenderWarning() } 28 |
29 | ) 30 | } 31 | 32 | maybeRenderWarning () { 33 | if (this.props.galleries.error) { 34 | return ( 35 |
36 |

The gallery content type you specified does not exist.

37 |

Please check your gallery content type token

38 |
39 | ) 40 | } 41 | } 42 | } 43 | 44 | GalleryList.propTypes = { 45 | app: PropTypes.object, 46 | galleries: PropTypes.object, 47 | loadGalleries: PropTypes.func 48 | } 49 | 50 | export default connectComponent(GalleryList) 51 | -------------------------------------------------------------------------------- /src/components/GalleryThumb.css: -------------------------------------------------------------------------------- 1 | .c-galleryThumb { 2 | display: flex; 3 | width: 100%; 4 | flex-direction: column; 5 | 6 | box-shadow : 0 .5em 3em #ddd; 7 | background: #fff;; 8 | 9 | text-align : center; 10 | } 11 | .c-galleryThumb__caption { 12 | padding : 1em; 13 | } 14 | 15 | .c-galleryThumb__figure { 16 | overflow: hidden; 17 | } 18 | 19 | .c-galleryThumb__figure:hover img { 20 | transform : scale(1.03125) translate(-50%, -50%); 21 | } 22 | 23 | .c-galleryThumb__imageContainer { 24 | display: block; 25 | position: relative; 26 | 27 | overflow: hidden; 28 | 29 | height: 0; 30 | 31 | padding : 0 0 65%; 32 | } 33 | 34 | .c-galleryThumb__imageContainer img { 35 | display: block; 36 | position: absolute; 37 | 38 | top: 50%; 39 | left: 50%; 40 | 41 | transform: translate(-50%, -50%); 42 | transition : transform .25s ease-in-out; 43 | } 44 | 45 | .c-galleryThumb__title { 46 | font-size : 1.25em; 47 | 48 | padding : 1em 0 .5em; 49 | } 50 | -------------------------------------------------------------------------------- /src/components/GalleryThumb.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import { Link } from 'react-router' 3 | import CSSModules from 'react-css-modules' 4 | import styles from './GalleryThumb.css' 5 | import Author from './Author' 6 | import Date from './Date' 7 | import Location from './Location' 8 | import ResponsiveImage from './ResponsiveImage' 9 | 10 | function GalleryThumb ({ gallery }) { 11 | return ( 12 |
13 |
14 | 15 | 16 | 17 | 18 |
19 |
{ gallery.fields.title }
20 | 21 |
22 | 23 |
24 | 25 | { renderTags(gallery) } 26 | 27 |
28 | 29 | 30 |
31 | 32 |
33 |
34 |
35 | View gallery 36 |
37 |
38 | ) 39 | } 40 | 41 | function renderTags (gallery) { 42 | if (gallery.fields.tags) { 43 | return ( 44 | 51 | ) 52 | } 53 | } 54 | 55 | GalleryThumb.propTypes = { gallery: PropTypes.object } 56 | 57 | export default CSSModules(GalleryThumb, styles) 58 | -------------------------------------------------------------------------------- /src/components/Location.css: -------------------------------------------------------------------------------- 1 | .c-location { 2 | padding : 1em .5em; 3 | 4 | display: flex; 5 | 6 | justify-content: center; 7 | align-items: center; 8 | } 9 | 10 | .c-location svg { 11 | margin-right: .5em; 12 | } 13 | -------------------------------------------------------------------------------- /src/components/Location.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import CSSModules from 'react-css-modules' 3 | import styles from './Location.css' 4 | 5 | function Location ({ location }) { 6 | if (location) { 7 | return ( 8 |
9 | 10 | 11 | 12 | 13 | { location.lat.toFixed(3) } { location.lon.toFixed(3) } 14 |
15 | ) 16 | } 17 | } 18 | 19 | Location.propTypes = { location: PropTypes.object } 20 | 21 | export default CSSModules(Location, styles) 22 | -------------------------------------------------------------------------------- /src/components/Main.js: -------------------------------------------------------------------------------- 1 | import App from './App' 2 | import { connectComponent } from '../store' 3 | 4 | const Main = connectComponent(App) 5 | 6 | export default Main 7 | -------------------------------------------------------------------------------- /src/components/NoMatch.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function NoMatch () { 4 | return ( 5 |
6 |

Can not find the site you are looking for.

7 |
8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /src/components/ResponsiveImage.css: -------------------------------------------------------------------------------- 1 | .responsiveImage { 2 | display: block; 3 | width: 100%; 4 | 5 | transition: opacity .25s ease-in; 6 | } 7 | -------------------------------------------------------------------------------- /src/components/ResponsiveImage.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import CSSModules from 'react-css-modules' 3 | import styles from './ResponsiveImage.css' 4 | 5 | class ResponsiveImage extends React.Component { 6 | constructor (props) { 7 | super(props) 8 | 9 | this.state = { src: props.src, alt: props.alt, mounted: false } 10 | } 11 | 12 | componentDidMount () { 13 | this.setState({ 14 | width: Math.round(this.node.getBoundingClientRect().width), 15 | mounted: true 16 | }) 17 | } 18 | 19 | render () { 20 | return ( 21 | {this.state.alt} this.node = node}/> 25 | ) 26 | } 27 | } 28 | 29 | ResponsiveImage.propTypes = { src: PropTypes.string, alt: PropTypes.string } 30 | 31 | export default CSSModules(ResponsiveImage, styles) 32 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill' 2 | import React from 'react' 3 | import { render } from 'react-dom' 4 | import { Router, Route, IndexRoute } from 'react-router' 5 | import App from './components/Main' 6 | import Gallery from './components/Gallery' 7 | import GalleryList from './components/GalleryList' 8 | import NoMatch from './components/NoMatch' 9 | import { Provider } from 'react-redux' 10 | import { store, history } from './store' 11 | 12 | const router = ( 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ) 26 | 27 | render(router, document.querySelector('main')) 28 | -------------------------------------------------------------------------------- /src/reducers/app.js: -------------------------------------------------------------------------------- 1 | import { deliveryAccessToken, galleryTypeId, spaceId } from '../../config' 2 | import { makeReducer } from './util' 3 | 4 | export const app = makeReducer(function (action) { 5 | switch (action.type) { 6 | case 'LOADED_CLIENT': 7 | return {authState: action.authState} 8 | } 9 | }, { authState: 'loading', deliveryAccessToken, galleryTypeId, spaceId }) 10 | -------------------------------------------------------------------------------- /src/reducers/galleries.js: -------------------------------------------------------------------------------- 1 | import { makeReducer } from './util' 2 | 3 | export const galleries = makeReducer(function (action) { 4 | switch (action.type) { 5 | case 'LOAD_GALLERIES_PENDING': 6 | return { fetching: true } 7 | case 'LOAD_GALLERIES_FULFILLED': 8 | return { 9 | fetching: false, 10 | entries: action.payload.reduce((collection, entry) => { 11 | collection[ entry.sys.id ] = entry 12 | return collection 13 | }, {}) 14 | } 15 | case 'LOAD_GALLERIES_REJECTED': 16 | return { error: true, fetching: false } 17 | 18 | case 'LOAD_GALLERY_PENDING': 19 | return { 20 | entries: { 21 | [ action.meta.id ]: { 22 | fetching: true 23 | } 24 | } 25 | } 26 | case 'LOAD_GALLERY_FULFILLED': 27 | action.payload.fetching = false 28 | 29 | action.payload.fields.images.forEach(image => { 30 | image.thumbnail = `${image.fields.photo.fields.file.url}?w=120` 31 | }) 32 | 33 | return { 34 | entries: { 35 | [ action.meta.id ]: action.payload 36 | } 37 | } 38 | case 'LOAD_GALLERY_REJECTED': 39 | return { 40 | entries: { 41 | [ action.meta.id ]: { 42 | error: true, 43 | fetching: false 44 | } 45 | } 46 | } 47 | } 48 | }, { entries: [] }) 49 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { app } from './app' 3 | import { galleries } from './galleries' 4 | 5 | const rootReducer = combineReducers({app, galleries}) 6 | 7 | export default rootReducer 8 | -------------------------------------------------------------------------------- /src/reducers/util.js: -------------------------------------------------------------------------------- 1 | import deepExtend from 'deep-extend' 2 | 3 | export function makeReducer (createUpdate, defaults = {}) { 4 | return function reduce (state, action) { 5 | const update = createUpdate(action) 6 | return deepExtend({}, state || defaults, update || {}) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/services/contentfulClient.js: -------------------------------------------------------------------------------- 1 | import { createClient } from 'contentful' 2 | 3 | let client 4 | let authorized 5 | 6 | export function initClient (spaceId, accessToken) { 7 | client = createClient({ 8 | space: spaceId, 9 | accessToken, 10 | host: 'cdn.contentful.com' 11 | }) 12 | return client.getSpace() 13 | .then((space) => { 14 | authorized = true 15 | return space 16 | }) 17 | } 18 | 19 | export function getClient () { 20 | return authorized && client 21 | } 22 | -------------------------------------------------------------------------------- /src/services/galleryStore.js: -------------------------------------------------------------------------------- 1 | import {getClient} from './contentfulClient' 2 | 3 | export function loadGalleries (contentTypeId) { 4 | return getClient().getEntries({ 5 | content_type: contentTypeId 6 | }).then(payload => { 7 | return payload.items 8 | }) 9 | } 10 | 11 | export function loadGallery (id) { 12 | return getClient().getEntries({'sys.id': id}).then(payload => { 13 | if (!payload.items.length) { 14 | throw new Error('Entry not found') 15 | } 16 | 17 | return payload.items[ 0 ] 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, bindActionCreators, applyMiddleware, compose } from 'redux' 2 | import logger from 'redux-logger' 3 | import thunk from 'redux-thunk' 4 | import promiseMiddleware from 'redux-promise-middleware' 5 | import { useRouterHistory } from 'react-router' 6 | import rootReducer from './reducers/index' 7 | import { createHistory } from 'history' 8 | 9 | import { connect } from 'react-redux' 10 | import * as actionCreators from './actions/actionCreators' 11 | 12 | const middleware = applyMiddleware(promiseMiddleware(), thunk, logger()) 13 | 14 | function RunDevToolExtensionIfNotInProduction () { 15 | const shouldExposeState = (!process.env.NODE_ENV || process.env.NODE_ENV !== 'production') && 16 | window.devToolsExtension 17 | 18 | return (shouldExposeState ? window.devToolsExtension() : (f) => f) 19 | } 20 | 21 | export const store = createStore( 22 | rootReducer, 23 | compose(middleware, RunDevToolExtensionIfNotInProduction()) 24 | ) 25 | 26 | export const history = useRouterHistory(createHistory)({ 27 | basename: (process.env.NODE_ENV && process.env.NODE_ENV === 'production') ? '/gallery-app-react/' : '/' 28 | }) 29 | 30 | export const mapStateToProps = state => state 31 | 32 | function mapDispatchToProps (dispatch) { 33 | return bindActionCreators(actionCreators, dispatch) 34 | } 35 | 36 | export function connectComponent (component) { 37 | return connect(mapStateToProps, mapDispatchToProps)(component) 38 | } 39 | -------------------------------------------------------------------------------- /src/styles/_base.scss: -------------------------------------------------------------------------------- 1 | html, body { 2 | min-height : 100%; 3 | } 4 | 5 | * { 6 | box-sizing: border-box; 7 | padding: 0; 8 | margin: 0; 9 | border: 0; 10 | } 11 | 12 | body { 13 | padding: 0; 14 | margin: 0; 15 | 16 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 17 | font-size : 100%; 18 | color: #555; 19 | 20 | background: linear-gradient( to bottom, #fafafa, #e8e8e5 ); 21 | border: none; 22 | } 23 | 24 | a { 25 | color: inherit; 26 | text-decoration: none; 27 | } 28 | 29 | img { 30 | max-width : 100%; 31 | } 32 | 33 | svg { 34 | fill: #555; 35 | } 36 | -------------------------------------------------------------------------------- /src/styles/_util.scss: -------------------------------------------------------------------------------- 1 | .u-paddingDefault { 2 | padding : 1em; 3 | } 4 | 5 | .u-paddingHorizDefault { 6 | padding-left : 1em; 7 | padding-right : 1em; 8 | } 9 | 10 | .u-marginTopDefault { 11 | margin-top: 1em; 12 | } 13 | 14 | .u-marginTopAuto { 15 | margin-top: auto; 16 | } 17 | 18 | .u-marginBottomDefault { 19 | margin-bottom : 1em; 20 | } 21 | 22 | .u-marginBottomSmall { 23 | margin-bottom : .5em; 24 | } 25 | 26 | .u-flexHorizCenter { 27 | display: flex; 28 | justify-content: center; 29 | } 30 | -------------------------------------------------------------------------------- /src/styles/main.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Base styling and resets 3 | */ 4 | @import 'base'; 5 | 6 | /** 7 | * css objets built to be reused across 8 | * several components 9 | */ 10 | @import 'objects/btn'; 11 | @import 'objects/container'; 12 | @import 'objects/keyframes'; 13 | @import 'objects/list'; 14 | @import 'objects/tag'; 15 | @import 'objects/warning'; 16 | 17 | /** 18 | * css utils to be reused across 19 | * the whole site 20 | * 21 | * -> this are supposed to be immutable and 22 | * should never be changed again 23 | */ 24 | @import 'util'; 25 | 26 | /** 27 | * vendor and plugin overwrite css 28 | */ 29 | @import "../../node_modules/react-image-gallery/src/image-gallery"; 30 | @import "plugins/reactModal"; 31 | @import "plugins/reactImageGallery"; 32 | -------------------------------------------------------------------------------- /src/styles/objects/_btn.scss: -------------------------------------------------------------------------------- 1 | .o-btnClose { 2 | display: flex; 3 | 4 | justify-content: center; 5 | align-items: center; 6 | 7 | width: 2em; 8 | height: 2em; 9 | 10 | font-size: 1.25em; 11 | line-height: 1; 12 | 13 | background-color: #fff; 14 | 15 | transform: translate(0, -50%); 16 | 17 | border: 3px solid currentColor; 18 | border-radius: 50%; 19 | 20 | > span { 21 | display: block; 22 | width: 100%; 23 | text-align: center; 24 | } 25 | } 26 | 27 | .o-btnClose:hover { 28 | color: #5aaed0; 29 | } 30 | 31 | .o-btnClose:focus, .o-btnClose:active { 32 | outline: none; 33 | color: #de5452; 34 | } 35 | 36 | 37 | .o-btnPrimary { 38 | display: block; 39 | 40 | padding: .5em; 41 | 42 | border: 2px solid currentColor; 43 | } 44 | 45 | .o-btnPrimary { 46 | display: block; 47 | 48 | width: 100%; 49 | 50 | padding: .5em; 51 | 52 | border: 2px solid currentColor; 53 | } 54 | 55 | .o-btnPrimary:hover { 56 | color: #5aaed0; 57 | } 58 | 59 | .o-btnPrimary:focus, .o-btnPrimary:active { 60 | outline: none; 61 | color: #de5452; 62 | } 63 | -------------------------------------------------------------------------------- /src/styles/objects/_container.scss: -------------------------------------------------------------------------------- 1 | .o-container { 2 | margin : 0 auto; 3 | } 4 | -------------------------------------------------------------------------------- /src/styles/objects/_keyframes.scss: -------------------------------------------------------------------------------- 1 | @keyframes rotate { 2 | from { 3 | transform: rotate(0); 4 | } 5 | 6 | to { 7 | transform: rotate(1turn); 8 | } 9 | } 10 | 11 | .k-rotate { 12 | animation: rotate 1s infinite; 13 | } -------------------------------------------------------------------------------- /src/styles/objects/_list.scss: -------------------------------------------------------------------------------- 1 | .o-listReset { 2 | list-style: none; 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | .o-listThirds { 8 | list-style: none; 9 | 10 | margin: 0 ; 11 | padding: 0; 12 | 13 | display: flex; 14 | flex-wrap: wrap; 15 | justify-content: center; 16 | 17 | > li { 18 | width: 100%; 19 | 20 | @media (min-width:45em) { 21 | width : 50%; 22 | } 23 | @media (min-width:65em) { 24 | width : 33.333%; 25 | } 26 | } 27 | } 28 | 29 | .o-listThirdsWithSpace { 30 | list-style: none; 31 | 32 | margin: 0; 33 | padding: 0; 34 | 35 | display: flex; 36 | flex-wrap: wrap; 37 | justify-content: center; 38 | 39 | > li { 40 | display : flex; 41 | 42 | width: 100%; 43 | 44 | padding : 0 .5em; 45 | margin-bottom: 1em; 46 | 47 | @media (min-width:45em) { 48 | width : 50%; 49 | } 50 | @media (min-width:65em) { 51 | width : 33.333%; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/styles/objects/_tag.scss: -------------------------------------------------------------------------------- 1 | .o-tag { 2 | display: inline-block; 3 | padding: .375em; 4 | margin : .25em; 5 | 6 | font-size: .875em; 7 | 8 | border-radius: .25em; 9 | background: #eee; 10 | color: currentColor; 11 | } 12 | -------------------------------------------------------------------------------- /src/styles/objects/_warning.scss: -------------------------------------------------------------------------------- 1 | .o-warning { 2 | font-size: 2em; 3 | color: #de5452; 4 | 5 | text-align: center; 6 | 7 | &__paragraph { 8 | font-size: .75em; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/styles/plugins/_reactImageGallery.scss: -------------------------------------------------------------------------------- 1 | .image-gallery-slide { 2 | .image-gallery-description { 3 | bottom : 2em; 4 | } 5 | } 6 | 7 | .image-gallery { 8 | width: 100vw; 9 | 10 | 11 | @media (min-width:65em) { 12 | width: 55vw; 13 | } 14 | } 15 | 16 | .image-gallery-title { 17 | font-size: 1.5em; 18 | margin-bottom: .25em; 19 | } 20 | 21 | .image-gallery-thumbnails { 22 | position: absolute; 23 | top: 100%; 24 | left: 0; 25 | right: 0; 26 | background: transparent; 27 | } 28 | -------------------------------------------------------------------------------- /src/styles/plugins/_reactModal.scss: -------------------------------------------------------------------------------- 1 | .ReactModal__Overlay { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | 6 | opacity: 0; 7 | 8 | background-color: rgba(255, 255, 255, 0.95); 9 | 10 | overflow: auto; 11 | 12 | &--after-open { 13 | opacity: 1; 14 | 15 | transition: opacity .15s ease-in-out; 16 | } 17 | 18 | &--before-close { 19 | opacity: 0; 20 | } 21 | } 22 | 23 | 24 | .ReactModal__Content { 25 | transform: translate(0, 2em); 26 | opacity: 0; 27 | 28 | &--after-open { 29 | transform: translate(0, 0); 30 | 31 | opacity: 1; 32 | transition: transform .2s cubic-bezier(.25,.35,.625,1.5); 33 | } 34 | 35 | &--before-close { 36 | transform: translate(0, 2em); 37 | opacity: 0; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | var autoprefixer = require('autoprefixer') 4 | 5 | module.exports = { 6 | context: path.join(__dirname, 'src'), 7 | entry: './main.js', 8 | output: { 9 | path: path.join(__dirname, 'dist'), 10 | filename: '[name].bundle.js', 11 | publicPath: '/' 12 | }, 13 | devtool: 'source-map', 14 | module: { 15 | loaders: [ 16 | { 17 | test: /\.scss$/, 18 | loaders: [ 'style', 'css', 'postcss', 'sass'] 19 | }, 20 | { 21 | test: /\.js?$/, 22 | exclude: /(node_modules|bower_components|dist)/, 23 | loader: 'babel' 24 | }, 25 | { 26 | test: /\.css$/, 27 | loaders: [ 28 | 'style?sourceMap', 29 | 'css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]' 30 | ] 31 | }, 32 | { 33 | test: /\.jpe?g$|\.svg$|\.png$/, 34 | exclude: /node_modules/, 35 | loader: 'file-loader?name=[path][name].[ext]' 36 | } 37 | ] 38 | }, 39 | plugins: [ 40 | new webpack.EnvironmentPlugin([ 41 | 'NODE_ENV' 42 | ]) 43 | ], 44 | devServer: { 45 | historyApiFallback: true 46 | }, 47 | postcss: [ autoprefixer({ browsers: ['last 3 versions'] }) ] 48 | } 49 | --------------------------------------------------------------------------------