",
8 | "license": "UNLICENSED",
9 | "scripts": {
10 | "lint": "eslint .",
11 | "start": "sanity start"
12 | },
13 | "keywords": [
14 | "sanity"
15 | ],
16 | "eslintConfig": {
17 | "extends": [
18 | "wesbos"
19 | ]
20 | },
21 | "dependencies": {
22 | "@sanity/base": "1.150.7",
23 | "@sanity/components": "1.150.7",
24 | "@sanity/core": "1.150.7",
25 | "@sanity/default-layout": "1.150.7",
26 | "@sanity/default-login": "1.150.1",
27 | "@sanity/desk-tool": "1.150.7",
28 | "@sanity/google-maps-input": "1.150.1",
29 | "@sanity/structure": "1.150.1",
30 | "@sanity/vision": "1.150.6",
31 | "prop-types": "^15.7",
32 | "react": "^16.13",
33 | "react-dom": "^16.13",
34 | "react-icons": "^3.11.0"
35 | },
36 | "devDependencies": {
37 | "babel-eslint": "^10.1.0",
38 | "eslint": "^7.8.1",
39 | "eslint-config-airbnb": "^18.2.0",
40 | "eslint-config-prettier": "^6.11.0",
41 | "eslint-config-wesbos": "^1.0.1",
42 | "eslint-plugin-html": "^6.1.0",
43 | "eslint-plugin-import": "^2.22.0",
44 | "eslint-plugin-jsx-a11y": "^6.3.1",
45 | "eslint-plugin-prettier": "^3.1.4",
46 | "eslint-plugin-react": "^7.20.6",
47 | "eslint-plugin-react-hooks": "^4.1.2",
48 | "prettier": "^2.1.1"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/all-sample-data.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesbos/master-gatsby/f5f152583ed638f658498686809dffc96d45e73e/starter-files/sanity/sample-data/all-sample-data.gz
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/delete-data-example.js:
--------------------------------------------------------------------------------
1 | /**
2 | * THIS SCRIPT DELETES DATA!
3 | *
4 | * To use this script:
5 | * 1. Put this script in your studio-folder
6 | * 2. Write a GROQ filter that outputs the documents you want to delete
7 | * 3. Run `sanity dataset export` to backup your dataset before deleting a bunch of documents
8 | * 4. Run `sanity exec blowItAllAway.js --with-user-token` to delete the documents
9 | *
10 | * NOTE: For the time being you should not delete more than ~1000 documents in one transaction. This will change in the future.
11 | * See docs:https://www.sanity.io/docs/http-api/http-mutations#deleting-multiple-documents-by-query
12 | */
13 |
14 | import client from 'part:@sanity/base/client';
15 |
16 | client
17 | // .delete({ query: '*[!defined(name) && _type == "topping"] ' })
18 | .delete({ query: '*[_type == "pizza"] ' })
19 | .then(console.log)
20 | .catch(console.error);
21 |
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/nice-pizza-pics/034da1be8a1e201f7a9d19db7ab7aaaccff4b36e-640x640.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesbos/master-gatsby/f5f152583ed638f658498686809dffc96d45e73e/starter-files/sanity/sample-data/nice-pizza-pics/034da1be8a1e201f7a9d19db7ab7aaaccff4b36e-640x640.jpg
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/nice-pizza-pics/03d72be92884e8fc493d421ea36fc0d55c9db092-1023x685.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesbos/master-gatsby/f5f152583ed638f658498686809dffc96d45e73e/starter-files/sanity/sample-data/nice-pizza-pics/03d72be92884e8fc493d421ea36fc0d55c9db092-1023x685.jpg
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/nice-pizza-pics/17df1249c1023ea580cadc014e040075f266020c-1834x2747.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesbos/master-gatsby/f5f152583ed638f658498686809dffc96d45e73e/starter-files/sanity/sample-data/nice-pizza-pics/17df1249c1023ea580cadc014e040075f266020c-1834x2747.jpg
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/nice-pizza-pics/1cf1657199f62951cffb774a7def8e145016d83b-900x675.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesbos/master-gatsby/f5f152583ed638f658498686809dffc96d45e73e/starter-files/sanity/sample-data/nice-pizza-pics/1cf1657199f62951cffb774a7def8e145016d83b-900x675.webp
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/nice-pizza-pics/29ef01719f3213f039098a6fdfd9b0c3b8c33951-512x338.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesbos/master-gatsby/f5f152583ed638f658498686809dffc96d45e73e/starter-files/sanity/sample-data/nice-pizza-pics/29ef01719f3213f039098a6fdfd9b0c3b8c33951-512x338.jpg
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/nice-pizza-pics/3005c4b2042ac18559ea32eb1c168e6ae576e96c-3034x4551.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesbos/master-gatsby/f5f152583ed638f658498686809dffc96d45e73e/starter-files/sanity/sample-data/nice-pizza-pics/3005c4b2042ac18559ea32eb1c168e6ae576e96c-3034x4551.jpg
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/nice-pizza-pics/5d85f921bbd8f5c4e7996a75b5487ac3bfd3d939-1024x683.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesbos/master-gatsby/f5f152583ed638f658498686809dffc96d45e73e/starter-files/sanity/sample-data/nice-pizza-pics/5d85f921bbd8f5c4e7996a75b5487ac3bfd3d939-1024x683.jpg
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/nice-pizza-pics/65fa2d78cf5837fdab395d3bd69b696b49dda894-1024x1024.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesbos/master-gatsby/f5f152583ed638f658498686809dffc96d45e73e/starter-files/sanity/sample-data/nice-pizza-pics/65fa2d78cf5837fdab395d3bd69b696b49dda894-1024x1024.jpg
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/nice-pizza-pics/67345e624204179722cc14cdc15f307d37105990-960x863.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesbos/master-gatsby/f5f152583ed638f658498686809dffc96d45e73e/starter-files/sanity/sample-data/nice-pizza-pics/67345e624204179722cc14cdc15f307d37105990-960x863.jpg
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/nice-pizza-pics/734bcf0b51c6d732082cc9fa0b638cdbb42c3bee-1600x1200.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesbos/master-gatsby/f5f152583ed638f658498686809dffc96d45e73e/starter-files/sanity/sample-data/nice-pizza-pics/734bcf0b51c6d732082cc9fa0b638cdbb42c3bee-1600x1200.jpg
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/nice-pizza-pics/7f7b7f9a4bf9f06192a5f1df8090cc7acc2706c6-640x640.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesbos/master-gatsby/f5f152583ed638f658498686809dffc96d45e73e/starter-files/sanity/sample-data/nice-pizza-pics/7f7b7f9a4bf9f06192a5f1df8090cc7acc2706c6-640x640.jpg
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/nice-pizza-pics/8cd0ae8e034d24856b0d42c467d56e4ca9af546c-2850x1900.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesbos/master-gatsby/f5f152583ed638f658498686809dffc96d45e73e/starter-files/sanity/sample-data/nice-pizza-pics/8cd0ae8e034d24856b0d42c467d56e4ca9af546c-2850x1900.jpg
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/nice-pizza-pics/91a6c349de9dc5152cac94be2ae20f095818f3a6-2850x1900.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesbos/master-gatsby/f5f152583ed638f658498686809dffc96d45e73e/starter-files/sanity/sample-data/nice-pizza-pics/91a6c349de9dc5152cac94be2ae20f095818f3a6-2850x1900.jpg
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/nice-pizza-pics/9467ef83787cb3cf05718c6581645ed87b7e9438-1482x2635.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesbos/master-gatsby/f5f152583ed638f658498686809dffc96d45e73e/starter-files/sanity/sample-data/nice-pizza-pics/9467ef83787cb3cf05718c6581645ed87b7e9438-1482x2635.jpg
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/nice-pizza-pics/9a37391565f0eef2c53072e4e91baf112332ad95-1024x768.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesbos/master-gatsby/f5f152583ed638f658498686809dffc96d45e73e/starter-files/sanity/sample-data/nice-pizza-pics/9a37391565f0eef2c53072e4e91baf112332ad95-1024x768.jpg
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/nice-pizza-pics/9afb00db57152a6f4a0fa85324ca62ad6c1af0f0-900x900.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesbos/master-gatsby/f5f152583ed638f658498686809dffc96d45e73e/starter-files/sanity/sample-data/nice-pizza-pics/9afb00db57152a6f4a0fa85324ca62ad6c1af0f0-900x900.webp
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/nice-pizza-pics/a7fa379273fd9857d136e443b97cae563214a655-1460x2190.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesbos/master-gatsby/f5f152583ed638f658498686809dffc96d45e73e/starter-files/sanity/sample-data/nice-pizza-pics/a7fa379273fd9857d136e443b97cae563214a655-1460x2190.jpg
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/nice-pizza-pics/a9d09b77001d7bf96de06c7308e68f80e0c7e9dc-1834x2746.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesbos/master-gatsby/f5f152583ed638f658498686809dffc96d45e73e/starter-files/sanity/sample-data/nice-pizza-pics/a9d09b77001d7bf96de06c7308e68f80e0c7e9dc-1834x2746.jpg
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/nice-pizza-pics/d786419d6931c2f898f93bfd0a6f083c12b9ee8c-1024x768.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesbos/master-gatsby/f5f152583ed638f658498686809dffc96d45e73e/starter-files/sanity/sample-data/nice-pizza-pics/d786419d6931c2f898f93bfd0a6f083c12b9ee8c-1024x768.jpg
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/nice-pizza-pics/e74f73f9fe571f5c1ec1d11e5741c6fb17a0e58f-2934x1693.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesbos/master-gatsby/f5f152583ed638f658498686809dffc96d45e73e/starter-files/sanity/sample-data/nice-pizza-pics/e74f73f9fe571f5c1ec1d11e5741c6fb17a0e58f-2934x1693.jpg
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/nice-pizza-pics/f3834acd68a85761d818067fa868d708937b337a-2600x2600.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesbos/master-gatsby/f5f152583ed638f658498686809dffc96d45e73e/starter-files/sanity/sample-data/nice-pizza-pics/f3834acd68a85761d818067fa868d708937b337a-2600x2600.jpg
--------------------------------------------------------------------------------
/starter-files/sanity/sample-data/text-data.md:
--------------------------------------------------------------------------------
1 | chimi hendirx
2 | * Chimichurri
3 | * Squash
4 | * Mushrooms
5 | * Roasted Garlic
6 |
7 | Here Comes Truffle
8 | * Roasted Garlic
9 | * Mushrooms
10 | * Truffle
11 |
12 | Dough Exotic
13 | * Steak
14 | * sardine oil
15 | * Chimichurri
16 |
17 | The Friendship Ender
18 | * Pineapple
19 | * Ham
20 | * Hot Peppers
21 |
22 | Piggy Smalls
23 | * Pulled Pork
24 | * Pepperoni
25 | * Bacon
26 | * Sausage
27 | * Onions
28 |
29 | White after Labour Day
30 | * Peppers
31 | * Olives
32 | * Basil
33 | * Tomatoes
34 |
35 | Nacho Average Pizza
36 | * Hot Peppers
37 | * Avocado Crema
38 | * Shredded Lettuce
39 | * Ground Beef
40 |
41 | The Bee Sting
42 | * Soppressata
43 | * Honey
44 |
45 | The Pear Necessities
46 | * Pear
47 | * Sweet Potato
48 | * Spinach
49 | * Honey
50 |
51 | Pepperphony
52 | * Vegan Pepperoni
53 | * Vegan Cheese
54 | * Peppers
55 | * Basil
56 |
57 | Holy Shiitake
58 | * Roasted Garlic
59 | * Mushrooms
60 | * Onions
61 | * Truffle
62 |
63 | Cluck Norris
64 | * Chicken
65 | * Onions
66 | * Hot Peppers
67 |
68 |
69 | ## people
70 |
71 |
72 | Slick is the proprietor, he inherited the business from his late grandfather. Refers to himself as the Pablo Escobar of pizza. He grows his own tomatoes and won’t let anyone else near them...a little odd but we like him.
73 |
74 | Roscoe is the leftover manager. Loves Puperoni. Currently on a diet.
75 |
76 | After spending a summer in the New Mexico desert, August became passionate about circular foods. First it was waffles, now it is pizza.
77 |
78 | Some say Waldo is searching for the freshest ingredients, but in reality pizza isn't a passion, it's a paycheck. Deep down, Waldo is still trying to find himself.
79 |
80 | Oak grows his own basil at home and brings it in. While he loves pizza, it pays the bills while he works on his hot sauce startup. Went his whole life calling the crust a "Pizza Border".
81 |
82 | Louise runs an Etsy shop that sells hand-painted elephant ceramics customized based on your astrology sign. Judges you for ordering extra sauce.
83 |
84 | Enoch played guitar in a ska band but was forced out due to creative differences. Claim to fame is that a picture of Enoch comes up first when you Google 'people enjoying Italian street foods'.
85 |
86 | Piper went to Italy once. Will tell you how much better the pizza is 'over there'. She has no college debt.
87 |
88 | Fia qualified for Canada's 2018 rhythmic gymnastics olympic but left the sport to focus full-time on pizza, where she's developing a new kind of pizza toss (a very fast one where you do a flip)
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/starter-files/sanity/sanity.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "api": {
4 | "projectId": "kutp3su7",
5 | "dataset": "production"
6 | },
7 | "project": {
8 | "name": "slicks-slices"
9 | },
10 | "plugins": [
11 | "@sanity/base",
12 | "@sanity/components",
13 | "@sanity/default-layout",
14 | "@sanity/default-login",
15 | "@sanity/desk-tool",
16 | "@sanity/google-maps-input"
17 | ],
18 | "env": {
19 | "development": {
20 | "plugins": [
21 | "@sanity/vision"
22 | ]
23 | }
24 | },
25 | "parts": [
26 | {
27 | "name": "part:@sanity/base/schema",
28 | "path": "./schemas/schema"
29 | }
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/starter-files/sanity/schemas/schema.js:
--------------------------------------------------------------------------------
1 | // First, we must import the schema creator
2 | import createSchema from 'part:@sanity/base/schema-creator';
3 | // Then import schema types from any plugins that might expose them
4 | import schemaTypes from 'all:part:@sanity/base/schema-type';
5 | // Then we give our schema to the builder and provide the result to Sanity
6 | export default createSchema({
7 | // We name our schema
8 | name: 'default',
9 | // Then proceed to concatenate our document type
10 | // to the ones provided by any plugins that are installed
11 | types: schemaTypes.concat([]),
12 | });
13 |
--------------------------------------------------------------------------------
/starter-files/sanity/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wesbos/master-gatsby/f5f152583ed638f658498686809dffc96d45e73e/starter-files/sanity/static/.gitkeep
--------------------------------------------------------------------------------
/stepped-solutions/04/404.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function FourOhFourPage() {
4 | return Hey - That page doesn't exist!!!
;
5 | }
6 |
--------------------------------------------------------------------------------
/stepped-solutions/04/beers.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Nav from '../components/Nav';
3 |
4 | export default function BeersPage() {
5 | return (
6 | <>
7 | Hey! I'm the Beers page
8 | >
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/stepped-solutions/04/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Nav from '../components/Nav';
3 | import Layout from '../components/Layout';
4 |
5 | export default function HomePage() {
6 | return (
7 | <>
8 | Hey! I'm the home page
9 | Hey I'm another element
10 | >
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/stepped-solutions/04/order.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function OrderPage() {
4 | return (
5 | <>
6 | Hey! I'm the Order page
7 | >
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/stepped-solutions/04/pizzas.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Layout from '../components/Layout';
3 |
4 | export default function PizzasPage() {
5 | return (
6 | <>
7 | Hey! I'm the Pizzas page
8 | >
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/stepped-solutions/04/slicemasters.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Nav from '../components/Nav';
3 |
4 | export default function SlicemastersPage() {
5 | return (
6 | <>
7 | Hey! I'm the Slicemasters page
8 | >
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/stepped-solutions/05/Nav.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'gatsby';
3 |
4 | export default function Nav() {
5 | return (
6 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/stepped-solutions/06/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function Footer() {
4 | return (
5 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/stepped-solutions/06/Layout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Nav from './Nav';
3 | import Footer from './Footer';
4 |
5 | export default function Layout({ children }) {
6 | return (
7 |
8 |
9 | {children}
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/stepped-solutions/06/gatsby-browser.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Layout from './src/components/Layout';
3 |
4 | export function wrapPageElement({ element, props }) {
5 | return {element};
6 | }
7 |
--------------------------------------------------------------------------------
/stepped-solutions/06/gatsby-ssr.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Layout from './src/components/Layout';
3 |
4 | export function wrapPageElement({ element, props }) {
5 | return {element};
6 | }
7 |
--------------------------------------------------------------------------------
/stepped-solutions/07/GlobalStyles.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 | import bg from '../assets/images/bg.svg';
3 | import stripes from '../assets/images/stripes.svg';
4 |
5 | const GlobalStyles = createGlobalStyle`
6 | :root {
7 | --red: #FF4949;
8 | --black: #2E2E2E;
9 | --yellow: #ffc600;
10 | --white: #fff;
11 | --grey: #efefef;
12 | }
13 | html {
14 | background-image: url(${bg});
15 | background-size: 450px;
16 | background-attachment: fixed;
17 | font-size: 10px;
18 | }
19 |
20 | body {
21 | font-size: 2rem;
22 | }
23 |
24 | fieldset {
25 | border-color: rgba(0,0,0,0.1);
26 | border-width: 1px;
27 | }
28 |
29 | button {
30 | background: var(--red);
31 | color: white;
32 | border: 0;
33 | padding: 0.6rem 1rem;
34 | border-radius: 2px;
35 | cursor: pointer;
36 | --cast: 2px;
37 | box-shadow: var(--cast) var(--cast) 0 var(--grey);
38 | text-shadow: 0.5px 0.5px 0 rgba(0,0,0,0.2);
39 | transition: all 0.2s;
40 | &:hover {
41 | --cast: 4px;
42 | }
43 | }
44 |
45 | .gatsby-image-wrapper img[src*=base64\\,] {
46 | image-rendering: -moz-crisp-edges;
47 | image-rendering: pixelated;
48 | }
49 |
50 | /* Scrollbar Styles */
51 | body::-webkit-scrollbar {
52 | width: 12px;
53 | }
54 | html {
55 | scrollbar-width: thin;
56 | scrollbar-color: var(--red) var(--white);
57 | }
58 | body::-webkit-scrollbar-track {
59 | background: var(--white);
60 | }
61 | body::-webkit-scrollbar-thumb {
62 | background-color: var(--red) ;
63 | border-radius: 6px;
64 | border: 3px solid var(--white);
65 | }
66 |
67 | hr {
68 | border: 0;
69 | height: 8px;
70 | background-image: url(${stripes});
71 | background-size: 1500px;
72 | }
73 |
74 | img {
75 | max-width: 100%;
76 | }
77 |
78 | `;
79 |
80 | export default GlobalStyles;
81 |
--------------------------------------------------------------------------------
/stepped-solutions/08/Typography.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 |
3 | import font from '../assets/fonts/frenchfries.woff';
4 |
5 | const Typography = createGlobalStyle`
6 | @font-face {
7 | font-family: FrenchFries;
8 | src: url(${font});
9 | }
10 | html {
11 | font-family: FrenchFries, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
12 | color: var(--black);
13 | }
14 | p, li {
15 | letter-spacing: 0.5px;
16 | }
17 | h1,h2,h3,h4,h5,h6 {
18 | font-weight: normal;
19 | margin: 0;
20 | }
21 | a {
22 | color: var(--black);
23 | text-decoration-color: var(--red);
24 | /* Chrome renders this weird with this font, so we turn it off */
25 | text-decoration-skip-ink: none;
26 | }
27 | mark, .mark {
28 | background: var(--yellow);
29 | padding: 0 2px 2px 2px;
30 | margin: 0;
31 | display: inline;
32 | line-height: 1;
33 | }
34 |
35 | .center {
36 | text-align: center;
37 | }
38 |
39 | .tilt {
40 | transform: rotate(-2deg);
41 | }
42 | `;
43 |
44 | export default Typography;
45 |
--------------------------------------------------------------------------------
/stepped-solutions/09/Logo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import stripes from '../assets/images/stripes.svg';
4 |
5 | const LogoStyles = styled.div`
6 | /* This value controls the entire size of the logo*/
7 | font-size: clamp(1px, 0.65vw, 8px);
8 | width: 30em;
9 | height: 30em;
10 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);
11 | margin: 0;
12 | --borderSize: 1em;
13 | background: white url(${stripes});
14 | background-size: 150em;
15 | border: var(--borderSize) solid white;
16 | display: flex;
17 | .inner {
18 | margin: var(--borderSize);
19 | flex: 1;
20 | background: white;
21 | display: grid;
22 | grid-template-rows: 20% 1fr 1fr;
23 | align-content: center;
24 | }
25 | .est {
26 | font-size: 1.5em;
27 | align-self: center;
28 | }
29 | h1 {
30 | display: grid;
31 | grid-template-rows: 8fr 2fr;
32 | align-items: center;
33 | margin: 0;
34 | grid-row: 2 / span 2;
35 | grid-gap: 2em;
36 | transform: translateY(-0.7em);
37 | }
38 |
39 | .slices {
40 | font-size: 3.2em;
41 | letter-spacing: 0.2em;
42 | transform: translateY(-0.15em);
43 | }
44 | .slicks {
45 | transform: scale(1.4);
46 | display: block;
47 | text-shadow: 0.18em 0.18em 0 rgba(0, 0, 0, 0.05);
48 | perspective: 100px;
49 | }
50 | .letter {
51 | font-size: 5em;
52 | color: var(--red);
53 | --scale: 1;
54 | --rotate: -10deg;
55 | --translateX: 0;
56 | --translateY: 0;
57 | --rotateX: 0deg;
58 | transform: scale(var(--scale)) rotate(var(--rotate))
59 | translateX(var(--translateX)) translateY(var(--translateY))
60 | rotateX(var(--rotateX));
61 | display: inline-block;
62 | line-height: 1;
63 | transition: transform 0.3s;
64 | &.S {
65 | --translateX: -0.05;
66 | }
67 | &.l {
68 | --rotate: 2deg;
69 | --scale: 1.4;
70 | --translateX: 0.05em;
71 | --translateY: -0.05em;
72 | }
73 | &.i {
74 | --scale: 0.9;
75 | --translateY: -0.1em;
76 | --translateX: 0.1em;
77 | }
78 | &.c {
79 | --rotate: 3deg;
80 | --scale: 0.9;
81 | --translateX: 0.1em;
82 | --translateY: 0.23em;
83 | }
84 | &.k {
85 | --rotate: -12deg;
86 | --scale: 1.2;
87 | --translateX: 0.06em;
88 | }
89 | &.apos {
90 | --translateX: 0.1em;
91 | }
92 | &.s {
93 | --rotate: 12deg;
94 | --scale: 0.9;
95 | --translateY: -0.14em;
96 | }
97 | }
98 | `;
99 |
100 | export default function Logo() {
101 | return (
102 |
103 |
104 | EST 1994
105 |
106 |
107 | S
108 | l
109 | i
110 | c
111 | k
112 | '
113 | s
114 |
115 | slices
116 |
117 |
118 |
119 | );
120 | }
121 |
--------------------------------------------------------------------------------
/stepped-solutions/09/Nav.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'gatsby';
3 | import styled from 'styled-components';
4 | import Logo from './Logo';
5 |
6 | const NavStyles = styled.nav`
7 | margin-bottom: 3rem;
8 | .logo {
9 | transform: translateY(-25%);
10 | }
11 | ul {
12 | margin: 0;
13 | padding: 0;
14 | text-align: center;
15 | list-style: none;
16 | display: grid;
17 | grid-template-columns: 1fr 1fr auto 1fr 1fr;
18 | grid-gap: 2rem;
19 | align-items: center;
20 | margin-top: -6rem;
21 | }
22 | li {
23 | --rotate: -2deg;
24 | transform: rotate(var(--rotate));
25 | order: 1;
26 | &:nth-child(1) {
27 | --rotate: 1deg;
28 | }
29 | &:nth-child(2) {
30 | --rotate: -2.5deg;
31 | }
32 | &:nth-child(4) {
33 | --rotate: 2.5deg;
34 | }
35 | &:hover {
36 | --rotate: 3deg;
37 | }
38 | }
39 | a {
40 | font-size: 3rem;
41 | text-decoration: none;
42 | &:hover {
43 | color: var(--red);
44 | }
45 | /* &[aria-current='page'] {
46 | color: var(--red);
47 | } */
48 | }
49 | `;
50 |
51 | export default function Nav() {
52 | return (
53 |
54 |
55 | -
56 | Hot Now
57 |
58 | -
59 | Pizza Menu
60 |
61 | -
62 |
63 |
64 |
65 |
66 | -
67 | SliceMasters
68 |
69 | -
70 | Order Ahead!
71 |
72 |
73 |
74 | );
75 | }
76 |
--------------------------------------------------------------------------------
/stepped-solutions/10/Layout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import 'normalize.css';
4 | import Nav from './Nav';
5 | import Footer from './Footer';
6 | import GlobalStyles from '../styles/GlobalStyles';
7 | import Typography from '../styles/Typography';
8 | import stripes from '../assets/images/stripes.svg';
9 |
10 | const SiteBorderStyles = styled.div`
11 | max-width: 1000px;
12 | margin: 12rem auto 4rem auto;
13 | margin-top: clamp(2rem, 10vw, 12rem);
14 | background: white url(${stripes});
15 | background-size: 1500px;
16 | padding: 5px;
17 | padding: clamp(5px, 1vw, 25px);
18 | box-shadow: 0 0 5px 3px rgba(0, 0, 0, 0.044);
19 | border: 5px solid white;
20 | @media (max-width: 1100px) {
21 | margin-left: 1.5rem;
22 | margin-right: 1.5rem;
23 | }
24 | `;
25 |
26 | const ContentStyles = styled.div`
27 | background: white;
28 | padding: 2rem;
29 | `;
30 |
31 | export default function Layout({ children }) {
32 | return (
33 | <>
34 |
35 |
36 |
37 |
38 |
39 | {children}
40 |
41 |
42 |
43 | >
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/stepped-solutions/11/pizza.js:
--------------------------------------------------------------------------------
1 | import { MdLocalPizza as icon } from 'react-icons/md';
2 |
3 | export default {
4 | // Computer Name
5 | name: 'pizza',
6 | // visible title
7 | title: 'Pizzas',
8 | type: 'document',
9 | icon,
10 | fields: [
11 | {
12 | name: 'name',
13 | title: 'Pizza Name',
14 | type: 'string',
15 | description: 'Name of the pizza',
16 | },
17 | {
18 | name: 'slug',
19 | title: 'Slug',
20 | type: 'slug',
21 | options: {
22 | source: 'name',
23 | maxLength: 100,
24 | },
25 | },
26 | {
27 | name: 'image',
28 | title: 'Image',
29 | type: 'image',
30 | options: {
31 | hotspot: true,
32 | },
33 | },
34 | {
35 | name: 'price',
36 | title: 'Price',
37 | type: 'number',
38 | description: 'Price of the pizza in cents',
39 | validation: Rule => Rule.min(1000),
40 | },
41 | {
42 | name: 'toppings',
43 | title: 'Toppings',
44 | type: 'array',
45 | of: [{ type: 'reference', to: [{ type: 'topping' }] }],
46 | // TODO: Add custom input component
47 | },
48 | ],
49 | };
50 |
--------------------------------------------------------------------------------
/stepped-solutions/11/schema.js:
--------------------------------------------------------------------------------
1 | // First, we must import the schema creator
2 | import createSchema from 'part:@sanity/base/schema-creator';
3 | // Then import schema types from any plugins that might expose them
4 | import schemaTypes from 'all:part:@sanity/base/schema-type';
5 | // Then we give our schema to the builder and provide the result to Sanity
6 |
7 | import pizza from './pizza';
8 |
9 | export default createSchema({
10 | // We name our schema
11 | name: 'default',
12 | // Then proceed to concatenate our document type
13 | // to the ones provided by any plugins that are installed
14 | types: schemaTypes.concat([pizza]),
15 | });
16 |
--------------------------------------------------------------------------------
/stepped-solutions/12/pizza.js:
--------------------------------------------------------------------------------
1 | import { MdLocalPizza as icon } from 'react-icons/md';
2 |
3 | export default {
4 | // Computer Name
5 | name: 'pizza',
6 | // visible title
7 | title: 'Pizzas',
8 | type: 'document',
9 | icon,
10 | fields: [
11 | {
12 | name: 'name',
13 | title: 'Pizza Name',
14 | type: 'string',
15 | description: 'Name of the pizza',
16 | },
17 | {
18 | name: 'slug',
19 | title: 'Slug',
20 | type: 'slug',
21 | options: {
22 | source: 'name',
23 | maxLength: 100,
24 | },
25 | },
26 | {
27 | name: 'image',
28 | title: 'Image',
29 | type: 'image',
30 | options: {
31 | hotspot: true,
32 | },
33 | },
34 | {
35 | name: 'price',
36 | title: 'Price',
37 | type: 'number',
38 | description: 'Price of the pizza in cents',
39 | validation: Rule => Rule.min(1000),
40 | },
41 | {
42 | name: 'toppings',
43 | title: 'Toppings',
44 | type: 'array',
45 | of: [{ type: 'reference', to: [{ type: 'topping' }] }],
46 | // TODO: Add custom input component
47 | },
48 | ],
49 | };
50 |
--------------------------------------------------------------------------------
/stepped-solutions/12/schema.js:
--------------------------------------------------------------------------------
1 | // First, we must import the schema creator
2 | import createSchema from 'part:@sanity/base/schema-creator';
3 | // Then import schema types from any plugins that might expose them
4 | import schemaTypes from 'all:part:@sanity/base/schema-type';
5 | // Then we give our schema to the builder and provide the result to Sanity
6 |
7 | import pizza from './pizza';
8 | import topping from './topping';
9 |
10 | export default createSchema({
11 | // We name our schema
12 | name: 'default',
13 | // Then proceed to concatenate our document type
14 | // to the ones provided by any plugins that are installed
15 | types: schemaTypes.concat([pizza, topping]),
16 | });
17 |
--------------------------------------------------------------------------------
/stepped-solutions/12/topping.js:
--------------------------------------------------------------------------------
1 | import { FaPepperHot as icon } from 'react-icons/fa';
2 |
3 | export default {
4 | // Computer Name
5 | name: 'topping',
6 | // visible title
7 | title: 'Toppings',
8 | type: 'document',
9 | icon,
10 | fields: [
11 | {
12 | name: 'name',
13 | title: 'Pizza Name',
14 | type: 'string',
15 | description: 'What is the name of the topping?',
16 | },
17 | {
18 | name: 'vegetarian',
19 | title: 'Vegetarian',
20 | type: 'boolean',
21 | description: 'What is the name of the topping?',
22 | options: {
23 | layout: 'checkbox',
24 | },
25 | },
26 | ],
27 | preview: {
28 | select: {
29 | name: 'name',
30 | vegetarian: 'vegetarian',
31 | },
32 | prepare: ({ name, vegetarian }) => ({
33 | title: `${name} ${vegetarian ? '🌱' : ''}`,
34 | }),
35 | },
36 | };
37 |
--------------------------------------------------------------------------------
/stepped-solutions/13/pizza.js:
--------------------------------------------------------------------------------
1 | import { MdLocalPizza as icon } from 'react-icons/md';
2 |
3 | export default {
4 | // Computer Name
5 | name: 'pizza',
6 | // visible title
7 | title: 'Pizzas',
8 | type: 'document',
9 | icon,
10 | fields: [
11 | {
12 | name: 'name',
13 | title: 'Pizza Name',
14 | type: 'string',
15 | description: 'Name of the pizza',
16 | },
17 | {
18 | name: 'slug',
19 | title: 'Slug',
20 | type: 'slug',
21 | options: {
22 | source: 'name',
23 | maxLength: 100,
24 | },
25 | },
26 | {
27 | name: 'image',
28 | title: 'Image',
29 | type: 'image',
30 | options: {
31 | hotspot: true,
32 | },
33 | },
34 | {
35 | name: 'price',
36 | title: 'Price',
37 | type: 'number',
38 | description: 'Price of the pizza in cents',
39 | validation: Rule => Rule.min(1000),
40 | // TODO: Create custom input component
41 | },
42 | {
43 | name: 'toppings',
44 | title: 'Toppings',
45 | type: 'array',
46 | of: [{ type: 'reference', to: [{ type: 'topping' }] }],
47 | },
48 | ],
49 | preview: {
50 | select: {
51 | title: 'name',
52 | media: 'image',
53 | topping0: 'toppings.0.name',
54 | topping1: 'toppings.1.name',
55 | topping2: 'toppings.2.name',
56 | topping3: 'toppings.3.name',
57 | },
58 | prepare: ({ title, media, ...toppings }) => {
59 | // 1. Filter undefined toppings out
60 | const tops = Object.values(toppings).filter(Boolean);
61 | // 2. return the preview object for the pizza
62 | return {
63 | title,
64 | media,
65 | subtitle: tops.join(', '),
66 | };
67 | },
68 | },
69 | };
70 |
--------------------------------------------------------------------------------
/stepped-solutions/14/person.js:
--------------------------------------------------------------------------------
1 | import { MdPerson as icon } from 'react-icons/md';
2 |
3 | export default {
4 | // Computer Name
5 | name: 'person',
6 | // visible title
7 | title: 'Slicemasters',
8 | type: 'document',
9 | icon,
10 | fields: [
11 | {
12 | name: 'name',
13 | title: 'Name',
14 | type: 'string',
15 | },
16 | {
17 | name: 'slug',
18 | title: 'Slug',
19 | type: 'slug',
20 | options: {
21 | source: 'name',
22 | maxLength: 100,
23 | },
24 | },
25 | {
26 | name: 'description',
27 | title: 'Description',
28 | type: 'text',
29 | description: 'Tell us a bit about this person',
30 | },
31 | {
32 | name: 'image',
33 | title: 'Image',
34 | type: 'image',
35 | options: {
36 | hotspot: true,
37 | },
38 | },
39 | ],
40 | };
41 |
--------------------------------------------------------------------------------
/stepped-solutions/14/schema.js:
--------------------------------------------------------------------------------
1 | // First, we must import the schema creator
2 | import createSchema from 'part:@sanity/base/schema-creator';
3 | // Then import schema types from any plugins that might expose them
4 | import schemaTypes from 'all:part:@sanity/base/schema-type';
5 | // Then we give our schema to the builder and provide the result to Sanity
6 |
7 | import pizza from './pizza';
8 | import topping from './topping';
9 | import person from './person';
10 |
11 | export default createSchema({
12 | // We name our schema
13 | name: 'default',
14 | // Then proceed to concatenate our document type
15 | // to the ones provided by any plugins that are installed
16 | types: schemaTypes.concat([pizza, topping, person]),
17 | });
18 |
--------------------------------------------------------------------------------
/stepped-solutions/15/PriceInput.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PatchEvent, { set, unset } from 'part:@sanity/form-builder/patch-event';
3 |
4 | function createPatchFrom(value) {
5 | return PatchEvent.from(value === '' ? unset() : set(Number(value)));
6 | }
7 |
8 | const formatMoney = Intl.NumberFormat('en-CA', {
9 | style: 'currency',
10 | currency: 'CAD',
11 | }).format;
12 |
13 | export default function PriceInput({ type, value, onChange, inputComponent }) {
14 | return (
15 |
16 |
17 | {type.title} - {value ? formatMoney(value / 100) : ''}
18 |
19 |
{type.description}
20 |
onChange(createPatchFrom(event.target.value))}
24 | ref={inputComponent}
25 | />
26 |
27 | );
28 | }
29 |
30 | PriceInput.focus = function() {
31 | this._inputElement.focus();
32 | };
33 |
--------------------------------------------------------------------------------
/stepped-solutions/15/person.js:
--------------------------------------------------------------------------------
1 | import { MdPerson as icon } from 'react-icons/md';
2 |
3 | export default {
4 | // Computer Name
5 | name: 'person',
6 | // visible title
7 | title: 'Slicemasters',
8 | type: 'document',
9 | icon,
10 | fields: [
11 | {
12 | name: 'name',
13 | title: 'Name',
14 | type: 'string',
15 | },
16 | {
17 | name: 'slug',
18 | title: 'Slug',
19 | type: 'slug',
20 | options: {
21 | source: 'name',
22 | maxLength: 100,
23 | },
24 | },
25 | {
26 | name: 'description',
27 | title: 'Description',
28 | type: 'text',
29 | description: 'Tell us a bit about this person',
30 | },
31 | {
32 | name: 'image',
33 | title: 'Image',
34 | type: 'image',
35 | options: {
36 | hotspot: true,
37 | },
38 | },
39 | ],
40 | };
41 |
--------------------------------------------------------------------------------
/stepped-solutions/16/gatsby-config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | siteMetadata: {
3 | title: `Slicks Slices`,
4 | siteUrl: 'https://gatsby.pizza',
5 | description: 'The best pizza place in Hamilton!',
6 | }
7 | };
8 |
--------------------------------------------------------------------------------
/stepped-solutions/17/gatsby-config.js:
--------------------------------------------------------------------------------
1 | import dotenv from 'dotenv';
2 |
3 | dotenv.config({ path: '.env' });
4 |
5 | export default {
6 | siteMetadata: {
7 | title: `Slicks Slices`,
8 | siteUrl: 'https://gatsby.pizza',
9 | description: 'The best pizza place in Hamilton!',
10 | },
11 | plugins: [
12 | 'gatsby-plugin-styled-components',
13 | {
14 | // this is the name of the plugin you are adding
15 | resolve: 'gatsby-source-sanity',
16 | options: {
17 | projectId: 'YOU-NEED-TO-PUT-YOUR-OWN-PROJECT-ID-HERE',
18 | dataset: 'production',
19 | watchMode: true,
20 | token: process.env.SANITY_TOKEN,
21 | },
22 | },
23 | ],
24 | };
25 |
--------------------------------------------------------------------------------
/stepped-solutions/18/PizzaList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'gatsby';
3 |
4 | function SinglePizza({ pizza }) {
5 | return (
6 |
7 |
8 |
9 | {pizza.name}
10 |
11 |
{pizza.toppings.map((topping) => topping.name).join(', ')}
12 |
13 |
14 | );
15 | }
16 |
17 | export default function PizzaList({ pizzas }) {
18 | return (
19 |
20 | {pizzas.map((pizza) => (
21 |
22 | ))}
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/stepped-solutions/18/pizzas.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { graphql } from 'gatsby';
3 | import PizzaList from '../components/PizzaList';
4 |
5 | export default function PizzasPage({ data }) {
6 | const pizzas = data.pizzas.nodes;
7 | return (
8 | <>
9 |
10 | >
11 | );
12 | }
13 |
14 | export const query = graphql`
15 | query PizzaQuery {
16 | pizzas: allSanityPizza {
17 | nodes {
18 | name
19 | id
20 | slug {
21 | current
22 | }
23 | toppings {
24 | id
25 | name
26 | }
27 | image {
28 | asset {
29 | fluid(maxWidth: 400) {
30 | ...GatsbySanityImageFluid
31 | }
32 | }
33 | }
34 | }
35 | }
36 | }
37 | `;
38 |
--------------------------------------------------------------------------------
/stepped-solutions/19/PizzaList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'gatsby';
3 | import Img from 'gatsby-image';
4 |
5 | function SinglePizza({ pizza }) {
6 | return (
7 |
8 |
9 |
10 | {pizza.name}
11 |
12 |
{pizza.toppings.map((topping) => topping.name).join(', ')}
13 |
![{pizza.name}]()
14 |
15 |
16 | );
17 | }
18 |
19 | export default function PizzaList({ pizzas }) {
20 | return (
21 |
22 | {pizzas.map((pizza) => (
23 |
24 | ))}
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/stepped-solutions/19/pizzas.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { graphql } from 'gatsby';
3 | import PizzaList from '../components/PizzaList';
4 |
5 | export default function PizzasPage({ data }) {
6 | const pizzas = data.pizzas.nodes;
7 | return (
8 | <>
9 |
10 | >
11 | );
12 | }
13 |
14 | export const query = graphql`
15 | query PizzaQuery {
16 | pizzas: allSanityPizza {
17 | nodes {
18 | name
19 | id
20 | slug {
21 | current
22 | }
23 | toppings {
24 | id
25 | name
26 | }
27 | image {
28 | asset {
29 | fixed(width: 600, height: 200) {
30 | ...GatsbySanityImageFixed
31 | }
32 | fluid(maxWidth: 400) {
33 | ...GatsbySanityImageFluid
34 | }
35 | }
36 | }
37 | }
38 | }
39 | }
40 | `;
41 |
--------------------------------------------------------------------------------
/stepped-solutions/21/PizzaList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'gatsby';
3 | import Img from 'gatsby-image';
4 | import styled from 'styled-components';
5 |
6 | const PizzaGridStyles = styled.div`
7 | display: grid;
8 | grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
9 | gap: 4rem;
10 | grid-auto-rows: auto auto 500px;
11 | `;
12 |
13 | const PizzaStyles = styled.div`
14 | display: grid;
15 | /* Take your row sizing not from the pizzaStyles div, but from the PizzaGridStyles grid */
16 | @supports not (grid-template-rows: subgrid) {
17 | --rows: auto auto 1fr;
18 | }
19 | grid-template-rows: var(--rows, subgrid);
20 | grid-row: span 3;
21 | grid-gap: 1rem;
22 | h2,
23 | p {
24 | margin: 0;
25 | }
26 | `;
27 |
28 | function SinglePizza({ pizza }) {
29 | return (
30 |
31 |
32 |
33 | {pizza.name}
34 |
35 |
36 | {pizza.toppings.map((topping) => topping.name).join(', ')}
37 |
38 |
39 | );
40 | }
41 |
42 | export default function PizzaList({ pizzas }) {
43 | return (
44 |
45 | {pizzas.map((pizza) => (
46 |
47 | ))}
48 |
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/stepped-solutions/22/ToppingsFilter.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useStaticQuery, graphql, Link } from 'gatsby';
3 | import styled from 'styled-components';
4 |
5 | const ToppingsStyles = styled.div`
6 | display: flex;
7 | flex-wrap: wrap;
8 | gap: 1rem;
9 | margin-bottom: 4rem;
10 | a {
11 | display: grid;
12 | grid-template-columns: auto 1fr;
13 | grid-gap: 0 1rem;
14 | align-items: center;
15 | padding: 5px;
16 | background: var(--grey);
17 | border-radius: 2px;
18 | .count {
19 | background: white;
20 | padding: 2px 5px;
21 | }
22 | .active {
23 | background: var(--yellow);
24 | }
25 | }
26 | `;
27 |
28 | function countPizzasInToppings(pizzas) {
29 | // Return the pizzas with counts
30 | const counts = pizzas
31 | .map((pizza) => pizza.toppings)
32 | .flat()
33 | .reduce((acc, topping) => {
34 | // check if this is an existing topping
35 | const existingTopping = acc[topping.id];
36 | if (existingTopping) {
37 | console.log('Existing Topping', existingTopping.name);
38 | // if it is, increment by 1
39 | existingTopping.count += 1;
40 | } else {
41 | console.log('New Topping', topping.name);
42 | // otherwise create a new entry in our acc and set it to one
43 | acc[topping.id] = {
44 | id: topping.id,
45 | name: topping.name,
46 | count: 1,
47 | };
48 | }
49 | return acc;
50 | }, {});
51 | // sort them based on their count
52 | const sortedToppings = Object.values(counts).sort(
53 | (a, b) => b.count - a.count
54 | );
55 | return sortedToppings;
56 | }
57 |
58 | export default function ToppingsFilter() {
59 | // Get a list of all the toppings
60 | // Get a list of all the Pizzas with their toppings
61 | const { toppings, pizzas } = useStaticQuery(graphql`
62 | query {
63 | toppings: allSanityTopping {
64 | nodes {
65 | name
66 | id
67 | vegetarian
68 | }
69 | }
70 | pizzas: allSanityPizza {
71 | nodes {
72 | toppings {
73 | name
74 | id
75 | }
76 | }
77 | }
78 | }
79 | `);
80 | // Count how many pizzas are in each topping
81 | const toppingsWithCounts = countPizzasInToppings(pizzas.nodes);
82 | console.log(toppingsWithCounts);
83 | // Loop over the list of toppings and display the topping and the count of pizzas in that topping
84 | // Link it up.. ... . . .
85 | return (
86 |
87 | {toppingsWithCounts.map((topping) => (
88 |
89 | {topping.name}
90 | {topping.count}
91 |
92 | ))}
93 |
94 | );
95 | }
96 |
--------------------------------------------------------------------------------
/stepped-solutions/22/pizzas.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { graphql } from 'gatsby';
3 | import PizzaList from '../components/PizzaList';
4 | import ToppingsFilter from '../components/ToppingsFilter';
5 |
6 | export default function PizzasPage({ data }) {
7 | const pizzas = data.pizzas.nodes;
8 | return (
9 | <>
10 |
11 |
12 | >
13 | );
14 | }
15 |
16 | export const query = graphql`
17 | query PizzaQuery {
18 | pizzas: allSanityPizza {
19 | nodes {
20 | name
21 | id
22 | slug {
23 | current
24 | }
25 | toppings {
26 | id
27 | name
28 | }
29 | image {
30 | asset {
31 | fixed(width: 600, height: 200) {
32 | ...GatsbySanityImageFixed
33 | }
34 | fluid(maxWidth: 400) {
35 | ...GatsbySanityImageFluid
36 | }
37 | }
38 | }
39 | }
40 | }
41 | }
42 | `;
43 |
--------------------------------------------------------------------------------
/stepped-solutions/23/Pizza.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { graphql } from 'gatsby';
3 |
4 | export default function SinglePizzaPage() {
5 | return Single Pizza!!!
;
6 | }
7 |
8 | // This needs to be dynamic based on the slug passed in via context in gatsby-node.js
9 | export const query = graphql`
10 | query($slug: String!) {
11 | pizza: sanityPizza(slug: { current: { eq: $slug } }) {
12 | name
13 | id
14 | image {
15 | asset {
16 | fluid(maxWidth: 800) {
17 | ...GatsbySanityImageFluid
18 | }
19 | }
20 | }
21 | toppings {
22 | name
23 | id
24 | vegetarian
25 | }
26 | }
27 | }
28 | `;
29 |
--------------------------------------------------------------------------------
/stepped-solutions/23/gatsby-node.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 |
3 | async function turnPizzasIntoPages({ graphql, actions }) {
4 | // 1. Get a template for this page
5 | const pizzaTemplate = path.resolve('./src/templates/Pizza.js');
6 | // 2. Query all pizzas
7 | const { data } = await graphql(`
8 | query {
9 | pizzas: allSanityPizza {
10 | nodes {
11 | name
12 | slug {
13 | current
14 | }
15 | }
16 | }
17 | }
18 | `);
19 | console.log(data);
20 | // 3. Loop over each pizza and create a page for that pizza
21 | data.pizzas.nodes.forEach((pizza) => {
22 | actions.createPage({
23 | // What is the URL for this new page??
24 | path: `pizza/${pizza.slug.current}`,
25 | component: pizzaTemplate,
26 | context: {
27 | slug: pizza.slug.current,
28 | },
29 | });
30 | });
31 | }
32 |
33 | export async function createPages(params) {
34 | // Create pages dynamically
35 | // 1. Pizzas
36 | await turnPizzasIntoPages(params);
37 | // 2. Toppings
38 | // 3. Slicemasters
39 | }
40 |
--------------------------------------------------------------------------------
/stepped-solutions/24/Pizza.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { graphql } from 'gatsby';
3 | import Img from 'gatsby-image';
4 | import styled from 'styled-components';
5 |
6 | const PizzaGrid = styled.div`
7 | display: grid;
8 | grid-gap: 2rem;
9 | grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
10 | `;
11 |
12 | export default function SinglePizzaPage({ data: { pizza } }) {
13 | return (
14 |
15 |
16 |
17 |
{pizza.name}
18 |
19 | {pizza.toppings.map((topping) => (
20 | - {topping.name}
21 | ))}
22 |
23 |
24 |
25 | );
26 | }
27 |
28 | // This needs to be dynamic based on the slug passed in via context in gatsby-node.js
29 | export const query = graphql`
30 | query($slug: String!) {
31 | pizza: sanityPizza(slug: { current: { eq: $slug } }) {
32 | name
33 | id
34 | image {
35 | asset {
36 | fluid(maxWidth: 800) {
37 | ...GatsbySanityImageFluid
38 | }
39 | }
40 | }
41 | toppings {
42 | name
43 | id
44 | vegetarian
45 | }
46 | }
47 | }
48 | `;
49 |
--------------------------------------------------------------------------------
/stepped-solutions/25/ToppingsFilter.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useStaticQuery, graphql, Link } from 'gatsby';
3 | import styled from 'styled-components';
4 |
5 | const ToppingsStyles = styled.div`
6 | display: flex;
7 | flex-wrap: wrap;
8 | gap: 1rem;
9 | margin-bottom: 4rem;
10 | a {
11 | display: grid;
12 | grid-template-columns: auto 1fr;
13 | grid-gap: 0 1rem;
14 | align-items: center;
15 | padding: 5px;
16 | background: var(--grey);
17 | border-radius: 2px;
18 | .count {
19 | background: white;
20 | padding: 2px 5px;
21 | }
22 | &[aria-current='page'] {
23 | background: var(--yellow);
24 | }
25 | }
26 | `;
27 |
28 | function countPizzasInToppings(pizzas) {
29 | // Return the pizzas with counts
30 | const counts = pizzas
31 | .map((pizza) => pizza.toppings)
32 | .flat()
33 | .reduce((acc, topping) => {
34 | // check if this is an existing topping
35 | const existingTopping = acc[topping.id];
36 | if (existingTopping) {
37 | console.log('Existing Topping', existingTopping.name);
38 | // if it is, increment by 1
39 | existingTopping.count += 1;
40 | } else {
41 | console.log('New Topping', topping.name);
42 | // otherwise create a new entry in our acc and set it to one
43 | acc[topping.id] = {
44 | id: topping.id,
45 | name: topping.name,
46 | count: 1,
47 | };
48 | }
49 | return acc;
50 | }, {});
51 | // sort them based on their count
52 | const sortedToppings = Object.values(counts).sort(
53 | (a, b) => b.count - a.count
54 | );
55 | return sortedToppings;
56 | }
57 |
58 | export default function ToppingsFilter({ activeTopping }) {
59 | // Get a list of all the toppings
60 | // Get a list of all the Pizzas with their toppings
61 | const { toppings, pizzas } = useStaticQuery(graphql`
62 | query {
63 | toppings: allSanityTopping {
64 | nodes {
65 | name
66 | id
67 | vegetarian
68 | }
69 | }
70 | pizzas: allSanityPizza {
71 | nodes {
72 | toppings {
73 | name
74 | id
75 | }
76 | }
77 | }
78 | }
79 | `);
80 | // Count how many pizzas are in each topping
81 | const toppingsWithCounts = countPizzasInToppings(pizzas.nodes);
82 | console.log(toppingsWithCounts);
83 | // Loop over the list of toppings and display the topping and the count of pizzas in that topping
84 | // Link it up.. ... . . .
85 | return (
86 |
87 |
88 | All
89 | {pizzas.nodes.length}
90 |
91 | {toppingsWithCounts.map((topping) => (
92 |
97 | {topping.name}
98 | {topping.count}
99 |
100 | ))}
101 |
102 | );
103 | }
104 |
--------------------------------------------------------------------------------
/stepped-solutions/25/gatsby-node.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 |
3 | async function turnPizzasIntoPages({ graphql, actions }) {
4 | // 1. Get a template for this page
5 | const pizzaTemplate = path.resolve('./src/templates/Pizza.js');
6 | // 2. Query all pizzas
7 | const { data } = await graphql(`
8 | query {
9 | pizzas: allSanityPizza {
10 | nodes {
11 | name
12 | slug {
13 | current
14 | }
15 | }
16 | }
17 | }
18 | `);
19 | // 3. Loop over each pizza and create a page for that pizza
20 | data.pizzas.nodes.forEach((pizza) => {
21 | actions.createPage({
22 | // What is the URL for this new page??
23 | path: `pizza/${pizza.slug.current}`,
24 | component: pizzaTemplate,
25 | context: {
26 | slug: pizza.slug.current,
27 | },
28 | });
29 | });
30 | }
31 |
32 | async function turnToppingsIntoPages({ graphql, actions }) {
33 | console.log(`Turning the Toppings into Pages!!!`);
34 | // 1. Get the template
35 | const toppingTemplate = path.resolve('./src/pages/pizzas.js');
36 | // 2. query all the toppings
37 | const { data } = await graphql(`
38 | query {
39 | toppings: allSanityTopping {
40 | nodes {
41 | name
42 | id
43 | }
44 | }
45 | }
46 | `);
47 | // 3. createPage for that topping
48 | data.toppings.nodes.forEach((topping) => {
49 | console.log(`Creating page for topping`, topping.name);
50 | actions.createPage({
51 | path: `topping/${topping.name}`,
52 | component: toppingTemplate,
53 | context: {
54 | topping: topping.name,
55 | // TODO Regex for Topping
56 | toppingRegex: `/${topping.name}/i`,
57 | },
58 | });
59 | });
60 | // 4. Pass topping data to pizza.js
61 | }
62 |
63 | export async function createPages(params) {
64 | // Create pages dynamically
65 | // Wait for all promises to be resolved before finishing this function
66 | await Promise.all([
67 | turnPizzasIntoPages(params),
68 | turnToppingsIntoPages(params),
69 | ]);
70 | // 1. Pizzas
71 | // 2. Toppings
72 | // 3. Slicemasters
73 | }
74 |
--------------------------------------------------------------------------------
/stepped-solutions/25/pizzas.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { graphql } from 'gatsby';
3 | import PizzaList from '../components/PizzaList';
4 | import ToppingsFilter from '../components/ToppingsFilter';
5 |
6 | export default function PizzasPage({ data, pageContext }) {
7 | const pizzas = data.pizzas.nodes;
8 | return (
9 | <>
10 |
11 |
12 | >
13 | );
14 | }
15 |
16 | export const query = graphql`
17 | query PizzaQuery($toppingRegex: String) {
18 | pizzas: allSanityPizza(
19 | filter: { toppings: { elemMatch: { name: { regex: $toppingRegex } } } }
20 | ) {
21 | nodes {
22 | name
23 | id
24 | slug {
25 | current
26 | }
27 | toppings {
28 | id
29 | name
30 | }
31 | image {
32 | asset {
33 | fixed(width: 600, height: 200) {
34 | ...GatsbySanityImageFixed
35 | }
36 | fluid(maxWidth: 400) {
37 | ...GatsbySanityImageFluid
38 | }
39 | }
40 | }
41 | }
42 | }
43 | }
44 | `;
45 |
--------------------------------------------------------------------------------
/stepped-solutions/26/gatsby-node.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import fetch from 'isomorphic-fetch';
3 |
4 | async function turnPizzasIntoPages({ graphql, actions }) {
5 | // 1. Get a template for this page
6 | const pizzaTemplate = path.resolve('./src/templates/Pizza.js');
7 | // 2. Query all pizzas
8 | const { data } = await graphql(`
9 | query {
10 | pizzas: allSanityPizza {
11 | nodes {
12 | name
13 | slug {
14 | current
15 | }
16 | }
17 | }
18 | }
19 | `);
20 | // 3. Loop over each pizza and create a page for that pizza
21 | data.pizzas.nodes.forEach((pizza) => {
22 | actions.createPage({
23 | // What is the URL for this new page??
24 | path: `pizza/${pizza.slug.current}`,
25 | component: pizzaTemplate,
26 | context: {
27 | slug: pizza.slug.current,
28 | },
29 | });
30 | });
31 | }
32 |
33 | async function turnToppingsIntoPages({ graphql, actions }) {
34 | // 1. Get the template
35 | const toppingTemplate = path.resolve('./src/pages/pizzas.js');
36 | // 2. query all the toppings
37 | const { data } = await graphql(`
38 | query {
39 | toppings: allSanityTopping {
40 | nodes {
41 | name
42 | id
43 | }
44 | }
45 | }
46 | `);
47 | // 3. createPage for that topping
48 | data.toppings.nodes.forEach((topping) => {
49 | actions.createPage({
50 | path: `topping/${topping.name}`,
51 | component: toppingTemplate,
52 | context: {
53 | topping: topping.name,
54 | // TODO Regex for Topping
55 | toppingRegex: `/${topping.name}/i`,
56 | },
57 | });
58 | });
59 | // 4. Pass topping data to pizza.js
60 | }
61 |
62 | async function fetchBeersAndTurnIntoNodes({
63 | actions,
64 | createNodeId,
65 | createContentDigest,
66 | }) {
67 | // 1. Fetch a list of beers
68 | const res = await fetch('https://sampleapis.com/beers/api/ale');
69 | const beers = await res.json();
70 | // 2. Loop over each one
71 | for (const beer of beers) {
72 | // create a node for each beer
73 | const nodeMeta = {
74 | id: createNodeId(`beer-${beer.name}`),
75 | parent: null,
76 | children: [],
77 | internal: {
78 | type: 'Beer',
79 | mediaType: 'application/json',
80 | contentDigest: createContentDigest(beer),
81 | },
82 | };
83 | // 3. Create a node for that beer
84 | actions.createNode({
85 | ...beer,
86 | ...nodeMeta,
87 | });
88 | }
89 | }
90 |
91 | export async function sourceNodes(params) {
92 | // fetch a list of beers and source them into our gatsby API!
93 | await Promise.all([fetchBeersAndTurnIntoNodes(params)]);
94 | }
95 |
96 | export async function createPages(params) {
97 | // Create pages dynamically
98 | // Wait for all promises to be resolved before finishing this function
99 | await Promise.all([
100 | turnPizzasIntoPages(params),
101 | turnToppingsIntoPages(params),
102 | ]);
103 | // 1. Pizzas
104 | // 2. Toppings
105 | // 3. Slicemasters
106 | }
107 |
--------------------------------------------------------------------------------
/stepped-solutions/27/beers.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { graphql } from 'gatsby';
3 | import styled from 'styled-components';
4 |
5 | const BeerGridStyles = styled.div`
6 | display: grid;
7 | gap: 2rem;
8 | grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
9 | `;
10 |
11 | const SingleBeerStyles = styled.div`
12 | border: 1px solid var(--grey);
13 | padding: 2rem;
14 | text-align: center;
15 | img {
16 | width: 100%;
17 | height: 200px;
18 | object-fit: contain;
19 | display: block;
20 | display: grid;
21 | align-items: center;
22 | font-size: 10px;
23 | }
24 | `;
25 |
26 | export default function BeersPage({ data }) {
27 | return (
28 | <>
29 |
30 | We have {data.beers.nodes.length} Beers Available. Dine in Only!
31 |
32 |
33 | {data.beers.nodes.map((beer) => {
34 | const rating = Math.round(beer.rating.average);
35 | return (
36 |
37 |
38 | {beer.name}
39 | {beer.price}
40 |
41 | {`⭐`.repeat(rating)}
42 |
43 | {`⭐`.repeat(5 - rating)}
44 |
45 | ({beer.rating.reviews})
46 |
47 |
48 | );
49 | })}
50 |
51 | >
52 | );
53 | }
54 |
55 | export const query = graphql`
56 | query {
57 | beers: allBeer {
58 | nodes {
59 | id
60 | name
61 | price
62 | image
63 | rating {
64 | average
65 | reviews
66 | }
67 | }
68 | }
69 | }
70 | `;
71 |
--------------------------------------------------------------------------------
/stepped-solutions/28/slicemasters.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { graphql, Link } from 'gatsby';
3 | import Img from 'gatsby-image';
4 | import styled from 'styled-components';
5 |
6 | const SlicemasterGrid = styled.div`
7 | display: grid;
8 | grid-gap: 2rem;
9 | grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
10 | `;
11 |
12 | const SlicemasterStyles = styled.div`
13 | a {
14 | text-decoration: none;
15 | }
16 | .gatsby-image-wrapper {
17 | height: 400px;
18 | }
19 | h2 {
20 | transform: rotate(-2deg);
21 | text-align: center;
22 | font-size: 4rem;
23 | margin-bottom: -2rem;
24 | position: relative;
25 | z-index: 2;
26 | }
27 | .description {
28 | background: var(--yellow);
29 | padding: 1rem;
30 | margin: 2rem;
31 | margin-top: -6rem;
32 | z-index: 2;
33 | position: relative;
34 | transform: rotate(1deg);
35 | text-align: center;
36 | }
37 | `;
38 |
39 | export default function SlicemastersPage({ data }) {
40 | const slicemasters = data.slicemasters.nodes;
41 | return (
42 | <>
43 |
44 | {slicemasters.map((person) => (
45 |
46 |
47 |
48 | {person.name}
49 |
50 |
51 |
52 | {person.description}
53 |
54 | ))}
55 |
56 | >
57 | );
58 | }
59 |
60 | export const query = graphql`
61 | query {
62 | slicemasters: allSanityPerson {
63 | totalCount
64 | nodes {
65 | name
66 | id
67 | slug {
68 | current
69 | }
70 | description
71 | image {
72 | asset {
73 | fluid(maxWidth: 410) {
74 | ...GatsbySanityImageFluid
75 | }
76 | }
77 | }
78 | }
79 | }
80 | }
81 | `;
82 |
--------------------------------------------------------------------------------
/stepped-solutions/29/slicemasters.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { graphql, Link } from 'gatsby';
3 | import Img from 'gatsby-image';
4 | import styled from 'styled-components';
5 |
6 | const SlicemasterGrid = styled.div`
7 | display: grid;
8 | grid-gap: 2rem;
9 | grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
10 | `;
11 |
12 | const SlicemasterStyles = styled.div`
13 | a {
14 | text-decoration: none;
15 | }
16 | .gatsby-image-wrapper {
17 | height: 400px;
18 | }
19 | h2 {
20 | transform: rotate(-2deg);
21 | text-align: center;
22 | font-size: 4rem;
23 | margin-bottom: -2rem;
24 | position: relative;
25 | z-index: 2;
26 | }
27 | .description {
28 | background: var(--yellow);
29 | padding: 1rem;
30 | margin: 2rem;
31 | margin-top: -6rem;
32 | z-index: 2;
33 | position: relative;
34 | transform: rotate(1deg);
35 | text-align: center;
36 | }
37 | `;
38 |
39 | export default function SlicemastersPage({ data }) {
40 | const slicemasters = data.slicemasters.nodes;
41 | return (
42 | <>
43 | {process.env.GATSBY_PAGE_SIZE}
44 | {process.env.SANITY_TOKEN}
45 |
46 | {slicemasters.map((person) => (
47 |
48 |
49 |
50 | {person.name}
51 |
52 |
53 |
54 | {person.description}
55 |
56 | ))}
57 |
58 | >
59 | );
60 | }
61 |
62 | export const query = graphql`
63 | query {
64 | slicemasters: allSanityPerson {
65 | totalCount
66 | nodes {
67 | name
68 | id
69 | slug {
70 | current
71 | }
72 | description
73 | image {
74 | asset {
75 | fluid(maxWidth: 410) {
76 | ...GatsbySanityImageFluid
77 | }
78 | }
79 | }
80 | }
81 | }
82 | }
83 | `;
84 |
--------------------------------------------------------------------------------
/stepped-solutions/30/slicemasters.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { graphql, Link } from 'gatsby';
3 | import Img from 'gatsby-image';
4 | import styled from 'styled-components';
5 |
6 | const SlicemasterGrid = styled.div`
7 | display: grid;
8 | grid-gap: 2rem;
9 | grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
10 | `;
11 |
12 | const SlicemasterStyles = styled.div`
13 | a {
14 | text-decoration: none;
15 | }
16 | .gatsby-image-wrapper {
17 | height: 400px;
18 | }
19 | h2 {
20 | transform: rotate(-2deg);
21 | text-align: center;
22 | font-size: 4rem;
23 | margin-bottom: -2rem;
24 | position: relative;
25 | z-index: 2;
26 | }
27 | .description {
28 | background: var(--yellow);
29 | padding: 1rem;
30 | margin: 2rem;
31 | margin-top: -6rem;
32 | z-index: 2;
33 | position: relative;
34 | transform: rotate(1deg);
35 | text-align: center;
36 | }
37 | `;
38 |
39 | export default function SlicemastersPage({ data }) {
40 | const slicemasters = data.slicemasters.nodes;
41 | return (
42 | <>
43 | {process.env.GATSBY_PAGE_SIZE}
44 | {process.env.SANITY_TOKEN}
45 |
46 | {slicemasters.map((person) => (
47 |
48 |
49 |
50 | {person.name}
51 |
52 |
53 |
54 | {person.description}
55 |
56 | ))}
57 |
58 | >
59 | );
60 | }
61 |
62 | export const query = graphql`
63 | query($skip: Int = 0, $pageSize: Int = 2) {
64 | slicemasters: allSanityPerson(limit: $pageSize, skip: $skip) {
65 | totalCount
66 | nodes {
67 | name
68 | id
69 | slug {
70 | current
71 | }
72 | description
73 | image {
74 | asset {
75 | fluid(maxWidth: 410) {
76 | ...GatsbySanityImageFluid
77 | }
78 | }
79 | }
80 | }
81 | }
82 | }
83 | `;
84 |
--------------------------------------------------------------------------------
/stepped-solutions/31/Pagination.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'gatsby';
3 | import styled from 'styled-components';
4 |
5 | const PaginationStyles = styled.div`
6 | display: flex;
7 | align-content: center;
8 | align-items: center;
9 | justify-items: center;
10 | border: 1px solid var(--grey);
11 | margin: 2rem 0;
12 | border-radius: 5px;
13 | text-align: center;
14 | & > * {
15 | padding: 1rem;
16 | flex: 1;
17 | border-right: 1px solid var(--grey);
18 | text-decoration: none;
19 | &[aria-current],
20 | &.current {
21 | color: var(--red);
22 | }
23 | &[disabled] {
24 | pointer-events: none;
25 | color: var(--grey);
26 | }
27 | }
28 | `;
29 |
30 | export default function Pagination({
31 | pageSize,
32 | totalCount,
33 | currentPage,
34 | skip,
35 | base,
36 | }) {
37 | // make some variables
38 | const totalPages = Math.ceil(totalCount / pageSize);
39 | const prevPage = currentPage - 1;
40 | const nextPage = currentPage + 1;
41 | const hasNextPage = nextPage <= totalPages;
42 | const hasPrevPage = prevPage >= 1;
43 | return (
44 |
45 |
46 | ← Prev
47 |
48 | {Array.from({ length: totalPages }).map((_, i) => (
49 | 0 ? i + 1 : ''}`}
52 | >
53 | {i + 1}
54 |
55 | ))}
56 |
57 | Next →
58 |
59 |
60 | );
61 | }
62 |
--------------------------------------------------------------------------------
/stepped-solutions/31/slicemasters.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { graphql, Link } from 'gatsby';
3 | import Img from 'gatsby-image';
4 | import styled from 'styled-components';
5 | import Pagination from '../components/Pagination';
6 |
7 | const SlicemasterGrid = styled.div`
8 | display: grid;
9 | grid-gap: 2rem;
10 | grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
11 | `;
12 |
13 | const SlicemasterStyles = styled.div`
14 | a {
15 | text-decoration: none;
16 | }
17 | .gatsby-image-wrapper {
18 | height: 400px;
19 | }
20 | h2 {
21 | transform: rotate(-2deg);
22 | text-align: center;
23 | font-size: 4rem;
24 | margin-bottom: -2rem;
25 | position: relative;
26 | z-index: 2;
27 | }
28 | .description {
29 | background: var(--yellow);
30 | padding: 1rem;
31 | margin: 2rem;
32 | margin-top: -6rem;
33 | z-index: 2;
34 | position: relative;
35 | transform: rotate(1deg);
36 | text-align: center;
37 | }
38 | `;
39 |
40 | export default function SlicemastersPage({ data, pageContext }) {
41 | const slicemasters = data.slicemasters.nodes;
42 | return (
43 | <>
44 |
51 |
52 | {slicemasters.map((person) => (
53 |
54 |
55 |
56 | {person.name}
57 |
58 |
59 |
60 | {person.description}
61 |
62 | ))}
63 |
64 | >
65 | );
66 | }
67 |
68 | export const query = graphql`
69 | query($skip: Int = 0, $pageSize: Int = 2) {
70 | slicemasters: allSanityPerson(limit: $pageSize, skip: $skip) {
71 | totalCount
72 | nodes {
73 | name
74 | id
75 | slug {
76 | current
77 | }
78 | description
79 | image {
80 | asset {
81 | fluid(maxWidth: 410) {
82 | ...GatsbySanityImageFluid
83 | }
84 | }
85 | }
86 | }
87 | }
88 | }
89 | `;
90 |
--------------------------------------------------------------------------------
/stepped-solutions/32/Slicemaster.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { graphql } from 'gatsby';
3 | import Img from 'gatsby-image';
4 |
5 | export default function SlicemasterPage({ data: { person } }) {
6 | console.log(person);
7 | return (
8 |
9 |
![]()
10 |
11 | {person.name}
12 |
13 |
{person.description}
14 |
15 | );
16 | }
17 |
18 | export const query = graphql`
19 | query($slug: String!) {
20 | person: sanityPerson(slug: { current: { eq: $slug } }) {
21 | name
22 | id
23 | description
24 | image {
25 | asset {
26 | fluid(maxWidth: 1000, maxHeight: 750) {
27 | ...GatsbySanityImageFluid
28 | }
29 | }
30 | }
31 | }
32 | }
33 | `;
34 |
--------------------------------------------------------------------------------
/stepped-solutions/33/Pizza.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { graphql } from 'gatsby';
3 | import Img from 'gatsby-image';
4 | import styled from 'styled-components';
5 | import SEO from '../components/SEO';
6 |
7 | const PizzaGrid = styled.div`
8 | display: grid;
9 | grid-gap: 2rem;
10 | grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
11 | `;
12 |
13 | export default function SinglePizzaPage({ data: { pizza } }) {
14 | return (
15 | <>
16 |
17 |
18 |
19 |
20 |
{pizza.name}
21 |
22 | {pizza.toppings.map((topping) => (
23 | - {topping.name}
24 | ))}
25 |
26 |
27 |
28 | >
29 | );
30 | }
31 |
32 | // This needs to be dynamic based on the slug passed in via context in gatsby-node.js
33 | export const query = graphql`
34 | query($slug: String!) {
35 | pizza: sanityPizza(slug: { current: { eq: $slug } }) {
36 | name
37 | id
38 | image {
39 | asset {
40 | fluid(maxWidth: 800) {
41 | ...GatsbySanityImageFluid
42 | }
43 | }
44 | }
45 | toppings {
46 | name
47 | id
48 | vegetarian
49 | }
50 | }
51 | }
52 | `;
53 |
--------------------------------------------------------------------------------
/stepped-solutions/33/SEO.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Helmet } from 'react-helmet';
3 | import { useStaticQuery, graphql } from 'gatsby';
4 |
5 | export default function SEO({ children, location, description, title, image }) {
6 | const { site } = useStaticQuery(graphql`
7 | query {
8 | site {
9 | siteMetadata {
10 | title
11 | description
12 | twitter
13 | }
14 | }
15 | }
16 | `);
17 | return (
18 |
19 |
20 | {title}
21 | {/* Fav Icons */}
22 |
23 |
24 | {/* Meta Tags */}
25 |
26 |
27 |
28 | {/* Open Graph */}
29 | {location && }
30 |
31 |
32 |
37 |
38 | {children}
39 |
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/stepped-solutions/33/Slicemaster.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { graphql } from 'gatsby';
3 | import Img from 'gatsby-image';
4 | import SEO from '../components/SEO';
5 |
6 | export default function SlicemasterPage({ data: { person } }) {
7 | return (
8 | <>
9 |
10 |
11 |
![]()
12 |
13 | {person.name}
14 |
15 |
{person.description}
16 |
17 | >
18 | );
19 | }
20 |
21 | export const query = graphql`
22 | query($slug: String!) {
23 | person: sanityPerson(slug: { current: { eq: $slug } }) {
24 | name
25 | id
26 | description
27 | image {
28 | asset {
29 | fluid(maxWidth: 1000, maxHeight: 750) {
30 | ...GatsbySanityImageFluid
31 | }
32 | }
33 | }
34 | }
35 | }
36 | `;
37 |
--------------------------------------------------------------------------------
/stepped-solutions/33/beers.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { graphql } from 'gatsby';
3 | import styled from 'styled-components';
4 | import SEO from '../components/SEO';
5 |
6 | const BeerGridStyles = styled.div`
7 | display: grid;
8 | gap: 2rem;
9 | grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
10 | `;
11 |
12 | const SingleBeerStyles = styled.div`
13 | border: 1px solid var(--grey);
14 | padding: 2rem;
15 | text-align: center;
16 | img {
17 | width: 100%;
18 | height: 200px;
19 | object-fit: contain;
20 | display: block;
21 | display: grid;
22 | align-items: center;
23 | font-size: 10px;
24 | }
25 | `;
26 |
27 | export default function BeersPage({ data }) {
28 | return (
29 | <>
30 |
31 |
32 | We have {data.beers.nodes.length} Beers Available. Dine in Only!
33 |
34 |
35 | {data.beers.nodes.map((beer) => {
36 | const rating = Math.round(beer.rating.average);
37 | return (
38 |
39 |
40 | {beer.name}
41 | {beer.price}
42 |
43 | {`⭐`.repeat(rating)}
44 |
45 | {`⭐`.repeat(5 - rating)}
46 |
47 | ({beer.rating.reviews})
48 |
49 |
50 | );
51 | })}
52 |
53 | >
54 | );
55 | }
56 |
57 | export const query = graphql`
58 | query {
59 | beers: allBeer {
60 | nodes {
61 | id
62 | name
63 | price
64 | image
65 | rating {
66 | average
67 | reviews
68 | }
69 | }
70 | }
71 | }
72 | `;
73 |
--------------------------------------------------------------------------------
/stepped-solutions/33/gatsby-config.js:
--------------------------------------------------------------------------------
1 | import dotenv from 'dotenv';
2 |
3 | dotenv.config({ path: '.env' });
4 |
5 | export default {
6 | siteMetadata: {
7 | title: `Slicks Slices`,
8 | siteUrl: 'https://gatsby.pizza',
9 | description: 'The best pizza place in Hamilton!',
10 | twitter: '@slicksSlices',
11 | },
12 | plugins: [
13 | 'gatsby-plugin-react-helmet',
14 | 'gatsby-plugin-styled-components',
15 | {
16 | // this is the name of the plugin you are adding
17 | resolve: 'gatsby-source-sanity',
18 | options: {
19 | projectId: '0jfvvkkd',
20 | dataset: 'production',
21 | watchMode: true,
22 | token: process.env.SANITY_TOKEN,
23 | },
24 | },
25 | ],
26 | };
27 |
--------------------------------------------------------------------------------
/stepped-solutions/33/order.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SEO from '../components/SEO';
3 |
4 | export default function OrderPage() {
5 | return (
6 | <>
7 |
8 | Hey! I'm the Order page
9 | >
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/stepped-solutions/33/pizzas.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { graphql } from 'gatsby';
3 | import PizzaList from '../components/PizzaList';
4 | import ToppingsFilter from '../components/ToppingsFilter';
5 | import SEO from '../components/SEO';
6 |
7 | export default function PizzasPage({ data, pageContext }) {
8 | const pizzas = data.pizzas.nodes;
9 | return (
10 | <>
11 |
18 |
19 |
20 | >
21 | );
22 | }
23 |
24 | export const query = graphql`
25 | query PizzaQuery($toppingRegex: String) {
26 | pizzas: allSanityPizza(
27 | filter: { toppings: { elemMatch: { name: { regex: $toppingRegex } } } }
28 | ) {
29 | nodes {
30 | name
31 | id
32 | slug {
33 | current
34 | }
35 | toppings {
36 | id
37 | name
38 | }
39 | image {
40 | asset {
41 | fixed(width: 600, height: 200) {
42 | ...GatsbySanityImageFixed
43 | }
44 | fluid(maxWidth: 400) {
45 | ...GatsbySanityImageFluid
46 | }
47 | }
48 | }
49 | }
50 | }
51 | }
52 | `;
53 |
--------------------------------------------------------------------------------
/stepped-solutions/33/slicemasters.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { graphql, Link } from 'gatsby';
3 | import Img from 'gatsby-image';
4 | import styled from 'styled-components';
5 | import Pagination from '../components/Pagination';
6 | import SEO from '../components/SEO';
7 |
8 | const SlicemasterGrid = styled.div`
9 | display: grid;
10 | grid-gap: 2rem;
11 | grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
12 | `;
13 |
14 | const SlicemasterStyles = styled.div`
15 | a {
16 | text-decoration: none;
17 | }
18 | .gatsby-image-wrapper {
19 | height: 400px;
20 | }
21 | h2 {
22 | transform: rotate(-2deg);
23 | text-align: center;
24 | font-size: 4rem;
25 | margin-bottom: -2rem;
26 | position: relative;
27 | z-index: 2;
28 | }
29 | .description {
30 | background: var(--yellow);
31 | padding: 1rem;
32 | margin: 2rem;
33 | margin-top: -6rem;
34 | z-index: 2;
35 | position: relative;
36 | transform: rotate(1deg);
37 | text-align: center;
38 | }
39 | `;
40 |
41 | export default function SlicemastersPage({ data, pageContext }) {
42 | const slicemasters = data.slicemasters.nodes;
43 | return (
44 | <>
45 |
46 |
53 |
54 | {slicemasters.map((person) => (
55 |
56 |
57 |
58 | {person.name}
59 |
60 |
61 |
62 | {person.description}
63 |
64 | ))}
65 |
66 | >
67 | );
68 | }
69 |
70 | export const query = graphql`
71 | query($skip: Int = 0, $pageSize: Int = 2) {
72 | slicemasters: allSanityPerson(limit: $pageSize, skip: $skip) {
73 | totalCount
74 | nodes {
75 | name
76 | id
77 | slug {
78 | current
79 | }
80 | description
81 | image {
82 | asset {
83 | fluid(maxWidth: 410) {
84 | ...GatsbySanityImageFluid
85 | }
86 | }
87 | }
88 | }
89 | }
90 | }
91 | `;
92 |
--------------------------------------------------------------------------------
/stepped-solutions/34/calculatePizzaPrice.js:
--------------------------------------------------------------------------------
1 | const sizes = {
2 | S: 0.75,
3 | M: 1,
4 | L: 1.25,
5 | };
6 |
7 | export default function calculatePizzaPrice(cents, size) {
8 | return cents * sizes[size];
9 | }
10 |
--------------------------------------------------------------------------------
/stepped-solutions/34/formatMoney.js:
--------------------------------------------------------------------------------
1 | const formatter = Intl.NumberFormat('en-CA', {
2 | style: 'currency',
3 | currency: 'CAD',
4 | });
5 |
6 | export default function formatMoney(cents) {
7 | return formatter.format(cents / 100);
8 | }
9 |
--------------------------------------------------------------------------------
/stepped-solutions/34/order.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { graphql } from 'gatsby';
3 | import Img from 'gatsby-image';
4 | import SEO from '../components/SEO';
5 | import useForm from '../utils/useForm';
6 | import calculatePizzaPrice from '../utils/calculatePizzaPrice';
7 | import formatMoney from '../utils/formatMoney';
8 |
9 | export default function OrderPage({ data }) {
10 | const { values, updateValue } = useForm({
11 | name: '',
12 | email: '',
13 | });
14 | const pizzas = data.pizzas.nodes;
15 | return (
16 | <>
17 |
18 |
65 | >
66 | );
67 | }
68 |
69 | export const query = graphql`
70 | query {
71 | pizzas: allSanityPizza {
72 | nodes {
73 | name
74 | id
75 | slug {
76 | current
77 | }
78 | price
79 | image {
80 | asset {
81 | fluid(maxWidth: 100) {
82 | ...GatsbySanityImageFluid
83 | }
84 | }
85 | }
86 | }
87 | }
88 | }
89 | `;
90 |
--------------------------------------------------------------------------------
/stepped-solutions/35/MenuItemStyles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const MenuItemStyles = styled.div`
4 | display: grid;
5 | grid-template-columns: 100px 1fr;
6 | grid-template-rows: 1fr 1fr;
7 | gap: 0 1.3rem;
8 | align-content: center;
9 | align-items: center;
10 | .gatsby-image-wrapper {
11 | grid-row: span 2;
12 | height: 100%;
13 | }
14 | p {
15 | margin: 0;
16 | }
17 | button {
18 | font-size: 1.5rem;
19 | }
20 | button + button {
21 | margin-left: 1rem;
22 | }
23 | .remove {
24 | background: none;
25 | color: var(--red);
26 | font-size: 3rem;
27 | position: absolute;
28 | top: 0;
29 | right: 0;
30 | box-shadow: none;
31 | line-height: 1rem;
32 | }
33 | `;
34 |
35 | export default MenuItemStyles;
36 |
--------------------------------------------------------------------------------
/stepped-solutions/35/OrderStyles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const OrderStyles = styled.form`
4 | display: grid;
5 | grid-template-columns: 1fr 1fr;
6 | gap: 20px;
7 | fieldset {
8 | grid-column: span 2;
9 | max-height: 600px;
10 | overflow: auto;
11 | display: grid;
12 | gap: 1rem;
13 | align-content: start;
14 | &.order,
15 | &.menu {
16 | grid-column: span 1;
17 | }
18 | }
19 | /* @media (max-width: 900px) {
20 | fieldset.menu,
21 | fieldset.order {
22 | grid-column: span 2;
23 | }
24 | } */
25 | `;
26 |
27 | export default OrderStyles;
28 |
--------------------------------------------------------------------------------
/stepped-solutions/35/order.js:
--------------------------------------------------------------------------------
1 | import { graphql } from 'gatsby';
2 | import React from 'react';
3 | import Img from 'gatsby-image';
4 | import SEO from '../components/SEO';
5 | import useForm from '../utils/useForm';
6 | import calculatePizzaPrice from '../utils/calculatePizzaPrice';
7 | import formatMoney from '../utils/formatMoney';
8 | import OrderStyles from '../styles/OrderStyles';
9 | import MenuItemStyles from '../styles/MenuItemStyles';
10 |
11 | export default function OrderPage({ data }) {
12 | const { values, updateValue } = useForm({
13 | name: '',
14 | email: '',
15 | });
16 |
17 | const pizzas = data.pizzas.nodes;
18 |
19 | return (
20 | <>
21 |
22 |
23 |
47 |
70 |
73 |
74 | >
75 | );
76 | }
77 |
78 | export const query = graphql`
79 | query {
80 | pizzas: allSanityPizza {
81 | nodes {
82 | name
83 | id
84 | slug {
85 | current
86 | }
87 | price
88 | image {
89 | asset {
90 | fluid(maxWidth: 100) {
91 | ...GatsbySanityImageFluid
92 | }
93 | }
94 | }
95 | }
96 | }
97 | }
98 | `;
99 |
--------------------------------------------------------------------------------
/stepped-solutions/36/PizzaOrder.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Img from 'gatsby-image';
3 | import MenuItemStyles from '../styles/MenuItemStyles';
4 | import calculatePizzaPrice from '../utils/calculatePizzaPrice';
5 | import formatMoney from '../utils/formatMoney';
6 |
7 | export default function PizzaOrder({ order, pizzas, removeFromOrder }) {
8 | return (
9 | <>
10 | {order.map((singleOrder, index) => {
11 | const pizza = pizzas.find((pizza) => pizza.id === singleOrder.id);
12 | return (
13 |
14 |
15 | {pizza.name}
16 |
17 | {formatMoney(calculatePizzaPrice(pizza.price, singleOrder.size))}
18 |
26 |
27 |
28 | );
29 | })}
30 | >
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/stepped-solutions/36/usePizza.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | export default function usePizza({ pizzas, inputs }) {
4 | // 1. Create some state to hold our order
5 | const [order, setOrder] = useState([]);
6 | // 2. Make a function add things to order
7 | function addToOrder(orderedPizza) {
8 | setOrder([...order, orderedPizza]);
9 | }
10 | // 3. Make a function remove things from order
11 | function removeFromOrder(index) {
12 | setOrder([
13 | // everything before the item we want to remove
14 | ...order.slice(0, index),
15 | // everything after the item we want to remove
16 | ...order.slice(index + 1),
17 | ]);
18 | }
19 | // 4. Send this data the a serevrless function when they check out
20 | // TODO
21 |
22 | return {
23 | order,
24 | addToOrder,
25 | removeFromOrder,
26 | };
27 | }
28 |
--------------------------------------------------------------------------------
/stepped-solutions/37/calculateOrderTotal.js:
--------------------------------------------------------------------------------
1 | import calculatePizzaPrice from './calculatePizzaPrice';
2 |
3 | export default function calculateOrderTotal(order, pizzas) {
4 | return order.reduce((runningTotal, singleOrder) => {
5 | const pizza = pizzas.find(
6 | (singlePizza) => singlePizza.id === singleOrder.id
7 | );
8 | return runningTotal + calculatePizzaPrice(pizza.price, singleOrder.size);
9 | }, 0);
10 | }
11 |
--------------------------------------------------------------------------------
/stepped-solutions/38/OrderContext.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | // Create a order context
4 | const OrderContext = React.createContext();
5 |
6 | export function OrderProvider({ children }) {
7 | // we need to stick state in here
8 | const [order, setOrder] = useState([]);
9 | return (
10 |
11 | {children}
12 |
13 | );
14 | }
15 |
16 | export default OrderContext;
17 |
--------------------------------------------------------------------------------
/stepped-solutions/38/PizzaList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'gatsby';
3 | import Img from 'gatsby-image';
4 | import styled from 'styled-components';
5 |
6 | const PizzaGridStyles = styled.div`
7 | display: grid;
8 | grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
9 | gap: 4rem;
10 | grid-auto-rows: auto auto 500px;
11 | `;
12 |
13 | const PizzaStyles = styled.div`
14 | display: grid;
15 | /* Take your row sizing not from the pizzaStyles div, but from the PizzaGridStyles grid */
16 | @supports not (grid-template-rows: subgrid) {
17 | --rows: auto auto 1fr;
18 | }
19 | grid-template-rows: var(--rows, subgrid);
20 | grid-row: span 3;
21 | grid-gap: 1rem;
22 | h2,
23 | p {
24 | margin: 0;
25 | }
26 | `;
27 |
28 | function SinglePizza({ pizza }) {
29 | return (
30 |
31 |
32 |
33 | {pizza.name}
34 |
35 |
36 | {pizza.toppings.map((topping) => topping.name).join(', ')}
37 |
38 |
39 | );
40 | }
41 |
42 | export default function PizzaList({ pizzas }) {
43 | return (
44 |
45 | {pizzas.map((pizza) => (
46 |
47 | ))}
48 |
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/stepped-solutions/38/gatsby-browser.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Layout from './src/components/Layout';
3 | import { OrderProvider } from './src/components/OrderContext';
4 |
5 | export function wrapPageElement({ element, props }) {
6 | return {element};
7 | }
8 |
9 | export function wrapRootElement({ element }) {
10 | return {element};
11 | }
12 |
--------------------------------------------------------------------------------
/stepped-solutions/38/usePizza.js:
--------------------------------------------------------------------------------
1 | import { useState, useContext } from 'react';
2 | import OrderContext from '../components/OrderContext';
3 |
4 | export default function usePizza({ pizzas, inputs }) {
5 | // 1. Create some state to hold our order
6 | // We got rid of this line because we moved useState up to the provider
7 | // const [order, setOrder] = useState([]);
8 | // Now we access both our state and our updater function (setOrder) via context
9 | const [order, setOrder] = useContext(OrderContext);
10 | // 2. Make a function add things to order
11 | function addToOrder(orderedPizza) {
12 | setOrder([...order, orderedPizza]);
13 | }
14 | // 3. Make a function remove things from order
15 | function removeFromOrder(index) {
16 | setOrder([
17 | // everything before the item we want to remove
18 | ...order.slice(0, index),
19 | // everything after the item we want to remove
20 | ...order.slice(index + 1),
21 | ]);
22 | }
23 | // 4. Send this data the a serevrless function when they check out
24 | // TODO
25 |
26 | return {
27 | order,
28 | addToOrder,
29 | removeFromOrder,
30 | };
31 | }
32 |
--------------------------------------------------------------------------------
/stepped-solutions/39/functions/hello/hello.js:
--------------------------------------------------------------------------------
1 | exports.handler = async (event, context) => {
2 | console.log(event);
3 | return {
4 | statusCode: 200,
5 | body: 'Hello!!',
6 | };
7 | };
8 |
--------------------------------------------------------------------------------
/stepped-solutions/39/functions/placeOrder/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "placeorder",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "nodemailer": {
8 | "version": "6.4.10",
9 | "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.10.tgz",
10 | "integrity": "sha512-j+pS9CURhPgk6r0ENr7dji+As2xZiHSvZeVnzKniLOw1eRAyM/7flP0u65tCnsapV8JFu+t0l/5VeHsCZEeh9g=="
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/stepped-solutions/39/functions/placeOrder/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "placeorder",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "placeOrder.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "nodemailer": "^6.4.10"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/stepped-solutions/39/functions/placeOrder/placeOrder.js:
--------------------------------------------------------------------------------
1 | const nodemailer = require('nodemailer');
2 |
3 | // create a transport for nodemailer
4 | const transporter = nodemailer.createTransport({
5 | host: process.env.MAIL_HOST,
6 | port: 587,
7 | auth: {
8 | user: process.env.MAIL_USER,
9 | pass: process.env.MAIL_PASS,
10 | },
11 | });
12 |
13 | exports.handler = async (event, context) => {
14 | // Test send an email
15 | const info = await transporter.sendMail({
16 | from: "Slick's Slices ",
17 | to: 'orders@example.com',
18 | subject: 'New order!',
19 | html: `Your new pizza order is here!
`,
20 | });
21 | console.log(info);
22 | return {
23 | statusCode: 200,
24 | body: JSON.stringify(info),
25 | };
26 | };
27 |
--------------------------------------------------------------------------------
/stepped-solutions/39/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | functions = "functions/"
3 |
--------------------------------------------------------------------------------
/stepped-solutions/40/attachNamesAndPrices.js:
--------------------------------------------------------------------------------
1 | import formatMoney from './formatMoney';
2 | import calculatePizzaPrice from './calculatePizzaPrice';
3 |
4 | export default function attachNamesAndPrices(order, pizzas) {
5 | return order.map((item) => {
6 | const pizza = pizzas.find((pizza) => pizza.id === item.id);
7 | return {
8 | ...item,
9 | name: pizza.name,
10 | thumbnail: pizza.image.asset.fluid.src,
11 | price: formatMoney(calculatePizzaPrice(pizza.price, item.size)),
12 | };
13 | });
14 | }
15 |
--------------------------------------------------------------------------------
/stepped-solutions/40/usePizza.js:
--------------------------------------------------------------------------------
1 | import { useState, useContext } from 'react';
2 | import OrderContext from '../components/OrderContext';
3 | import calculateOrderTotal from './calculateOrderTotal';
4 | import formatMoney from './formatMoney';
5 | import attachNamesAndPrices from './attachNamesAndPrices';
6 |
7 | export default function usePizza({ pizzas, values }) {
8 | // 1. Create some state to hold our order
9 | // We got rid of this line because we moved useState up to the provider
10 | // const [order, setOrder] = useState([]);
11 | // Now we access both our state and our updater function (setOrder) via context
12 | const [order, setOrder] = useContext(OrderContext);
13 | const [error, setError] = useState();
14 | const [loading, setLoading] = useState(false);
15 | const [message, setMessage] = useState('');
16 |
17 | // 2. Make a function add things to order
18 | function addToOrder(orderedPizza) {
19 | setOrder([...order, orderedPizza]);
20 | }
21 | // 3. Make a function remove things from order
22 | function removeFromOrder(index) {
23 | setOrder([
24 | // everything before the item we want to remove
25 | ...order.slice(0, index),
26 | // everything after the item we want to remove
27 | ...order.slice(index + 1),
28 | ]);
29 | }
30 |
31 | // this is the function that is run when someone submits the form
32 | async function submitOrder(e) {
33 | e.preventDefault();
34 | console.log(e);
35 | setLoading(true);
36 | setError(null);
37 | setMessage('Go eat!');
38 |
39 | // gather all the data
40 | const body = {
41 | order: attachNamesAndPrices(order, pizzas),
42 | total: formatMoney(calculateOrderTotal(order, pizzas)),
43 | name: values.name,
44 | email: values.email,
45 | };
46 | // 4. Send this data the a serevrless function when they check out
47 | const res = await fetch(
48 | `${process.env.GATSBY_SERVERLESS_BASE}/placeOrder`,
49 | {
50 | method: 'POST',
51 | headers: {
52 | 'Content-Type': 'application/json',
53 | },
54 | body: JSON.stringify(body),
55 | }
56 | );
57 | const text = JSON.parse(await res.text());
58 |
59 | // check if everything worked
60 | if (res.status >= 400 && res.status < 600) {
61 | setLoading(false); // turn off loading
62 | setError(text.message);
63 | } else {
64 | // it worked!
65 | setLoading(false);
66 | setMessage('Success! Come on down for your pizza');
67 | }
68 | }
69 |
70 | return {
71 | order,
72 | addToOrder,
73 | removeFromOrder,
74 | error,
75 | loading,
76 | message,
77 | submitOrder,
78 | };
79 | }
80 |
--------------------------------------------------------------------------------
/stepped-solutions/41/placeOrder.js:
--------------------------------------------------------------------------------
1 | const nodemailer = require('nodemailer');
2 |
3 | function generateOrderEmail({ order, total }) {
4 | return `
5 |
Your Recent Order for ${total}
6 |
Please start walking over, we will have your order ready in the next 20 mins.
7 |
8 | ${order
9 | .map(
10 | (item) => `-
11 |
12 | ${item.size} ${item.name} - ${item.price}
13 | `
14 | )
15 | .join('')}
16 |
17 |
Your total is $${total} due at pickup
18 |
23 |
`;
24 | }
25 |
26 | // create a transport for nodemailer
27 | const transporter = nodemailer.createTransport({
28 | host: process.env.MAIL_HOST,
29 | port: 587,
30 | auth: {
31 | user: process.env.MAIL_USER,
32 | pass: process.env.MAIL_PASS,
33 | },
34 | });
35 |
36 | exports.handler = async (event, context) => {
37 | const body = JSON.parse(event.body);
38 | console.log(body);
39 | // Validate the data coming in is correct
40 | const requiredFields = ['email', 'name', 'order'];
41 |
42 | for (const field of requiredFields) {
43 | console.log(`Checking that ${field} is good`);
44 | if (!body[field]) {
45 | return {
46 | statusCode: 400,
47 | body: JSON.stringify({
48 | message: `Oops! You are missing the ${field} field`,
49 | }),
50 | };
51 | }
52 | }
53 |
54 | // send the email
55 | const info = await transporter.sendMail({
56 | from: "Slick's Slices ",
57 | to: `${body.name} <${body.email}>, orders@example.com`,
58 | subject: 'New order!',
59 | html: generateOrderEmail({ order: body.order, total: body.total }),
60 | });
61 | return {
62 | statusCode: 200,
63 | body: JSON.stringify({ message: 'Success' }),
64 | };
65 | };
66 |
--------------------------------------------------------------------------------
/stepped-solutions/41/usePizza.js:
--------------------------------------------------------------------------------
1 | import { useState, useContext } from 'react';
2 | import OrderContext from '../components/OrderContext';
3 | import calculateOrderTotal from './calculateOrderTotal';
4 | import formatMoney from './formatMoney';
5 | import attachNamesAndPrices from './attachNamesAndPrices';
6 |
7 | export default function usePizza({ pizzas, values }) {
8 | // 1. Create some state to hold our order
9 | // We got rid of this line because we moved useState up to the provider
10 | // const [order, setOrder] = useState([]);
11 | // Now we access both our state and our updater function (setOrder) via context
12 | const [order, setOrder] = useContext(OrderContext);
13 | const [error, setError] = useState();
14 | const [loading, setLoading] = useState(false);
15 | const [message, setMessage] = useState('');
16 |
17 | // 2. Make a function add things to order
18 | function addToOrder(orderedPizza) {
19 | setOrder([...order, orderedPizza]);
20 | }
21 | // 3. Make a function remove things from order
22 | function removeFromOrder(index) {
23 | setOrder([
24 | // everything before the item we want to remove
25 | ...order.slice(0, index),
26 | // everything after the item we want to remove
27 | ...order.slice(index + 1),
28 | ]);
29 | }
30 |
31 | // this is the function that is run when someone submits the form
32 | async function submitOrder(e) {
33 | e.preventDefault();
34 | console.log(e);
35 | setLoading(true);
36 | setError(null);
37 | setMessage('Go eat!');
38 |
39 | // gather all the data
40 | const body = {
41 | order: attachNamesAndPrices(order, pizzas),
42 | total: formatMoney(calculateOrderTotal(order, pizzas)),
43 | name: values.name,
44 | email: values.email,
45 | };
46 | // 4. Send this data the a serevrless function when they check out
47 | const res = await fetch(
48 | `${process.env.GATSBY_SERVERLESS_BASE}/placeOrder`,
49 | {
50 | method: 'POST',
51 | headers: {
52 | 'Content-Type': 'application/json',
53 | },
54 | body: JSON.stringify(body),
55 | }
56 | );
57 | const text = JSON.parse(await res.text());
58 |
59 | // check if everything worked
60 | if (res.status >= 400 && res.status < 600) {
61 | setLoading(false); // turn off loading
62 | setError(text.message);
63 | } else {
64 | // it worked!
65 | setLoading(false);
66 | setMessage('Success! Come on down for your pizza');
67 | }
68 | }
69 |
70 | return {
71 | order,
72 | addToOrder,
73 | removeFromOrder,
74 | error,
75 | loading,
76 | message,
77 | submitOrder,
78 | };
79 | }
80 |
--------------------------------------------------------------------------------
/stepped-solutions/42/placeOrder.js:
--------------------------------------------------------------------------------
1 | const nodemailer = require('nodemailer');
2 |
3 | function generateOrderEmail({ order, total }) {
4 | return `
5 |
Your Recent Order for ${total}
6 |
Please start walking over, we will have your order ready in the next 20 mins.
7 |
8 | ${order
9 | .map(
10 | (item) => `-
11 |
12 | ${item.size} ${item.name} - ${item.price}
13 | `
14 | )
15 | .join('')}
16 |
17 |
Your total is $${total} due at pickup
18 |
23 |
`;
24 | }
25 |
26 | // create a transport for nodemailer
27 | const transporter = nodemailer.createTransport({
28 | host: process.env.MAIL_HOST,
29 | port: 587,
30 | auth: {
31 | user: process.env.MAIL_USER,
32 | pass: process.env.MAIL_PASS,
33 | },
34 | });
35 |
36 | function wait(ms = 0) {
37 | return new Promise((resolve, reject) => {
38 | setTimeout(resolve, ms);
39 | });
40 | }
41 |
42 | exports.handler = async (event, context) => {
43 | const body = JSON.parse(event.body);
44 | console.log(body);
45 | // Validate the data coming in is correct
46 | const requiredFields = ['email', 'name', 'order'];
47 |
48 | for (const field of requiredFields) {
49 | console.log(`Checking that ${field} is good`);
50 | if (!body[field]) {
51 | return {
52 | statusCode: 400,
53 | body: JSON.stringify({
54 | message: `Oops! You are missing the ${field} field`,
55 | }),
56 | };
57 | }
58 | }
59 |
60 | // make sure they actually have items in that order
61 | if (!body.order.length) {
62 | return {
63 | statusCode: 400,
64 | body: JSON.stringify({
65 | message: `Why would you order nothing?!`,
66 | }),
67 | };
68 | }
69 |
70 | // send the email
71 | const info = await transporter.sendMail({
72 | from: "Slick's Slices ",
73 | to: `${body.name} <${body.email}>, orders@example.com`,
74 | subject: 'New order!',
75 | html: generateOrderEmail({ order: body.order, total: body.total }),
76 | });
77 | return {
78 | statusCode: 200,
79 | body: JSON.stringify({ message: 'Success' }),
80 | };
81 | };
82 |
--------------------------------------------------------------------------------
/stepped-solutions/42/usePizza.js:
--------------------------------------------------------------------------------
1 | import { useState, useContext } from 'react';
2 | import OrderContext from '../components/OrderContext';
3 | import calculateOrderTotal from './calculateOrderTotal';
4 | import formatMoney from './formatMoney';
5 | import attachNamesAndPrices from './attachNamesAndPrices';
6 |
7 | export default function usePizza({ pizzas, values }) {
8 | // 1. Create some state to hold our order
9 | // We got rid of this line because we moved useState up to the provider
10 | // const [order, setOrder] = useState([]);
11 | // Now we access both our state and our updater function (setOrder) via context
12 | const [order, setOrder] = useContext(OrderContext);
13 | const [error, setError] = useState();
14 | const [loading, setLoading] = useState(false);
15 | const [message, setMessage] = useState('');
16 |
17 | // 2. Make a function add things to order
18 | function addToOrder(orderedPizza) {
19 | setOrder([...order, orderedPizza]);
20 | }
21 | // 3. Make a function remove things from order
22 | function removeFromOrder(index) {
23 | setOrder([
24 | // everything before the item we want to remove
25 | ...order.slice(0, index),
26 | // everything after the item we want to remove
27 | ...order.slice(index + 1),
28 | ]);
29 | }
30 |
31 | // this is the function that is run when someone submits the form
32 | async function submitOrder(e) {
33 | e.preventDefault();
34 | console.log(e);
35 | setLoading(true);
36 | setError(null);
37 | // setMessage('Go eat!');
38 |
39 | // gather all the data
40 | const body = {
41 | order: attachNamesAndPrices(order, pizzas),
42 | total: formatMoney(calculateOrderTotal(order, pizzas)),
43 | name: values.name,
44 | email: values.email,
45 | };
46 | // 4. Send this data the a serevrless function when they check out
47 | const res = await fetch(
48 | `${process.env.GATSBY_SERVERLESS_BASE}/placeOrder`,
49 | {
50 | method: 'POST',
51 | headers: {
52 | 'Content-Type': 'application/json',
53 | },
54 | body: JSON.stringify(body),
55 | }
56 | );
57 | const text = JSON.parse(await res.text());
58 |
59 | // check if everything worked
60 | if (res.status >= 400 && res.status < 600) {
61 | setLoading(false); // turn off loading
62 | setError(text.message);
63 | } else {
64 | // it worked!
65 | setLoading(false);
66 | setMessage('Success! Come on down for your pizza');
67 | }
68 | }
69 |
70 | return {
71 | order,
72 | addToOrder,
73 | removeFromOrder,
74 | error,
75 | loading,
76 | message,
77 | submitOrder,
78 | };
79 | }
80 |
--------------------------------------------------------------------------------
/stepped-solutions/43/OrderStyles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const OrderStyles = styled.form`
4 | display: grid;
5 | grid-template-columns: 1fr 1fr;
6 | gap: 20px;
7 | fieldset {
8 | grid-column: span 2;
9 | max-height: 600px;
10 | overflow: auto;
11 | display: grid;
12 | gap: 1rem;
13 | align-content: start;
14 | &.order,
15 | &.menu {
16 | grid-column: span 1;
17 | }
18 | }
19 | .mapleSyrup {
20 | display: none;
21 | }
22 | /* @media (max-width: 900px) {
23 | fieldset.menu,
24 | fieldset.order {
25 | grid-column: span 2;
26 | }
27 | } */
28 | `;
29 |
30 | export default OrderStyles;
31 |
--------------------------------------------------------------------------------
/stepped-solutions/43/placeOrder.js:
--------------------------------------------------------------------------------
1 | const nodemailer = require('nodemailer');
2 |
3 | function generateOrderEmail({ order, total }) {
4 | return `
5 |
Your Recent Order for ${total}
6 |
Please start walking over, we will have your order ready in the next 20 mins.
7 |
8 | ${order
9 | .map(
10 | (item) => `-
11 |
12 | ${item.size} ${item.name} - ${item.price}
13 | `
14 | )
15 | .join('')}
16 |
17 |
Your total is $${total} due at pickup
18 |
23 |
`;
24 | }
25 |
26 | // create a transport for nodemailer
27 | const transporter = nodemailer.createTransport({
28 | host: process.env.MAIL_HOST,
29 | port: 587,
30 | auth: {
31 | user: process.env.MAIL_USER,
32 | pass: process.env.MAIL_PASS,
33 | },
34 | });
35 |
36 | function wait(ms = 0) {
37 | return new Promise((resolve, reject) => {
38 | setTimeout(resolve, ms);
39 | });
40 | }
41 |
42 | exports.handler = async (event, context) => {
43 | const body = JSON.parse(event.body);
44 | // Check if they have filled out the honeypot
45 | if (body.mapleSyrup) {
46 | return {
47 | statusCode: 400,
48 | body: JSON.stringify({ message: 'Boop beep bop zzzzstt good bye' }),
49 | };
50 | }
51 | // Validate the data coming in is correct
52 | const requiredFields = ['email', 'name', 'order'];
53 |
54 | for (const field of requiredFields) {
55 | console.log(`Checking that ${field} is good`);
56 | if (!body[field]) {
57 | return {
58 | statusCode: 400,
59 | body: JSON.stringify({
60 | message: `Oops! You are missing the ${field} field`,
61 | }),
62 | };
63 | }
64 | }
65 |
66 | // make sure they actually have items in that order
67 | if (!body.order.length) {
68 | return {
69 | statusCode: 400,
70 | body: JSON.stringify({
71 | message: `Why would you order nothing?!`,
72 | }),
73 | };
74 | }
75 |
76 | // send the email
77 | const info = await transporter.sendMail({
78 | from: "Slick's Slices ",
79 | to: `${body.name} <${body.email}>, orders@example.com`,
80 | subject: 'New order!',
81 | html: generateOrderEmail({ order: body.order, total: body.total }),
82 | });
83 | return {
84 | statusCode: 200,
85 | body: JSON.stringify({ message: 'Success' }),
86 | };
87 | };
88 |
--------------------------------------------------------------------------------
/stepped-solutions/43/usePizza.js:
--------------------------------------------------------------------------------
1 | import { useState, useContext } from 'react';
2 | import OrderContext from '../components/OrderContext';
3 | import calculateOrderTotal from './calculateOrderTotal';
4 | import formatMoney from './formatMoney';
5 | import attachNamesAndPrices from './attachNamesAndPrices';
6 |
7 | export default function usePizza({ pizzas, values }) {
8 | // 1. Create some state to hold our order
9 | // We got rid of this line because we moved useState up to the provider
10 | // const [order, setOrder] = useState([]);
11 | // Now we access both our state and our updater function (setOrder) via context
12 | const [order, setOrder] = useContext(OrderContext);
13 | const [error, setError] = useState();
14 | const [loading, setLoading] = useState(false);
15 | const [message, setMessage] = useState('');
16 |
17 | // 2. Make a function add things to order
18 | function addToOrder(orderedPizza) {
19 | setOrder([...order, orderedPizza]);
20 | }
21 | // 3. Make a function remove things from order
22 | function removeFromOrder(index) {
23 | setOrder([
24 | // everything before the item we want to remove
25 | ...order.slice(0, index),
26 | // everything after the item we want to remove
27 | ...order.slice(index + 1),
28 | ]);
29 | }
30 |
31 | // this is the function that is run when someone submits the form
32 | async function submitOrder(e) {
33 | e.preventDefault();
34 | console.log(e);
35 | setLoading(true);
36 | setError(null);
37 | // setMessage('Go eat!');
38 |
39 | // gather all the data
40 | const body = {
41 | order: attachNamesAndPrices(order, pizzas),
42 | total: formatMoney(calculateOrderTotal(order, pizzas)),
43 | name: values.name,
44 | email: values.email,
45 | mapleSyrup: values.mapleSyrup,
46 | };
47 | // 4. Send this data the a serevrless function when they check out
48 | const res = await fetch(
49 | `${process.env.GATSBY_SERVERLESS_BASE}/placeOrder`,
50 | {
51 | method: 'POST',
52 | headers: {
53 | 'Content-Type': 'application/json',
54 | },
55 | body: JSON.stringify(body),
56 | }
57 | );
58 | const text = JSON.parse(await res.text());
59 |
60 | // check if everything worked
61 | if (res.status >= 400 && res.status < 600) {
62 | setLoading(false); // turn off loading
63 | setError(text.message);
64 | } else {
65 | // it worked!
66 | setLoading(false);
67 | setMessage('Success! Come on down for your pizza');
68 | }
69 | }
70 |
71 | return {
72 | order,
73 | addToOrder,
74 | removeFromOrder,
75 | error,
76 | loading,
77 | message,
78 | submitOrder,
79 | };
80 | }
81 |
--------------------------------------------------------------------------------
/stepped-solutions/44/sanity.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "api": {
4 | "projectId": "HEY YOU YA YOU FILL YOUR ID IN HERE",
5 | "dataset": "production"
6 | },
7 | "project": {
8 | "name": "slicks-slices"
9 | },
10 | "plugins": [
11 | "@sanity/base",
12 | "@sanity/components",
13 | "@sanity/default-layout",
14 | "@sanity/default-login",
15 | "@sanity/desk-tool"
16 | ],
17 | "env": {
18 | "development": {
19 | "plugins": [
20 | "@sanity/vision"
21 | ]
22 | }
23 | },
24 | "parts": [
25 | {
26 | "name": "part:@sanity/base/schema",
27 | "path": "./schemas/schema"
28 | },
29 | {
30 | "name": "part:@sanity/desk-tool/structure",
31 | "path": "./sidebar.js"
32 | }
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/stepped-solutions/44/schema.js:
--------------------------------------------------------------------------------
1 | // First, we must import the schema creator
2 | import createSchema from 'part:@sanity/base/schema-creator';
3 | // Then import schema types from any plugins that might expose them
4 | import schemaTypes from 'all:part:@sanity/base/schema-type';
5 | // Then we give our schema to the builder and provide the result to Sanity
6 |
7 | import pizza from './pizza';
8 | import topping from './topping';
9 | import person from './person';
10 | import storeSettings from './storeSettings';
11 |
12 | export default createSchema({
13 | // We name our schema
14 | name: 'default',
15 | // Then proceed to concatenate our document type
16 | // to the ones provided by any plugins that are installed
17 | types: schemaTypes.concat([pizza, topping, person, storeSettings]),
18 | });
19 |
--------------------------------------------------------------------------------
/stepped-solutions/44/sidebar.js:
--------------------------------------------------------------------------------
1 | import React, { Children } from 'react';
2 | import S from '@sanity/desk-tool/structure-builder';
3 |
4 | // build a custom sidebar
5 | export default function Sidebar() {
6 | return S.list()
7 | .title(`Slick's Slices`)
8 | .items([
9 | // create new sub item
10 | S.listItem()
11 | .title('Home Page')
12 | .icon(() => 🔥)
13 | .child(
14 | S.editor()
15 | .schemaType('storeSettings')
16 | // make a new document ID, so we don't have a random string of numbers
17 | .documentId('downtown')
18 | ),
19 | // add in the rest of our document items
20 | ...S.documentTypeListItems().filter(
21 | item => item.getId() !== 'storeSettings'
22 | ),
23 | ]);
24 | }
25 |
--------------------------------------------------------------------------------
/stepped-solutions/44/storeSettings.js:
--------------------------------------------------------------------------------
1 | import { MdStore as icon } from 'react-icons/md';
2 |
3 | export default {
4 | // Computer Name
5 | name: 'storeSettings',
6 | // visible title
7 | title: 'Settings',
8 | type: 'document',
9 | icon,
10 | fields: [
11 | {
12 | name: 'name',
13 | title: 'Store Name',
14 | type: 'string',
15 | description: 'Name of the pizza',
16 | },
17 | {
18 | name: 'slicemaster',
19 | title: 'Slicemasters Currently Slicing',
20 | type: 'array',
21 | of: [{ type: 'reference', to: [{ type: 'person' }] }],
22 | },
23 | {
24 | name: 'hotSlices',
25 | title: 'Hot Slices available in the case',
26 | type: 'array',
27 | of: [{ type: 'reference', to: [{ type: 'pizza' }] }],
28 | },
29 | ],
30 | };
31 |
--------------------------------------------------------------------------------
/stepped-solutions/45/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import useLatestData from '../utils/useLatestData';
3 |
4 | function CurrentlySlicing() {
5 | return (
6 |
9 | );
10 | }
11 | function HotSlices() {
12 | return (
13 |
16 | );
17 | }
18 |
19 | export default function HomePage() {
20 | const { slicemasters, hotSlices } = useLatestData();
21 |
22 | return (
23 |
24 |
The Best Pizza Downtown!
25 |
Open 11am to 11pm Every Single Day
26 |
27 |
28 |
29 |
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/stepped-solutions/45/useLatestData.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 |
3 | export default function useLatestData() {
4 | // hot slices
5 | const [hotSlices, setHotSlices] = useState();
6 | // slicemasters
7 | const [slicemasters, setSlicemasters] = useState();
8 | // Use a side effect to fetcht he data from the graphql endpoint
9 | useEffect(function () {
10 | console.log('FETCHING DATA');
11 | // when the component loads, fetch the data
12 | fetch(process.env.GATSBY_GRAPHQL_ENDPOINT, {
13 | method: 'POST',
14 | headers: {
15 | 'Content-Type': 'application/json',
16 | },
17 | body: JSON.stringify({
18 | query: `
19 | query {
20 | StoreSettings(id: "downtown") {
21 | name
22 | slicemaster {
23 | name
24 | }
25 | hotSlices {
26 | name
27 | }
28 | }
29 | }
30 | `,
31 | }),
32 | })
33 | .then((res) => res.json())
34 | .then((res) => {
35 | // TODO: checl for errors
36 | // set the data to state
37 | setHotSlices(res.data.StoreSettings.hotSlices);
38 | setSlicemasters(res.data.StoreSettings.slicemaster);
39 | });
40 | }, []);
41 | return {
42 | hotSlices,
43 | slicemasters,
44 | };
45 | }
46 |
--------------------------------------------------------------------------------
/stepped-solutions/46/Grids.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const HomePageGrid = styled.div`
4 | display: grid;
5 | gap: 2rem;
6 | grid-template-columns: repeat(2, minmax(auto, 1fr));
7 | `;
8 |
9 | export const ItemsGrid = styled.div`
10 | display: grid;
11 | gap: 2rem;
12 | grid-template-columns: 1fr 1fr;
13 | `;
14 |
15 | // Single Grid Item (for home page)
16 | export const ItemStyles = styled.div`
17 | text-align: center;
18 | position: relative;
19 | img {
20 | height: auto;
21 | font-size: 0;
22 | }
23 | p {
24 | transform: rotate(-2deg) translateY(-50%);
25 | position: absolute;
26 | width: 100%;
27 | left: 0;
28 | }
29 | .mark {
30 | display: inline;
31 | }
32 | @keyframes shine {
33 | from {
34 | background-position: 200%;
35 | }
36 | to {
37 | background-position: -40px;
38 | }
39 | }
40 | img.loading {
41 | --shine: white;
42 | --background: var(--grey);
43 | background-image: linear-gradient(
44 | 90deg,
45 | var(--background) 0px,
46 | var(--shine) 40px,
47 | var(--background) 80px
48 | );
49 | background-size: 500px;
50 | animation: shine 1s infinite linear;
51 | }
52 | `;
53 |
--------------------------------------------------------------------------------
/stepped-solutions/46/LoadingGrid.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ItemsGrid, ItemStyles } from '../styles/Grids';
3 |
4 | export default function LoadingGrid({ count }) {
5 | return (
6 |
7 | {Array.from({ length: count }, (_, i) => (
8 |
9 |
10 | Loading...
11 |
12 |
19 |
20 | ))}
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/stepped-solutions/46/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import useLatestData from '../utils/useLatestData';
3 | import { HomePageGrid } from '../styles/Grids';
4 | import LoadingGrid from '../components/LoadingGrid';
5 |
6 | function CurrentlySlicing({ slicemasters }) {
7 | console.log(slicemasters);
8 | return (
9 |
10 | {!slicemasters &&
}
11 | {slicemasters && !slicemasters?.length && (
12 |
No one is working right now!
13 | )}
14 |
15 | );
16 | }
17 | function HotSlices({ hotSlices }) {
18 | return (
19 |
20 | {!hotSlices &&
}
21 | {hotSlices && !hotSlices?.length &&
Nothin' in the Case
}
22 |
23 | );
24 | }
25 |
26 | export default function HomePage() {
27 | const { slicemasters, hotSlices } = useLatestData();
28 |
29 | return (
30 |
31 |
The Best Pizza Downtown!
32 |
Open 11am to 11pm Every Single Day
33 |
34 |
35 |
36 |
37 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/stepped-solutions/47/GlobalStyles.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 | import bg from '../assets/images/bg.svg';
3 | import stripes from '../assets/images/stripes.svg';
4 |
5 | const GlobalStyles = createGlobalStyle`
6 | :root {
7 | --red: #FF4949;
8 | --black: #2E2E2E;
9 | --yellow: #ffc600;
10 | --white: #fff;
11 | --grey: #efefef;
12 | }
13 | html {
14 | background-image: url(${bg});
15 | background-size: 450px;
16 | background-attachment: fixed;
17 | font-size: 10px;
18 | }
19 |
20 | body {
21 | font-size: 2rem;
22 | }
23 |
24 | fieldset {
25 | border-color: rgba(0,0,0,0.1);
26 | border-width: 1px;
27 | }
28 |
29 | button {
30 | background: var(--red);
31 | color: white;
32 | border: 0;
33 | padding: 0.6rem 1rem;
34 | border-radius: 2px;
35 | cursor: pointer;
36 | --cast: 2px;
37 | box-shadow: var(--cast) var(--cast) 0 var(--grey);
38 | text-shadow: 0.5px 0.5px 0 rgba(0,0,0,0.2);
39 | transition: all 0.2s;
40 | &:hover {
41 | --cast: 4px;
42 | }
43 | }
44 |
45 | .gatsby-image-wrapper img[src*=base64\\,] {
46 | image-rendering: -moz-crisp-edges;
47 | image-rendering: pixelated;
48 | }
49 |
50 | /* Scrollbar Styles */
51 | body::-webkit-scrollbar {
52 | width: 12px;
53 | }
54 | html {
55 | scrollbar-width: thin;
56 | scrollbar-color: var(--red) var(--white);
57 | }
58 | body::-webkit-scrollbar-track {
59 | background: var(--white);
60 | }
61 | body::-webkit-scrollbar-thumb {
62 | background-color: var(--red) ;
63 | border-radius: 6px;
64 | border: 3px solid var(--white);
65 | }
66 |
67 | hr {
68 | border: 0;
69 | height: 8px;
70 | background-image: url(${stripes});
71 | background-size: 1500px;
72 | }
73 |
74 | img {
75 | max-width: 100%;
76 | }
77 |
78 | .tilt {
79 | transform: rotate(-2deg);
80 | position: relative;
81 | display: inline-block;
82 | }
83 |
84 | `;
85 |
86 | export default GlobalStyles;
87 |
--------------------------------------------------------------------------------
/stepped-solutions/47/Grids.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const HomePageGrid = styled.div`
4 | display: grid;
5 | gap: 2rem;
6 | grid-template-columns: repeat(2, minmax(auto, 1fr));
7 | `;
8 |
9 | export const ItemsGrid = styled.div`
10 | display: grid;
11 | gap: 2rem;
12 | grid-template-columns: 1fr 1fr;
13 | `;
14 |
15 | // Single Grid Item (for home page)
16 | export const ItemStyles = styled.div`
17 | text-align: center;
18 | position: relative;
19 | img {
20 | height: auto;
21 | font-size: 0;
22 | }
23 | p {
24 | transform: rotate(-2deg) translateY(-140%);
25 | position: absolute;
26 | width: 100%;
27 | left: 0;
28 | }
29 | .mark {
30 | display: inline;
31 | }
32 | @keyframes shine {
33 | from {
34 | background-position: 200%;
35 | }
36 | to {
37 | background-position: -40px;
38 | }
39 | }
40 | img.loading {
41 | --shine: white;
42 | --background: var(--grey);
43 | background-image: linear-gradient(
44 | 90deg,
45 | var(--background) 0px,
46 | var(--shine) 40px,
47 | var(--background) 80px
48 | );
49 | background-size: 500px;
50 | animation: shine 1s infinite linear;
51 | }
52 | `;
53 |
--------------------------------------------------------------------------------
/stepped-solutions/47/ItemGrid.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ItemsGrid, ItemStyles } from '../styles/Grids';
3 |
4 | export default function ItemGrid({ items }) {
5 | return (
6 |
7 | {items.map((item) => (
8 |
9 |
10 | {item.name}
11 |
12 |
22 |
23 | ))}
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/stepped-solutions/47/Nav.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'gatsby';
3 | import styled from 'styled-components';
4 | import Logo from './Logo';
5 |
6 | const NavStyles = styled.nav`
7 | /* margin-bottom: 3rem; */
8 | .logo {
9 | transform: translateY(-25%);
10 | }
11 | ul {
12 | margin: 0;
13 | padding: 0;
14 | text-align: center;
15 | list-style: none;
16 | display: grid;
17 | grid-template-columns: 1fr 1fr auto 1fr 1fr;
18 | grid-gap: 2rem;
19 | align-items: center;
20 | margin-top: -6rem;
21 | }
22 | li {
23 | --rotate: -2deg;
24 | transform: rotate(var(--rotate));
25 | order: 1;
26 | &:nth-child(1) {
27 | --rotate: 1deg;
28 | }
29 | &:nth-child(2) {
30 | --rotate: -2.5deg;
31 | }
32 | &:nth-child(4) {
33 | --rotate: 2.5deg;
34 | }
35 | &:hover {
36 | --rotate: 3deg;
37 | }
38 | }
39 | a {
40 | font-size: 3rem;
41 | text-decoration: none;
42 | &:hover {
43 | color: var(--red);
44 | }
45 | /* &[aria-current='page'] {
46 | color: var(--red);
47 | } */
48 | }
49 | `;
50 |
51 | export default function Nav() {
52 | return (
53 |
54 |
55 | -
56 | Hot Now
57 |
58 | -
59 | Pizza Menu
60 |
61 | -
62 |
63 |
64 |
65 |
66 | -
67 | SliceMasters
68 |
69 | -
70 | Order Ahead!
71 |
72 |
73 |
74 | );
75 | }
76 |
--------------------------------------------------------------------------------
/stepped-solutions/47/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import useLatestData from '../utils/useLatestData';
3 | import { HomePageGrid } from '../styles/Grids';
4 | import LoadingGrid from '../components/LoadingGrid';
5 | import ItemGrid from '../components/ItemGrid';
6 |
7 | function CurrentlySlicing({ slicemasters }) {
8 | console.log(slicemasters);
9 | return (
10 |
11 |
12 | Slicemasters On
13 |
14 |
Standing by, ready to slice you up!
15 | {!slicemasters &&
}
16 | {slicemasters && !slicemasters?.length && (
17 |
No one is working right now!
18 | )}
19 | {slicemasters?.length &&
}
20 |
21 | );
22 | }
23 | function HotSlices({ hotSlices }) {
24 | return (
25 |
26 |
27 | Hot Slices
28 |
29 |
Come on by, buy the slice!
30 | {!hotSlices &&
}{' '}
31 | {hotSlices && !hotSlices?.length &&
Nothin' in the Case
}
32 | {hotSlices?.length &&
}
33 |
34 | );
35 | }
36 |
37 | export default function HomePage() {
38 | const { slicemasters, hotSlices } = useLatestData();
39 |
40 | return (
41 |
42 |
The Best Pizza Downtown!
43 |
Open 11am to 11pm Every Single Day
44 |
45 |
46 |
47 |
48 |
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/stepped-solutions/47/useLatestData.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 |
3 | const gql = String.raw;
4 |
5 | const deets = `
6 | name
7 | _id
8 | image {
9 | asset {
10 | url
11 | metadata {
12 | lqip
13 | }
14 | }
15 | }
16 | `;
17 |
18 | export default function useLatestData() {
19 | // hot slices
20 | const [hotSlices, setHotSlices] = useState();
21 | // slicemasters
22 | const [slicemasters, setSlicemasters] = useState();
23 | // Use a side effect to fetcht he data from the graphql endpoint
24 | useEffect(function () {
25 | console.log('FETCHING DATA');
26 | // when the component loads, fetch the data
27 | fetch(process.env.GATSBY_GRAPHQL_ENDPOINT, {
28 | method: 'POST',
29 | headers: {
30 | 'Content-Type': 'application/json',
31 | },
32 | body: JSON.stringify({
33 | query: gql`
34 | query {
35 | StoreSettings(id: "downtown") {
36 | name
37 | slicemaster {
38 | ${deets}
39 | }
40 | hotSlices {
41 | ${deets}
42 | }
43 | }
44 | }
45 | `,
46 | }),
47 | })
48 | .then((res) => res.json())
49 | .then((res) => {
50 | // TODO: checl for errors
51 | // set the data to state
52 | setHotSlices(res.data.StoreSettings.hotSlices);
53 | setSlicemasters(res.data.StoreSettings.slicemaster);
54 | })
55 | .catch((err) => {
56 | console.log('SHOOOOOT');
57 | console.log(err);
58 | });
59 | }, []);
60 | return {
61 | hotSlices,
62 | slicemasters,
63 | };
64 | }
65 |
--------------------------------------------------------------------------------