Skip to content

Commit

Permalink
Merge pull request #94 from kachun333/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
kachun333 authored Feb 12, 2023
2 parents c6cbfef + 0be7e69 commit 6f2a26d
Show file tree
Hide file tree
Showing 9 changed files with 189 additions and 7 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"arrow-body-style": "off",
"react/prop-types": "off",
"react/require-default-props": "off",
"react/jsx-no-bind": "off",
"react/no-array-index-key": "warn",
"react/jsx-filename-extension": [
2,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kpmk-1617-grad-mag",
"version": "3.0.0",
"version": "3.0.3",
"private": true,
"dependencies": {
"@emotion/react": "^11.10.5",
Expand Down
6 changes: 0 additions & 6 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import React from "react";
import { createRoot } from "react-dom/client";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import routes from "routes";
import * as serviceWorkerRegistration from "./serviceWorkerRegistration";

const router = createBrowserRouter(routes);

Expand All @@ -18,8 +17,3 @@ root.render(
<RouterProvider router={router} />
</React.StrictMode>
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorkerRegistration.register();
2 changes: 2 additions & 0 deletions src/layout/AppLayout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import AppFab from "layout/AppFab";
import Footer from "layout/Footer";
import NavbarDesktop from "layout/NavbarDesktop";
import NavbarMobile from "layout/NavbarMobile";
import ServiceWorker from "layout/ServiceWorker";
import AppProvider from "providers/AppProvider";
import React from "react";
import { Outlet, ScrollRestoration } from "react-router-dom";
Expand Down Expand Up @@ -37,6 +38,7 @@ const AppLayout: React.FC = () => {
<ShowOnScroll>
<AppFab />
</ShowOnScroll>
<ServiceWorker />
</AppProvider>
);
};
Expand Down
49 changes: 49 additions & 0 deletions src/layout/ServiceWorker/hooks/useServiceWorker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useEffect, useRef, useState } from "react";
import * as serviceWorkerRegistration from "serviceWorkerRegistration";

interface UseServiceWorkerRes {
hasUpdate: boolean;
isLoading: boolean;
updateApp: () => void;
}

function useServiceWorker(): UseServiceWorkerRes {
const [hasUpdate, setHasUpdate] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const serviceWorkerRef = useRef<ServiceWorker | null>(null);

useEffect(() => {
function onHasUpdate(registration: ServiceWorkerRegistration) {
serviceWorkerRef.current = registration.waiting;
setHasUpdate(true);
}

serviceWorkerRegistration.register({
onUpdate: onHasUpdate,
onHasWaiting: onHasUpdate,
});
}, []);

function updateApp() {
setIsLoading(true);
if (!serviceWorkerRef.current) return;
const sw = serviceWorkerRef.current;
// Add listener for state change of service worker
sw.onstatechange = () => {
if (sw?.state === "activated" && navigator.serviceWorker.controller) {
// Reload page if waiting was successfully skipped
window.location.reload();
}
};
sw.postMessage({ type: "SKIP_WAITING" });
setHasUpdate(false);
}

return {
hasUpdate,
isLoading,
updateApp,
};
}

export default useServiceWorker;
26 changes: 26 additions & 0 deletions src/layout/ServiceWorker/index.constants.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import RocketLaunch from "@mui/icons-material/RocketLaunch";
import { CircularProgress } from "@mui/material";
import React from "react";
import * as S from "./index.styled";

interface SnackbarObj {
avatar: React.ReactNode;
title: string;
caption: string;
}

export const HAS_UPDATE_SNACKBAR: SnackbarObj = {
avatar: (
<S.StyledAvatar alt="update app icon">
<RocketLaunch />
</S.StyledAvatar>
),
title: "Update Available",
caption: "Click here for updates",
};

export const UPDATING_SNACKBAR: SnackbarObj = {
avatar: <CircularProgress />,
title: "Updating",
caption: "Just a moment...",
};
57 changes: 57 additions & 0 deletions src/layout/ServiceWorker/index.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Avatar, Typography } from "@mui/material";
import Paper from "@mui/material/Paper";
import { styled } from "@mui/material/styles";

