72 | )
73 | }
74 |
75 | export default Home
76 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals'
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler): void => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry)
7 | getFID(onPerfEntry)
8 | getFCP(onPerfEntry)
9 | getLCP(onPerfEntry)
10 | getTTFB(onPerfEntry)
11 | })
12 | }
13 | }
14 |
15 | export default reportWebVitals
16 |
--------------------------------------------------------------------------------
/src/service-worker.ts:
--------------------------------------------------------------------------------
1 | ///
2 | /* eslint-disable no-restricted-globals */
3 |
4 | // This service worker can be customized!
5 | // See https://developers.google.com/web/tools/workbox/modules
6 | // for the list of available Workbox modules, or add any other
7 | // code you'd like.
8 | // You can also remove this file if you'd prefer not to use a
9 | // service worker, and the Workbox build step will be skipped.
10 |
11 | import { clientsClaim } from 'workbox-core'
12 | import { ExpirationPlugin } from 'workbox-expiration'
13 | import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching'
14 | import { registerRoute } from 'workbox-routing'
15 | import { StaleWhileRevalidate } from 'workbox-strategies'
16 |
17 | declare const self: ServiceWorkerGlobalScope
18 |
19 | clientsClaim()
20 |
21 | // Precache all of the assets generated by your build process.
22 | // Their URLs are injected into the manifest variable below.
23 | // This variable must be present somewhere in your service worker file,
24 | // even if you decide not to use precaching. See https://cra.link/PWA
25 | precacheAndRoute(self.__WB_MANIFEST)
26 |
27 | // Set up App Shell-style routing, so that all navigation requests
28 | // are fulfilled with your index.html shell. Learn more at
29 | // https://developers.google.com/web/fundamentals/architecture/app-shell
30 | const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$')
31 | registerRoute(
32 | // Return false to exempt requests from being fulfilled by index.html.
33 | ({ request, url }: { request: Request, url: URL }) => {
34 | // If this isn't a navigation, skip.
35 | if (request.mode !== 'navigate') {
36 | return false
37 | }
38 |
39 | // If this is a URL that starts with /_, skip.
40 | if (url.pathname.startsWith('/_')) {
41 | return false
42 | }
43 |
44 | // If this looks like a URL for a resource, because it contains
45 | // a file extension, skip.
46 | if (url.pathname.match(fileExtensionRegexp)) {
47 | return false
48 | }
49 |
50 | // Return true to signal that we want to use the handler.
51 | return true
52 | },
53 | createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html')
54 | )
55 |
56 | // An example runtime caching route for requests that aren't handled by the
57 | // precache, in this case same-origin .png requests like those from in public/
58 | registerRoute(
59 | // Add in any other file extensions or routing criteria as needed.
60 | ({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'),
61 | // Customize this strategy as needed, e.g., by changing to CacheFirst.
62 | new StaleWhileRevalidate({
63 | cacheName: 'images',
64 | plugins: [
65 | // Ensure that once this runtime cache reaches a maximum size the
66 | // least-recently used images are removed.
67 | new ExpirationPlugin({ maxEntries: 50 }),
68 | ],
69 | })
70 | )
71 |
72 | // This allows the web app to trigger skipWaiting via
73 | // registration.waiting.postMessage({type: 'SKIP_WAITING'})
74 | self.addEventListener('message', (event) => {
75 | if (event.data && event.data.type === 'SKIP_WAITING') {
76 | self.skipWaiting()
77 | }
78 | })
79 |
80 | // Any other custom service worker logic can go here.
81 |
--------------------------------------------------------------------------------
/src/serviceWorkerRegistration.ts:
--------------------------------------------------------------------------------
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://cra.link/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(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
19 | )
20 |
21 | type Config = {
22 | onSuccess?: (registration: ServiceWorkerRegistration) => void,
23 | onUpdate?: (registration: ServiceWorkerRegistration) => void
24 | }
25 |
26 | export function register(config?: Config): void {
27 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
28 | // The URL constructor is available in all browsers that support SW.
29 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href)
30 | if (publicUrl.origin !== window.location.origin) {
31 | // Our service worker won't work if PUBLIC_URL is on a different origin
32 | // from what our page is served on. This might happen if a CDN is used to
33 | // serve assets see https://github.com/facebook/create-react-app/issues/2374
34 | return
35 | }
36 |
37 | window.addEventListener('load', () => {
38 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
39 |
40 | if (isLocalhost) {
41 | // This is running on localhost. Let's check if a service worker still exists or not.
42 | checkValidServiceWorker(swUrl, config)
43 |
44 | // Add some additional logging to localhost, pointing developers to the
45 | // service worker/PWA documentation.
46 | navigator.serviceWorker.ready.then(() => {
47 | console.log(
48 | 'This web app is being served cache-first by a service ' +
49 | 'worker. To learn more, visit https://cra.link/PWA'
50 | )
51 | })
52 | } else {
53 | // Is not localhost. Just register service worker
54 | registerValidSW(swUrl, config)
55 | }
56 | })
57 | }
58 | }
59 |
60 | function registerValidSW(swUrl: string, config?: Config) {
61 | navigator.serviceWorker
62 | .register(swUrl)
63 | .then((registration) => {
64 | registration.onupdatefound = () => {
65 | const installingWorker = registration.installing
66 | if (installingWorker == null) {
67 | return
68 | }
69 | installingWorker.onstatechange = () => {
70 | if (installingWorker.state === 'installed') {
71 | if (navigator.serviceWorker.controller) {
72 | // At this point, the updated precached content has been fetched,
73 | // but the previous service worker will still serve the older
74 | // content until all client tabs are closed.
75 | console.log(
76 | 'New content is available and will be used when all ' +
77 | 'tabs for this page are closed. See https://cra.link/PWA.'
78 | )
79 |
80 | // Execute callback
81 | if (config && config.onUpdate) {
82 | config.onUpdate(registration)
83 | }
84 | } else {
85 | // At this point, everything has been precached.
86 | // It's the perfect time to display a
87 | // "Content is cached for offline use." message.
88 | console.log('Content is cached for offline use.')
89 |
90 | // Execute callback
91 | if (config && config.onSuccess) {
92 | config.onSuccess(registration)
93 | }
94 | }
95 | }
96 | }
97 | }
98 | })
99 | .catch((error) => {
100 | console.error('Error during service worker registration:', error)
101 | })
102 | }
103 |
104 | function checkValidServiceWorker(swUrl: string, config?: Config) {
105 | // Check if the service worker can be found. If it can't reload the page.
106 | fetch(swUrl, {
107 | headers: { 'Service-Worker': 'script' },
108 | })
109 | .then((response) => {
110 | // Ensure service worker exists, and that we really are getting a JS file.
111 | const contentType = response.headers.get('content-type')
112 | if (
113 | response.status === 404 ||
114 | contentType != null && contentType.indexOf('javascript') === -1
115 | ) {
116 | // No service worker found. Probably a different app. Reload the page.
117 | navigator.serviceWorker.ready.then((registration) => {
118 | registration.unregister().then(() => {
119 | window.location.reload()
120 | })
121 | })
122 | } else {
123 | // Service worker found. Proceed as normal.
124 | registerValidSW(swUrl, config)
125 | }
126 | })
127 | .catch(() => {
128 | console.log('No internet connection found. App is running in offline mode.')
129 | })
130 | }
131 |
132 | export function unregister(): void {
133 | if ('serviceWorker' in navigator) {
134 | navigator.serviceWorker.ready
135 | .then((registration) => {
136 | registration.unregister()
137 | })
138 | .catch((error) => {
139 | console.error(error.message)
140 | })
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/setupTests.ts:
--------------------------------------------------------------------------------
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'
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx",
22 | "noImplicitAny": false
23 | },
24 | "include": [
25 | "src"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------