Commit 8eeb9e4a authored by MrMan's avatar MrMan

Merge pull request #25 from t3hmrman/topic-6-create-matches-page

Topic 6 create matches page
parents 708572ff 9e581495
......@@ -11,8 +11,13 @@ export default Reflux.createActions([
"matchResultDetected",
"pass",
"pullTinderRecommendations",
"receivedSentMessageReceipt",
"republishTinderUser",
"retrieveMatchedUserInfo",
"retrieveTinderUpdates",
"sendMessageToMatch",
"sentimentSuccessfullyProcessed",
"successfullyDeletedMatch",
"updateLocationFromGPS",
"updateTinderPreferences"
]);
......@@ -46,6 +46,7 @@ class AppPage extends React.Component {
}
viewMatches() { this.context.history.pushState(null, '/matches'); }
viewRecs() { this.context.history.pushState(null, '/recs'); }
goToPreferencesPage() { this.context.history.pushState(null, '/preferences'); }
render() {
......@@ -61,7 +62,13 @@ class AppPage extends React.Component {
<div className="pinned-to-bottom app-page-controls">
<div className="row landing-wide-button">
<button onClick={this.viewMatches.bind(this)} className="pure-u-1 pure-button button-lg squared button-success">
<button onClick={this.viewRecs.bind(this)} className="pure-u-1 pure-button button-lg squared button-success">
<i className="fa fa-binoculars"></i> {this.i18n`nav.sections.findAMatch`}
</button>
</div>
<div className="row landing-wide-button">
<button onClick={this.viewMatches.bind(this)} className="pure-u-1 pure-button button-lg squared button-error">
<i className="fa fa-heart"></i> {this.i18n`nav.sections.matches`}
</button>
</div>
......@@ -73,7 +80,7 @@ class AppPage extends React.Component {
</div>
<div className="row landing-wide-button">
<button onClick={this.doLogout.bind(this)} className="pure-u-1 pure-button button-lg squared button-error">
<button onClick={this.doLogout.bind(this)} className="pure-u-1 pure-button button-lg squared">
<i className="fa fa-sign-out"></i> {this.i18n`nav.sections.logout`}
</button>
</div>
......
import React from "react";
import TinderMatchStore from "../stores/tinder-match";
import Actions from "../actions";
import MomentFuzzyDate from "./moment-fuzzy-date";
import _ from "lodash";
import { generateI18NTemplateFn } from "../i18n";
const DEFAULT_STATE = {
matchId: null,
matchedUser: null, // User that was matched to logged in user
matchedUserId: null, // ID of the user that was matched to logged in user
unseenMessageCount: 0,
lastMessageSentDate: null
};
class MatchConvoLink extends React.Component {
constructor(props) {
super(props);
this.state = DEFAULT_STATE;
this.unsubFunctions = {};
this.i18n = generateI18NTemplateFn("en-US");
// Process props
this.state = _.extend(this.state, {
matchId: props.matchId || this.state.matchId,
matchedUserId: props.matchedUserId || this.state.matchedUserId,
matchedUser: props.matchedUser || this.state.matchedUser,
unseenMessageCount: props.unseenMessageCount || this.state.unseenMessageCount,
lastMessageSentDate: props.lastMessageSentDate || this.state.lastMessageSentDate
});
console.log("[MATCH CONVO LINK COMPONENT] Match convo link component constructed, initial state:", this.state);
}
componentDidMount() {
this.unsubFunctions["store-tinder-match"] = TinderMatchStore.listen(store => {
console.log("[MATCH CONVO LINK COMPONENT] Detected tinder match store update:", store);
// Update the user info state if the user info is relevant to this component
if (_.has(store.matchedUserInfo, this.state.matchedUserId)) {
console.log(`[MATCH CONVO LINK COMPONENT] Found match ID ${this.state.matchedUserId} in update, updating state`);
this.setState({matchedUser: store.matchedUserInfo[this.state.matchedUserId]});
}
});
// Request a user's information if not provided
if (!_.isNull(this.state.matchedUserId) && _.isNull(this.state.matchedUser)) {
console.log(`[MATCH CONVO LINK COMPONENT] No user info for matched user with id ${this.state.matchedUserId}, requesting...`);
Actions.retrieveMatchedUserInfo(this.state.matchedUserId);
}
}
componentWillUnmount() {
_.each(this.unsubFunctions, f => f());
}
render() {
console.log("[MATCH CONVO LINK COMPONENT] Rendering link for match with ID:", this.state.matchedUserId);
let matchedUser = _.get(this.state, "matchedUser", {});
// TODO: show different view (progressive?) if user isn't loaded yet
if (_.isNull(matchedUser)) {
return (
<div className="match-convo-link-container">
<div className="pure-g">
<div className="pure-u-1 center-aligned-text">{this.i18n`nav.loadingMessage`}</div>
</div>
</div>
);
}
return (
<div className="match-convo-link-container">
<div className="pure-g">
<div className="pure-u-5-6 right-aligned-text">
<div className="slim-right-padding slim-vertical-padding">
<h3 className="match-convo-link-name slim-text no-vertical-margins">{matchedUser.name}</h3>
<p className="match-convo-link-last-active-blurb no-vertical-margins super-slim-text">
<i className="fa fa-clock-o"></i>&nbsp;
<MomentFuzzyDate date={this.state.lastMessageSentDate}/>
</p>
</div>
</div>
<div className="pure-u-1-6 center-aligned-text">
<div className="pure-g">
<div className="message-count-container match-convo-link-rhs nephritis-bg slim-vertical-padding">
<div className="pure-u-1 slim-vertical-padding"><i className="fa fa-comment"></i></div>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default MatchConvoLink;
import React from "react";
import Actions from "../actions";
import MomentFuzzyDate from "./moment-fuzzy-date";
import _ from "lodash";
import { updatePropertyWithEventTargetValue } from "../util";
import { generateI18NTemplateFn } from "../i18n";
const DEFAULT_CLOSE_FN = () => console.log("[MATCH CONVO PAGE COMPONENT] Would have closed convo page!");
const DEFAULT_STATE = {messageText: ""};
class MatchConvoPage extends React.Component {
constructor(props) {
super(props);
this.state = DEFAULT_STATE;
this.unsubFunctions = {};
this.i18n = generateI18NTemplateFn("en-US");
console.log("[MATCH CONVO PAGE COMPONENT] Match convo link component constructed, initial state:", this.state);
}
sendMessageToMatch() {
if (!_.has(this.props, "match")) {
throw new Error("Match not provided for convo page");
}
// Gather message info
let matchId = _.get(this.props, "match.id");
let msg = this.state.messageText;
console.log("[MATCH CONVO PAGE] Attempting to send message to match:", {
messageText: msg,
matchId: matchId});
Actions.sendMessageToMatch(matchId, msg);
}
render() {
let messages = _.get(this.props, "match.messages", []);
// Generate list of messages
let messageListElements = messages.map((m,i) => {
let receivedOrSent = m.to === this.props.viewingUser._id ? "received" : "sent";
return (
<div key={m._id + '-msg-' + i} className="pure-u-1">
<div className={`match-convo-message ${receivedOrSent}`}>
<div className="match-convo-message slim-padding">{m.message}</div>
<div className="match-convo-sent-date no-vertical-margins right-aligned-text slim-right-padding">
<MomentFuzzyDate date={m.sent_date}/>
</div>
</div>
</div>
);
});
let closeButton = "";
if (this.props.showCloseButton) {
closeButton = (
<div id="match-convo-close-btn"
onClick={() => { if (this.props.closeFn) { this.props.closeFn(); }}}>
<div className="close-icon-container center-aligned-text"><i className="fa fa-times large"></i></div>
</div>
);
}
return (
<div className="match-convo-page-container">
{closeButton}
<div className="match-convo-page-messages-container">
<div className="pure-g">
{messageListElements}
</div>
</div>
<div className="match-convo-page-chat-box abs pinned-to-bottom full-width">
<div className="pure-g full-height">
<div className="pure-u-4-5 full-height">
<textarea placeholder="Enter message..."
className="pure-u-1 full-height squared"
value={this.state.messageText}
onChange={updatePropertyWithEventTargetValue("messageText", this)}></textarea>
</div>
<div className="pure-u-1-5 full-height">
<div className="full-height nephritis-bg center-aligned-text"
onClick={this.sendMessageToMatch.bind(this)}>
<i className="fa fa-send large white-face hacked-vertical-center"></i>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default MatchConvoPage;
import React from "react";
import { Link, PropTypes } from "react-router";
import TinderRecommendationStore from "../stores/tinder-recommendation";
import LHSNav from "./lhs-nav";
import GenderIcon from "./gender-icon";
import DistanceDescription from "./distance-description";
import MomentFuzzyDate from "./moment-fuzzy-date";
import PictureStrip from "./picture-strip";
import { PropTypes } from "react-router";
import Actions from "../actions";
import LHSNav from "./lhs-nav";
import TinderMatchStore from "../stores/tinder-match";
import TinderUserStore from "../stores/tinder-user";
import MatchConvoLink from "./match-convo-link";
import MatchConvoPage from "./match-convo-page";
import Constants from "../constants";
import _ from "lodash";
import { generateI18NTemplateFn } from "../i18n";
const DEFAULT_STATE = {recPhotoIndex: 0, rec: null, detailVisible: true};
const DEFAULT_STATE = { matches: [], matchedUserInfo: {}, selectedMatch: null, user: null};
class MatchesPage extends React.Component {
constructor(props) {
super(props);
this.unsubFunctions = {};
this.state = DEFAULT_STATE;
this.unsubFunctions = {};
this.i18n = generateI18NTemplateFn("en-US");
// Set rec if available through the TinderRecommendationStore
if (_.isNull(this.state.rec) && !_.isNull(TinderRecommendationStore.state.currentRec)) {
this.state.rec = _.get(TinderRecommendationStore, "state.currentRec", null);
// Set matches if user is available through the TinderMatchStore
if (_.isNull(this.state.matches) && !_.isNull(TinderMatchStore.state.user)) {
let matches = _.clone(TinderMatchStore.state.matches, true);
this.state = _.extend(this.state, {matches});
}
// Set tinder user if user is available through the TinderUserStore
if (_.isNull(this.state.user) && !_.isNull(TinderUserStore.state.user)) {
let user = _.clone(TinderUserStore.state.user, true);
this.state = _.extend(this.state, {user});
}
console.log("[MATCHES PAGE COMPONENT] Match page constructed, initial state:", this.state);
}
componentDidMount() {
this.unsubFunctions["store-tinder-recommendations"] = TinderRecommendationStore.listen(state => {
console.log("[MATCHES PAGE COMPONENT] Detected recommendations update, updating list");
this.unsubFunctions["store-tinder-match"] = TinderMatchStore.listen(store => {
console.log("[MATCHES PAGE COMPONENT] Detected tinder match store update", store);
this.setState({matches: store.matches, matchedUserInfo: store.matchedUserInfo});
});
// Update list of recommendations
this.setState({rec: state.currentRec});
this.unsubFunctions["store-tinder-user"] = TinderUserStore.listen(store => {
console.log("[MATCHES PAGE COMPONENT] Detected tinder user store update", store);
this.setState({user: store.user});
});
// Attempt to fetch updates from twitter whenever matches page loads,
// this will trigger the match store to have updates
Actions.retrieveTinderUpdates();
}
componentWillUnmount() {
_.each(this.unsubFunctions, f => f());
}
toggleDetail() { this.setState({detailVisible: !this.state.detailVisible}); }
showMatchChatPage(id) {
console.log("[MATCHES PAGE] Opening chat page for match with ID ", id);
}
pass() { Actions.pass(this.state.rec); }
like() { Actions.like(this.state.rec); }
hideMatchConvoContainer() {
console.log("[MATCHES PAGE] Hiding match convo container...");
this.setState({selectedMatch: null});
}
pullRecommendations() { Actions.pullTinderRecommendations(); }
gotoRecsPage() { this.context.history.pushState(null, '/recs'); }
showMatchConvoContainerWithMatch(m) {
console.log("[MATCHES PAGE] Showing convo container for match:", m);
this.setState({selectedMatch: m});
}
render() {
// Return early if there are no rec
if (_.isEmpty(this.state.rec)) {
return (
<div>
<h3>0 Potential Matches</h3>
<h4>Maybe you&#39;d like to update your preferences?</h4>
<div className="row">
<button onClick={this.pullRecommendations} className="pure-button button-success in-stack">
<i className="fa fa-refresh"></i> Find Recommendations
</button>
</div>
<div className="row">
<button className="pure-button button-secondary in-stack">
<i className="fa fa-wrench"></i> Manage Preferences
</button>
</div>
<div className="row">
<button onClick={this.context.history.goBack} className="pure-button button-error">
<i className="fa fa-arrow-left"></i> Back
</button>
</div>
console.log(`[MATCHES PAGE] Rendering matches page with ${this.state.matches.length} matches`);
// Generate elemtn that will list or show empty message
let matchListElem;
if (_.isEmpty(this.state.matches)) {
// Generate no matches message
matchListElem = (
<div id="no-matches-message" className="pure-u-1 center-aligned-text">
<h3 className="slim-text">No matches found</h3>
<button onClick={this.gotoRecsPage.bind(this)}
className="pure-button button-success">
<i className="fa fa-binoculars"></i> {this.i18n`pages.matches.findSomeoneNew`}
</button>
</div>
);
} else {
// Generate list of matches
let matchLinks = _.map(this.state.matches, m => {
let unseen = _.reduce(m.messages, (acc, m) => !m.seen ? acc+1 : acc , 0);
let lastMessageSentDate = _.get(_.last(m.messages), "sent_date", null);
return (
<div key={m.id} onClick={() => this.showMatchConvoContainerWithMatch(m)} >
<MatchConvoLink unseenMessageCount={unseen}
matchId={m.id}
matchedUserId={m.matchedUserId}
lastMessageSentDate={lastMessageSentDate} />
</div>
);
});
matchListElem = (
<div className="pure-u-1">
{matchLinks}
</div>
);
}
// Generate teaser fragment
let teaserFragment = null;
if (_.has(this.state.rec, "teaser.string")) {
let teaserFragment = <p className="no-vertical-margins picture-overlay-text">{this.state.rec.teaser.string}</p>;
}
// Generate detail fragment
let detailFragment = (
<div className={`match-detail picture-overlay-text ${this.state.detailVisible ? "visible" : "invisible"}`} >
<p className="picture-overlay-text"><DistanceDescription distance={this.state.rec.distance_mi}/></p>
<p className="picture-overlay-text"><i className="fa fa-users"></i> {this.i18n`pages.matches.friendsInCommon ${this.state.rec.common_friend_count}`}</p>
<p className="picture-overlay-text"><i className="fa fa-thumbs-up"></i> {this.i18n`pages.matches.commonInterests ${this.state.rec.common_like_count}`}</p>
<p className="picture-overlay-text"><i className="fa fa-clock-o"></i> {this.i18n`pages.matches.lastActive`} <MomentFuzzyDate date={this.state.rec.ping_time}/></p>
</div>
);
let convoShown = _.isNull(this.state.selectedMatch) ? "" : "onscreen";
let selectedMatch = this.state.selectedMatch;
let matchedUser = _.isNull(selectedMatch) ? {} : _.get(this.state.matchedUserInfo, this.state.selectedMatch.id);
let viewingUser = this.state.user;
return (
<div className="full-height">
<div className="matches-page-header">
<div className="pure-g">
<LHSNav backEnabled={false}>
<i className="fa fa-info-circle xlarge picture-overlay-text"
onClick={this.toggleDetail.bind(this)}></i>
</LHSNav>
</div>
<div className="matches-page-container">
<LHSNav />
<div>
<div className="pure-g">
<div className="pure-u-1 right-aligned-text">
<div className="right-aligned-text">
<h1 className="no-vertical-margins slim-text">{this.i18n`nav.sections.matches`}</h1>
<h3 className="no-vertical-margins super-slim-text">{this.i18n`pages.matches.pageSubtitle`}</h3>
</div>
</div>
</div>
<div className="pure-g">
{matchListElem}
</div>
</div>
<div className="pure-g">
<div className="pure-u-1-4"></div>
<div className="pure-u-3-4 right-aligned-text">
<h1 className="picture-overlay-text no-margin">
{this.state.rec.name} <GenderIcon gender={this.state.rec.gender}/>
</h1>
<h3 className="picture-overlay-text no-margin">{this.state.rec.bio}</h3>
</div>
</div>
</div>
{teaserFragment}
<PictureStrip photos={this.state.rec.photos} index={this.state.recPhotoIndex} orientation="vertical"/>
{detailFragment}
<div className="pinned-to-bottom matches-page-controls">
<div className="pure-g">
<div className="pure-u-1-2">
<button onClick={this.pass.bind(this)} className="pure-u-1 pure-button button-lg squared button-error">
<i className="fa fa-binoculars"></i>
</button>
</div>
<div className="pure-u-1-2">
<button onClick={this.like.bind(this)} className="pure-u-1 pure-button button-lg squared button-success">
<i className="fa fa-heart"></i>
</button>
</div>
</div>
</div>
</div>
<div id="match-convo-sliding-container" className={`offscreen-right ${convoShown}`}>
<MatchConvoPage match={selectedMatch}
matchedUser={matchedUser}
viewingUser={viewingUser}
showCloseButton={true}
closeFn={this.hideMatchConvoContainer.bind(this)}/>
</div>
</div>
);
}
}
......
import React from "react";
import { Link, PropTypes } from "react-router";
import TinderRecommendationStore from "../stores/tinder-recommendation";
import LHSNav from "./lhs-nav";
import GenderIcon from "./gender-icon";
import DistanceDescription from "./distance-description";
import MomentFuzzyDate from "./moment-fuzzy-date";
import PictureStrip from "./picture-strip";
import Actions from "../actions";
import _ from "lodash";
import { generateI18NTemplateFn } from "../i18n";
const DEFAULT_STATE = {recPhotoIndex: 0, rec: null, detailVisible: true};
class RecsPage extends React.Component {
constructor(props) {
super(props);
this.unsubFunctions = {};
this.state = DEFAULT_STATE;
this.i18n = generateI18NTemplateFn("en-US");
// Set rec if available through the TinderRecommendationStore
if (_.isNull(this.state.rec) && !_.isNull(TinderRecommendationStore.state.currentRec)) {
this.state.rec = _.get(TinderRecommendationStore, "state.currentRec", null);
}
}
componentDidMount() {
this.unsubFunctions["store-tinder-recommendations"] = TinderRecommendationStore.listen(state => {
console.log("[RECS PAGE COMPONENT] Detected recommendations update, updating list");
// Update list of recommendations
this.setState({rec: state.currentRec});
});
}
componentWillUnmount() {
_.each(this.unsubFunctions, f => f());
}
toggleDetail() { this.setState({detailVisible: !this.state.detailVisible}); }
pass() { Actions.pass(this.state.rec); }
like() { Actions.like(this.state.rec); }
pullRecommendations() { Actions.pullTinderRecommendations(); }
render() {
// Return early if there are no rec
if (_.isEmpty(this.state.rec)) {
return (
<div>
<h3>0 Potential Recs</h3>
<h4>Maybe you&#39;d like to update your preferences?</h4>
<div className="row">
<button onClick={this.pullRecommendations} className="pure-button button-success in-stack">
<i className="fa fa-refresh"></i> Find Recommendations
</button>
</div>
<div className="row">
<button className="pure-button button-secondary in-stack">
<i className="fa fa-wrench"></i> Manage Preferences
</button>
</div>
<div className="row">
<button onClick={this.context.history.goBack} className="pure-button button-error">
<i className="fa fa-arrow-left"></i> Back
</button>
</div>
</div>
);
}
// Generate teaser fragment
let teaserFragment = null;
if (_.has(this.state.rec, "teaser.string")) {
let teaserFragment = <p className="no-vertical-margins picture-overlay-text">{this.state.rec.teaser.string}</p>;
}
// Generate detail fragment
let detailFragment = (
<div className={`rec-detail picture-overlay-text ${this.state.detailVisible ? "visible" : "invisible"}`} >
<p className="picture-overlay-text"><DistanceDescription distance={this.state.rec.distance_mi}/></p>
<p className="picture-overlay-text"><i className="fa fa-users"></i> {this.i18n`pages.recs.friendsInCommon ${this.state.rec.common_friend_count}`}</p>
<p className="picture-overlay-text"><i className="fa fa-thumbs-up"></i> {this.i18n`pages.recs.commonInterests ${this.state.rec.common_like_count}`}</p>
<p className="picture-overlay-text"><i className="fa fa-clock-o"></i> {this.i18n`pages.recs.lastActive`} <MomentFuzzyDate date={this.state.rec.ping_time}/></p>
</div>
);
return (
<div className="full-height">
<div className="recs-page-header">
<div className="pure-g">
<LHSNav backEnabled={false}>
<i className="fa fa-info-circle xlarge picture-overlay-text"
onClick={this.toggleDetail.bind(this)}></i>
</LHSNav>
</div>
<div className="pure-g">
<div className="pure-u-1-4"></div>
<div className="pure-u-3-4 right-aligned-text">
<h1 className="picture-overlay-text no-margin">
{this.state.rec.name} <GenderIcon gender={this.state.rec.gender}/>
</h1>
<h3 className="picture-overlay-text no-margin">{this.state.rec.bio}</h3>
</div>
</div>
</div>
{teaserFragment}
<PictureStrip photos={this.state.rec.photos} index={this.state.recPhotoIndex} orientation="vertical"/>
{detailFragment}
<div className="pinned-to-bottom recs-page-controls">
<div className="pure-g">
<div className="pure-u-1-2">
<button onClick={this.pass.bind(this)} className="pure-u-1 pure-button button-lg squared button-error">
<i className="fa fa-binoculars"></i>
</button>
</div>
<div className="pure-u-1-2">
<button onClick={this.like.bind(this)} className="pure-u-1 pure-button button-lg squared button-success">
<i className="fa fa-heart"></i>
</button>
</div>
</div>
</div>
</div>
);
}
}
// Add mixins
RecsPage.contextTypes = {
history: PropTypes.history
};
export default RecsPage;
// App-related
let LOCALSTORAGE_KEY_ERROR_STORE_STATE = "store-error-state";
let LOCALSTORAGE_KEY_FB_AUTH_STORE_STATE = "store-fb-auth-state";
let LOCALSTORAGE_KEY_FB_USER_STORE_STATE = "store-fb-user-state";
let LOCALSTORAGE_KEY_TINDER_AUTH_STORE_STATE = "store-tinder-auth-state";
let LOCALSTORAGE_KEY_TINDER_RECOMMENDATION_STORE_STATE = "store-tinder-recommendation-state";
let LOCALSTORAGE_KEY_TINDER_LOCATION_STORE_STATE = "store-tinder-location-state";
let LOCALSTORAGE_KEY_TINDER_MATCH_STORE_STATE = "store-tinder-match-state";
let LOCALSTORAGE_KEY_TINDER_RECOMMENDATION_STORE_STATE = "store-tinder-recommendation-state";
let LOCALSTORAGE_KEY_TINDER_SENTIMENT_STORE_STATE = "store-tinder-sentiment-state";
let LOCALSTORAGE_KEY_TINDER_UPDATE_STORE_STATE = "store-tinder-update-state";
let LOCALSTORAGE_KEY_TINDER_USER_STORE_STATE = "store-tinder-user-state";
let LOCALSTORAGE_KEY_ERROR_STORE_STATE = "store-error-state";
let TINDER_CLIENT_ID = "464891386855067";
// URLs
......@@ -20,12 +22,24 @@ let TINDER_RECS_URL = "https://api.gotinder.com/user/recs";
let TINDER_LOCATION_URL = "https://api.gotinder.com/user/ping";
let TINDER_AUTH_URL = "https://api.gotinder.com/auth";
let TINDER_PROFILE_URL = "https://api.gotinder.com/profile";
let TINDER_UPDATES_URL = "https://api.gotinder.com/updates";
// URL template generators
function TINDER_SENTIMENT_URL(strings, ...values) {
return `https://api.gotinder.com/${values[0]}/${values[1]}`;
}
function TINDER_MATCH_MESSAGE_URL(strings, ...values) {
return `https://api.gotinder.com/user/matches/${values[0]}`;
}
function TINDER_USER_INFO_URL(strings, ...values) {
return `https://api.gotinder.com/user/${values[0]}`;
}
function TINDER_DELETE_MATCH_URL(strings, ...values) {
return `https://api.gotinder.com/user/matches/${values[0]}`;
}
// Tinder-related
let TINDER_HEADERS_WITHOUT_AUTH_TOKEN = {
......@@ -53,25 +67,31 @@ export default {
APP_BACKGROUND_CLASS,
FB_AUTH_TOKEN_HREF,
FB_CURRENT_USER_URL,
LOCALSTORAGE_KEY_ERROR_STORE_STATE,
LOCALSTORAGE_KEY_FB_AUTH_STORE_STATE,
LOCALSTORAGE_KEY_FB_USER_STORE_STATE,
LOCALSTORAGE_KEY_TINDER_AUTH_STORE_STATE,