Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added manifest.json, works with no navbar for PWA #6640

Merged
merged 27 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c9d9c62
Added manifest.json, works on iOS with no navbar for PWA
mw2000 Feb 8, 2024
1fc5a50
Added working ios prompt
mw2000 Feb 13, 2024
4d5aefd
Some css additions
mw2000 Feb 13, 2024
715e144
Fixed some scss errors and flushed out android testing + design accur…
mw2000 Feb 14, 2024
cc45afa
Added fixes suggested by Marcin (some outstanding)
mw2000 Feb 15, 2024
f899593
Merge branch 'master' into 6226-add-manifestjson-for-pwa-standalone-mode
mw2000 Feb 15, 2024
499df9c
Added correct common svg
mw2000 Feb 15, 2024
2dfffad
Added new png asset for manifest instead of svg
mw2000 Feb 15, 2024
e56e4e2
Added icon from phosphorous and resolved some scss issues
mw2000 Feb 15, 2024
6ddba82
Small .scss fixes
mw2000 Feb 15, 2024
0571299
extracted magic strings to a const
mw2000 Feb 15, 2024
90c9c73
no prompt on marketing page
mw2000 Feb 15, 2024
594e01f
Addressed latest round of fixes suggested by Marcin
mw2000 Feb 21, 2024
75f2908
Merge branch 'master' into 6226-add-manifestjson-for-pwa-standalone-mode
mw2000 Feb 21, 2024
7540f48
Addressed more comments + fixed cancel button that broke last commit
mw2000 Feb 21, 2024
731d761
Removed console logs
mw2000 Feb 21, 2024
025697d
Fix for icons and background color
mw2000 Feb 21, 2024
446ea2a
Removed OG icon from add to homescreen prompt
mw2000 Feb 21, 2024
18ae1f6
Growl now behind prompt
mw2000 Feb 21, 2024
0a813a2
Opening to dashboard
mw2000 Feb 22, 2024
52c75bf
Merge remote-tracking branch 'origin/master' into 6226-add-manifestjs…
masvelio Feb 22, 2024
dfa9f89
Changed copy
mw2000 Feb 22, 2024
130324f
Merge branch '6226-add-manifestjson-for-pwa-standalone-mode' of https…
mw2000 Feb 22, 2024
a06f2f6
Adding a delay for loading prompt
mw2000 Feb 22, 2024
3dee829
Removed ternary
mw2000 Feb 22, 2024
19c3ec9
Merge branch 'master' into 6226-add-manifestjson-for-pwa-standalone-mode
mw2000 Feb 22, 2024
0d19569
Reset back to ternary
mw2000 Feb 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions packages/commonwealth/client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@
sizes="64x64"
href="/static/brand_assets/64x64.png"
/>
<link rel="apple-touch-icon" href="/static/brand_assets/180x180.png" />
<link
rel="manifest"
href="/static/manifest.json"
crossorigin="use-credentials"
/>
<link rel="apple-touch-icon" href="/static/img/branding/common-white.png" />
<meta name="title" content="Common" />
<meta
name="description"
Expand All @@ -33,7 +38,7 @@
/>
<meta
name="twitter:image:src"
content="https://commonwealth.im/static/brand_assets/logo_stacked.png"
content="https://commonwealth.im/static/img/branding/common.png"
/>

<meta property="og:type" content="website" />
Expand All @@ -46,7 +51,7 @@
/>
<meta
property="og:image"
content="https://commonwealth.im/static/brand_assets/logo_stacked.png"
content="https://commonwealth.im/static/img/branding/common.png"
/>
<meta property="og:image:width" content="339" />
<meta property="og:image:height" content="221" />
Expand Down
33 changes: 31 additions & 2 deletions packages/commonwealth/client/scripts/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import useInitApp from 'hooks/useInitApp';
import router from 'navigation/Router';
import React, { StrictMode } from 'react';
import React, { StrictMode, useEffect, useState } from 'react';
import { RouterProvider } from 'react-router-dom';
import { ToastContainer } from 'react-toastify';
import { queryClient } from 'state/api/config';
import { openFeatureProvider } from './helpers/feature-flags';
import useAppStatus from './hooks/useAppStatus';
import { AddToHomeScreenPrompt } from './views/components/AddToHomeScreenPrompt';

import CWLoadingSpinner from './views/components/component_kit/new_designs/CWLoadingSpinner';

