Skip to content
This repository has been archived by the owner on Jun 28, 2021. It is now read-only.

App install banner #427 #508

Merged
merged 18 commits into from
Dec 8, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ tests/functional/output/*
test/functional/screenshots/*
.ssh
webpack-stats.debug.json
*.DS_Store
2 changes: 1 addition & 1 deletion src/components/Ayah/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export default class Ayah extends Component {
renderMedia() {
const { ayah, mediaActions } = this.props;

if (!ayah.mediaContent.length) return false;
if (!!ayah.mediaContent) return false;

return (
<div>
Expand Down
183 changes: 183 additions & 0 deletions src/components/SmartBanner/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import React, { Component, PropTypes } from 'react';
import useragent from 'express-useragent';
import cookie from 'react-cookie';

class SmartBanner extends Component {
static propTypes = {
daysHidden: PropTypes.number,
daysReminder: PropTypes.number,
appStoreLanguage: PropTypes.string,
button: PropTypes.string,
storeText: PropTypes.objectOf(PropTypes.string),
price: PropTypes.objectOf(PropTypes.string),
force: PropTypes.string,
title: PropTypes.string,
author: PropTypes.string,
};

static defaultProps = {
daysHidden: 15,
daysReminder: 90,
appStoreLanguage: 'us',
button: 'View',
storeText: {
ios: 'On the App Store',
android: 'In Google Play',
windows: 'In Windows Store',
kindle: 'In the Amazon Appstore',
},
price: {
ios: 'Free',
android: 'Free',
windows: 'Free',
kindle: 'Free',
},
force: '',
title: '',
author: '',
};

state = {
settings: {},
deviceType: '',
appId: ''
};

setSettings(forceDeviceType) {
const agent = useragent.parse(window.navigator.userAgent);
let deviceType = '';
const osVersion = parseInt(agent.version, 10);

if (forceDeviceType) {
deviceType = forceDeviceType;
} else if ((agent.isAndroid || agent.isAndroidTablet) && (agent.isChrome ? osVersion < 44 : true)) {
deviceType = 'android';
} else if ((agent.isiPad || agent.isiPhone) && (agent.isSafari ? osVersion < 6 : true)) {
deviceType = 'ios';
}

this.setState({deviceType: deviceType});
if (deviceType) {
this.setSettingsForDevice(deviceType);
}
}

parseAppId(metaName) {
const meta = window.document.querySelector(`meta[name="${metaName}"]`);
return /app-id=([^\s,]+)/.exec(meta.getAttribute('content'))[1];;
}

setSettingsForDevice(deviceType) {
const mixins = {
ios: {
icon: 'app-banner-ios.jpg',
appMeta: 'google-play-app',
getStoreLink: () =>
`https://itunes.apple.com/${this.props.appStoreLanguage}/app/id`,
},
android: {
icon: 'app-banner-android.png',
appMeta: 'apple-itunes-app',
getStoreLink: () =>
'http://play.google.com/store/apps/details?id=',
}
};

if (mixins[deviceType]) {
this.setState({
settings: mixins[deviceType],
appId: this.parseAppId(mixins[deviceType].appMeta)
});
}
}

hide() {
window.document.querySelector('html').classList.remove('smartbanner-show');
}

show() {
window.document.querySelector('html').classList.add('smartbanner-show');
}

close() {
this.hide();
cookie.save('smartbanner-closed', 'true', {
path: '/',
expires: +new Date() + this.props.daysHidden * 1000 * 60 * 60 * 24,
});
}

install() {
this.hide();
cookie.save('smartbanner-installed', 'true', {
path: '/',
expires: +new Date() + this.props.daysReminder * 1000 * 60 * 60 * 24,
});
}

retrieveInfo() {
const link = this.state.settings.getStoreLink() + this.state.appId;
const inStore = `
${this.props.price[this.state.deviceType]} - ${this.props.storeText[this.state.deviceType]}`;
const icon = require(`../../../static/images/${this.state.settings.icon}`);

return {
icon,
link,
inStore,
};
}

componentDidMount() {
if (__CLIENT__) {
this.setSettings(this.props.force);
}
}

render() {
// Don't show banner when:
// 1) if device isn't iOS or Android
// 2) website is loaded in app,
// 3) user dismissed banner,
// 4) or we have no app id in meta

if (!this.state.deviceType
|| window.navigator.standalone
|| cookie.load('smartbanner-closed')
|| cookie.load('smartbanner-installed')) {
return null;
}

if (!this.state.appId) {
return null;
}

this.show();

const { icon, link, inStore } = this.retrieveInfo();
const wrapperClassName = `smartbanner smartbanner-${this.state.deviceType}`;
const iconStyle = {
backgroundImage: `url(${icon})`,
};

return (
<div className={wrapperClassName}>
<div className="smartbanner-container">
<a className="smartbanner-close" onClick={::this.close} data-metrics-event-name="SmartBanner:close"><i className="fa fa-times-circle"></i></a>
<span className="smartbanner-icon" style={iconStyle}></span>
<div className="smartbanner-info">
<div className="smartbanner-title">{this.props.title}</div>
<div>{this.props.author}</div>
<span>{inStore}</span>
</div>

<a href={link} onClick={::this.install} className="smartbanner-button" data-metrics-event-name="SmartBanner:InstallAapp">
<span className="smartbanner-button-text">{this.props.button}</span>
</a>
</div>
</div>
);
}
}

export default SmartBanner;
38 changes: 35 additions & 3 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,43 @@ module.exports = Object.assign({
{name: 'twitter:description', content: description},
{name: 'twitter:image', content: 'https://quran.com/images/thumbnail.png'},
{name: 'twitter:image:width', content: '200'},
{name: 'twitter:image:height', content: '200'}
{name: 'twitter:image:height', content: '200'},
{name: 'google-play-app', content: 'app-id=com.quran.labs.androidquran'},
{name: 'apple-itunes-app', content: 'app-id=1118663303' },
{name: 'mobile-web-app-capable', content: 'yes'},
{name: 'apple-mobile-web-app-capable', content: 'yes'},
{name: 'apple-mobile-web-app-title', content: title},
{name: 'apple-mobile-web-app-status-bar-style', content: 'black'},
{name: 'application-name', content: 'Al-Quran - القرآن الكريم'},
{name: 'msapplication-TileColor', content: '#004f54'},
{name: 'msapplication-tooltip', content: description},
{name: 'msapplication-starturl', content: "https://quran.com"},
{name: 'msapplication-navbutton-color', content: '#004f54'},
{name: 'msapplication-square70x70logo', content: '/mstitle-70x70.jpg'},
{name: 'msapplication-square150x150logo', content: '/mstitle-150x150.jpg'},
{name: 'msapplication-wide310x150logo', content: '/mstitle-310x150.jpg'},
{name: 'msapplication-square310x310logo', content: '/mstitle-310x310.jpg'}
],
link: [
{rel: 'apple-touch-icon', href: '/images/apple-touch-icon.png'},
{rel: 'apple-touch-icon-precomposed', href: '/images/apple-touch-icon-precomposed.png'},
{rel: 'manifest', href: 'manifest.json'},
{rel: 'search', type: 'application/opensearchdescription+xml', href: '/opensearch.xml', title: 'Quran.com'},
{rel:'fluid-icon', href: '/apple-touch-icon-180x180.png', title: 'Quran.com'},
{rel: 'icon', type: 'image/png', href: '/favicon-32x32.png', sizes:'32x32'},
{rel: 'icon', type: 'image/png', href: '/android-chrome-192x192.png', sizes: '192x192'},
{rel: 'icon', type: 'image/png', href: '/favicon-16x16.png', sizes: '16x16'},
{rel: 'mask-icon', href: '/safari-pinned-tab.svg', color: '#004f54'},
{rel: 'shortcut icon', href: '/favicon.ico', type: 'image/x-icon'},
{rel: 'apple-touch-icon', href: 'apple-touch-icon.png'},
{rel: 'apple-touch-icon', sizes: '57x57', href:' /apple-touch-icon-57x57.png'},
{rel: 'apple-touch-icon', sizes: '72x72', href: '/apple-touch-icon-72x72.png'},
{rel: 'apple-touch-icon', sizes: '76x76', href: '/apple-touch-icon-76x76.png'},
{rel: 'apple-touch-icon', sizes: '114x114', href: '/apple-touch-icon-114x114.png'},
{rel: 'apple-touch-icon', sizes: '120x120', href: '/apple-touch-icon-120x120.png'},
{rel: 'apple-touch-icon', sizes: '144x144', href: '/apple-touch-icon-144x144.png'},
{rel: 'apple-touch-icon', sizes: '152x152', href: '/apple-touch-icon-152x152.png'},
{rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon-180x180.png'},
{rel: 'preconnect', href: 'https://quran-1f14.kxcdn.com', crossorigin: ''},
{rel: 'preconnect', href: 'https://assets-1f14.kxcdn.com', crossorigin: ''},
{rel: 'stylesheet', href: 'https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css'}
],
/* SEO: https://developers.google.com/structured-data/slsb-overview#markup_examples */
Expand Down
3 changes: 3 additions & 0 deletions src/containers/App/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { connect } from 'react-redux';
import { asyncConnect } from 'redux-connect';
import Helmet from 'react-helmet';
import Modal from 'react-bootstrap/lib/Modal';
import SmartBanner from 'components/SmartBanner';