const initialBottom = 32;
const FABHeight = 56;
const marginTopFAB = 8;
const translateY = marginTopFAB + FABHeight;

export const StyledPaper = styled(Paper, {
shouldForwardProp: (prop) => prop !== "show" && prop !== "hasFAB",
})<{ show: boolean; hasFAB: boolean }>(({ theme, show, hasFAB }) => {
const opacity = show ? 1 : 0;
const styleWithFAB = hasFAB && {
transform: `translate(-50%, -${translateY}px)`,
transition: `transform ${theme.transitions.duration.leavingScreen}ms ${theme.transitions.easing.sharp}`,
};

return {
zIndex: 2000, // highest in App
cursor: "pointer",
width: 320,
padding: `${theme.spacing(1.5)} ${theme.spacing(2)}`,
position: "fixed",
bottom: initialBottom,
left: "50%",
opacity,
transform: "translate(-50%)",
transition: `
opacity ${theme.transitions.duration.enteringScreen}ms ${theme.transitions.easing.easeIn},
transform ${theme.transitions.duration.enteringScreen}ms ${theme.transitions.easing.sharp}
`,

[theme.breakpoints.down("sm")]: {
width: "95%",
...styleWithFAB,
},
};
});

export const StyledContainer = styled("div")({
display: "flex",
alignItems: "center",
});

export const StyledTextContainer = styled("div")({
flex: 1,
});

export const StyledCaption = styled(Typography)({
/* so that caption is nicely align */
marginTop: -4,
});

export const StyledAvatar = styled(Avatar)(({ theme }) => ({
backgroundColor: theme.palette.primary.main,
}));
40 changes: 40 additions & 0 deletions src/layout/ServiceWorker/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Typography } from "@mui/material";
import useScrollTrigger from "@mui/material/useScrollTrigger";
import React, { useEffect, useState } from "react";
import useServiceWorker from "./hooks/useServiceWorker";
import { HAS_UPDATE_SNACKBAR, UPDATING_SNACKBAR } from "./index.constants";
import * as S from "./index.styled";

const ServiceWorker: React.FC = () => {
const trigger = useScrollTrigger();
const { isLoading, hasUpdate, updateApp } = useServiceWorker();
const [showUpdatePrompt, setShowUpdatePrompt] = useState(false);

useEffect(() => {
const id = setTimeout(() => setShowUpdatePrompt(hasUpdate), 500);
return () => clearTimeout(id);
}, [hasUpdate]);

if (!hasUpdate) return null;
const snackbarObj = isLoading ? UPDATING_SNACKBAR : HAS_UPDATE_SNACKBAR;
return (
<S.StyledPaper
show={showUpdatePrompt}
hasFAB={trigger}
onClick={updateApp}
elevation={10}
>
<S.StyledContainer>
<S.StyledTextContainer>
<Typography variant="h6">{snackbarObj.title}</Typography>
<Typography variant="caption" component="div">
{snackbarObj.caption}
</Typography>
</S.StyledTextContainer>
{snackbarObj.avatar}
</S.StyledContainer>
</S.StyledPaper>
);
};

export default ServiceWorker;
13 changes: 13 additions & 0 deletions src/serviceWorkerRegistration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,25 @@ interface Config {
onSuccess?: (registration: ServiceWorkerRegistration) => void;
// eslint-disable-next-line no-unused-vars
onUpdate?: (registration: ServiceWorkerRegistration) => void;
// eslint-disable-next-line no-unused-vars
onHasWaiting?: (registration: ServiceWorkerRegistration) => void;
}

function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then((registration) => {
if (registration.waiting && registration.active) {
// The page has been loaded when there's already a waiting and active SW.
// This would happen if skipWaiting() isn't being called, and there are
// still old tabs open.
console.log(
"Please close all tabs to get updates. Alternatively, click the update prompt"
);
if (config && config.onHasWaiting) {
config.onHasWaiting(registration);
}
}
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
Expand Down

0 comments on commit 6f2a26d

Please sign in to comment.