├── .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 | 
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 |
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 |
112 | {
113 | gallery.fields.images.map((entry, index) => {
114 | return (
115 | -
116 |
117 |
118 |
119 |
120 |
{ entry.fields.title }
121 |
122 |
123 | )
124 | })
125 | }
126 |
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 |
150 | {
151 | gallery.fields.tags.map(
152 | (entry, index) => (- { entry }
)
153 | )
154 | }
155 |
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 |
16 | {
17 | Object.keys(this.props.galleries.entries).map(id => {
18 | return (
19 | -
20 |
21 |
22 | )
23 | })
24 | }
25 |
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 |
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 |
45 | {
46 | gallery.fields.tags.map(
47 | (entry, index) => (- { entry }
)
48 | )
49 | }
50 |
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 |
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.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 |
--------------------------------------------------------------------------------