const ModalHeader = Modal.Header;
const ModalTitle = Modal.Title;
const ModalBody = Modal.Body;
Expand Down Expand Up @@ -40,6 +42,7 @@ class App extends Component {
<Helmet {...config.app.head} />
<FontStyles />
{children}
<SmartBanner title="The Noble Quran - القرآن الكريم" button="Install"/>
<Footer />
<Modal bsSize="large" show={!!media.content} onHide={removeMedia}>
<ModalHeader closeButton>
Expand Down
4 changes: 2 additions & 2 deletions src/containers/Surah/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
}

:local .surah-container {
padding-top: 130px;
padding-top: 50px;

@media(max-width: $screen-xs-max) {
padding-top: 100px;
padding-top: 50px;
}
}
6 changes: 6 additions & 0 deletions src/helpers/Html.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ const Html = ({ store, component, assets }) => {
}}
charSet="UTF-8"
/>
<script
dangerouslySetInnerHTML={{
__html: `if ('serviceWorker' in navigator) {navigator.serviceWorker.register('/quran-service-worker.js', {scope: './'}).then(function(registration) {}).catch(function(error) {});}`
}}
charSet="UTF-8"
/>
<script
dangerouslySetInnerHTML={{__html: `window.reduxData=${serialize(store.getState())};`}}
charSet="UTF-8"
Expand Down
2 changes: 1 addition & 1 deletion src/server/config/express.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export default function(server) {
server.use(cors());

// Static content
server.use(favicon(path.join((process.env.PWD || process.env.pm_cwd) , '/static/images/favicon.ico')));
server.use(favicon(path.join((process.env.PWD || process.env.pm_cwd) , '/static/favicon.ico')));
server.use(express.static(path.join(process.env.PWD || process.env.pm_cwd, '/static')));
server.use('/public', express.static(path.join((process.env.PWD || process.env.pm_cwd), '/static/dist')));
// server.use('/build', express.static(path.join((process.env.PWD || process.env.pm_cwd), '/static/dist')));
Expand Down
Loading