const Splash = () => {
Expand All @@ -24,6 +27,26 @@ OpenFeature.setProvider(openFeatureProvider);

const App = () => {
const { customDomain, isLoading } = useInitApp();
const { isAddedToHomeScreen, isMarketingPage, isIOS, isAndroid } =
useAppStatus();

const [showPrompt, setShowPrompt] = useState(false);
const [isSplashUnloaded, setIsSplashUnloaded] = useState(false);

useEffect(() => {
if (!isLoading) {
// Delay the unloading of the Splash component
setTimeout(() => {
setIsSplashUnloaded(true);
}, 1000); // Adjust the delay as needed
}
}, [isLoading]);

useEffect(() => {
if (isSplashUnloaded) {
setShowPrompt(true);
}
}, [isSplashUnloaded]);

return (
<StrictMode>
Expand All @@ -32,8 +55,14 @@ const App = () => {
{isLoading ? (
<Splash />
) : (
<RouterProvider router={router(customDomain)} />
<>
<RouterProvider router={router(customDomain)} />
{isAddedToHomeScreen || isMarketingPage || !showPrompt ? null : (
<AddToHomeScreenPrompt isIOS={isIOS} isAndroid={isAndroid} />
)}
</>
)}

<ToastContainer />
<ReactQueryDevtools />
</OpenFeatureProvider>
Expand Down
19 changes: 19 additions & 0 deletions packages/commonwealth/client/scripts/hooks/useAppStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const useAppStatus = () => {
const isAddedToHomeScreen = window.matchMedia(
'(display-mode: standalone)',
).matches;
const isMarketingPage = window.location.pathname === '/';
const isIOS = window.navigator.userAgent.match(/(iPad|iPhone|iPod)/g)
? true
: false;
mw2000 marked this conversation as resolved.
Show resolved Hide resolved
const isAndroid = window.navigator.userAgent.match(/Android/g) ? true : false;
mw2000 marked this conversation as resolved.
Show resolved Hide resolved

return {
isAddedToHomeScreen,
isMarketingPage,
isIOS,
isAndroid,
};
};

export default useAppStatus;
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { useEffect, useState } from 'react';
import { AndroidPrompt } from './AndroidPrompt';
import { IOSPrompt } from './IOSPrompt';
import { HIDE_PROMPT, HIDE_PROMPT_DAYS, HIDE_PROMPT_TIME } from './constants';

interface AddToHomeScreenPromptProps {
isIOS: boolean;
isAndroid: boolean;
}

export const AddToHomeScreenPrompt = ({
isIOS,
isAndroid,
}: AddToHomeScreenPromptProps) => {
const [showPrompt, setShowPrompt] = useState(true);

useEffect(() => {
const hidePromptTime = localStorage.getItem(HIDE_PROMPT_TIME);
if (hidePromptTime && new Date().getTime() < Number(hidePromptTime)) {
setShowPrompt(false);
}

if (sessionStorage.getItem(HIDE_PROMPT)) {
setShowPrompt(false);
}
}, [showPrompt]);

const hidePromptForNDays = () => {
const maxDays = 30;

let n = Number(localStorage.getItem(HIDE_PROMPT_DAYS)) || 1;
n = n * 2 > maxDays ? maxDays : n * 2;
const hideUntil = new Date().getTime() + n * 24; //* 60 * 60 * 1000;
localStorage.setItem(HIDE_PROMPT_TIME, hideUntil.toString());
localStorage.setItem(HIDE_PROMPT_DAYS, n.toString());

setShowPrompt(false);
};

return showPrompt ? (
isIOS ? (
<IOSPrompt
hidePromptAction={hidePromptForNDays}
showPrompt={showPrompt}
setShowPrompt={setShowPrompt}
/>
) : isAndroid ? (
<AndroidPrompt
hidePromptAction={hidePromptForNDays}
showPrompt={showPrompt}
setShowPrompt={setShowPrompt}
/>
) : null
) : null;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
@import '../../../../../styles/shared';

.AndroidPrompt {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1002;

.header {
display: flex;
align-items: center;
padding-top: 8px;

.icon {
margin-right: 16px;
}
}

.app {
.app-name {
color: $neutral-900;
text-align: center;
font-variant-numeric: lining-nums tabular-nums;
font-family: $font-family-roboto;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 16px;
letter-spacing: 0.16px;
}
.app-url {
color: $neutral-500;
text-align: center;
font-variant-numeric: lining-nums tabular-nums;
font-family: $font-family-roboto;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 16px;
letter-spacing: 0.24px;
}
}

.button-container {
display: flex;
justify-content: flex-end;
gap: 32px;
}

.prompt-button {
color: $primary-500;
text-align: right;
font-variant-numeric: lining-nums tabular-nums;
font-family: 'Roboto';
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 24px;
letter-spacing: 0.16px;
background-color: $white;
padding: 0px;
margin: 0px;
}

.prompt-content {
background-color: $white;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -1px rgba(0, 0, 0, 0.06);
padding: 8px 24px 8px;
border-radius: 26px;
margin-left: 18px;
margin-right: 18px;

.title {
font-family: $font-family-roboto;
font-size: 24px;
font-style: normal;
font-weight: 400;
line-height: 24px;
letter-spacing: -0.24px;
color: $neutral-900;
padding: 24px 0px 8px;
}

.description {
margin-top: 16px;
align-self: stretch;
color: $neutral-900;
font-variant-numeric: lining-nums tabular-nums;
font-family: $font-family-roboto;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 24px;
letter-spacing: 0.16px;
}

.hide-prompt {
flex: 1 0 0;
margin-top: 16px;
color: $neutral-900;
font-variant-numeric: lining-nums tabular-nums;
font-family: $font-family-roboto !important;
font-size: 16px !important;
font-style: normal;
font-weight: 400;
line-height: 24px;
letter-spacing: 0.16px;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React, { useState } from 'react';
import { CWCheckbox } from '../../component_kit/cw_checkbox';
import { CWText } from '../../component_kit/cw_text';
import { CWButton } from '../../component_kit/new_designs/cw_button';
import { HIDE_PROMPT } from '../constants';
import './AndroidPrompt.scss';

interface AndroidPromptProps {
hidePromptAction: () => void;
showPrompt: boolean;
setShowPrompt: (showPrompt: boolean) => void;
}

export const AndroidPrompt = ({
hidePromptAction,
showPrompt,
setShowPrompt,
}: AndroidPromptProps) => {
let installPromptEvent = null;
const [checkboxChecked, setCheckboxChecked] = useState(false);

window.addEventListener('beforeinstallprompt', (event) => {
// Prevent Chrome 67 and earlier from automatically showing the prompt
event.preventDefault();

installPromptEvent = event;
});

const handleInstallClick = () => {
installPromptEvent.prompt();

// Wait for the user to respond to the prompt
installPromptEvent.userChoice.then((choiceResult) => {
if (choiceResult.outcome === 'accepted') {
// Hide after install prompt is accepted
console.log('User accepted the install prompt');
sessionStorage.setItem(HIDE_PROMPT, 'true');
setShowPrompt(false);
} else {
// Hide after install prompt is dismissed
sessionStorage.setItem(HIDE_PROMPT, 'true');
setShowPrompt(false);
}
});
};

const handleCancelClick = () => {
// Hide the prompt for the rest of the session
sessionStorage.setItem(HIDE_PROMPT, 'true');
setShowPrompt(false);
// If the checkbox is checked, hide the prompt for N days
if (checkboxChecked) {
hidePromptAction();
}
};

const handleCheckboxChange = (event) => {
setCheckboxChecked(event.target.checked);
};

return (
<div className="AndroidPrompt">
<div className="prompt-content">
<CWText className="title">Install App</CWText>
<div className="header">
<div className="icon">
<img src="/static/img/branding/common.svg" alt="Commonwealth" />
</div>
<div className="app">
<CWText className="app-name">Common</CWText>
<CWText className="app-url">common.xyz</CWText>
</div>
</div>
<CWText className="description">
For the best mobile experience we recommend installing the Common
web-app.
</CWText>
<CWCheckbox
className="hide-prompt"
label="Show less often"
onChange={handleCheckboxChange}
/>
<div className="button-container">
<CWButton
buttonType="tertiary"
className="prompt-button"
label="Cancel"
onClick={handleCancelClick}
/>
<CWButton
buttonType="tertiary"
className="prompt-button"
label="Install"
onClick={handleInstallClick}
/>
</div>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './AndroidPrompt';
Loading
Loading