Added banner to be displayed when the service worker has updated the app in the background

This commit is contained in:
Alejandro Celaya 2021-06-06 18:41:10 +02:00
parent 4546b74b6f
commit 5caa648112
9 changed files with 90 additions and 20 deletions

View file

@ -1,4 +1,5 @@
@import './utils/base'; @import './utils/base';
@import './utils/mixins/horizontal-align';
.app-container { .app-container {
height: 100%; height: 100%;
@ -24,3 +25,18 @@
padding: 0 15px; padding: 0 15px;
} }
} }
.app__update-banner.app__update-banner {
@include horizontal-align();
position: fixed;
top: $headerHeight - 25px;
padding: 0 4rem 0 0;
z-index: 1040;
margin: 0;
color: var(--text-color);
text-align: center;
width: 700px;
max-width: calc(100% - 30px);
box-shadow: 0 0 1rem var(--brand-color);
}

View file

@ -1,15 +1,19 @@
import { useEffect, FC } from 'react'; import { useEffect, FC } from 'react';
import { Route, Switch } from 'react-router-dom'; import { Route, Switch } from 'react-router-dom';
import { Alert } from 'reactstrap';
import NotFound from './common/NotFound'; import NotFound from './common/NotFound';
import { ServersMap } from './servers/data'; import { ServersMap } from './servers/data';
import { Settings } from './settings/reducers/settings'; import { Settings } from './settings/reducers/settings';
import { changeThemeInMarkup } from './utils/theme'; import { changeThemeInMarkup } from './utils/theme';
import { SimpleCard } from './utils/SimpleCard';
import './App.scss'; import './App.scss';
interface AppProps { interface AppProps {
fetchServers: Function; fetchServers: () => void;
servers: ServersMap; servers: ServersMap;
settings: Settings; settings: Settings;
resetAppUpdate: () => void;
appUpdated: boolean;
} }
const App = ( const App = (
@ -20,7 +24,7 @@ const App = (
EditServer: FC, EditServer: FC,
Settings: FC, Settings: FC,
ShlinkVersionsContainer: FC, ShlinkVersionsContainer: FC,
) => ({ fetchServers, servers, settings }: AppProps) => { ) => ({ fetchServers, servers, settings, appUpdated, resetAppUpdate }: AppProps) => {
useEffect(() => { useEffect(() => {
// On first load, try to fetch the remote servers if the list is empty // On first load, try to fetch the remote servers if the list is empty
if (Object.keys(servers).length === 0) { if (Object.keys(servers).length === 0) {
@ -50,6 +54,17 @@ const App = (
<ShlinkVersionsContainer /> <ShlinkVersionsContainer />
</div> </div>
</div> </div>
<Alert
className="app__update-banner"
tag={SimpleCard}
color="secondary"
isOpen={appUpdated}
toggle={resetAppUpdate}
>
<h4 className="mb-4">This app has just been updated!</h4>
<p className="mb-0">Restart it to enjoy the new features.</p>
</Alert>
</div> </div>
); );
}; };

View file

@ -0,0 +1,18 @@
import { Action } from 'redux';
import { buildActionCreator, buildReducer } from '../../utils/helpers/redux';
/* eslint-disable padding-line-between-statements */
export const APP_UPDATE_AVAILABLE = 'shlink/appUpdates/APP_UPDATE_AVAILABLE';
export const RESET_APP_UPDATE = 'shlink/appUpdates/RESET_APP_UPDATE';
/* eslint-enable padding-line-between-statements */
const initialState = false;
export default buildReducer<boolean, Action<string>>({
[APP_UPDATE_AVAILABLE]: () => true,
[RESET_APP_UPDATE]: () => false,
}, initialState);
export const appUpdateAvailable = buildActionCreator(APP_UPDATE_AVAILABLE);
export const resetAppUpdate = buildActionCreator(RESET_APP_UPDATE);

View file

@ -0,0 +1,26 @@
import Bottle from 'bottlejs';
import { appUpdateAvailable, resetAppUpdate } from '../reducers/appUpdates';
import App from '../../App';
import { ConnectDecorator } from '../../container/types';
const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
// Components
bottle.serviceFactory(
'App',
App,
'MainHeader',
'Home',
'MenuLayout',
'CreateServer',
'EditServer',
'Settings',
'ShlinkVersionsContainer',
);
bottle.decorator('App', connect([ 'servers', 'settings', 'appUpdated' ], [ 'fetchServers', 'resetAppUpdate' ]));
// Actions
bottle.serviceFactory('appUpdateAvailable', () => appUpdateAvailable);
bottle.serviceFactory('resetAppUpdate', () => resetAppUpdate);
};
export default provideServices;

View file

@ -3,9 +3,9 @@ import { Link } from 'react-router-dom';
import { Card, Row } from 'reactstrap'; import { Card, Row } from 'reactstrap';
import { ExternalLink } from 'react-external-link'; import { ExternalLink } from 'react-external-link';
import ServersListGroup from '../servers/ServersListGroup'; import ServersListGroup from '../servers/ServersListGroup';
import './Home.scss';
import { ServersMap } from '../servers/data'; import { ServersMap } from '../servers/data';
import { ShlinkLogo } from './img/ShlinkLogo'; import { ShlinkLogo } from './img/ShlinkLogo';
import './Home.scss';
export interface HomeProps { export interface HomeProps {
servers: ServersMap; servers: ServersMap;

View file

@ -2,7 +2,6 @@ import Bottle, { IContainer } from 'bottlejs';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import { connect as reduxConnect } from 'react-redux'; import { connect as reduxConnect } from 'react-redux';
import { pick } from 'ramda'; import { pick } from 'ramda';
import App from '../App';
import provideApiServices from '../api/services/provideServices'; import provideApiServices from '../api/services/provideServices';
import provideCommonServices from '../common/services/provideServices'; import provideCommonServices from '../common/services/provideServices';
import provideShortUrlsServices from '../short-urls/services/provideServices'; import provideShortUrlsServices from '../short-urls/services/provideServices';
@ -13,6 +12,7 @@ import provideUtilsServices from '../utils/services/provideServices';
import provideMercureServices from '../mercure/services/provideServices'; import provideMercureServices from '../mercure/services/provideServices';
import provideSettingsServices from '../settings/services/provideServices'; import provideSettingsServices from '../settings/services/provideServices';
import provideDomainsServices from '../domains/services/provideServices'; import provideDomainsServices from '../domains/services/provideServices';
import provideAppServices from '../app/services/provideServices';
import { ConnectDecorator } from './types'; import { ConnectDecorator } from './types';
type LazyActionMap = Record<string, Function>; type LazyActionMap = Record<string, Function>;
@ -33,19 +33,7 @@ const connect: ConnectDecorator = (propsFromState: string[] | null, actionServic
actionServiceNames.reduce(mapActionService, {}), actionServiceNames.reduce(mapActionService, {}),
); );
bottle.serviceFactory( provideAppServices(bottle, connect);
'App',
App,
'MainHeader',
'Home',
'MenuLayout',
'CreateServer',
'EditServer',
'Settings',
'ShlinkVersionsContainer',
);
bottle.decorator('App', connect([ 'servers', 'settings' ], [ 'fetchServers' ]));
provideCommonServices(bottle, connect, withRouter); provideCommonServices(bottle, connect, withRouter);
provideApiServices(bottle); provideApiServices(bottle);
provideShortUrlsServices(bottle, connect); provideShortUrlsServices(bottle, connect);

View file

@ -35,6 +35,7 @@ export interface ShlinkState {
settings: Settings; settings: Settings;
domainsList: DomainsList; domainsList: DomainsList;
visitsOverview: VisitsOverview; visitsOverview: VisitsOverview;
appUpdated: boolean;
} }
export type ConnectDecorator = (props: string[] | null, actions?: string[]) => any; export type ConnectDecorator = (props: string[] | null, actions?: string[]) => any;

View file

@ -5,7 +5,7 @@ import { homepage } from '../package.json';
import container from './container'; import container from './container';
import store from './container/store'; import store from './container/store';
import { fixLeafletIcons } from './utils/helpers/leaflet'; import { fixLeafletIcons } from './utils/helpers/leaflet';
import * as serviceWorkerRegistration from './serviceWorkerRegistration'; import { register as registerServiceWorker } from './serviceWorkerRegistration';
import 'react-datepicker/dist/react-datepicker.css'; import 'react-datepicker/dist/react-datepicker.css';
import 'leaflet/dist/leaflet.css'; import 'leaflet/dist/leaflet.css';
import './index.scss'; import './index.scss';
@ -13,7 +13,7 @@ import './index.scss';
// This overwrites icons used for leaflet maps, fixing some issues caused by webpack while processing the CSS // This overwrites icons used for leaflet maps, fixing some issues caused by webpack while processing the CSS
fixLeafletIcons(); fixLeafletIcons();
const { App, ScrollToTop, ErrorHandler } = container; const { App, ScrollToTop, ErrorHandler, appUpdateAvailable } = container;
render( render(
<Provider store={store}> <Provider store={store}>
@ -31,4 +31,8 @@ render(
// If you want your app to work offline and load faster, you can change // If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls. // unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://cra.link/PWA // Learn more about service workers: https://cra.link/PWA
serviceWorkerRegistration.register(); registerServiceWorker({
onUpdate() {
store.dispatch(appUpdateAvailable()); // eslint-disable-line @typescript-eslint/no-unsafe-call
},
});

View file

@ -17,6 +17,7 @@ import mercureInfoReducer from '../mercure/reducers/mercureInfo';
import settingsReducer from '../settings/reducers/settings'; import settingsReducer from '../settings/reducers/settings';
import domainsListReducer from '../domains/reducers/domainsList'; import domainsListReducer from '../domains/reducers/domainsList';
import visitsOverviewReducer from '../visits/reducers/visitsOverview'; import visitsOverviewReducer from '../visits/reducers/visitsOverview';
import appUpdatesReducer from '../app/reducers/appUpdates';
import { ShlinkState } from '../container/types'; import { ShlinkState } from '../container/types';
export default combineReducers<ShlinkState>({ export default combineReducers<ShlinkState>({
@ -38,4 +39,5 @@ export default combineReducers<ShlinkState>({
settings: settingsReducer, settings: settingsReducer,
domainsList: domainsListReducer, domainsList: domainsListReducer,
visitsOverview: visitsOverviewReducer, visitsOverview: visitsOverviewReducer,
appUpdated: appUpdatesReducer,
}); });