├── .gitignore ├── README.md ├── mysanityblog ├── README.md ├── config │ ├── .checksums │ └── @sanity │ │ ├── data-aspects.json │ │ ├── default-layout.json │ │ ├── default-login.json │ │ └── form-builder.json ├── package.json ├── plugins │ └── .gitkeep ├── sanity.json ├── schemas │ ├── author.js │ ├── blockContent.js │ ├── category.js │ ├── post.js │ └── schema.js ├── static │ ├── .gitkeep │ └── favicon.ico ├── tsconfig.json └── yarn.lock ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.css ├── App.js ├── App.test.js ├── client.js ├── components │ ├── AllPosts.js │ └── OnePost.js ├── index.css ├── index.js ├── logo.svg ├── serviceWorker.js └── setupTests.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Getting the App Up and Running 4 | 5 | - `git clone` and `cd` into the project 6 | 7 | ### React App 8 | - `npm install` to install all dependencies 9 | - `npm start` 10 | - Navigate to `localhost:3000` to view app 11 | 12 | ### Sanity Studio 13 | 14 | To get our Sanity Studio up and running, we need to have a projectId in `src/client.js`. If you already have a `projectId` then be sure to add that value there. Otherwise you will need to `sanity init`. All instructions are below. 15 | 16 | If you already have a `projectId`: 17 | - Run the command `cd mysanityblog` 18 | - Run `sanity install` to install Sanity dependencies 19 | - Add `projectId` to `src/client.js` 20 | - Run `sanity start` 21 | - Navigate to `localhost:3333` to view Sanity Studio 22 | 23 | If you _do not_ have a `projectId`: 24 | - Run the command `cd mysanityblog` 25 | - Run `sanity init` 26 | - Answer the following questions: 27 | * Create new project — Hit Enter. 28 | * Your project name: — We can name it whatever we would like. Let’s use My Sanity Blog for this project. 29 | * Use the default dataset configuration? — The default dataset configuration has a public dataset named "production", let's stick with that. So type in "Y" and hit Enter. 30 | * Project output path: — This will show us the path where our sanity project will live. The path should show the path that leads to this: /sanity-react/mysanityblog. Hit Enter. 31 | * Select project template: — Here we are going to choose Blog (schema). Using the arrow keys, navigate to that so it’s showing blue. Hit Enter once there. Success! 32 | - Add new `projectId` to `src/client.js` (detailed instructions below) 33 | - Run `sanity start` 34 | - Navigate to `localhost:3333` to view Sanity Studio 35 | 36 | ## Sanity Management 37 | 38 | Connecting the React app and Sanity project can be done with the following steps: 39 | 40 | - Navigate to [manage.sanity.io](https://manage.sanity.io/) 41 | - Click on the name of your project (name was created during `sanity init`) 42 | - Find the `projectId` at the top of the project's dashboard. 43 | * Navigate to `src/client.js` in the code and insert the projectId where it says "YOUR PROJECT ID HERE" 44 | - Back in the project's dashboard, go to the "Settings" tab 45 | * Click on API 46 | * Under "CORS Origins" click on "ADD NEW ORIGIN" 47 | * Insert `http://localhost:3000` 48 | * Click on "ADD NEW ORIGIN" 49 | 50 | **Happy Blogging!** 51 | -------------------------------------------------------------------------------- /mysanityblog/README.md: -------------------------------------------------------------------------------- 1 | # Sanity Blogging Content Studio 2 | 3 | Congratulations, you have now installed the Sanity Content Studio, an open source real-time content editing environment connected to the Sanity backend. 4 | 5 | Now you can do the following things: 6 | 7 | - [Read “getting started” in the docs](https://www.sanity.io/docs/introduction/getting-started?utm_source=readme) 8 | - Check out the example frontend: [React/Next.js](https://github.com/sanity-io/tutorial-sanity-blog-react-next) 9 | - [Read the blog post about this template](https://www.sanity.io/blog/build-your-own-blog-with-sanity-and-next-js?utm_source=readme) 10 | - [Join the community Slack](https://slack.sanity.io/?utm_source=readme) 11 | - [Extend and build plugins](https://www.sanity.io/docs/content-studio/extending?utm_source=readme) 12 | -------------------------------------------------------------------------------- /mysanityblog/config/.checksums: -------------------------------------------------------------------------------- 1 | { 2 | "#": "Used by Sanity to keep track of configuration file checksums, do not delete or modify!", 3 | "@sanity/default-layout": "bb034f391ba508a6ca8cd971967cbedeb131c4d19b17b28a0895f32db5d568ea", 4 | "@sanity/default-login": "6fb6d3800aa71346e1b84d95bbcaa287879456f2922372bb0294e30b968cd37f", 5 | "@sanity/form-builder": "b38478227ba5e22c91981da4b53436df22e48ff25238a55a973ed620be5068aa", 6 | "@sanity/data-aspects": "d199e2c199b3e26cd28b68dc84d7fc01c9186bf5089580f2e2446994d36b3cb6" 7 | } 8 | -------------------------------------------------------------------------------- /mysanityblog/config/@sanity/data-aspects.json: -------------------------------------------------------------------------------- 1 | { 2 | "listOptions": {} 3 | } 4 | -------------------------------------------------------------------------------- /mysanityblog/config/@sanity/default-layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "toolSwitcher": { 3 | "order": [], 4 | "hidden": [] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /mysanityblog/config/@sanity/default-login.json: -------------------------------------------------------------------------------- 1 | { 2 | "providers": { 3 | "mode": "append", 4 | "redirectOnSingle": false, 5 | "entries": [] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /mysanityblog/config/@sanity/form-builder.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": { 3 | "directUploads": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /mysanityblog/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mysanityblog", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "main": "package.json", 7 | "author": "Kapehe Jorgenson ", 8 | "license": "UNLICENSED", 9 | "scripts": { 10 | "start": "sanity start", 11 | "test": "sanity check" 12 | }, 13 | "keywords": [ 14 | "sanity" 15 | ], 16 | "dependencies": { 17 | "@sanity/base": "^1.149.19", 18 | "@sanity/components": "^1.149.19", 19 | "@sanity/core": "^1.149.19", 20 | "@sanity/default-layout": "^1.149.19", 21 | "@sanity/default-login": "^1.149.18", 22 | "@sanity/desk-tool": "^1.149.19", 23 | "@sanity/vision": "^1.149.16", 24 | "prop-types": "^15.6", 25 | "react": "^16.2", 26 | "react-dom": "^16.2" 27 | }, 28 | "devDependencies": {} 29 | } 30 | -------------------------------------------------------------------------------- /mysanityblog/plugins/.gitkeep: -------------------------------------------------------------------------------- 1 | User-specific packages can be placed here 2 | -------------------------------------------------------------------------------- /mysanityblog/sanity.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "project": { 4 | "name": "My Sanity Blog" 5 | }, 6 | "api": { 7 | "projectId": "us3msdvj", 8 | "dataset": "production" 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 | } 31 | -------------------------------------------------------------------------------- /mysanityblog/schemas/author.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'author', 3 | title: 'Author', 4 | type: 'document', 5 | fields: [ 6 | { 7 | name: 'name', 8 | title: 'Name', 9 | type: 'string' 10 | }, 11 | { 12 | name: 'slug', 13 | title: 'Slug', 14 | type: 'slug', 15 | options: { 16 | source: 'name', 17 | maxLength: 96 18 | } 19 | }, 20 | { 21 | name: 'image', 22 | title: 'Image', 23 | type: 'image', 24 | options: { 25 | hotspot: true 26 | } 27 | }, 28 | { 29 | name: 'bio', 30 | title: 'Bio', 31 | type: 'array', 32 | of: [ 33 | { 34 | title: 'Block', 35 | type: 'block', 36 | styles: [{title: 'Normal', value: 'normal'}], 37 | lists: [] 38 | } 39 | ] 40 | } 41 | ], 42 | preview: { 43 | select: { 44 | title: 'name', 45 | media: 'image' 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /mysanityblog/schemas/blockContent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the schema definition for the rich text fields used for 3 | * for this blog studio. When you import it in schemas.js it can be 4 | * reused in other parts of the studio with: 5 | * { 6 | * name: 'someName', 7 | * title: 'Some title', 8 | * type: 'blockContent' 9 | * } 10 | */ 11 | export default { 12 | title: 'Block Content', 13 | name: 'blockContent', 14 | type: 'array', 15 | of: [ 16 | { 17 | title: 'Block', 18 | type: 'block', 19 | // Styles let you set what your user can mark up blocks with. These 20 | // correspond with HTML tags, but you can set any title or value 21 | // you want and decide how you want to deal with it where you want to 22 | // use your content. 23 | styles: [ 24 | {title: 'Normal', value: 'normal'}, 25 | {title: 'H1', value: 'h1'}, 26 | {title: 'H2', value: 'h2'}, 27 | {title: 'H3', value: 'h3'}, 28 | {title: 'H4', value: 'h4'}, 29 | {title: 'Quote', value: 'blockquote'} 30 | ], 31 | lists: [{title: 'Bullet', value: 'bullet'}], 32 | // Marks let you mark up inline text in the block editor. 33 | marks: { 34 | // Decorators usually describe a single property – e.g. a typographic 35 | // preference or highlighting by editors. 36 | decorators: [{title: 'Strong', value: 'strong'}, {title: 'Emphasis', value: 'em'}], 37 | // Annotations can be any object structure – e.g. a link or a footnote. 38 | annotations: [ 39 | { 40 | title: 'URL', 41 | name: 'link', 42 | type: 'object', 43 | fields: [ 44 | { 45 | title: 'URL', 46 | name: 'href', 47 | type: 'url' 48 | } 49 | ] 50 | } 51 | ] 52 | } 53 | }, 54 | // You can add additional types here. Note that you can't use 55 | // primitive types such as 'string' and 'number' in the same array 56 | // as a block type. 57 | { 58 | type: 'image', 59 | options: {hotspot: true} 60 | } 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /mysanityblog/schemas/category.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'category', 3 | title: 'Category', 4 | type: 'document', 5 | fields: [ 6 | { 7 | name: 'title', 8 | title: 'Title', 9 | type: 'string' 10 | }, 11 | { 12 | name: 'description', 13 | title: 'Description', 14 | type: 'text' 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /mysanityblog/schemas/post.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'post', 3 | title: 'Post', 4 | type: 'document', 5 | fields: [ 6 | { 7 | name: 'title', 8 | title: 'Title', 9 | type: 'string' 10 | }, 11 | { 12 | name: 'slug', 13 | title: 'Slug', 14 | type: 'slug', 15 | options: { 16 | source: 'title', 17 | maxLength: 96 18 | } 19 | }, 20 | { 21 | name: 'author', 22 | title: 'Author', 23 | type: 'reference', 24 | to: {type: 'author'} 25 | }, 26 | { 27 | name: 'mainImage', 28 | title: 'Main image', 29 | type: 'image', 30 | options: { 31 | hotspot: true 32 | } 33 | }, 34 | { 35 | name: 'categories', 36 | title: 'Categories', 37 | type: 'array', 38 | of: [{type: 'reference', to: {type: 'category'}}] 39 | }, 40 | { 41 | name: 'publishedAt', 42 | title: 'Published at', 43 | type: 'datetime' 44 | }, 45 | { 46 | name: 'body', 47 | title: 'Body', 48 | type: 'blockContent' 49 | } 50 | ], 51 | 52 | preview: { 53 | select: { 54 | title: 'title', 55 | author: 'author.name', 56 | media: 'mainImage' 57 | }, 58 | prepare(selection) { 59 | const {author} = selection 60 | return Object.assign({}, selection, { 61 | subtitle: author && `by ${author}` 62 | }) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /mysanityblog/schemas/schema.js: -------------------------------------------------------------------------------- 1 | // First, we must import the schema creator 2 | import createSchema from 'part:@sanity/base/schema-creator' 3 | 4 | // Then import schema types from any plugins that might expose them 5 | import schemaTypes from 'all:part:@sanity/base/schema-type' 6 | 7 | // We import object and document schemas 8 | import blockContent from './blockContent' 9 | import category from './category' 10 | import post from './post' 11 | import author from './author' 12 | 13 | // Then we give our schema to the builder and provide the result to Sanity 14 | export default createSchema({ 15 | // We name our schema 16 | name: 'default', 17 | // Then proceed to concatenate our document type 18 | // to the ones provided by any plugins that are installed 19 | types: schemaTypes.concat([ 20 | // The following are document types which will appear 21 | // in the studio. 22 | post, 23 | author, 24 | category, 25 | // When added to this list, object types can be used as 26 | // { type: 'typename' } in other document schemas 27 | blockContent 28 | ]) 29 | }) 30 | -------------------------------------------------------------------------------- /mysanityblog/static/.gitkeep: -------------------------------------------------------------------------------- 1 | Files placed here will be served by the Sanity server under the `/static`-prefix 2 | -------------------------------------------------------------------------------- /mysanityblog/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanity-io/create-react-app-blog/b73ce4464fddfb777355aace5037daea0b94de3f/mysanityblog/static/favicon.ico -------------------------------------------------------------------------------- /mysanityblog/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./node_modules/@sanity/base/types/**/*.ts", "./**/*.ts", "./**/*.tsx"] 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sanity-react", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@sanity/block-content-to-react": "^2.0.7", 7 | "@sanity/client": "^1.149.18", 8 | "@sanity/image-url": "^0.140.19", 9 | "@testing-library/jest-dom": "^4.2.4", 10 | "@testing-library/react": "^9.3.2", 11 | "@testing-library/user-event": "^7.1.2", 12 | "react": "^16.13.1", 13 | "react-dom": "^16.13.1", 14 | "react-router-dom": "^5.2.0", 15 | "react-scripts": "3.4.3" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test", 21 | "eject": "react-scripts eject" 22 | }, 23 | "eslintConfig": { 24 | "extends": "react-app" 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanity-io/create-react-app-blog/b73ce4464fddfb777355aace5037daea0b94de3f/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanity-io/create-react-app-blog/b73ce4464fddfb777355aace5037daea0b94de3f/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanity-io/create-react-app-blog/b73ce4464fddfb777355aace5037daea0b94de3f/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BrowserRouter, Route } from "react-router-dom"; 3 | import AllPosts from "./components/AllPosts.js"; 4 | import OnePost from "./components/OnePost.js"; 5 | 6 | function App() { 7 | return ( 8 | 9 |
10 | 11 | 12 |
13 |
14 | ); 15 | } 16 | 17 | export default App; 18 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /src/client.js: -------------------------------------------------------------------------------- 1 | import sanityClient from "@sanity/client"; 2 | 3 | export default sanityClient({ 4 | projectId: "YOUR PROJECT ID HERE", // find this at manage.sanity.io, run `sanity init` to initialize 5 | dataset: "production", // this is from when we answered those question from 'sanity init' 6 | useCdn: true, 7 | }); 8 | -------------------------------------------------------------------------------- /src/components/AllPosts.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import sanityClient from "../client.js"; 4 | 5 | export default function AllPosts() { 6 | const [allPostsData, setAllPosts] = useState(null); 7 | 8 | useEffect(() => { 9 | sanityClient 10 | .fetch( 11 | `*[_type == "post"]{ 12 | title, 13 | slug, 14 | mainImage{ 15 | asset->{ 16 | _id, 17 | url 18 | } 19 | } 20 | }` 21 | ) 22 | .then((data) => setAllPosts(data)) 23 | .catch(console.error); 24 | }, []); 25 | 26 | console.log(allPostsData); 27 | 28 | return ( 29 |
30 |
31 |

Blog Posts

32 |

33 | Welcome to my blog posts page! 34 |

35 | 36 |
37 | {allPostsData && 38 | allPostsData.map((post, index) => ( 39 | 40 | 44 | 49 | 50 |

51 | {post.title} 52 |

53 |
54 |
55 | 56 | ))} 57 |
58 |
59 |
60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /src/components/OnePost.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useParams } from "react-router-dom"; 3 | import sanityClient from "../client.js"; 4 | import BlockContent from "@sanity/block-content-to-react"; 5 | import imageUrlBuilder from "@sanity/image-url"; 6 | 7 | const builder = imageUrlBuilder(sanityClient); 8 | function urlFor(source) { 9 | return builder.image(source); 10 | } 11 | 12 | export default function OnePost() { 13 | const [postData, setPostData] = useState(null); 14 | const { slug } = useParams(); 15 | 16 | useEffect(() => { 17 | sanityClient 18 | .fetch( 19 | `*[slug.current == "${slug}"]{ 20 | title, 21 | slug, 22 | mainImage{ 23 | asset->{ 24 | _id, 25 | url 26 | } 27 | }, 28 | body, 29 | "name": author->name, 30 | "authorImage": author->image 31 | }` 32 | ) 33 | .then((data) => setPostData(data[0])) 34 | .catch(console.error); 35 | }, [slug]); 36 | 37 | if (!postData) return
Loading...
; 38 | 39 | return ( 40 |
41 |
42 |
43 |
44 | {/* Title Section */} 45 |
46 |

47 | {postData.title} 48 |

49 |
50 | Author is Kap 55 |

56 | {postData.name} 57 |

58 |
59 |
60 |
61 | 67 |
68 |
69 | 74 |
75 |
76 |
77 | ); 78 | } 79 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import url("https://unpkg.com/@tailwindcss/typography@0.2.x/dist/typography.min.css"); 2 | @import url("https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css"); 3 | @import url("https://fonts.googleapis.com/css2?family=Amatic+SC:wght@700&display=swap"); 4 | 5 | .cursive { 6 | font-family: "Amatic SC", cursive; 7 | font-weight: 700; 8 | } 9 | 10 | .prose img { 11 | margin-left: auto; 12 | margin-right: auto; 13 | border-radius: 5px; 14 | } 15 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want your app to work offline and load faster, you can change 15 | // unregister() to register() below. Note this comes with some pitfalls. 16 | // Learn more about service workers: https://bit.ly/CRA-PWA 17 | serviceWorker.unregister(); 18 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' }, 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready 134 | .then(registration => { 135 | registration.unregister(); 136 | }) 137 | .catch(error => { 138 | console.error(error.message); 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | --------------------------------------------------------------------------------