diff --git a/.gitignore b/.gitignore index 135ab60a2..707bb3209 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ tests/functional/output/* test/functional/screenshots/* .ssh webpack-stats.debug.json +*.DS_Store diff --git a/src/components/Ayah/index.js b/src/components/Ayah/index.js index eaba2464b..1ab4d87c4 100644 --- a/src/components/Ayah/index.js +++ b/src/components/Ayah/index.js @@ -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 (
diff --git a/src/components/SmartBanner/index.js b/src/components/SmartBanner/index.js new file mode 100644 index 000000000..ee306d183 --- /dev/null +++ b/src/components/SmartBanner/index.js @@ -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 ( +
+
+ + +
+
{this.props.title}
+
{this.props.author}
+ {inStore} +
+ + + {this.props.button} + +
+
+ ); + } +} + +export default SmartBanner; diff --git a/src/config.js b/src/config.js index 53a406b34..1f729c683 100644 --- a/src/config.js +++ b/src/config.js @@ -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 */ diff --git a/src/containers/App/index.js b/src/containers/App/index.js index 39c5bb204..8ed722076 100644 --- a/src/containers/App/index.js +++ b/src/containers/App/index.js @@ -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; @@ -40,6 +42,7 @@ class App extends Component { {children} +