diff --git a/dist/index.html b/dist/index.html
index b873b1e..44a8036 100644
--- a/dist/index.html
+++ b/dist/index.html
@@ -2,19 +2,161 @@
- My JavaScript App
+ backTREK
+
+
+
+
+
Loading . . .
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app.js b/src/app.js
index e7af594..075776b 100644
--- a/src/app.js
+++ b/src/app.js
@@ -6,8 +6,208 @@ import _ from 'underscore';
import './css/foundation.css';
import './css/style.css';
-console.log('it loaded!');
+// Models and Collections
+import Trip from './app/models/trip';
+import TripList from './app/collections/trip-list';
+import Reservation from './app/models/reservation';
+
+let tripTemplate;
+let emptyTripTemplate;
+let tripDetailTemplate;
+let reserveModalTemplate;
+let addTripModalTemplate;
+
+// Trip List
+
+const tripList = new TripList();
+
+const render = function render(tripList) {
+ $('.trip-row').remove();
+ const tripListElement = $('#trip-list ul');
+ if (tripList.length > 0) {
+ tripList.forEach((trip) => {
+ const generatedHTML = $(tripTemplate(trip.attributes));
+ tripListElement.append(generatedHTML);
+ });
+ } else {
+ const generatedHTML = $(emptyTripTemplate());
+ tripListElement.append(generatedHTML);
+ }
+};
+
+const show = function show(e) {
+ // don't minimize trip details when the reserve button is clicked
+ if ($(e.target).is('#reserve-btn')) return;
+ const id = parseInt(findElementTripID(e));
+ const tripElement = $(`#${id}`);
+ if (tripElement.hasClass('show')) {
+ clearShow();
+ } else {
+ const trip = tripList.findWhere({id: id});
+ trip.fetch({
+ success: () => {
+ clearShow();
+ $('.trip-row').removeClass('show');
+ $('.trip-details').remove();
+ const generatedHTML = $(tripDetailTemplate(trip.attributes));
+ const reserveBtn = ('#reserve-btn');
+ tripElement.append(generatedHTML).addClass('show');
+ }
+ });
+ }
+};
+
+const clearShow = function clearShow() {
+ $('.trip-row').removeClass('show');
+ $('.trip-detail-holder').remove();
+};
+
+const sort = function sort(e) {
+ $('.current-sort').removeClass('current-sort');
+ let targetElement = $(e.target);
+ let field = targetElement[0].id;
+ tripList.comparator = field;
+ targetElement.addClass('current-sort');
+ tripList.sort();
+ render(tripList);
+};
+
+// Filtering
+
+const filter = function filter(e) {
+ e.preventDefault();
+ const form = $('#trip-search');
+ const searchData = getFormData(form, ['search-type', 'query']);
+ const newList = tripList.filterBy(searchData['search-type'], searchData['query']);
+ render(newList);
+};
+
+// Modals
+
+const addTripModal = function addTripModal() {
+ $('body').append(addTripModalTemplate());
+};
+
+const reserveModal = function reserveModal(e) {
+ const id = findElementTripID(e);
+ const generatedHTML = $(reserveModalTemplate({'id': id}));
+ const form = $('#reservation-form');
+ // form.on('submit', submitReservation);
+ $('body').append(generatedHTML);
+};
+
+const clearModal = function clearModal(e) {
+ if ($(e.target).hasClass('modal-close')) {
+ $('.modal').remove();
+ }
+};
+
+// Forms
+
+const submitTrip = function submitTrip(e) {
+ e.preventDefault();
+ clearErrors();
+ const form = $(e.target);
+ const formData = getFormData(form, ['name', 'continent', 'category', 'weeks', 'cost', 'about']);
+ const newTrip = new Trip(formData);
+ saveIfValid(newTrip, form, 'trip');
+};
+
+const submitReservation = function submitReservation(e) {
+ e.preventDefault();
+ clearErrors();
+ $('.form-messages').html('');
+ const form = $('#reservation-form');
+ const id = form.data('id');
+ const formData = getFormData(form, ['name', 'email']);
+ formData['tripID'] = id;
+ const newReservation = new Reservation(formData);
+ saveIfValid(newReservation, form, 'reservation');
+};
+
+const getFormData = function getFormData(target, values) {
+ const formData = {};
+ values.forEach((value) => {
+ let targetElement = target.find(`[name="${ value }"]`);
+ formData[value] = targetElement.val();
+ });
+ return formData;
+};
+
+const saveIfValid = function saveIfValid(object, form, type) {
+ if (object.isValid()) {
+ object.save({}, {
+ success: (response) => {
+ formSuccess(type, form)
+ },
+ error: (status, response) => {
+ const errors = ($.parseJSON(response.responseText))['errors'];
+ printErrors(errors, type);
+ },
+ });
+ } else {
+ printErrors(object.validationError, type);
+ }
+};
+
+const formSuccess = function formSuccess(item, form) {
+ const messageBox = $('#form-messages');
+ messageBox.html(`Successfully created ${item}!
`);
+ form[0].reset();
+}
+
+const printErrors = function printErrors(errors, type) {
+ for(let field in errors) {
+ let errorElementID = `#${type}-${field} label`
+ let errorElement = $(errorElementID);
+ errorElement.addClass('has-errors');
+ errorElement.append(`${errors[field]}
`);
+ }
+};
+
+const clearErrors = function clearErrors() {
+ $('.has-errors').removeClass('has-errors');
+ $('.error').remove();
+};
+
+const findElementTripID = function findElementTripID(e) {
+ return $(e.target).closest('li.trip-row').attr('id');
+};
$(document).ready( () => {
- $('main').html('Hello World!
');
+ $('.on-load').hide();
+ tripTemplate = _.template($('#trip-template').html());
+ emptyTripTemplate = _.template($('#empty-trip-template').html());
+ tripDetailTemplate = _.template($('#trip-detail-template').html());
+ reserveModalTemplate = _.template($('#reserve-modal-template').html());
+ addTripModalTemplate = _.template($('#add-trip-modal-template').html());
+
+ tripList.on('update', render);
+
+ $('#intro-button').on('click', (e) => {
+ $('#intro-button').hide(200);
+ $('#loading').show(500);
+ tripList.fetch({
+ success: () => {
+ $('#loading').hide(200);
+ $('#trip-list').show(500);
+ },
+ });
+ $('.on-load').show(500);
+ });
+
+ $('#trip-list').on('click', 'li', show);
+
+ $('.sort').on('click', sort);
+
+ $('#add-trip').on('click', addTripModal);
+ $('body').on('click', '.modal-close', clearModal);
+
+ $('body').on('keyup', '#trip-search', filter);
+ $('#trip-search').on('submit', e => e.preventDefault());
+
+ $('#trip-list').on('click', '#reserve-btn', reserveModal);
+ $(document).on('submit', '#reservation-form', submitReservation);
+ $(document).on('submit', '#add-trip-form', submitTrip);
+
});
diff --git a/src/app/collections/trip-list.js b/src/app/collections/trip-list.js
new file mode 100644
index 0000000..04ac71d
--- /dev/null
+++ b/src/app/collections/trip-list.js
@@ -0,0 +1,21 @@
+import Backbone from 'backbone';
+import Trip from '../models/trip';
+
+const TripList = Backbone.Collection.extend({
+ model: Trip,
+ url: 'https://ada-backtrek-api.herokuapp.com/trips/',
+
+ filterBy: function(field, value) {
+ const newList = this.filter(function(trip) {
+ if (['cost', 'weeks'].includes(field)){
+ value = parseFloat(value);
+ return value >= trip.get(field);
+ } else {
+ return trip.get(field).includes(value);
+ }
+ });
+ return new TripList(newList);
+ },
+});
+
+export default TripList;
diff --git a/src/app/images/michal-parzuchowski-40545.jpg b/src/app/images/michal-parzuchowski-40545.jpg
new file mode 100644
index 0000000..dc6743c
Binary files /dev/null and b/src/app/images/michal-parzuchowski-40545.jpg differ
diff --git a/src/app/models/reservation.js b/src/app/models/reservation.js
new file mode 100644
index 0000000..8b842b3
--- /dev/null
+++ b/src/app/models/reservation.js
@@ -0,0 +1,19 @@
+import Backbone from 'backbone';
+
+const Reservation = Backbone.Model.extend({
+ validate(attr) {
+ const errors = {};
+ if (attr['name'] == "") {
+ errors['name'] = ['cannot be blank'];
+ }
+ if (attr['email'] == "") {
+ errors['email'] = ['cannot be blank'];
+ }
+ return Object.keys(errors).length > 0 ? errors : false;
+ },
+ urlRoot() {
+ return `https://ada-backtrek-api.herokuapp.com/trips/${ this.get('tripID') }/reservations`;
+ },
+});
+
+export default Reservation;
diff --git a/src/app/models/trip.js b/src/app/models/trip.js
new file mode 100644
index 0000000..40a5c3d
--- /dev/null
+++ b/src/app/models/trip.js
@@ -0,0 +1,36 @@
+import Backbone from 'backbone';
+
+const Trip = Backbone.Model.extend({
+ defaults: {
+ name: 'Untitled Trip',
+ continent: 'Unknown',
+ category: 'none',
+ weeks: 0,
+ about: 'no info',
+ cost: 0,
+ },
+ urlRoot: 'https://ada-backtrek-api.herokuapp.com/trips/',
+
+ validate(attr) {
+ const errors = {};
+ if (!['Africa', 'Antartica', 'Asia', 'Australasia', 'Europe', 'North America', 'South America'].includes(attr['continent'])) {
+ errors['continent'] = 'must be valid Earth continent';
+ }
+ ['weeks', 'cost'].forEach((field) => {
+ if (parseFloat(attr[field]) === NaN) {
+ errors[field] = "must be a number";
+ }
+ if (!parseFloat(attr[field]) > 0) {
+ errors[field] = "must be greater than 0";
+ }
+ });
+ ['name', 'continent', 'category', 'weeks', 'cost'].forEach((field) => {
+ if (attr[field] === '') {
+ errors[field] = "can't be blank";
+ }
+ });
+ return Object.keys(errors).length > 0 ? errors : false;
+ },
+});
+
+export default Trip;
diff --git a/src/css/style.css b/src/css/style.css
index b7b2d44..4ac76c6 100644
--- a/src/css/style.css
+++ b/src/css/style.css
@@ -1,2 +1,274 @@
+:root {
+--white: #fefefe;
+--transpwhite: rgba(254, 254, 254, 0.7);
+--dkwhite: #f0f0f0;
+--transpdkwhite: rgba(240, 240, 240, 0.7);
+--black: #333333;
+--transpblack: rgba(51, 51, 51, 0.5);
+--peach: #fec0aa;
+--ltblue: #98C1D9;
+--transpltblue: rgba(152, 193, 217, 0.7);
+--salmon: #EE6352;
+--indigo: #223843;
+--dkgrey: #6D696A;
+--grey: #E3E4E8;
+--transpgrey: rgba(227, 227, 232, 0.7);
+}
-/* Your styles go here! */
+body {
+ background-attachment: fixed;
+ background-color: var(--white);
+ background-image: url('../app/images/michal-parzuchowski-40545.jpg');
+ background-size: cover;
+ background-position: center center;
+}
+
+h1 {
+ /* website title */
+ color: var(--white);
+ display: inline-block;
+ font-family: 'Comfortaa', sans-serif;
+}
+
+h2 {
+ /* modal titles */
+ color: var(--indigo);
+ font-size: 2rem;
+ font-family: 'Comfortaa', sans-serif;
+}
+
+h3 {
+ /* loading message */
+ color: var(--transpwhite);
+ font-size: 3rem;
+ text-align: center;
+ font-family: 'Comfortaa', sans-serif;
+}
+
+h4 {
+ /* list headers */
+ color: var(--indigo);
+ display: inline-block;
+ font-family: 'Comfortaa', sans-serif;
+ margin: 0;
+ overflow: hidden;
+ padding: .5rem 0 0;
+}
+
+h6 {
+ /* list data */
+ color: var(--black);
+ display: inline-block;
+ overflow: hidden;
+}
+
+p, label {
+ color: var(--black);
+}
+
+ul {
+ list-style: none;
+}
+
+form {
+ overflow: auto;
+}
+
+.row {
+ max-width: 100%;
+}
+
+.button {
+ border-radius: 1rem;
+ background-color: var(--ltblue);
+ font-family: 'Comfortaa', sans-serif;
+}
+
+.button:hover {
+ background-color: var(--peach);
+}
+
+.has-errors {
+ color: tomato;
+ font-weight: bold;
+}
+
+p.error {
+ color: tomato;
+ display: inline;
+ font-style: italic;
+ font-weight: normal;
+ margin-left: 1rem;
+}
+
+p.success {
+ color: mediumseagreen;
+ font-style: italic;
+ font-weight: normal;
+ margin-left: 1rem;
+}
+
+/**********/
+/* HEADER */
+/**********/
+
+header {
+ overflow: auto;
+ padding: 1rem;
+}
+
+.add-trip {
+ border-radius: 50%;
+ float: right;
+ font-size: 2rem;
+ height: 3rem;
+ line-height: 3rem;
+ padding: 0;
+ width: 3rem;
+}
+
+.form-holder {
+ padding: 1rem .5rem 0 .5rem;
+}
+
+.form-holder input, .form-holder select {
+ border-radius: 1rem;
+}
+
+/* SEARCH */
+
+header form {
+ float: none !important;
+ margin: auto;
+ overflow: auto;
+}
+
+[type="submit"] {
+ width: 100%;
+}
+
+/*************/
+/* TRIP LIST */
+/*************/
+
+.intro-button {
+ align-items: center;
+ display: flex;
+ float: none;
+ font-size: 8vh;
+ height: 30vh;
+ justify-content: center;
+ margin: 1rem auto;
+}
+
+.trip-list ul {
+ margin: 0 auto;
+ width: 95%;
+}
+
+.trip-list li {
+ padding: 1rem 2rem;
+}
+
+.headers {
+ background-color: var(--transpgrey);
+ overflow: auto;
+}
+
+.trip-row {
+ background-color: var(--transpwhite);
+}
+
+.trip-row:nth-of-type(odd) {
+ background-color: var(--transpdkwhite);
+}
+
+.name-col {
+ width: 23%;
+}
+
+.cont-col {
+ text-align: center;
+ width: 20%;
+}
+
+.cat-col {
+ text-align: center;
+ width: 20%;
+}
+
+.weeks-col {
+ text-align: center;
+ width: 15%;
+}
+
+.cost-col {
+ text-align: right;
+ width: 15%;
+}
+
+.current-sort {
+ color: var(--salmon);
+ text-decoration: underline;
+}
+
+.empty-col {
+ color: var(--dkgrey);
+ display: block;
+ font-size: 1.25rem;
+ font-style: italic;
+ margin: auto;
+ text-align: center;
+}
+
+/* TRIP DISPLAY */
+
+.show h6 {
+ font-weight: bold;
+}
+
+.reserve-btn {
+ background-color: var(--indigo);
+ margin-bottom: 0;
+}
+
+.reserve-holder {
+ text-align: center;
+}
+
+/**********/
+/* MODALS */
+/**********/
+
+.modal-shadow {
+ background-color: var(--transpblack);
+ height: 100vh;
+ position: fixed;
+ width: 100%;
+ top: 0;
+ left: 0;
+}
+
+.modal-body {
+ background-color: var(--white);
+ border-radius: 1rem;
+ box-shadow: 5px 5px 20px var(--black);
+ float: none;
+ margin: 10% auto;
+ padding: 2rem;
+}
+
+.close-btn {
+ border: 1px solid var(--grey);
+ border-radius: 5rem;
+ float: right;
+ font-weight: bold;
+ height: 2rem;
+ line-height: 1.75;
+ text-align: center;
+ width: 2rem;
+}
+
+a.close-btn {
+ color: var(--grey);
+}