Commit 9e581495 authored by MrMan's avatar MrMan

Resolves #6 Create matches page

- Theoretically working send message functionality
- Rough UI, but working
parent e4fe203f
......@@ -15,6 +15,7 @@ export default Reflux.createActions([
"republishTinderUser",
"retrieveMatchedUserInfo",
"retrieveTinderUpdates",
"sendMessageToMatch",
"sentimentSuccessfullyProcessed",
"successfullyDeletedMatch",
"updateLocationFromGPS",
......
......@@ -5,7 +5,13 @@ import MomentFuzzyDate from "./moment-fuzzy-date";
import _ from "lodash";
import { generateI18NTemplateFn } from "../i18n";
const DEFAULT_STATE = {user: null, matchId: null, userInfo: null};
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) {
......@@ -17,8 +23,10 @@ class MatchConvoLink extends React.Component {
// Process props
this.state = _.extend(this.state, {
matchId: props.matchId || this.state.matchId,
user: props.user || this.state.user,
unseenMessageCount: props.unseenMessageCount || this.state.unseenMessageCount
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);
......@@ -29,16 +37,16 @@ class MatchConvoLink extends React.Component {
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.matchId)) {
console.log(`[MATCH CONVO LINK COMPONENT] Found match ID ${this.state.matchId} in update, updating state`);
this.setState({user: store.matchedUserInfo[this.state.matchId]});
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.matchId) && _.isNull(this.state.user)) {
console.log(`[MATCH CONVO LINK COMPONENT] User info not provided for match with id ${this.state.matchId}, requesting...`);
Actions.retrieveMatchedUserInfo(this.state.matchId);
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);
}
}
......@@ -47,11 +55,12 @@ class MatchConvoLink extends React.Component {
}
render() {
console.log("[MATCH CONVO LINK COMPONENT] Rendering link for match with ID:", this.state.matchId);
console.log("[MATCH CONVO LINK COMPONENT] Rendering link for match with ID:", this.state.matchedUserId);
let matchedUser = _.get(this.state, "matchedUser", {});
let user = _.get(this.state, "user", {});
// TODO: show different view (progressive?) if user isn't loaded yet
if (_.isNull(user)) {
if (_.isNull(matchedUser)) {
return (
<div className="match-convo-link-container">
<div className="pure-g">
......@@ -65,10 +74,11 @@ class MatchConvoLink extends React.Component {
<div className="match-convo-link-container">
<div className="pure-g">
<div className="pure-u-5-6 right-aligned-text">
<div className="slim-right-padding">
<h3 className="match-convo-link-name slim-text no-vertical-margins">{user.name}</h3>
<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">
{this.i18n`pages.matches.lastActive`} <MomentFuzzyDate date={user.ping_time}/>
<i className="fa fa-clock-o"></i>&nbsp;
<MomentFuzzyDate date={this.state.lastMessageSentDate}/>
</p>
</div>
</div>
......@@ -77,8 +87,7 @@ class MatchConvoLink extends React.Component {
<div className="pure-g">
<div className="message-count-container match-convo-link-rhs nephritis-bg slim-vertical-padding">
<div className="pure-u-1">{this.state.unseenMessageCount}</div>
<div className="pure-u-1"><i className="fa fa-comment"></i></div>
<div className="pure-u-1 slim-vertical-padding"><i className="fa fa-comment"></i></div>
</div>
</div>
</div>
......
......@@ -2,10 +2,11 @@ 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 = {};
const DEFAULT_STATE = {messageText: ""};
class MatchConvoPage extends React.Component {
constructor(props) {
......@@ -17,6 +18,21 @@ class MatchConvoPage extends React.Component {
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", []);
......@@ -39,10 +55,10 @@ class MatchConvoPage extends React.Component {
let closeButton = "";
if (this.props.showCloseButton) {
closeButton = (
<div id="match-convo-close-btn"
onClick={() => { if (this.props.closeFn) { this.props.closeFn(); }}}>
<h2 className="no-margin"><i className="fa fa-times large"></i></h2>
</div>
<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>
);
}
......@@ -60,11 +76,15 @@ class MatchConvoPage extends React.Component {
<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"></textarea>
<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">
<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>
......
......@@ -91,9 +91,13 @@ class MatchesPage extends React.Component {
// 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} />
<MatchConvoLink unseenMessageCount={unseen}
matchId={m.id}
matchedUserId={m.matchedUserId}
lastMessageSentDate={lastMessageSentDate} />
</div>
);
});
......
......@@ -47,7 +47,8 @@ export default {
},
matches: {
pageSubtitle: "Talk with your matches",
findSomeoneNew: "Find someone new"
findSomeoneNew: "Find someone new",
lastMessageSent: "Last message sent"
}
}
};
......@@ -29,11 +29,12 @@ let TinderMatchStore = Reflux.createStore({
// Subscribe to actions
this.listenTo(Actions.retrieveMatchedUserInfo, this.retrieveMatchedUserInfo);
this.listenTo(Actions.sendMessageToMatch, this.sendMessageToMatch);
// Save the Tinder access token when provided by the TinderUpdateStore
TinderUpdateStore.listen(update => {
console.log("[TINDER MATCH STORE] Detected Tinder update store update fetch:", update);
this.processTinderUpdate(update.lastUpdate);
TinderUpdateStore.listen(store => {
console.log("[TINDER MATCH STORE] Detected Tinder update store update fetch:", store);
this.processTinderUpdate(store.currentUser, store.lastUpdate);
});
// Save the Tinder access token when provided by the TinderAuthStore
......@@ -61,34 +62,60 @@ let TinderMatchStore = Reflux.createStore({
*
* @param {string} matchId - Match ID of the matched user
*/
retrieveMatchedUserInfo(matchId) {
// Retrieve matched user info
let userId = _.get(this.state.matches, matchId);
retrieveMatchedUserInfo(userId) {
if (_.isNull(userId) || _.isUndefined(userId)) {
throw new Error("Failed to find match with given match ID [" + matchId + "]");
throw new Error("Invalid userId");
}
// Attempt to find user Info for given match
// Attempt to find given matched user's info
console.log(`[TINDER MATCH STORE] Retrieveing user info for user with ID ${userId}`);
TinderAPI.GETWithToken(generateTinderURLForUserInfo(userId), this.state.tinderAccessToken)
.then(resp => resp.json())
.then(user => {
this.state.matchedUserInfo[matchId] = user;
.then(resp => {
if (resp.status !== 200) {
console.log(`[TINDER MATCH STORE] Failed to retrieve user data for user with ID ${userId} :`, resp);
return;
}
let user = resp.results;
let userId = _.get(user, "_id");
// Backup if info is new
if (!_.has(this.state.matchedUserInfo, matchId)) { this.backupState(); }
if (_.isUndefined(userId)) {
console.log("[TINDER MATCH STORE] Invalid user object received from Tinder API:", user);
return;
}
this.state.matchedUserInfo[userId] = user;
if (!_.has(this.state.matchedUserInfo, userId)) { this.backupState(); }
this.trigger(this.state);
})
.catch(err => {
console.log("[TINDER MATCH STORE] Error occurred while trying to get matched user's info:", err);
});
},
/**
* Send a message to a twitter match
*
* @param {string} id - tinder match ID
* @param {string} matchId - Tinder match id
* @param {string} msg - the message to send
*/
sendMessageToMatch(matchId, message) {
if (!this.hasNecessaryCredentials()) { throw new Error("Necessary creds missing"); }
// Throw error in the case of an invalid match ID or message
if (_.isUndefined(matchId) || _.isNull(matchId) ||
_.isUndefined(message) || _.isNull(message)) {
throw new Error(`Invalid parameters - match ID and message must be present`);
}
// Trim the message, return if empty
message = message.trim();
if (_.isEmpty(message)) {
console.log("[TINDER MATCH STORE] Ignoring empty message send");
return;
}
// Attempt to send message to match
console.log("[TINDER MATCH STORE] Attempting to send message to match with id:", matchId);
TinderAPI.POSTWithToken(generateTinderURLForMatchMessageSend(matchId),
......@@ -97,6 +124,9 @@ let TinderMatchStore = Reflux.createStore({
.then(resp => resp.json())
.then(resp => {
Actions.receivedSentMessageReceipt(matchId, resp);
})
.catch(err => {
console.log(`[TINDER MATCH STORE] Failed to send message to match with ID ${matchId}:`, err);
});
},
......@@ -117,9 +147,10 @@ let TinderMatchStore = Reflux.createStore({
/**
* Parse a tinder update for matches and other information relevant to this store
*
* @param {object} update - The update from tinder
* @param {object} currentUser - Current kindling user
* @param {object} update - The update object from tinder
*/
processTinderUpdate(update) {
processTinderUpdate(currentUser, update) {
console.log("[TINDER MATCH STORE] Parsing received tinder update", update);
let matches = update.matches || [];
......@@ -140,8 +171,16 @@ let TinderMatchStore = Reflux.createStore({
this.state.matches[matchId].messages.push(_.extend(m, {seen: false}));
});
// Set match ID
// Determine ID of matched user by finding a message and using to or from user IDs
let matchedUserId = "";
if (!_.isEmpty(m.messages)) {
let first = _.first(m.messages);
matchedUserId = m.message.to === currentUser._id ? m.message.from : m.message.to;
}
// Set match ID and matched user ID
this.state.matches[matchId].id = matchId;
this.state.matches[matchId].matchedUserId = matchedUserId;
});
// Save state and alert listeners to updates
......
......@@ -2,12 +2,13 @@ import Reflux from "reflux";
import Actions from "../actions";
import Constants from "../constants";
import TinderAuthStore from "./tinder-auth";
import TinderUserStore from "./tinder-user";
import TinderAPI from "../tinder-api";
import LocalStorageBackupMixinGenerator from "../mixins/localstorage-backup";
import fetch from "vendor/fetch";
import _ from "lodash";
const DEFAULT_STATE = {lastUpdate: null, tinderAccessToken: null};
const DEFAULT_STATE = {lastUpdate: null, currentUser: null, tinderAccessToken: null};
const LocalStorageBackupMixin = LocalStorageBackupMixinGenerator(Constants.LOCALSTORAGE_KEY_TINDER_UPDATE_STORE_STATE);
let TinderUpdateStore = Reflux.createStore({
......@@ -25,10 +26,18 @@ let TinderUpdateStore = Reflux.createStore({
this.backupState();
});
// Save the Tinder user when provided by the TinderUserStore
TinderUserStore.listen(store => {
console.log("[TINDER UPDATE STORE] Detected tinder user store update", store);
this.state.currentUser = store.user;
this.backupState();
});
// Gather state for the store from localstorage/other sources
// Also attempt to pull state from parent stores if they have initialized and have state
this.state = _.extend(DEFAULT_STATE, this.gatherState());
this.state.tinderAccessToken = this.state.tinderAccessToken || TinderAuthStore.state.token;
this.state.currentUser = this.state.currentUser || TinderUserStore.state.user;
console.log("[TINDER UPDATE STORE] Initial state:", _.extend({}, this.state));
},
......
......@@ -101,3 +101,16 @@ export function isValidTinderUserPreferencesObject(prefs) {
_.isNumber(prefs.age_filter_max) &&
_.has(prefs, "gender");
}
/**
* Helper function for a text area based on the value from a change event (ex textareas, input boxes)
*
* @param {string} propName - The name of the property to be updated
*/
export function updatePropertyWithEventTargetValue(propName, comp) {
return (e) => {
let statePartial = {};
statePartial[propName] = e.target.value;
comp.setState(statePartial);
};
}
......@@ -124,7 +124,7 @@ body {
min-height: 100%;
top: 0;
background-color: white;
transition: left 1s;
transition: left 0.5s;
}
.match-convo-page-container {
......@@ -200,13 +200,15 @@ section.collapsible-labeled-form-section > label {
left: 0;
z-index: 1;
position: absolute;
color: white;
padding-left: .25em;
paddint-top: .25em;
}
#match-convo-close-btn > div.close-icon-container {
background-color: rgba(40,40,40,0.6);
border-radius: 1em;
padding: .1em;
height: 30px;
width: 30px;
font-size: 20px;
border-radius: 30px;
color: white;
}
.match-convo-page-messages-container {
......
......@@ -18,6 +18,7 @@ const EXPECTED_ACTIONS = [
"republishTinderUser",
"retrieveMatchedUserInfo",
"retrieveTinderUpdates",
"sendMessageToMatch",
"sentimentSuccessfullyProcessed",
"successfullyDeletedMatch",
"updateLocationFromGPS",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment