diff --git a/webmaker/package.json b/webmaker/package.json
index 5102a6b..7ec727b 100644
--- a/webmaker/package.json
+++ b/webmaker/package.json
@@ -28,6 +28,7 @@
},
"dependencies": {
"codemirror": "^5.37.0",
+ "firebase": "^5.0.4",
"preact": "^8.2.6",
"preact-compat": "^3.17.0",
"preact-router": "^2.5.7"
diff --git a/webmaker/src/analytics.js b/webmaker/src/analytics.js
new file mode 100644
index 0000000..1616a3b
--- /dev/null
+++ b/webmaker/src/analytics.js
@@ -0,0 +1,49 @@
+import {
+ log
+} from "./utils";
+
+/* global ga */
+
+// eslint-disable-next-line max-params
+export function trackEvent(category, action, label, value) {
+ if (window.DEBUG) {
+ log('trackevent', category, action, label, value);
+ return;
+ }
+ if (window.ga) {
+ ga('send', 'event', category, action, label, value);
+ }
+};
+
+// if online, load after sometime
+if (navigator.onLine && !window.DEBUG) {
+ /* eslint-disable */
+
+ // prettier-ignore
+ setTimeout(function () {
+ (function (i, s, o, g, r, a, m) {
+ i['GoogleAnalyticsObject'] = r;
+ i[r] = i[r] || function () {
+ (i[r].q = i[r].q || []).push(arguments)
+ }, i[r].l = 1 * new Date();
+ a = s.createElement(o),
+ m = s.getElementsByTagName(o)[0];
+ a.async = 1;
+ a.src = g;
+ m.parentNode.insertBefore(a, m)
+ })(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
+
+ if (location.href.indexOf('chrome-extension://') === -1) {
+ ga('create', 'UA-87786708-1');
+ } else {
+ ga('create', 'UA-87786708-1', {
+ 'cookieDomain': 'none'
+ });
+ // required for chrome extension protocol
+ ga('set', 'checkProtocolTask', function () { /* nothing */ });
+ }
+ ga('send', 'pageview');
+ }, 100);
+
+ /* eslint-enable */
+}
diff --git a/webmaker/src/auth.js b/webmaker/src/auth.js
new file mode 100644
index 0000000..bd4fa28
--- /dev/null
+++ b/webmaker/src/auth.js
@@ -0,0 +1,43 @@
+import {
+ trackEvent
+} from './analytics';
+
+import firebase from 'firebase/app'
+
+export const auth = {
+ logout() {
+ firebase.auth().signOut();
+ },
+ login(providerName) {
+ var provider;
+ if (providerName === 'facebook') {
+ provider = new firebase.auth.FacebookAuthProvider();
+ } else if (providerName === 'twitter') {
+ provider = new firebase.auth.TwitterAuthProvider();
+ } else if (providerName === 'google') {
+ provider = new firebase.auth.GoogleAuthProvider();
+ provider.addScope('https://www.googleapis.com/auth/userinfo.profile');
+ } else {
+ provider = new firebase.auth.GithubAuthProvider();
+ }
+
+ return firebase
+ .auth()
+ .signInWithPopup(provider)
+ .then(function () {
+ trackEvent('fn', 'loggedIn', providerName);
+ // Save to recommend next time
+ window.db.local.set({
+ lastAuthProvider: providerName
+ });
+ })
+ .catch(function (error) {
+ utils.log(error);
+ if (error.code === 'auth/account-exists-with-different-credential') {
+ alert(
+ 'You have already signed up with the same email using different social login'
+ );
+ }
+ });
+ }
+}
diff --git a/webmaker/src/components/Login.jsx b/webmaker/src/components/Login.jsx
new file mode 100644
index 0000000..5b73786
--- /dev/null
+++ b/webmaker/src/components/Login.jsx
@@ -0,0 +1,74 @@
+import { h, Component } from 'preact';
+import { jsLibs, cssLibs } from '../libraryList';
+import { trackEvent } from '../analytics';
+import { auth } from '../auth';
+
+export default class Login extends Component {
+ login(e) {
+ const provider = e.target.dataset.authProvider;
+ trackEvent('ui', 'loginProviderClick', provider);
+ auth.login(provider);
+ }
+ logout(e) {
+ if (this.unsavedEditCount) {
+ var shouldDiscard = confirm(
+ 'You have unsaved changes. Do you still want to logout?'
+ );
+ if (!shouldDiscard) {
+ return;
+ }
+ }
+ trackEvent('fn', 'loggedOut');
+ auth.logout();
+ }
+ render() {
+ return (
+
+
Login / Signup
+
+
+
+
+
+
+
+
+
+
+
+
Join a community of 50,000+ Developers
+
+
+ );
+ }
+}
diff --git a/webmaker/src/components/MainHeader.jsx b/webmaker/src/components/MainHeader.jsx
index 5c09d21..8dae8cb 100644
--- a/webmaker/src/components/MainHeader.jsx
+++ b/webmaker/src/components/MainHeader.jsx
@@ -94,7 +94,7 @@ export default class Header extends Component {
Open
diff --git a/webmaker/src/components/Profile.jsx b/webmaker/src/components/Profile.jsx
new file mode 100644
index 0000000..5b7777d
--- /dev/null
+++ b/webmaker/src/components/Profile.jsx
@@ -0,0 +1,35 @@
+import { h, Component } from 'preact';
+
+export default class Profile extends Component {
+ render() {
+ return (
+
+

+
+ {this.props.user && this.props.user.displayName
+ ? this.props.user.displayName
+ : 'Anonymous Creator'}
+
+
+
+
+
+ );
+ }
+}
diff --git a/webmaker/src/components/app.jsx b/webmaker/src/components/app.jsx
index dd83473..c4d71bb 100644
--- a/webmaker/src/components/app.jsx
+++ b/webmaker/src/components/app.jsx
@@ -8,6 +8,7 @@ import SavedItemPane from './SavedItemPane.jsx';
import AddLibrary from './AddLibrary.jsx';
import Modal from './Modal.jsx';
import HelpModal from './HelpModal.jsx';
+import Login from './Login.jsx';
import { log, generateRandomId } from '../utils';
import { itemService } from '../itemService';
import '../db';
@@ -17,6 +18,9 @@ import { modes, cssModes } from '../codeModes';
import { trackEvent } from '../analytics';
import { deferred } from '../deferred';
import { alertsService } from '../notifications';
+import firebase from 'firebase/app';
+import 'firebase/auth';
+import Profile from './Profile';
if (module.hot) {
require('preact/debug');
@@ -38,6 +42,8 @@ export default class App extends Component {
isAddLibraryModalOpen: false,
isHelpModalOpen: false,
isNotificationsModalOpen: false,
+ isLoginModalOpen: false,
+ isProfileModalOpen: false,
prefs: {},
currentItem: {
title: '',
@@ -68,6 +74,45 @@ export default class App extends Component {
infiniteLoopTimeout: 1000
};
this.prefs = {};
+
+ firebase.auth().onAuthStateChanged(user => {
+ this.setState({ isLoginModalOpen: false });
+ if (user) {
+ log('You are -> ', user);
+ alertsService.add('You are now logged in!');
+ this.setState({ user });
+ window.user = user;
+ if (
+ !window.localStorage[LocalStorageKeys.ASKED_TO_IMPORT_CREATIONS] &&
+ window.oldSavedCreationsCountEl
+ ) {
+ this.fetchItems(false, true).then(items => {
+ if (!items.length) {
+ return;
+ }
+ this.oldSavedItems = items;
+ // window.oldSavedCreationsCountEl.textContent = items.length;
+ this.setState({
+ isAskToImportModalOpen: true
+ });
+ trackEvent('ui', 'askToImportModalSeen');
+ });
+ }
+ window.db.getUser(user.uid).then(customUser => {
+ if (customUser) {
+ const prefs = { ...this.state.prefs };
+ Object.assign(prefs, user.settings);
+ this.setState({ prefs: prefs });
+ this.updateSetting();
+ }
+ });
+ } else {
+ this.setState({ user: undefined });
+ delete window.user;
+ // User is signed out.
+ }
+ this.updateProfileUi();
+ });
}
componentWillMount() {
@@ -121,6 +166,14 @@ export default class App extends Component {
this.updateSetting();
});
}
+ updateProfileUi() {
+ if (this.state.user) {
+ document.body.classList.add('is-logged-in');
+ } else {
+ document.body.classList.remove('is-logged-in');
+ }
+ }
+
refreshEditor() {}
createNewItem() {
var d = new Date();
@@ -561,6 +614,27 @@ export default class App extends Component {
}
autoSaveLoop() {}
+ loginBtnClickHandler() {
+ this.setState({ isLoginModalOpen: true });
+ }
+ profileBtnClickHandler() {
+ this.setState({ isProfileModalOpen: true });
+ }
+
+ logout(e) {
+ e.preventDefault();
+ if (unsavedEditCount) {
+ var shouldDiscard = confirm(
+ 'You have unsaved changes. Do you still want to logout?'
+ );
+ if (!shouldDiscard) {
+ return;
+ }
+ }
+ trackEvent('fn', 'loggedOut');
+ window.logout();
+ }
+
render() {
return (
@@ -569,10 +643,13 @@ export default class App extends Component {
externalLibCount={this.state.externalLibCount}
openBtnHandler={this.openSavedItemsPane.bind(this)}
saveBtnHandler={this.saveBtnClickHandler.bind(this)}
+ loginBtnHandler={this.loginBtnClickHandler.bind(this)}
+ profileBtnHandler={this.profileBtnClickHandler.bind(this)}
addLibraryBtnHandler={this.openAddLibrary.bind(this)}
isFetchingItems={this.state.isFetchingItems}
isSaving={this.state.isSaving}
titleInputBlurHandler={this.titleInputBlurHandler.bind(this)}
+ user={this.state.user}
/>
+
this.setState({ isLoginModalOpen: false })}
+ >
+
+
+
this.setState({ isProfileModalOpen: false })}
+ >
+
+
this.setState({ isHelpModalOpen: false })}
diff --git a/webmaker/src/db.js b/webmaker/src/db.js
index 3d81231..2340874 100644
--- a/webmaker/src/db.js
+++ b/webmaker/src/db.js
@@ -1,6 +1,14 @@
+import './firebaseInit';
+import firebase from 'firebase/app';
+import 'firebase/firestore';
import {
deferred
-} from './deferred'
+} from './deferred';
+import {
+ trackEvent
+} from './analytics';
+
+
(() => {
const FAUX_DELAY = 1;
@@ -53,6 +61,10 @@ import {
.then(function () {
// Initialize Cloud Firestore through firebase
db = firebase.firestore();
+ // const settings = {
+ // timestampsInSnapshots: true
+ // };
+ // db.settings(settings);
utils.log('firebase db ready', db);
resolve(db);
})
@@ -64,7 +76,7 @@ import {
alert(
"Opening Web Maker web app in multiple tabs isn't supported at present and it seems like you already have it opened in another tab. Please use in one tab."
);
- window.trackEvent('fn', 'multiTabError');
+ trackEvent('fn', 'multiTabError');
} else if (err.code === 'unimplemented') {
// The current browser does not support all of the
// features required to enable persistence
diff --git a/webmaker/src/firebaseInit.js b/webmaker/src/firebaseInit.js
new file mode 100644
index 0000000..2374ce8
--- /dev/null
+++ b/webmaker/src/firebaseInit.js
@@ -0,0 +1,11 @@
+import firebase from 'firebase/app';
+// import 'firebase/firestore';
+const config = {
+ apiKey: 'AIzaSyBl8Dz7ZOE7aP75mipYl2zKdLSRzBU2fFc',
+ authDomain: 'web-maker-app.firebaseapp.com',
+ databaseURL: 'https://web-maker-app.firebaseio.com',
+ projectId: 'web-maker-app',
+ storageBucket: 'web-maker-app.appspot.com',
+ messagingSenderId: '560473480645'
+};
+firebase.initializeApp(config);
diff --git a/webmaker/src/notifications.js b/webmaker/src/notifications.js
new file mode 100644
index 0000000..b41f30e
--- /dev/null
+++ b/webmaker/src/notifications.js
@@ -0,0 +1,20 @@
+var hideTimeout;
+
+function addNotification(msg) {
+ const noticationContainerEL = $('#js-alerts-container');
+
+ // var n = document.createElement('div');
+ // div.textContent = msg;
+ // noticationContainerEL.appendChild(n);
+ noticationContainerEL.textContent = msg;
+ noticationContainerEL.classList.add('is-active');
+
+ clearTimeout(hideTimeout);
+ hideTimeout = setTimeout(function () {
+ noticationContainerEL.classList.remove('is-active');
+ }, 2000);
+}
+
+export const alertsService = {
+ add: addNotification
+};