├── .eslintignore ├── .eslintrc ├── .gitignore ├── .nvmrc ├── .prettierrc ├── .vscode └── settings.json ├── README.md ├── gatsby-config.js ├── gatsby-node.js ├── jsconfig.json ├── package-lock.json ├── package.json ├── src ├── _sass │ ├── _global.scss │ ├── _grid.scss │ ├── _settings.scss │ ├── _utilities.scss │ └── index.scss ├── components │ ├── Banner │ │ └── Banner.js │ ├── Contact │ │ └── Contact.js │ ├── FakeImg │ │ └── FakeImg.js │ ├── Forms │ │ └── ProspectSignupForm.js │ ├── Nav │ │ └── Nav.js │ ├── PostcodeValidator │ │ └── PostcodeValidator.js │ ├── Product │ │ ├── Product.js │ │ ├── ProductHolder.js │ │ └── shopifyOptions.js │ ├── Section │ │ └── Section.js │ ├── ShopRecommendation │ │ └── ShopRecommendation.js │ ├── TitleAndMetaTags │ │ └── TitleAndMetaTags.js │ └── Typewriter │ │ └── Typewriter.js ├── data │ └── postcodes.json ├── icons │ ├── BurgerIcon.js │ ├── CalendarIcon.js │ └── CrossIcon.js ├── images │ ├── banner-mobile.jpg │ ├── banner.jpg │ ├── eatit.jpg │ ├── family.jpg │ ├── full-width-image-dark.jpg │ ├── full-width-image-rolling.jpg │ ├── full-width-image-twohands.jpg │ ├── img-1.jpg │ ├── img-2.jpg │ ├── ingredients.jpg │ ├── press-logos │ │ ├── evening-standard.png │ │ ├── londonist.png │ │ ├── metro.png │ │ ├── msn.png │ │ ├── munchies.png │ │ ├── the-sunday-times.png │ │ ├── timeout.png │ │ └── vice.png │ ├── product.png │ ├── rita.jpg │ ├── social │ │ ├── 16x16.png │ │ ├── 32x32.png │ │ ├── 64x64.png │ │ ├── hero-1.jpg │ │ ├── hero-2.jpg │ │ ├── hero-3.jpg │ │ ├── hero-4.jpg │ │ └── voucher.jpg │ ├── stock.jpg │ └── story.jpg ├── layouts │ └── index.js ├── pages │ ├── 404.js │ ├── buy.js │ ├── index.js │ ├── ingredients.js │ ├── press.js │ ├── shops │ │ ├── eat17-hackney.md │ │ ├── eat17-walthamstow.md │ │ ├── green-flavour.md │ │ ├── harringay-local-store.md │ │ ├── index.js │ │ ├── portobello-wholefoods.md │ │ ├── raw-store.md │ │ ├── the-deli-downstairs.md │ │ └── the-grocery.md │ └── story.js └── utils │ ├── createGroupedArray.js │ └── loadScript.js └── static ├── admin └── config.yml ├── product-request-form.pdf └── social ├── 16x16.png ├── 32x32.png ├── 64x64.png ├── hero-1.jpg ├── hero-2.jpg ├── hero-3.jpg ├── hero-4.jpg └── voucher.jpg /.eslintignore: -------------------------------------------------------------------------------- 1 | # Ignore node modules 2 | node_modules/* 3 | 4 | # Ignore built files 5 | public/* -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "fbjs", 4 | "eslint:recommended", 5 | "prettier" 6 | ], 7 | "plugins": [ 8 | "prettier", 9 | "react" 10 | ], 11 | "parser": "babel-eslint", 12 | "rules": { 13 | "relay/graphql-naming": 0 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project dependencies 2 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 3 | node_modules 4 | .cache/ 5 | # Build directory 6 | public/ 7 | .DS_Store 8 | yarn-error.log 9 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/carbon 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "jsxBracketSameLine": true, 4 | "printWidth": 90, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": true 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dona Rita Website 2 | 3 | ## Install dependencies 4 | ``` 5 | yarn install 6 | ``` 7 | 8 | ## Development 9 | ``` 10 | yarn develop 11 | ``` 12 | 13 | ## Build 14 | ``` 15 | yarn build 16 | ``` 17 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteMetadata: { 3 | title: 'Dona Rita' 4 | }, 5 | plugins: [ 6 | 'gatsby-plugin-layout', 7 | 'gatsby-plugin-react-helmet', 8 | 'gatsby-plugin-sass', 9 | { 10 | resolve: 'gatsby-plugin-nprogress', 11 | options: { 12 | color: '#f09a7a', 13 | showSpinner: false 14 | } 15 | }, 16 | 'gatsby-transformer-json', 17 | 'gatsby-transformer-remark', 18 | { 19 | resolve: `gatsby-source-filesystem`, 20 | options: { 21 | name: `data`, 22 | path: `${__dirname}/src/data/` 23 | } 24 | }, 25 | { 26 | resolve: `gatsby-source-filesystem`, 27 | options: { 28 | name: `pages`, 29 | path: `${__dirname}/src/pages/` 30 | } 31 | }, 32 | 'gatsby-plugin-netlify-cms', 33 | 'gatsby-plugin-netlify' 34 | ] 35 | }; 36 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | // Implement the Gatsby API `createPages`. This is called once the 2 | // data layer is bootstrapped to let plugins create pages from data. 3 | exports.createPages = ({ actions }) => { 4 | // We need `createRedirect` action in `actions` collection 5 | // to make the redirection magic happen. 6 | // https://www.gatsbyjs.org/docs/bound-action-creators/ 7 | const { createRedirect } = actions; 8 | 9 | // create the one-off redirect 10 | // https://www.gatsbyjs.org/docs/bound-action-creators/#createRedirect 11 | createRedirect({ 12 | fromPath: '/stockists', 13 | isPermanent: true, 14 | redirectInBrowser: true, 15 | toPath: '/shops' 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/jsconfig.json -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dona-rita-website", 3 | "description": "Dona Rita Website", 4 | "version": "1.0.0", 5 | "author": "Pedro Duarte ", 6 | "main": "n/a", 7 | "scripts": { 8 | "build": "gatsby build", 9 | "develop": "gatsby develop", 10 | "diff": "prettier --config .prettierrc --list-different \"src/**/*.js\"", 11 | "format": "prettier --config .prettierrc --write \"src/**/*.js\"", 12 | "test": "eslint .", 13 | "precommit": "lint-staged", 14 | "prepush": "yarn test" 15 | }, 16 | "dependencies": { 17 | "gatsby": "^2.0.44", 18 | "gatsby-plugin-layout": "^1.0.8", 19 | "gatsby-plugin-netlify": "^2.0.4", 20 | "gatsby-plugin-netlify-cms": "^3.0.7", 21 | "gatsby-plugin-nprogress": "^2.0.6", 22 | "gatsby-plugin-react-helmet": "^3.0.1", 23 | "gatsby-plugin-sass": "^2.0.4", 24 | "gatsby-source-filesystem": "^2.0.8", 25 | "gatsby-transformer-json": "^2.1.5", 26 | "gatsby-transformer-remark": "^2.1.12", 27 | "netlify-cms": "^2.1.3", 28 | "node-sass": "^4.10.0", 29 | "query-string": "^6.2.0", 30 | "react": "^16.6.1", 31 | "react-dom": "^16.6.1", 32 | "react-ga": "^2.5.3", 33 | "react-helmet": "^5.2.0", 34 | "react-load-script": "^0.0.6", 35 | "react-typist": "^2.0.4", 36 | "react-visibility-sensor": "^5.0.2" 37 | }, 38 | "lint-staged": { 39 | "*.{js,jsx}": [ 40 | "prettier --config .prettierrc --write", 41 | "git add" 42 | ] 43 | }, 44 | "devDependencies": { 45 | "babel-eslint": "^10.0.1", 46 | "eslint": "^5.8.0", 47 | "eslint-config-fbjs": "^2.1.0", 48 | "eslint-config-prettier": "^3.1.0", 49 | "eslint-plugin-babel": "^5.2.1", 50 | "eslint-plugin-flowtype": "^3.2.0", 51 | "eslint-plugin-jsx-a11y": "^6.1.2", 52 | "eslint-plugin-prettier": "^3.0.0", 53 | "eslint-plugin-react": "^7.11.1", 54 | "eslint-plugin-relay": "^0.0.28", 55 | "husky": "^1.1.3", 56 | "lint-staged": "^8.0.4", 57 | "prettier": "^1.15.1" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/_sass/_global.scss: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | font-family: $fontDefault; 9 | font-size: 14px; 10 | line-height: 24px; 11 | letter-spacing: 0.02em; 12 | 13 | background: $colorBackground; 14 | 15 | @include medium { 16 | font-size: 18px; 17 | line-height: 30px; 18 | 19 | // For pages with full coloured background 20 | // this is needed to keep the 10px spacing around it. 21 | // Like everything else this approach needs improving 22 | // at some point 23 | padding-top: 10px; 24 | } 25 | } 26 | ::selection { 27 | background-color: $colorSalmon; 28 | color: white; 29 | } 30 | address { 31 | font-style: normal; 32 | } 33 | 34 | .main { 35 | opacity: 0; 36 | animation: fadeContent 1600ms 600ms ease-in-out forwards; 37 | } 38 | 39 | @keyframes fadeContent { 40 | 100% { 41 | opacity: 1; 42 | } 43 | } 44 | 45 | .typist { 46 | display: inline-block; 47 | } 48 | 49 | .logo { 50 | margin: 20px 0 0 20px; 51 | 52 | @include medium { 53 | position: fixed; 54 | margin: 0; 55 | top: 20px; 56 | left: 20px; 57 | z-index: 2; 58 | } 59 | } 60 | 61 | h1, 62 | h2, 63 | h3 { 64 | font-family: $fontHighlight; 65 | } 66 | 67 | h1, 68 | .h1 { 69 | font-size: 36px; 70 | line-height: 36px; 71 | letter-spacing: 1px; 72 | margin-top: 20px; 73 | margin-bottom: 20px; 74 | 75 | @include medium { 76 | font-size: 60px; 77 | line-height: 60px; 78 | letter-spacing: 1px; 79 | margin-top: 40px; 80 | margin-bottom: 40px; 81 | } 82 | } 83 | 84 | h2, 85 | .h2, 86 | h3, 87 | .h3 { 88 | font-size: 20px; 89 | line-height: 36px; 90 | margin-top: 20px; 91 | margin-bottom: 20px; 92 | 93 | @include medium { 94 | font-size: 30px; 95 | line-height: 36px; 96 | margin-top: 40px; 97 | margin-bottom: 40px; 98 | } 99 | } 100 | 101 | h3, 102 | .h3 { 103 | margin-top: 20px; 104 | 105 | @include medium { 106 | margin-top: 40px; 107 | } 108 | } 109 | 110 | p { 111 | margin-bottom: 20px; 112 | 113 | @include medium { 114 | margin-bottom: 40px; 115 | } 116 | } 117 | 118 | a { 119 | color: inherit; 120 | } 121 | 122 | .main-nav { 123 | &.-mobile { 124 | display: none; 125 | flex-direction: column; 126 | align-items: center; 127 | justify-content: center; 128 | 129 | position: fixed; 130 | top: 0; 131 | left: 0; 132 | z-index: 9; 133 | 134 | background: white; 135 | height: 100vh; 136 | width: 100%; 137 | 138 | font-size: 16px; 139 | line-height: 30px; 140 | 141 | &.is-opened { 142 | display: flex; 143 | } 144 | 145 | a { 146 | display: inline-block; 147 | margin: 10px 0; 148 | text-decoration: none; 149 | } 150 | 151 | @include medium { 152 | display: none !important; 153 | } 154 | } 155 | 156 | &.-desktop { 157 | margin-top: 15px; 158 | display: none; 159 | 160 | @include medium { 161 | display: flex; 162 | align-items: baseline; 163 | } 164 | 165 | a:not(.button) { 166 | text-decoration: none; 167 | padding: 10px 0; 168 | display: block; 169 | 170 | @include medium { 171 | display: inline-block; 172 | padding: 15px 20px; 173 | 174 | &:not(.active):hover { 175 | text-decoration: underline; 176 | } 177 | } 178 | 179 | &:first-child { 180 | @include medium { 181 | margin-left: -20px; // offset padding of first link 182 | } 183 | } 184 | 185 | &.active { 186 | color: $colorYellow; 187 | } 188 | } 189 | 190 | .button { 191 | margin-left: auto; 192 | } 193 | } 194 | } 195 | 196 | .mobile-menu { 197 | position: absolute; 198 | top: 10px; 199 | right: 10px; 200 | z-index: 8; 201 | 202 | @include medium { 203 | display: none; 204 | } 205 | } 206 | 207 | .button { 208 | text-decoration: none; 209 | display: inline-block; 210 | border: 1px solid currentColor; 211 | font-size: 14px; 212 | line-height: 30px; 213 | padding: 15px 30px; 214 | letter-spacing: 0.2em; 215 | 216 | &:hover { 217 | background-color: $colorYellow; 218 | text-decoration: none; 219 | } 220 | 221 | .button-group & { 222 | display: block; 223 | width: 100%; 224 | text-align: center; 225 | 226 | &:not(:last-child) { 227 | border-bottom: none; 228 | } 229 | } 230 | } 231 | 232 | .small { 233 | font-size: 12px; 234 | line-height: 22px; 235 | margin-bottom: 20px; 236 | 237 | @include medium { 238 | font-size: 13px; 239 | line-height: 24px; 240 | } 241 | } 242 | 243 | .faded { 244 | color: rgba(black, 0.4); 245 | 246 | .-blue & { 247 | color: rgba(white, 0.4); 248 | 249 | a { 250 | color: rgba(white, 0.6); 251 | } 252 | } 253 | } 254 | 255 | .measure { 256 | @include medium { 257 | padding-right: 60px; 258 | } 259 | } 260 | 261 | .measure-wider { 262 | @include wide { 263 | padding-right: 60px; 264 | } 265 | } 266 | 267 | .title-desc { 268 | display: block; 269 | color: $colorSalmon; 270 | 271 | &::selection { 272 | background-color: black; 273 | } 274 | } 275 | 276 | sup { 277 | font-family: $fontHighlight; 278 | font-size: 25px; 279 | line-height: 0; 280 | font-weight: bold; 281 | 282 | &.-yellow { 283 | color: $colorYellow; 284 | } 285 | &.-salmon { 286 | color: $colorSalmon; 287 | } 288 | &.-blue { 289 | color: $colorBlue; 290 | } 291 | } 292 | 293 | .footnote { 294 | position: relative; 295 | margin-top: 20px; 296 | 297 | @include medium { 298 | margin-top: 0; 299 | } 300 | 301 | &::before { 302 | content: attr(data-id); 303 | font-family: $fontHighlight; 304 | font-weight: bold; 305 | font-size: 25px; 306 | line-height: 25px; 307 | color: inherit; 308 | 309 | position: absolute; 310 | top: -24px; 311 | left: 0; 312 | } 313 | 314 | &.-yellow::before { 315 | color: $colorYellow; 316 | } 317 | &.-salmon::before { 318 | color: $colorSalmon; 319 | } 320 | &.-blue::before { 321 | color: $colorBlue; 322 | } 323 | } 324 | 325 | img { 326 | vertical-align: middle; 327 | width: 100%; 328 | margin-bottom: 20px; 329 | 330 | @include medium { 331 | margin-bottom: 10px; 332 | } 333 | } 334 | 335 | // REFACTOR BELOW 336 | .section { 337 | padding: 20px 10px; 338 | margin-bottom: 10px; 339 | position: relative; 340 | 341 | &:first-child { 342 | @include medium { 343 | padding-top: 80px; 344 | } 345 | 346 | @include wide { 347 | padding-top: 100px; 348 | } 349 | } 350 | 351 | @include medium { 352 | padding: 40px 0; 353 | 354 | /* 355 | Pseudo elements to create a 10px "gutter" and overlap it 356 | on the left and right sides of the section with the 357 | color of the background to give it a recessed effect 358 | without affecting the grid. MVP bro. 359 | */ 360 | &:before, 361 | &:after { 362 | content: ''; 363 | position: absolute; 364 | background: $colorBackground; 365 | width: 10px; 366 | height: 100%; 367 | top: 0; 368 | } 369 | &:before { 370 | left: 0; 371 | } 372 | &:after { 373 | right: 0; 374 | } 375 | } 376 | 377 | &.-white { 378 | background-color: white; 379 | } 380 | 381 | &.-blue { 382 | background-color: $colorBlue; 383 | color: white; 384 | } 385 | 386 | &.-salmon { 387 | background-color: $colorSalmon; 388 | color: white; 389 | } 390 | 391 | &.-yellow { 392 | background-color: $colorYellow; 393 | color: white; 394 | } 395 | } 396 | 397 | .product-section { 398 | padding-top: 70px; 399 | 400 | @include medium { 401 | padding-top: 140px; 402 | } 403 | } 404 | 405 | // SHOPS 406 | .shops-list { 407 | @include medium { 408 | margin: 30px 0; 409 | } 410 | 411 | // 💩 412 | div div { 413 | margin-bottom: 30px; 414 | 415 | @include medium { 416 | padding-right: 15px; 417 | } 418 | } 419 | 420 | h3 { 421 | margin-bottom: 15px; 422 | } 423 | } 424 | 425 | .shops-interest { 426 | padding-bottom: 60px; 427 | } 428 | 429 | // HOME PRODUCT 430 | .home-product { 431 | padding: 60px 0; 432 | 433 | @include medium { 434 | padding-top: 120px; 435 | padding-bottom: 160px; 436 | } 437 | } 438 | 439 | // PRESS ICONS 440 | .press-icons { 441 | a { 442 | display: block; 443 | opacity: 0.4; 444 | transition: opacity 150ms ease-out; 445 | 446 | &:hover { 447 | opacity: 1; 448 | } 449 | } 450 | } 451 | 452 | // TESTIMONIAL 453 | .testimonial { 454 | @include medium { 455 | padding-top: 120px; 456 | padding-bottom: 120px; 457 | } 458 | } 459 | 460 | // CONTACT 461 | .contact { 462 | font-size: 14px; 463 | line-height: 24px; 464 | } 465 | 466 | .contact-phone { 467 | font-family: $fontHighlight; 468 | font-weight: bold; 469 | display: block; 470 | font-size: 30px; 471 | line-height: 30px; 472 | } 473 | 474 | .contact-or { 475 | display: block; 476 | line-height: 1px; 477 | height: 1px; 478 | background: #979797; 479 | text-align: center; 480 | margin: 40px 0; 481 | 482 | span { 483 | display: inline-block; 484 | padding: 0 10px; 485 | position: relative; 486 | top: -10px; 487 | line-height: 20px; 488 | background: $colorBackground; 489 | } 490 | } 491 | 492 | .contact-link { 493 | display: block; 494 | font-size: 20px; 495 | line-height: 32px; 496 | } 497 | 498 | // UTILITIES 499 | .yellow { 500 | color: $colorYellow; 501 | } 502 | .salmon { 503 | color: $colorSalmon; 504 | } 505 | .blue { 506 | color: $colorBlue; 507 | } 508 | 509 | .center { 510 | text-align: center; 511 | } 512 | 513 | // FAKE IMG 514 | .fake-img-group { 515 | @include medium { 516 | height: 33vmax; 517 | max-height: 420px; 518 | } 519 | } 520 | 521 | .fake-img-wrapper { 522 | @include medium { 523 | height: 100%; 524 | } 525 | } 526 | 527 | .fake-img { 528 | height: 0; 529 | background-size: cover; 530 | background-repeat: no-repeat; 531 | background-position: center center; 532 | margin-bottom: 20px; 533 | position: relative; 534 | background-color: $colorSalmon; 535 | 536 | @include medium { 537 | margin-bottom: 0; 538 | } 539 | 540 | &::after { 541 | content: ''; 542 | position: absolute; 543 | top: 0; 544 | left: 0; 545 | right: 0; 546 | bottom: 0; 547 | background-color: $colorSalmon; 548 | } 549 | 550 | &.is-loaded::after { 551 | opacity: 0; 552 | transition: opacity 2000ms $easeOutQuart; 553 | } 554 | 555 | &.-two-x-three { 556 | padding-top: (3 / 2) * 100 + unquote('%'); 557 | } 558 | &.-three-x-two { 559 | padding-top: (2 / 3) * 100 + unquote('%'); 560 | } 561 | &.-sixteen-x-seven { 562 | padding-top: (7 / 16) * 100 + unquote('%'); 563 | } 564 | &.-square { 565 | padding-top: 100%; 566 | } 567 | 568 | .fake-img-group & { 569 | @include medium { 570 | height: 100%; 571 | padding-top: 0; 572 | } 573 | } 574 | } 575 | 576 | // BANNER 577 | .banner { 578 | max-width: 100% !important; 579 | padding: 0 20px; 580 | 581 | @include medium { 582 | padding: 0 10px; 583 | margin-bottom: 10px; 584 | } 585 | } 586 | 587 | .banner-img { 588 | &.-small { 589 | @include medium { 590 | display: none; 591 | } 592 | } 593 | &.-medium { 594 | display: none; 595 | @include medium { 596 | display: block; 597 | } 598 | } 599 | } 600 | 601 | .fluid { 602 | width: 100%; 603 | } 604 | 605 | // FORMS 606 | input[type='text'], 607 | input[type='number'], 608 | input[type='email'] { 609 | -webkit-appearance: none; 610 | appearance: none; 611 | font-size: 16px; 612 | font-family: inherit; 613 | line-height: 30px; 614 | padding: 15px 30px; 615 | border: 1px solid darken($colorBackground, 10%); 616 | border-radius: 0; 617 | margin: 0 20px 20px 0; 618 | 619 | width: 100%; 620 | 621 | @include medium { 622 | width: 200px; 623 | } 624 | 625 | &:hover { 626 | border-color: #999; 627 | } 628 | 629 | &:focus { 630 | border-color: black; 631 | outline: none; 632 | } 633 | } 634 | 635 | select { 636 | -webkit-appearance: none; 637 | appearance: none; 638 | font-size: 16px; 639 | font-family: inherit; 640 | line-height: 30px; 641 | padding: 15px 40px 15px 30px; 642 | border: 1px solid darken($colorBackground, 10%); 643 | border-radius: 0; 644 | margin: 0 20px 20px 0; 645 | 646 | width: 100%; 647 | 648 | @include medium { 649 | width: 200px; 650 | } 651 | 652 | &:hover { 653 | border-color: #999; 654 | } 655 | 656 | &:focus { 657 | border-color: black; 658 | outline: none; 659 | } 660 | } 661 | 662 | button { 663 | -webkit-appearance: none; 664 | appearance: none; 665 | display: inline-block; 666 | background: transparent; 667 | border: 1px solid currentColor; 668 | font-size: 14px; 669 | font-family: inherit; 670 | line-height: 30px; 671 | padding: 15px 30px; 672 | letter-spacing: 0.2em; 673 | margin: 0 20px 20px 0; 674 | 675 | width: 100%; 676 | 677 | border-radius: 0; // thanks to chrome 678 | 679 | @include medium { 680 | width: auto; 681 | } 682 | 683 | &:not(:disabled):hover { 684 | background-color: $colorSalmon; 685 | text-decoration: none; 686 | } 687 | 688 | &:focus { 689 | outline: none; 690 | } 691 | 692 | &:disabled { 693 | opacity: 0.5; 694 | } 695 | } 696 | 697 | .postcode-message { 698 | font-size: 14px; 699 | padding: 20px; 700 | margin-bottom: 20px; 701 | color: white; 702 | background-color: $colorSalmon; 703 | 704 | &.postcode-shop { 705 | background-color: $colorBlue; 706 | } 707 | } 708 | 709 | .postcode-shop div + div { 710 | margin-top: 30px; 711 | } 712 | 713 | .delivery-date { 714 | background-color: $colorYellow; 715 | color: white; 716 | padding: 10px 20px; 717 | 718 | display: flex; 719 | align-items: center; 720 | 721 | svg { 722 | width: 26px; 723 | margin-right: 10px; 724 | } 725 | } 726 | 727 | // Deliery interest form styling 728 | .delivery-interest-form { 729 | p { 730 | margin-bottom: 10px; 731 | } 732 | 733 | .mc-field-group { 734 | @include medium { 735 | display: flex; 736 | } 737 | } 738 | 739 | input[type='email'], 740 | button { 741 | padding: 10px; 742 | } 743 | 744 | input[type='email'] { 745 | @include medium { 746 | flex: 3; 747 | } 748 | } 749 | 750 | button { 751 | color: white; 752 | 753 | @include medium { 754 | flex: 1; 755 | } 756 | 757 | &:not(:disabled):hover { 758 | background-color: white; 759 | color: $colorSalmon; 760 | } 761 | } 762 | } 763 | 764 | // Shopify 765 | .shopify-buy__option-select-wrapper { 766 | position: relative; 767 | display: inline; 768 | } 769 | 770 | .shopify-buy__select-icon { 771 | width: 15px; 772 | position: absolute; 773 | right: 45px; 774 | top: 50%; 775 | transform: translateY(-50%); 776 | } 777 | 778 | .shopify-buy__quantity-container, 779 | .shopify-buy__btn-wrapper { 780 | display: inline-block; 781 | width: 100%; 782 | 783 | @include medium { 784 | width: auto; 785 | } 786 | } 787 | 788 | .shopify-buy__option-select__label { 789 | display: none; 790 | } 791 | .shopify-buy__quantity-container { 792 | position: relative; 793 | padding-top: 30px; 794 | 795 | &:after { 796 | content: 'Qty:'; 797 | font-size: 12px; 798 | line-height: 22px; 799 | color: rgba(black, 0.4); 800 | 801 | position: absolute; 802 | top: 0; 803 | left: 0; 804 | 805 | @include medium { 806 | font-size: 13px; 807 | line-height: 24px; 808 | } 809 | } 810 | } 811 | 812 | // sad notice ☹️ 813 | .sad-notice { 814 | position: fixed; 815 | z-index: 8; 816 | width: 100vw; 817 | height: 100vh; 818 | left: 0; 819 | top: 0; 820 | background: $colorSalmon; 821 | display: flex; 822 | align-items: center; 823 | justify-content: center; 824 | 825 | ~ * { 826 | display: none; 827 | } 828 | } 829 | 830 | .sad-notice-inner { 831 | width: 90vw; 832 | max-width: 620px; 833 | max-height: 90vh; 834 | overflow: auto; 835 | margin: 24px; 836 | background: white; 837 | padding: 24px; 838 | opacity: 0; 839 | animation: modalEnterKeyframes 666ms 333ms ease-out forwards; 840 | 841 | @include medium { 842 | padding: 40px; 843 | } 844 | } 845 | 846 | @keyframes modalEnterKeyframes { 847 | 0% { 848 | opacity: 0; 849 | transform: scale(0.9); 850 | } 851 | 50% { 852 | opacity: 1; 853 | } 854 | 100% { 855 | opacity: 1; 856 | transform: scale(1); 857 | } 858 | } 859 | -------------------------------------------------------------------------------- /src/_sass/_grid.scss: -------------------------------------------------------------------------------- 1 | .grid { 2 | $colWidth: 100 / 12; 3 | 4 | padding-left: 5px; 5 | padding-right: 5px; 6 | 7 | max-width: 480px; 8 | margin-left: auto; 9 | margin-right: auto; 10 | 11 | @include medium { 12 | max-width: 100%; 13 | } 14 | 15 | @include wide { 16 | max-width: 1280px; 17 | } 18 | 19 | &:before, 20 | &:after { 21 | content: " "; 22 | display: table; 23 | } 24 | &:after { clear: both; } 25 | 26 | .col { 27 | float: left; 28 | max-width: 100%; 29 | padding-left: 5px; 30 | padding-right: 5px; 31 | background-clip: content-box; 32 | } 33 | 34 | @for $i from 1 through 12 { 35 | $width: ($colWidth * $i) + unquote("%"); 36 | .sm-#{$i} { width: $width; } 37 | .sm-push-#{$i} { margin-left: $width; } 38 | .sm-pull-#{$i} { margin-right: $width; } 39 | } 40 | 41 | @for $i from 1 through 12 { 42 | $width: ($colWidth * $i) + unquote("%"); 43 | @include medium { 44 | .md-#{$i} { width: $width; } 45 | .md-push-#{$i} { margin-left: $width; } 46 | .md-pull-#{$i} { margin-right: $width; } 47 | } 48 | } 49 | @include medium { 50 | .md-push-0 { margin-left: 0; } 51 | .md-pull-0 { margin-right: 0; } 52 | } 53 | 54 | @for $i from 1 through 12 { 55 | $width: ($colWidth * $i) + unquote("%"); 56 | @include wide { 57 | .lg-#{$i} { width: $width; } 58 | .lg-push-#{$i} { margin-left: $width; } 59 | .lg-pull-#{$i} { margin-right: $width; } 60 | } 61 | } 62 | @include wide { 63 | .lg-push-0 { margin-left: 0; } 64 | .lg-pull-0 { margin-right: 0; } 65 | } 66 | } 67 | 68 | 69 | /** 70 | * For debuggin. 71 | * Adding class "debug" on 72 | */ 73 | .debug { 74 | .debug-grid { 75 | position: fixed; 76 | top: 0; 77 | left: 0; 78 | width: 100%; 79 | height: 100vh; 80 | z-index: 999; 81 | pointer-events: none; 82 | 83 | .col { 84 | height: 100vh; 85 | background-color: rgba(#000, 0.05); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/_sass/_settings.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Settings 4 | * 5 | **/ 6 | 7 | $fontDefault: Montserrat, serif; 8 | $fontHighlight: Garamond, Times, sans-serif; 9 | 10 | // Defining colors 11 | $colorBackground: #f6f6f6; 12 | $colorYellow: #e6a941; 13 | $colorBlue: #2839a4; 14 | $colorSalmon: #f09a7a; 15 | 16 | // Animation 17 | $easeInQuart: cubic-bezier(0.895, 0.03, 0.685, 0.22); 18 | $easeOutQuart: cubic-bezier(0.165, 0.84, 0.44, 1); 19 | $easeInOutCirc: cubic-bezier(0.785, 0.135, 0.15, 0.86); 20 | $easeInOutQuad: cubic-bezier(0.455, 0.03, 0.515, 0.955); 21 | 22 | @mixin small { 23 | @media (min-width: 600px) { 24 | @content; 25 | } 26 | } 27 | 28 | @mixin medium { 29 | @media (min-width: 800px) { 30 | @content; 31 | } 32 | } 33 | 34 | @mixin wide { 35 | @media (min-width: 1050px) { 36 | @content; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/_sass/_utilities.scss: -------------------------------------------------------------------------------- 1 | .color-blue { 2 | color: $colorBlue; 3 | } 4 | 5 | .color-white { 6 | color: white; 7 | } 8 | 9 | .color-black { 10 | color: black; 11 | } 12 | 13 | .color-salmon { 14 | color: $colorSalmon; 15 | } 16 | 17 | .background-salmon { 18 | background-color: $colorSalmon; 19 | } 20 | 21 | .background-yellow { 22 | background-color: $colorYellow; 23 | } 24 | -------------------------------------------------------------------------------- /src/_sass/index.scss: -------------------------------------------------------------------------------- 1 | @import './_settings'; 2 | @import './_global'; 3 | @import './_grid'; 4 | 5 | @import './_utilities'; 6 | -------------------------------------------------------------------------------- /src/components/Banner/Banner.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { FakeImg } from '../FakeImg/FakeImg'; 4 | 5 | export function Banner({ img, mobileImg }) { 6 | return ( 7 |
8 | 9 | 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/components/Contact/Contact.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export function Contact() { 4 | return ( 5 |
6 |
7 |

Contact us

8 |
9 | 10 |
11 |

12 | Call Clara at 13 | +44 (0) 79522 16979 14 | 15 | or 16 | 17 | drop us a line at 18 | 19 | oi@donarita.co.uk 20 | 21 |

22 | 23 |
24 | 25 | Instagram 26 | 27 | 28 | Twitter 29 | 30 | 31 | Facebook 32 | 33 |
34 |
35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/components/FakeImg/FakeImg.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import VisibilitySensor from 'react-visibility-sensor'; 3 | 4 | export class FakeImg extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | 8 | this.state = { inView: false }; 9 | } 10 | 11 | handleChange = isVisible => { 12 | this.setState({ inView: isVisible }); 13 | }; 14 | 15 | render() { 16 | return ( 17 | 21 |
27 | 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/Forms/ProspectSignupForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export function ProspectSignupForm({ postcode, outcode }) { 4 | return ( 5 |
6 |
14 |

Sorry, we don't deliver there.

15 |

But we’re expanding fast! To vote for your area, enter your email address.

16 |
17 | 23 | 30 | 37 | 38 |
39 | 40 | {/* real people should not fill this in and expect good things - do not remove this or risk form bot signups */} 41 | 49 |
50 |
51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /src/components/Nav/Nav.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'gatsby'; 3 | 4 | import { BurgerIcon } from '../../icons/BurgerIcon'; 5 | import { CrossIcon } from '../../icons/CrossIcon'; 6 | 7 | export class Nav extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { isOpened: false }; 11 | } 12 | 13 | handleClose = () => this.setState({ isOpened: false }); 14 | handleOpen = () => this.setState({ isOpened: true }); 15 | 16 | renderLinks = (links, onClick) => { 17 | return links.map(link => ( 18 | 24 | {link.name} 25 | 26 | )); 27 | }; 28 | 29 | render() { 30 | const links = [ 31 | { name: 'Home', to: '/' }, 32 | { name: 'Story', to: '/story' }, 33 | { name: 'Press', to: '/press' }, 34 | { name: 'Shops', to: '/shops' }, 35 | { name: 'Buy online', to: '/buy' } 36 | ]; 37 | return ( 38 |
39 | 44 | 45 | 46 | 47 | 59 | 62 |
63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/components/PostcodeValidator/PostcodeValidator.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export class PostcodeValidator extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | 7 | this.state = { value: '' }; 8 | } 9 | 10 | render() { 11 | return ( 12 |
13 | 20 | 23 |
24 | ); 25 | } 26 | 27 | handleChange = event => { 28 | this.setState({ value: event.target.value }); 29 | }; 30 | 31 | handleSubmit = () => { 32 | const { onValidPostcode, onInvalidPostcode } = this.props; 33 | const { value } = this.state; 34 | 35 | fetch(`https://api.postcodes.io/postcodes/${value}`) 36 | .then(response => response.json()) 37 | .then(data => { 38 | const { status, result } = data; 39 | if (status === 200) { 40 | onValidPostcode(result.postcode, result.outcode); 41 | return; 42 | } 43 | onInvalidPostcode(value); 44 | }); 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/components/Product/Product.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactGA from 'react-ga'; 3 | import queryString from 'query-string'; 4 | 5 | import { ProductHolder } from './ProductHolder'; 6 | import { PostcodeValidator } from '../PostcodeValidator/PostcodeValidator'; 7 | import { ShopRecommendation } from '../ShopRecommendation/ShopRecommendation'; 8 | import { ProspectSignupForm } from '../Forms/ProspectSignupForm'; 9 | 10 | import productImg from '../../images/product.png'; 11 | 12 | export class Product extends React.Component { 13 | constructor(props) { 14 | super(props); 15 | 16 | this.state = { 17 | userHasInteracted: false, 18 | postcode: '', 19 | outcode: '', 20 | isValid: false, 21 | isDeliverable: false 22 | }; 23 | } 24 | 25 | handleValidPostcode = (postcode, outcode) => { 26 | const isDeliverable = this.props.postcodes.indexOf(outcode) > -1; 27 | 28 | this.setState(prevState => { 29 | return { 30 | userHasInteracted: true, 31 | postcode, 32 | outcode, 33 | isValid: true, 34 | isDeliverable 35 | }; 36 | }); 37 | }; 38 | 39 | handleInvalidPostcode = postcode => { 40 | this.setState(prevState => { 41 | return { 42 | userHasInteracted: true, 43 | postcode, 44 | outcode: '', 45 | isValid: false, 46 | isDeliverable: false 47 | }; 48 | }); 49 | }; 50 | 51 | getParsedQueryString = () => { 52 | return queryString.parse(this.props.location.search); 53 | }; 54 | 55 | shouldSkipValidation = () => { 56 | return this.getParsedQueryString().skip !== undefined ? true : false; 57 | }; 58 | 59 | getInitialQty = () => { 60 | return this.getParsedQueryString().qty; 61 | }; 62 | 63 | componentWillUpdate(nextProps, nextState) { 64 | if (nextState.isValid && process.env.NODE_ENV !== 'development') { 65 | ReactGA.event({ 66 | category: 'Postcode', 67 | action: nextState.isDeliverable ? 'deliverable' : 'undeliverable', 68 | label: nextState.outcode 69 | }); 70 | } 71 | } 72 | 73 | render() { 74 | return ( 75 |
76 |
77 |
78 | 79 |
80 | 81 |
82 |

Bake-at-home frozen pack.

83 |

15 cheesy balls – £5.

84 | 85 | {this.props.soldout && ( 86 |
87 |

CLOSED.

88 |

89 | If you'd like to read more about our closure,{' '} 90 | 91 | read our goodbye letter 92 | 93 | . 94 |

95 |

96 | If you really need some Pao de Queijo in your life, Rita might be able 97 | to offer a catering option. You can contact her at{' '} 98 | ritaduarte@hotmail.co.uk. 99 |

100 |
101 | )} 102 | 103 | {!this.props.soldout && ( 104 |
105 |

106 | We're a small family business and we only deliver in certain postcodes. 107 | Enter your postcode below and let's hope we can deliver to you. 108 |

109 | 110 | {!this.state.isDeliverable && !this.shouldSkipValidation() && ( 111 | 115 | )} 116 | 117 | {(this.state.isDeliverable || this.shouldSkipValidation()) && ( 118 | 119 | )} 120 | 121 | {!this.state.isValid && this.state.userHasInteracted && ( 122 |

Please enter a valid postcode.

123 | )} 124 | 125 | {this.state.isValid && 126 | !this.state.isDeliverable && 127 | this.state.userHasInteracted && ( 128 |
129 | 133 | 137 |
138 | )} 139 |
140 | )} 141 |
142 |
143 |
144 | ); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/components/Product/ProductHolder.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { CalendarIcon } from '../../icons/CalendarIcon'; 4 | import { shopifyOptions } from './shopifyOptions'; 5 | import { loadScript } from '../../utils/loadScript'; 6 | 7 | export class ProductHolder extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | 11 | this.buyButton = null; 12 | this.qtyInput = null; 13 | this.Product = null; 14 | 15 | this.state = { isBelowMin: false }; 16 | } 17 | 18 | onInputChange = event => { 19 | if (Number(event.target.value) >= this.props.minQty) { 20 | this.buyButton.removeAttribute('disabled'); 21 | this.setState({ isBelowMin: false }); 22 | } else { 23 | this.buyButton.setAttribute('disabled', 'disabled'); 24 | this.setState({ isBelowMin: true }); 25 | } 26 | }; 27 | 28 | afterShopifyInit = component => { 29 | this.Product.selectedQuantity = this.props.initialQty; 30 | 31 | this.buyButton = component.node.querySelector('.shopify-buy__btn'); 32 | this.qtyInput = component.node.querySelector('.shopify-buy__quantity'); 33 | 34 | this.buyButton.setAttribute('disabled', 'disabled'); 35 | 36 | this.qtyInput.addEventListener('change', this.onInputChange); 37 | this.qtyInput.addEventListener('blur', this.onInputChange); 38 | this.qtyInput.addEventListener('keyup', this.onInputChange); 39 | }; 40 | 41 | afterShopifyRender = component => { 42 | if (this.Product.selectedQuantity < this.props.minQty) { 43 | this.Product.selectedQuantity = this.props.minQty; 44 | } 45 | }; 46 | 47 | shopifyReady = ui => { 48 | ui.createComponent('product', { 49 | id: [9367035273], 50 | node: document.getElementById('product-inject'), 51 | moneyFormat: '%C2%A3%7B%7Bamount%7D%7D', 52 | options: shopifyOptions(this.afterShopifyInit, this.afterShopifyRender) 53 | }); 54 | 55 | // 💩 everytime `ui.createComponent` is called is adds a new product to 56 | // to `ui.components.product` array. I can't find a way to append the 57 | // input to the dom without calling `createComponent`. Because of this 58 | // I need to ensure `this.Product` always refers to the latest instance 59 | // of `ui.components.product` 60 | const length = 61 | ui.components.product.length > 1 ? ui.components.product.length - 1 : 0; 62 | this.Product = ui.components.product[length]; 63 | }; 64 | 65 | componentDidMount() { 66 | const shopifyBuyInit = () => { 67 | const client = window.ShopifyBuy.buildClient({ 68 | domain: 'dona-rita.myshopify.com', 69 | apiKey: '260e658ec8cdc689ca1342a79adba733', 70 | appId: '6' 71 | }); 72 | 73 | window.ShopifyBuy.UI.onReady(client).then(ui => this.shopifyReady(ui)); 74 | }; 75 | 76 | if (window.ShopifyBuy && window.ShopifyBuy.UI) { 77 | shopifyBuyInit(); 78 | } else { 79 | loadScript( 80 | 'https://sdks.shopifycdn.com/buy-button/latest/buy-button-storefront.min.js' 81 | ).then(shopifyBuyInit, error => { 82 | throw new Error(`Failed to load script: ${error}`); 83 | }); 84 | } 85 | } 86 | 87 | componentWillUnmount() { 88 | this.qtyInput.removeEventListener('change', this.onInputChange); 89 | this.qtyInput.removeEventListener('blur', this.onInputChange); 90 | this.qtyInput.removeEventListener('keyup', this.onInputChange); 91 | } 92 | 93 | render() { 94 | return ( 95 |
96 |
97 |

98 | Delivery: we will email you to find a convenient delivery 99 | date 100 |

101 |
102 |
103 | {this.state.isBelowMin && ( 104 |

105 | The minimum order quantity is {this.props.minQty}. 106 |

107 | )} 108 |
109 | ); 110 | } 111 | } 112 | 113 | ProductHolder.defaultProps = { 114 | minQty: 2, 115 | initialQty: 2 116 | }; 117 | -------------------------------------------------------------------------------- /src/components/Product/shopifyOptions.js: -------------------------------------------------------------------------------- 1 | export function shopifyOptions(afterInitCb, afterRenderCb) { 2 | return { 3 | product: { 4 | iframe: false, 5 | buttonDestination: 'directCheckout', 6 | variantId: 'all', 7 | width: '240px', 8 | contents: { 9 | img: false, 10 | title: false, 11 | price: false, 12 | description: false, 13 | button: true, 14 | quantity: true, 15 | options: false // Hide variants 16 | }, 17 | text: { 18 | button: 'Buy me' 19 | }, 20 | events: { 21 | afterInit: afterInitCb, 22 | afterRender: afterRenderCb 23 | } 24 | }, 25 | cart: { 26 | popup: false 27 | } 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /src/components/Section/Section.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export function Section({ className, children }) { 4 | return
{children}
; 5 | } 6 | -------------------------------------------------------------------------------- /src/components/ShopRecommendation/ShopRecommendation.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export function ShopRecommendation({ stockists, outcode }) { 4 | return ( 5 |
6 |

🎉 Head down to your nearest store to buy your frozen pack.

7 | {stockists 8 | .filter(({ node }) => node.frontmatter.outcode === outcode) 9 | .map(({ node }) => ( 10 |
11 |
12 | {node.frontmatter.name} 13 |
14 | {node.frontmatter.address} 15 |
16 | {node.frontmatter.postCode} 17 |
18 | 23 | View on map 24 | 25 |
26 | ))} 27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/components/TitleAndMetaTags/TitleAndMetaTags.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Helmet from 'react-helmet'; 3 | 4 | export function TitleAndMetaTags({ url, pathname, title, description }) { 5 | return ( 6 | 7 | 8 | {title} – {description} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ); 28 | } 29 | 30 | TitleAndMetaTags.defaultProps = { 31 | url: 'https://donarita.co.uk', 32 | pathname: '', 33 | title: 'Dona Rita', 34 | description: 'Brazilian cheese bread. Pão de Queijo.' 35 | }; 36 | -------------------------------------------------------------------------------- /src/components/Typewriter/Typewriter.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Typist from 'react-typist'; 3 | 4 | export class Typewriter extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | 8 | this.state = { typing: true, counter: 0 }; 9 | } 10 | 11 | handleDone = () => { 12 | this.setState({ typing: false }, () => { 13 | this.setState(prevState => { 14 | return { typing: true, counter: prevState.counter + 1 }; 15 | }); 16 | }); 17 | }; 18 | 19 | render() { 20 | const typewriter = ( 21 | 22 | 29 | {this.props.texts.map((text, i) => { 30 | return ( 31 | 32 | {text} 33 | 34 | 35 | 36 | ); 37 | })} 38 | {' '} 39 | 40 | ); 41 | 42 | return this.state.typing ? typewriter : null; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/data/postcodes.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "allowed": [ 4 | "E1", 5 | "E11", 6 | "E12", 7 | "E14", 8 | "E15", 9 | "E16", 10 | "E1W", 11 | "E2", 12 | "E20", 13 | "E3", 14 | "E5", 15 | "E6", 16 | "E7", 17 | "E8", 18 | "E9", 19 | "E98", 20 | "EC1", 21 | "EC1A", 22 | "EC1M", 23 | "EC1N", 24 | "EC1P", 25 | "EC1R", 26 | "EC1V", 27 | "EC1Y", 28 | "EC2", 29 | "EC2A", 30 | "EC2M", 31 | "EC2N", 32 | "EC2P", 33 | "EC2R", 34 | "EC2V", 35 | "EC2Y", 36 | "EC3", 37 | "EC3A", 38 | "EC3M", 39 | "EC3N", 40 | "EC3P", 41 | "EC3R", 42 | "EC3V", 43 | "EC4", 44 | "EC4A", 45 | "EC4M", 46 | "EC4N", 47 | "EC4P", 48 | "EC4R", 49 | "EC4V", 50 | "EC4Y", 51 | "EC50", 52 | "N1", 53 | "N16", 54 | "N1P", 55 | "N5", 56 | "E18", 57 | "IG2", 58 | "IG7", 59 | "IG8", 60 | "IG9", 61 | "IG10" 62 | ] 63 | } 64 | ] 65 | -------------------------------------------------------------------------------- /src/icons/BurgerIcon.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export function BurgerIcon() { 4 | return ( 5 | 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/icons/CalendarIcon.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export function CalendarIcon() { 4 | return ( 5 | 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/icons/CrossIcon.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export function CrossIcon() { 4 | return ( 5 | 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/images/banner-mobile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/banner-mobile.jpg -------------------------------------------------------------------------------- /src/images/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/banner.jpg -------------------------------------------------------------------------------- /src/images/eatit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/eatit.jpg -------------------------------------------------------------------------------- /src/images/family.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/family.jpg -------------------------------------------------------------------------------- /src/images/full-width-image-dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/full-width-image-dark.jpg -------------------------------------------------------------------------------- /src/images/full-width-image-rolling.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/full-width-image-rolling.jpg -------------------------------------------------------------------------------- /src/images/full-width-image-twohands.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/full-width-image-twohands.jpg -------------------------------------------------------------------------------- /src/images/img-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/img-1.jpg -------------------------------------------------------------------------------- /src/images/img-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/img-2.jpg -------------------------------------------------------------------------------- /src/images/ingredients.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/ingredients.jpg -------------------------------------------------------------------------------- /src/images/press-logos/evening-standard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/press-logos/evening-standard.png -------------------------------------------------------------------------------- /src/images/press-logos/londonist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/press-logos/londonist.png -------------------------------------------------------------------------------- /src/images/press-logos/metro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/press-logos/metro.png -------------------------------------------------------------------------------- /src/images/press-logos/msn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/press-logos/msn.png -------------------------------------------------------------------------------- /src/images/press-logos/munchies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/press-logos/munchies.png -------------------------------------------------------------------------------- /src/images/press-logos/the-sunday-times.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/press-logos/the-sunday-times.png -------------------------------------------------------------------------------- /src/images/press-logos/timeout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/press-logos/timeout.png -------------------------------------------------------------------------------- /src/images/press-logos/vice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/press-logos/vice.png -------------------------------------------------------------------------------- /src/images/product.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/product.png -------------------------------------------------------------------------------- /src/images/rita.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/rita.jpg -------------------------------------------------------------------------------- /src/images/social/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/social/16x16.png -------------------------------------------------------------------------------- /src/images/social/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/social/32x32.png -------------------------------------------------------------------------------- /src/images/social/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/social/64x64.png -------------------------------------------------------------------------------- /src/images/social/hero-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/social/hero-1.jpg -------------------------------------------------------------------------------- /src/images/social/hero-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/social/hero-2.jpg -------------------------------------------------------------------------------- /src/images/social/hero-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/social/hero-3.jpg -------------------------------------------------------------------------------- /src/images/social/hero-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/social/hero-4.jpg -------------------------------------------------------------------------------- /src/images/social/voucher.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/social/voucher.jpg -------------------------------------------------------------------------------- /src/images/stock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/stock.jpg -------------------------------------------------------------------------------- /src/images/story.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peduarte/dona-rita-website/046b481ecc8144cbd456bba1399832c32be79b6a/src/images/story.jpg -------------------------------------------------------------------------------- /src/layouts/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Helmet from 'react-helmet'; 3 | import ReactGA from 'react-ga'; 4 | 5 | import { Nav } from '../components/Nav/Nav'; 6 | import '../_sass/index.scss'; 7 | 8 | const faviconSizes = [16, 32, 64]; 9 | 10 | export default class Layout extends React.Component { 11 | constructor(props) { 12 | super(props); 13 | this.state = { 14 | showSadNotice: true 15 | }; 16 | } 17 | 18 | componentDidMount() { 19 | const { location } = this.props; 20 | ReactGA.initialize('UA-88250609-1'); 21 | ReactGA.pageview(location.pathname + location.search); 22 | } 23 | 24 | componentDidUpdate() { 25 | const { location } = this.props; 26 | ReactGA.pageview(location.pathname + location.search); 27 | } 28 | 29 | render() { 30 | const { children } = this.props; 31 | return ( 32 |
33 | 34 | 38 | {faviconSizes.map(size => ( 39 | 46 | ))} 47 | 48 | {this.state.showSadNotice && ( 49 |
50 |
51 |

Time to say goodbye.

52 |

53 | After a year filled with cheese balls, excitement and hard work we're 54 | stopping our project to focus on other life events. We had a blast and we 55 | couldn't have done it without you - Thank you! You can still browse our 56 | website but the online shop is now closed. If you'd like to know more 57 | about our closure,{' '} 58 | 59 | read our goodbye letter 60 | 61 | . 62 |

63 |

64 | If you really need some Pão de Queijo in your life, Rita might be able to 65 | offer a catering option. You can contact her at{' '} 66 | ritaduarte@hotmail.co.uk. 67 |

68 | 71 |
72 |
73 | )} 74 |
77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/pages/404.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const NotFoundPage = () => ( 4 |
5 |

NOT FOUND

6 |

You just hit a route that doesn't exist... the sadness.

7 |
8 | ); 9 | 10 | export default NotFoundPage; 11 | -------------------------------------------------------------------------------- /src/pages/buy.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { graphql } from 'gatsby'; 3 | 4 | import { TitleAndMetaTags } from '../components/TitleAndMetaTags/TitleAndMetaTags'; 5 | import { Section } from '../components/Section/Section'; 6 | import { Contact } from '../components/Contact/Contact'; 7 | import { Product } from '../components/Product/Product'; 8 | import { Banner } from '../components/Banner/Banner'; 9 | 10 | import bannerMobileImg from '../images/banner-mobile.jpg'; 11 | import bannerImg from '../images/banner.jpg'; 12 | 13 | function BuyPage({ data, location }) { 14 | return ( 15 |
16 | 17 | 18 | 24 | 25 | 26 | 27 |
28 | 29 |
30 |
31 | ); 32 | } 33 | 34 | // eslint-disable-next-line no-undef 35 | export const pageQuery = graphql` 36 | query BuyPageQuery { 37 | allPostcodesJson { 38 | edges { 39 | node { 40 | allowed 41 | } 42 | } 43 | } 44 | allMarkdownRemark { 45 | edges { 46 | node { 47 | frontmatter { 48 | name 49 | address 50 | postcode 51 | outcode 52 | } 53 | } 54 | } 55 | } 56 | } 57 | `; 58 | 59 | export default BuyPage; 60 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link, graphql } from 'gatsby'; 3 | import Script from 'react-load-script'; 4 | 5 | // import { Layout } from '../components/Layout/Layout'; 6 | import { TitleAndMetaTags } from '../components/TitleAndMetaTags/TitleAndMetaTags'; 7 | import { Section } from '../components/Section/Section'; 8 | import { Product } from '../components/Product/Product'; 9 | import { Contact } from '../components/Contact/Contact'; 10 | import { Typewriter } from '../components/Typewriter/Typewriter'; 11 | import { Banner } from '../components/Banner/Banner'; 12 | import { FakeImg } from '../components/FakeImg/FakeImg'; 13 | 14 | import img1 from '../images/img-1.jpg'; 15 | import img2 from '../images/img-2.jpg'; 16 | import ritaImg from '../images/rita.jpg'; 17 | import bannerImg from '../images/banner.jpg'; 18 | import bannerMobileImg from '../images/banner-mobile.jpg'; 19 | 20 | function handleScriptLoad() { 21 | if (typeof window !== `undefined` && window.netlifyIdentity) { 22 | window.netlifyIdentity.on('init', user => { 23 | if (!user) { 24 | window.netlifyIdentity.on('login', () => { 25 | document.location.href = '/admin/'; 26 | }); 27 | } 28 | }); 29 | } 30 | window.netlifyIdentity.init(); 31 | } 32 | 33 | function IndexPage({ data, location }) { 34 | return ( 35 | // 36 |
37 |