Refactor UI, Add ability to remove suggestions
This commit is contained in:
		
							
								
								
									
										16
									
								
								errors.go
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								errors.go
									
									
									
									
									
								
							@@ -12,14 +12,14 @@ type Error struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var error_codes = map[int]string{
 | 
			
		||||
	1: "Not Signed In",
 | 
			
		||||
	2: "Unauthorized Access",
 | 
			
		||||
	3: "Invalid Request",
 | 
			
		||||
	4: "User Exists",
 | 
			
		||||
	5: "Attendee Exists",
 | 
			
		||||
	6: "Suggestion Exists",
 | 
			
		||||
	7: "Attendee In Use",
 | 
			
		||||
	//  5:   "Connection Failed", //client-side error
 | 
			
		||||
	1:   "Not Signed In",
 | 
			
		||||
	2:   "Unauthorized Access",
 | 
			
		||||
	3:   "Invalid Request",
 | 
			
		||||
	4:   "User Exists",
 | 
			
		||||
	5:   "Attendee Exists",
 | 
			
		||||
	6:   "Suggestion Exists",
 | 
			
		||||
	7:   "Attendee In Use",
 | 
			
		||||
	8:   "Suggestion Not Last",
 | 
			
		||||
	999: "Internal Error",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,19 @@ function suggestionCreated(suggestion) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function removeSuggestion() {
 | 
			
		||||
	return {
 | 
			
		||||
		type: SuggestionConstants.REMOVE_SUGGESTION
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function suggestionRemoved(suggestionId) {
 | 
			
		||||
	return {
 | 
			
		||||
		type: SuggestionConstants.SUGGESTION_REMOVED,
 | 
			
		||||
		suggestionId: suggestionId
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function fetchPopularSuggestions() {
 | 
			
		||||
	return {
 | 
			
		||||
		type: SuggestionConstants.FETCH_POPULAR_SUGGESTIONS
 | 
			
		||||
@@ -129,8 +142,33 @@ function fetchPopular() {
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function remove(suggestion) {
 | 
			
		||||
	return function(dispatch) {
 | 
			
		||||
		dispatch(removeSuggestion());
 | 
			
		||||
 | 
			
		||||
		$.ajax({
 | 
			
		||||
			type: "DELETE",
 | 
			
		||||
			dataType: "json",
 | 
			
		||||
			url: "suggestion/"+suggestion.SuggestionId+"/",
 | 
			
		||||
			success: function(data, status, jqXHR) {
 | 
			
		||||
				var e = new Error();
 | 
			
		||||
				e.fromJSON(data);
 | 
			
		||||
				if (e.isError()) {
 | 
			
		||||
					ErrorActions.serverError(e);
 | 
			
		||||
				} else {
 | 
			
		||||
					dispatch(suggestionRemoved(suggestion.SuggestionId));
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
			error: function(jqXHR, status, error) {
 | 
			
		||||
				ErrorActions.ajaxError(e);
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
	fetchAll: fetchAll,
 | 
			
		||||
	create: create,
 | 
			
		||||
	remove: remove,
 | 
			
		||||
	fetchPopular: fetchPopular
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ var ReactBootstrap = require('react-bootstrap');
 | 
			
		||||
var Grid = ReactBootstrap.Grid;
 | 
			
		||||
var Row = ReactBootstrap.Row;
 | 
			
		||||
var Col = ReactBootstrap.Col;
 | 
			
		||||
var FormGroup = ReactBootstrap.FormGroup;
 | 
			
		||||
var ControlLabel = ReactBootstrap.ControlLabel;
 | 
			
		||||
var Button = ReactBootstrap.Button;
 | 
			
		||||
 | 
			
		||||
@@ -102,31 +103,38 @@ module.exports = React.createClass({
 | 
			
		||||
		var attendeeList = this.getAttendeeList();
 | 
			
		||||
		var buttonDisabled = this.state.attendee == null || !this.state.suggestion;
 | 
			
		||||
		return (
 | 
			
		||||
			<Grid><Row>
 | 
			
		||||
				<Col xs={4}>
 | 
			
		||||
					<Combobox
 | 
			
		||||
						value={this.state.attendee}
 | 
			
		||||
						data={attendeeList}
 | 
			
		||||
						valueField='AttendeeId'
 | 
			
		||||
						textField='Name'
 | 
			
		||||
						onChange={this.onChangeAttendee} />
 | 
			
		||||
				</Col>
 | 
			
		||||
				<Col xs={1}>
 | 
			
		||||
				</Col>
 | 
			
		||||
				<Col xs={4}>
 | 
			
		||||
					<Combobox
 | 
			
		||||
						value={this.state.suggestion}
 | 
			
		||||
						data={this.unusedPopularSuggestions()}
 | 
			
		||||
						valueField='RestaurantName'
 | 
			
		||||
						textField='RestaurantName'
 | 
			
		||||
						onChange={this.onChangeSuggestion} />
 | 
			
		||||
				</Col>
 | 
			
		||||
				<Col xs={1}>
 | 
			
		||||
				</Col>
 | 
			
		||||
				<Col xs={2}>
 | 
			
		||||
					<Button bsStyle="success" disabled={buttonDisabled} onClick={this.onAddSuggestion}>Add Suggestion/Veto</Button>
 | 
			
		||||
				</Col>
 | 
			
		||||
			</Row></Grid>
 | 
			
		||||
			<Row>
 | 
			
		||||
			<Col xs={4}>
 | 
			
		||||
				<FormGroup controlId="newsuggestionrestaurantname">
 | 
			
		||||
				<ControlLabel>Add Suggestion/Veto To:</ControlLabel>
 | 
			
		||||
				<Combobox
 | 
			
		||||
					value={this.state.suggestion}
 | 
			
		||||
					data={this.unusedPopularSuggestions()}
 | 
			
		||||
					valueField='RestaurantName'
 | 
			
		||||
					textField='RestaurantName'
 | 
			
		||||
					onChange={this.onChangeSuggestion} />
 | 
			
		||||
				</FormGroup>
 | 
			
		||||
			</Col>
 | 
			
		||||
			<Col xs={1}></Col>
 | 
			
		||||
			<Col xs={4}>
 | 
			
		||||
				<FormGroup controlId="newsuggestionby">
 | 
			
		||||
				<ControlLabel>Suggested By:</ControlLabel>
 | 
			
		||||
				<Combobox
 | 
			
		||||
					value={this.state.attendee}
 | 
			
		||||
					data={attendeeList}
 | 
			
		||||
					valueField='AttendeeId'
 | 
			
		||||
					textField='Name'
 | 
			
		||||
					onChange={this.onChangeAttendee} />
 | 
			
		||||
				</FormGroup>
 | 
			
		||||
			</Col>
 | 
			
		||||
			<Col xs={1}></Col>
 | 
			
		||||
			<Col xs={2}>
 | 
			
		||||
				<FormGroup controlId="addnewsuggestionbutton">
 | 
			
		||||
				<ControlLabel><br /></ControlLabel>
 | 
			
		||||
				<Button bsStyle="success" disabled={buttonDisabled} onClick={this.onAddSuggestion}>Add</Button>
 | 
			
		||||
				</FormGroup>
 | 
			
		||||
			</Col>
 | 
			
		||||
			</Row>
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,8 @@ var ControlLabel = ReactBootstrap.ControlLabel;
 | 
			
		||||
var Grid = ReactBootstrap.Grid;
 | 
			
		||||
var Row = ReactBootstrap.Row;
 | 
			
		||||
var Col = ReactBootstrap.Col;
 | 
			
		||||
var Panel = ReactBootstrap.Panel;
 | 
			
		||||
var Button = ReactBootstrap.Button;
 | 
			
		||||
 | 
			
		||||
var Multiselect = require('react-widgets').Multiselect;
 | 
			
		||||
 | 
			
		||||
@@ -55,22 +57,47 @@ module.exports = React.createClass({
 | 
			
		||||
		var attendeeList = this.getAttendeeList();
 | 
			
		||||
 | 
			
		||||
		var suggestionIds = Object.keys(this.props.suggestions);
 | 
			
		||||
		suggestionIds.sort(function(a, b){return parseInt(a, 10) - parseInt(b, 10);});
 | 
			
		||||
		suggestionIds.sort(function(a, b){return parseInt(b, 10) - parseInt(a, 10);});
 | 
			
		||||
		var suggestions = [];
 | 
			
		||||
		for (var i in suggestionIds) {
 | 
			
		||||
			var suggestion = this.props.suggestions[suggestionIds[i]];
 | 
			
		||||
			suggestions.push((
 | 
			
		||||
				<Row key={suggestion.SuggestionId}>
 | 
			
		||||
					<Col xs={4}>{this.props.attendees[suggestion.AttendeeId].Name}</Col>
 | 
			
		||||
					<Col xs={1}></Col>
 | 
			
		||||
					<Col xs={4}>{suggestion.RestaurantName}</Col>
 | 
			
		||||
				</Row>
 | 
			
		||||
			));
 | 
			
		||||
			var suggestorName = "Unknown";
 | 
			
		||||
			if (this.props.attendees.hasOwnProperty(suggestion.AttendeeId))
 | 
			
		||||
					suggestorName = this.props.attendees[suggestion.AttendeeId].Name;
 | 
			
		||||
 | 
			
		||||
			if (i == 0) {
 | 
			
		||||
				var self = this;
 | 
			
		||||
				var popFunction = function(){
 | 
			
		||||
					return function(){self.props.removeSuggestion(suggestion);};
 | 
			
		||||
				}();
 | 
			
		||||
				suggestions.push((
 | 
			
		||||
					<Row key={suggestion.SuggestionId}><Panel>
 | 
			
		||||
						<Col xs={4}><ControlLabel>Current Suggestion:</ControlLabel></Col>
 | 
			
		||||
						<Col xs={1}></Col>
 | 
			
		||||
						<Col xs={4}>{suggestion.RestaurantName} (by {suggestorName})</Col>
 | 
			
		||||
						<Col xs={1}></Col>
 | 
			
		||||
						<Col xs={2}>
 | 
			
		||||
							<Button bsStyle="danger" onClick={popFunction}>Pop</Button>
 | 
			
		||||
						</Col>
 | 
			
		||||
					</Panel></Row>
 | 
			
		||||
				));
 | 
			
		||||
			} else {
 | 
			
		||||
				suggestions.push((
 | 
			
		||||
					<Row key={suggestion.SuggestionId}>
 | 
			
		||||
						<Col xs={4}><ControlLabel>{(suggestionIds.length - i).toString() + "."}</ControlLabel></Col>
 | 
			
		||||
						<Col xs={1}></Col>
 | 
			
		||||
						<Col xs={4}>{suggestion.RestaurantName} (by {suggestorName})</Col>
 | 
			
		||||
						<Col xs={1}></Col>
 | 
			
		||||
						<Col xs={2}>
 | 
			
		||||
						</Col>
 | 
			
		||||
					</Row>
 | 
			
		||||
				));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return (
 | 
			
		||||
			<div><form>
 | 
			
		||||
				<Grid><Row><Col xs={12}>
 | 
			
		||||
				<Grid fluid><Row><Col xs={12}>
 | 
			
		||||
				<FormGroup controlId="attendees">
 | 
			
		||||
				<ControlLabel>Attendees</ControlLabel>
 | 
			
		||||
				<Multiselect
 | 
			
		||||
@@ -83,20 +110,13 @@ module.exports = React.createClass({
 | 
			
		||||
					onCreate={this.onCreateAttendee} />
 | 
			
		||||
				</FormGroup>
 | 
			
		||||
				</Col></Row>
 | 
			
		||||
				<Row>
 | 
			
		||||
					<Col xs={4}><ControlLabel>Suggested By:</ControlLabel></Col>
 | 
			
		||||
					<Col xs={1}></Col>
 | 
			
		||||
					<Col xs={4}><ControlLabel>Restaurant Name:</ControlLabel></Col>
 | 
			
		||||
				</Row>
 | 
			
		||||
				{suggestions}
 | 
			
		||||
				</Grid>
 | 
			
		||||
				<FormGroup controlId="newsuggestion">
 | 
			
		||||
				<NewSuggestion
 | 
			
		||||
					createSuggestion={this.props.createSuggestion}
 | 
			
		||||
					attendees={this.props.attendees}
 | 
			
		||||
					suggestions={this.props.suggestions}
 | 
			
		||||
					popularSuggestions={this.props.popularSuggestions} />
 | 
			
		||||
				</FormGroup>
 | 
			
		||||
				{suggestions}
 | 
			
		||||
				</Grid>
 | 
			
		||||
			</form></div>
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,8 @@ module.exports = keyMirror({
 | 
			
		||||
	SUGGESTIONS_FETCHED: null,
 | 
			
		||||
	CREATE_SUGGESTION: null,
 | 
			
		||||
	SUGGESTION_CREATED: null,
 | 
			
		||||
	REMOVE_SUGGESTION: null,
 | 
			
		||||
	SUGGESTION_REMOVED: null,
 | 
			
		||||
	FETCH_POPULAR_SUGGESTIONS: null,
 | 
			
		||||
	POPULAR_SUGGESTIONS_FETCHED: null,
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,8 @@ function mapDispatchToProps(dispatch) {
 | 
			
		||||
	return {
 | 
			
		||||
		createAttendee: function(attendee) {dispatch(AttendeeActions.create(attendee))},
 | 
			
		||||
		removeAttendee: function(attendee) {dispatch(AttendeeActions.remove(attendee))},
 | 
			
		||||
		createSuggestion: function(suggestion) {dispatch(SuggestionActions.create(suggestion))}
 | 
			
		||||
		createSuggestion: function(suggestion) {dispatch(SuggestionActions.create(suggestion))},
 | 
			
		||||
		removeSuggestion: function(suggestion) {dispatch(SuggestionActions.remove(suggestion))}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,10 @@ module.exports = function(state = {}, action) {
 | 
			
		||||
				[suggestion.SuggestionId]: suggestion
 | 
			
		||||
			});
 | 
			
		||||
			return suggestions;
 | 
			
		||||
		case SuggestionConstants.SUGGESTION_REMOVED:
 | 
			
		||||
			var suggestions = assign({}, state);
 | 
			
		||||
			delete suggestions[action.suggestionId];
 | 
			
		||||
			return suggestions;
 | 
			
		||||
		case UserConstants.USER_LOGGEDOUT:
 | 
			
		||||
			return {};
 | 
			
		||||
		default:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
<html>
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
<title>Lunch</title>
 | 
			
		||||
<meta name="viewport" content="width=device-width, initial-scale=1">
 | 
			
		||||
 | 
			
		||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
 | 
			
		||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css">
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ div#content {
 | 
			
		||||
	width: 95%;
 | 
			
		||||
	height: 100%;
 | 
			
		||||
	min-width: 20em;
 | 
			
		||||
	max-width: 100em;
 | 
			
		||||
	max-width: 50em;
 | 
			
		||||
	margin: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -16,3 +16,8 @@ div.tab-content {
 | 
			
		||||
	border-left: 1px solid #ddd;
 | 
			
		||||
	border-right: 1px solid #ddd;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Make panels inside row line up with other rows by eliminating padding */
 | 
			
		||||
div.row div.panel div.panel-body {
 | 
			
		||||
	padding: 15px 0 15px 0
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"gopkg.in/gorp.v1"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net/http"
 | 
			
		||||
@@ -52,6 +53,11 @@ func (aeu SuggestionExistsError) Error() string {
 | 
			
		||||
	return "Suggestion exists"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SuggestionNotLastError struct{}
 | 
			
		||||
 | 
			
		||||
func (snle SuggestionNotLastError) Error() string {
 | 
			
		||||
	return "Suggestion not last on the stack"
 | 
			
		||||
}
 | 
			
		||||
func (s *PopularSuggestion) Write(w http.ResponseWriter) error {
 | 
			
		||||
	enc := json.NewEncoder(w)
 | 
			
		||||
	return enc.Encode(s)
 | 
			
		||||
@@ -82,6 +88,26 @@ func GetSuggestions(userid int64, date time.Time) (*[]*Suggestion, error) {
 | 
			
		||||
	return &suggestions, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetSuggestion(suggestionid int64, userid int64, date time.Time) (*Suggestion, error) {
 | 
			
		||||
	var s Suggestion
 | 
			
		||||
 | 
			
		||||
	err := DB.SelectOne(&s, "SELECT * from suggestions where UserId=? AND SuggestionId=? AND Date=?", userid, suggestionid, date)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return &s, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func VetoedBy(transaction *gorp.Transaction, suggestion *Suggestion) (*[]*Suggestion, error) {
 | 
			
		||||
	var suggestions []*Suggestion
 | 
			
		||||
 | 
			
		||||
	_, err := transaction.Select(&suggestions, "SELECT * from suggestions WHERE UserId=? AND Date=? AND VetoingId=?", suggestion.UserId, suggestion.Date, suggestion.SuggestionId)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return &suggestions, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetPopularSuggestions() (*[]*PopularSuggestion, error) {
 | 
			
		||||
	var suggestions []*Suggestion
 | 
			
		||||
	suggestionMap := make(map[string]int64)
 | 
			
		||||
@@ -135,6 +161,42 @@ func InsertSuggestion(s *Suggestion) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func DeleteSuggestion(s *Suggestion) error {
 | 
			
		||||
	transaction, err := DB.Begin()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Ensure suggestion hasn't been vetoed
 | 
			
		||||
	suggestions, err := VetoedBy(transaction, s)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		transaction.Rollback()
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if len(*suggestions) > 0 {
 | 
			
		||||
		transaction.Rollback()
 | 
			
		||||
		return SuggestionNotLastError{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	count, err := transaction.Delete(s)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		transaction.Rollback()
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if count != 1 {
 | 
			
		||||
		transaction.Rollback()
 | 
			
		||||
		return errors.New("Was going to delete more than one suggestion")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = transaction.Commit()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		transaction.Rollback()
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SuggestionHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	user, err := GetUserFromSession(r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -196,8 +258,33 @@ func SuggestionHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
			log.Print(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	} else if r.Method == "DELETE" {
 | 
			
		||||
		suggestionid, err := GetURLID(r.URL.Path)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			WriteError(w, 3 /* Invalid Request */)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		suggestion, err := GetSuggestion(suggestionid, user.UserId, today)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			WriteError(w, 3 /*Invalid Request*/)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err = DeleteSuggestion(suggestion)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if _, ok := err.(SuggestionNotLastError); ok {
 | 
			
		||||
				WriteError(w, 8 /*Suggestion Not Last*/)
 | 
			
		||||
			} else {
 | 
			
		||||
				WriteError(w, 999 /*Internal Error*/)
 | 
			
		||||
				log.Print(err)
 | 
			
		||||
			}
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		WriteSuccess(w)
 | 
			
		||||
	} else {
 | 
			
		||||
		/* No PUT or DELETE */
 | 
			
		||||
		/* No PUT */
 | 
			
		||||
		WriteError(w, 3 /*Invalid Request*/)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user